Skip to content

Commit 725fafd

Browse files
refactor: roll out stream helpers to unbound spec (#1090)
Co-authored-by: Roomote <roomote@roomote.dev>
1 parent b47b19a commit 725fafd

2 files changed

Lines changed: 49 additions & 54 deletions

File tree

src/api/providers/__tests__/unbound.spec.ts

Lines changed: 48 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
22
import OpenAI from "openai"
33

44
import { UnboundHandler } from "../unbound"
5+
import { asyncStreamFrom, collectStream } from "../../../test-utils/stream"
56

67
vi.mock("openai", () => {
78
const createMock = vi.fn()
@@ -54,61 +55,59 @@ describe("UnboundHandler", () => {
5455

5556
it("streams reasoning chunks from delta.reasoning_content", async () => {
5657
const mockCreate = (OpenAI as unknown as any)().chat.completions.create
57-
mockCreate.mockResolvedValue({
58-
async *[Symbol.asyncIterator]() {
59-
yield { choices: [{ delta: { reasoning_content: "thinking..." } }] }
60-
yield { choices: [{ delta: { content: "answer" } }] }
61-
yield { choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } }
62-
},
63-
})
58+
mockCreate.mockResolvedValue(
59+
asyncStreamFrom([
60+
{ choices: [{ delta: { reasoning_content: "thinking..." } }] },
61+
{ choices: [{ delta: { content: "answer" } }] },
62+
{ choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } },
63+
]),
64+
)
6465

6566
const handler = new UnboundHandler({
6667
unboundApiKey: "test-key",
6768
unboundModelId: "openai/gpt-4o",
6869
})
6970

70-
const chunks: any[] = []
71-
for await (const chunk of handler.createMessage("system", [{ role: "user", content: "hi" }], {
72-
taskId: "t",
73-
tools: [],
74-
})) {
75-
chunks.push(chunk)
76-
}
71+
const chunks = await collectStream(
72+
handler.createMessage("system", [{ role: "user", content: "hi" }], {
73+
taskId: "t",
74+
tools: [],
75+
}),
76+
)
7777

7878
expect(chunks).toContainEqual({ type: "reasoning", text: "thinking..." })
7979
})
8080

8181
it("falls back to delta.reasoning when reasoning_content is absent", async () => {
8282
const mockCreate = (OpenAI as unknown as any)().chat.completions.create
83-
mockCreate.mockResolvedValue({
84-
async *[Symbol.asyncIterator]() {
85-
yield { choices: [{ delta: { reasoning: "router-style thought" } }] }
86-
yield { choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } }
87-
},
88-
})
83+
mockCreate.mockResolvedValue(
84+
asyncStreamFrom([
85+
{ choices: [{ delta: { reasoning: "router-style thought" } }] },
86+
{ choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } },
87+
]),
88+
)
8989

9090
const handler = new UnboundHandler({
9191
unboundApiKey: "test-key",
9292
unboundModelId: "openai/gpt-4o",
9393
})
9494

95-
const chunks: any[] = []
96-
for await (const chunk of handler.createMessage("system", [{ role: "user", content: "hi" }], {
97-
taskId: "t",
98-
tools: [],
99-
})) {
100-
chunks.push(chunk)
101-
}
95+
const chunks = await collectStream(
96+
handler.createMessage("system", [{ role: "user", content: "hi" }], {
97+
taskId: "t",
98+
tools: [],
99+
}),
100+
)
102101

103102
expect(chunks).toContainEqual({ type: "reasoning", text: "router-style thought" })
104103
})
105104

106105
it("prefers delta.reasoning_content over delta.reasoning when both are present", async () => {
107106
const mockCreate = (OpenAI as unknown as any)().chat.completions.create
108107

109-
mockCreate.mockResolvedValue({
110-
async *[Symbol.asyncIterator]() {
111-
yield {
108+
mockCreate.mockResolvedValue(
109+
asyncStreamFrom([
110+
{
112111
choices: [
113112
{
114113
delta: {
@@ -117,24 +116,22 @@ describe("UnboundHandler", () => {
117116
},
118117
},
119118
],
120-
}
121-
yield { choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } }
122-
},
123-
})
119+
},
120+
{ choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } },
121+
]),
122+
)
124123

125124
const handler = new UnboundHandler({
126125
unboundApiKey: "test-key",
127126
unboundModelId: "openai/gpt-4o",
128127
})
129128

130-
const chunks: any[] = []
131-
132-
for await (const chunk of handler.createMessage("system", [{ role: "user", content: "hi" }], {
133-
taskId: "t",
134-
tools: [],
135-
})) {
136-
chunks.push(chunk)
137-
}
129+
const chunks = await collectStream(
130+
handler.createMessage("system", [{ role: "user", content: "hi" }], {
131+
taskId: "t",
132+
tools: [],
133+
}),
134+
)
138135

139136
const reasoningChunks = chunks.filter((chunk) => chunk.type === "reasoning")
140137

@@ -143,17 +140,17 @@ describe("UnboundHandler", () => {
143140

144141
it("identifies itself as Zoo Code in per-request Unbound metadata", async () => {
145142
const mockCreate = (OpenAI as unknown as any)().chat.completions.create
146-
mockCreate.mockResolvedValue({
147-
async *[Symbol.asyncIterator]() {
148-
yield {
143+
mockCreate.mockResolvedValue(
144+
asyncStreamFrom([
145+
{
149146
choices: [{ delta: { content: "ok" } }],
150-
}
151-
yield {
147+
},
148+
{
152149
choices: [{ delta: {} }],
153150
usage: { prompt_tokens: 1, completion_tokens: 1 },
154-
}
155-
},
156-
})
151+
},
152+
]),
153+
)
157154

158155
const handler = new UnboundHandler({
159156
unboundApiKey: "test-key",
@@ -167,9 +164,7 @@ describe("UnboundHandler", () => {
167164
tools: [],
168165
})
169166

170-
for await (const _chunk of stream) {
171-
// drain stream
172-
}
167+
await collectStream(stream)
173168

174169
expect(mockCreate).toHaveBeenCalledWith(
175170
expect.objectContaining({

src/eslint-suppressions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@
271271
},
272272
"api/providers/__tests__/unbound.spec.ts": {
273273
"@typescript-eslint/no-explicit-any": {
274-
"count": 8
274+
"count": 5
275275
}
276276
},
277277
"api/providers/__tests__/vercel-ai-gateway.spec.ts": {

0 commit comments

Comments
 (0)