Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 48 additions & 53 deletions src/api/providers/__tests__/unbound.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"

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

vi.mock("openai", () => {
const createMock = vi.fn()
Expand Down Expand Up @@ -54,61 +55,59 @@ describe("UnboundHandler", () => {

it("streams reasoning chunks from delta.reasoning_content", async () => {
const mockCreate = (OpenAI as unknown as any)().chat.completions.create
mockCreate.mockResolvedValue({
async *[Symbol.asyncIterator]() {
yield { choices: [{ delta: { reasoning_content: "thinking..." } }] }
yield { choices: [{ delta: { content: "answer" } }] }
yield { choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } }
},
})
mockCreate.mockResolvedValue(
asyncStreamFrom([
{ choices: [{ delta: { reasoning_content: "thinking..." } }] },
{ choices: [{ delta: { content: "answer" } }] },
{ choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } },
]),
)

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

const chunks: any[] = []
for await (const chunk of handler.createMessage("system", [{ role: "user", content: "hi" }], {
taskId: "t",
tools: [],
})) {
chunks.push(chunk)
}
const chunks = await collectStream(
handler.createMessage("system", [{ role: "user", content: "hi" }], {
taskId: "t",
tools: [],
}),
)

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

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

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

const chunks: any[] = []
for await (const chunk of handler.createMessage("system", [{ role: "user", content: "hi" }], {
taskId: "t",
tools: [],
})) {
chunks.push(chunk)
}
const chunks = await collectStream(
handler.createMessage("system", [{ role: "user", content: "hi" }], {
taskId: "t",
tools: [],
}),
)

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

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

mockCreate.mockResolvedValue({
async *[Symbol.asyncIterator]() {
yield {
mockCreate.mockResolvedValue(
asyncStreamFrom([
{
choices: [
{
delta: {
Expand All @@ -117,24 +116,22 @@ describe("UnboundHandler", () => {
},
},
],
}
yield { choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } }
},
})
},
{ choices: [{ delta: {} }], usage: { prompt_tokens: 1, completion_tokens: 1 } },
]),
)

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

const chunks: any[] = []

for await (const chunk of handler.createMessage("system", [{ role: "user", content: "hi" }], {
taskId: "t",
tools: [],
})) {
chunks.push(chunk)
}
const chunks = await collectStream(
handler.createMessage("system", [{ role: "user", content: "hi" }], {
taskId: "t",
tools: [],
}),
)

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

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

it("identifies itself as Zoo Code in per-request Unbound metadata", async () => {
const mockCreate = (OpenAI as unknown as any)().chat.completions.create
mockCreate.mockResolvedValue({
async *[Symbol.asyncIterator]() {
yield {
mockCreate.mockResolvedValue(
asyncStreamFrom([
{
choices: [{ delta: { content: "ok" } }],
}
yield {
},
{
choices: [{ delta: {} }],
usage: { prompt_tokens: 1, completion_tokens: 1 },
}
},
})
},
]),
)

const handler = new UnboundHandler({
unboundApiKey: "test-key",
Expand All @@ -167,9 +164,7 @@ describe("UnboundHandler", () => {
tools: [],
})

for await (const _chunk of stream) {
// drain stream
}
await collectStream(stream)

expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
Expand Down
2 changes: 1 addition & 1 deletion src/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@
},
"api/providers/__tests__/unbound.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 8
"count": 5
}
},
"api/providers/__tests__/vercel-ai-gateway.spec.ts": {
Expand Down
Loading