deepseek/deepseek-v4-flash-vision-exp is served by the Provider API but was
missing from the hardcoded MODEL_INPUT_MODALITIES allowlist, so pi rejected
any conversation containing an image block. Notably this also rejected images
returned by the read tool via toolResult:
Error: Selected Command Code model does not support image content in tool results
Adding the allowlist entry lets modelSupportsImageInput() return true and the
converters forward images using the current Command Code wire format.
Coverage:
- regression test for modelSupportsImageInput("deepseek/deepseek-v4-flash-vision-exp")
- end-to-end stream test for a tool-result image forwarded as a following user
image (the concrete read reproduction), not only a user-attached image
- end-to-end stream test asserting a text-only model still rejects tool-result
images before any network access
Refs: https://github.com/patlux/pi-commandcode-provider/issues/54
851 lines
27 KiB
TypeScript
851 lines
27 KiB
TypeScript
/**
|
|
* Integration tests for the real streamCommandCode core using a local mock
|
|
* Command Code server. No real API key or pi runtime required.
|
|
*/
|
|
|
|
import assert from "node:assert/strict"
|
|
import { after, before, beforeEach, describe, it } from "node:test"
|
|
|
|
import type { AssistantMessageEvent } from "../src/core.ts"
|
|
import { MODEL_EFFORTS, thinkingLevelMapForEfforts } from "../src/models.ts"
|
|
import {
|
|
collectEvents,
|
|
createTestDeps,
|
|
makeContext,
|
|
makeModel,
|
|
objectAt,
|
|
startMockCommandCodeServer,
|
|
type MockCommandCodeServer,
|
|
} from "./helpers.ts"
|
|
|
|
let server: MockCommandCodeServer
|
|
|
|
before(async () => {
|
|
server = await startMockCommandCodeServer()
|
|
})
|
|
|
|
after(async () => {
|
|
await server.close()
|
|
})
|
|
|
|
beforeEach(() => {
|
|
server.reset()
|
|
})
|
|
|
|
function eventTypes(events: readonly AssistantMessageEvent[]): string[] {
|
|
return events.map((event) => event.type)
|
|
}
|
|
|
|
describe("streamCommandCode — auth", () => {
|
|
it("emits a missing-key error without touching the network", async () => {
|
|
const { streamCommandCode } = createTestDeps({
|
|
apiBase: server.baseUrl(),
|
|
env: {},
|
|
authPaths: [],
|
|
})
|
|
const stream = streamCommandCode(makeModel(), makeContext(), {
|
|
apiKey: "",
|
|
})
|
|
const events = await collectEvents(stream)
|
|
|
|
assert.deepEqual(eventTypes(events), ["error"])
|
|
assert.equal(events[0].type, "error")
|
|
assert.equal(events[0].reason, "error")
|
|
assert.match(events[0].error.errorMessage ?? "", /No Command Code API key/)
|
|
assert.equal(server.requestCount(), 0)
|
|
})
|
|
|
|
it("ignores the literal env-var name and falls back to env", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({
|
|
apiBase: server.baseUrl(),
|
|
env: { COMMANDCODE_API_KEY: "env-key" },
|
|
})
|
|
|
|
await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "COMMANDCODE_API_KEY" }),
|
|
)
|
|
|
|
assert.equal(
|
|
server.lastRequestHeaders().authorization,
|
|
"Bearer env-key",
|
|
"should resolve from env, not send the literal var name as the token",
|
|
)
|
|
})
|
|
|
|
it("uses options.apiKey in the Authorization header", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({
|
|
apiBase: server.baseUrl(),
|
|
env: { COMMANDCODE_API_KEY: "env-key" },
|
|
})
|
|
|
|
await collectEvents(streamCommandCode(makeModel(), makeContext(), { apiKey: "option-key" }))
|
|
|
|
assert.equal(server.lastRequestHeaders().authorization, "Bearer option-key")
|
|
})
|
|
})
|
|
|
|
describe("streamCommandCode — successful streams", () => {
|
|
it("emits start → text events → done and accumulates usage", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [
|
|
JSON.stringify({ type: "text-delta", text: "Hel" }),
|
|
JSON.stringify({ type: "text-delta", text: "lo" }),
|
|
JSON.stringify({
|
|
type: "finish",
|
|
finishReason: "stop",
|
|
totalUsage: {
|
|
inputTokens: 3124,
|
|
outputTokens: 15,
|
|
inputTokenDetails: { noCacheTokens: 52, cacheReadTokens: 3072 },
|
|
},
|
|
}),
|
|
],
|
|
})
|
|
const { streamCommandCode, calculatedUsages } = createTestDeps({
|
|
apiBase: server.baseUrl(),
|
|
})
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
assert.deepEqual(eventTypes(events), [
|
|
"start",
|
|
"text_start",
|
|
"text_delta",
|
|
"text_delta",
|
|
"text_end",
|
|
"done",
|
|
])
|
|
const done = events.at(-1)
|
|
assert.equal(done?.type, "done")
|
|
if (done?.type !== "done") throw new Error("expected done")
|
|
assert.equal(done.reason, "stop")
|
|
assert.equal(done.message.content[0]?.type, "text")
|
|
assert.equal(
|
|
done.message.content[0]?.type === "text" ? done.message.content[0].text : "",
|
|
"Hello",
|
|
)
|
|
assert.equal(done.message.usage.input, 52)
|
|
assert.equal(done.message.usage.cacheRead, 3072)
|
|
assert.equal(done.message.usage.cacheWrite, 0)
|
|
assert.equal(done.message.usage.totalTokens, 3139)
|
|
assert.equal(calculatedUsages.length, 1)
|
|
})
|
|
|
|
it("sends images for vision-capable models", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
await collectEvents(
|
|
streamCommandCode(
|
|
makeModel({ id: "gpt-5.6-luna" }),
|
|
makeContext({
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: [
|
|
{ type: "text", text: "inspect" },
|
|
{ type: "image", data: "aGVsbG8=", mimeType: "image/png" },
|
|
],
|
|
},
|
|
],
|
|
}),
|
|
{ apiKey: "mock-key" },
|
|
),
|
|
)
|
|
|
|
assert.equal(
|
|
objectAt(server.lastRequestBody(), ["params", "messages", "0", "content", "1", "image"]),
|
|
"data:image/png;base64,aGVsbG8=",
|
|
)
|
|
})
|
|
|
|
it("forwards a tool-result image as a following user image for vision-capable models", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(
|
|
makeModel({ id: "deepseek/deepseek-v4-flash-vision-exp" }),
|
|
makeContext({
|
|
messages: [
|
|
{ role: "user", content: "read the image" },
|
|
{
|
|
role: "assistant",
|
|
content: [{ type: "toolCall", id: "c1", name: "read", arguments: {} }],
|
|
},
|
|
{
|
|
role: "toolResult",
|
|
toolCallId: "c1",
|
|
toolName: "read",
|
|
content: [
|
|
{ type: "text", text: "image attached" },
|
|
{ type: "image", data: "aGVsbG8=", mimeType: "image/png" },
|
|
],
|
|
},
|
|
],
|
|
}),
|
|
{ apiKey: "mock-key" },
|
|
),
|
|
)
|
|
|
|
// No error: the tool-result image must not be rejected for this model.
|
|
assert.equal(events.at(-1)?.type, "done")
|
|
|
|
const body = server.lastRequestBody()
|
|
// The tool-result text is forwarded on the tool message at index 2.
|
|
assert.equal(
|
|
objectAt(body, ["params", "messages", "2", "content", "0", "output", "value"]),
|
|
"image attached",
|
|
)
|
|
// The tool-result image is forwarded as a following user image message at index 3.
|
|
assert.equal(objectAt(body, ["params", "messages", "3", "role"]), "user")
|
|
assert.equal(
|
|
objectAt(body, ["params", "messages", "3", "content", "0", "image"]),
|
|
"data:image/png;base64,aGVsbG8=",
|
|
)
|
|
})
|
|
|
|
it("rejects a tool-result image before network access for text-only models", async () => {
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(
|
|
makeModel({ id: "deepseek/deepseek-v4-pro" }),
|
|
makeContext({
|
|
messages: [
|
|
{ role: "user", content: "read the image" },
|
|
{
|
|
role: "assistant",
|
|
content: [{ type: "toolCall", id: "c1", name: "read", arguments: {} }],
|
|
},
|
|
{
|
|
role: "toolResult",
|
|
toolCallId: "c1",
|
|
toolName: "read",
|
|
content: [{ type: "image", data: "aGVsbG8=", mimeType: "image/png" }],
|
|
},
|
|
],
|
|
}),
|
|
{ apiKey: "mock-key" },
|
|
),
|
|
)
|
|
|
|
assert.equal(events.at(-1)?.type, "error")
|
|
assert.match(events.at(-1)?.error.errorMessage ?? "", /does not support image content/i)
|
|
assert.equal(server.requestCount(), 0)
|
|
})
|
|
|
|
it("rejects images before network access for text-only models", async () => {
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(
|
|
makeModel({ id: "deepseek/deepseek-v4-pro" }),
|
|
makeContext({
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: [{ type: "image", data: "aGVsbG8=", mimeType: "image/png" }],
|
|
},
|
|
],
|
|
}),
|
|
{ apiKey: "mock-key" },
|
|
),
|
|
)
|
|
|
|
assert.equal(events.at(-1)?.type, "error")
|
|
assert.equal(server.requestCount(), 0)
|
|
})
|
|
|
|
it("derives uncached input when noCacheTokens is missing", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [
|
|
JSON.stringify({
|
|
type: "finish",
|
|
finishReason: "stop",
|
|
totalUsage: {
|
|
inputTokens: 100,
|
|
outputTokens: 10,
|
|
inputTokenDetails: { cacheReadTokens: 75 },
|
|
},
|
|
}),
|
|
],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
const done = events.at(-1)
|
|
assert.equal(done?.type, "done")
|
|
if (done?.type !== "done") throw new Error("expected done")
|
|
assert.equal(done.message.usage.input, 25)
|
|
assert.equal(done.message.usage.cacheRead, 75)
|
|
assert.equal(done.message.usage.cacheWrite, 0)
|
|
assert.equal(done.message.usage.totalTokens, 110)
|
|
})
|
|
|
|
it("accounts for cache writes separately from uncached input", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [
|
|
JSON.stringify({
|
|
type: "finish",
|
|
finishReason: "stop",
|
|
totalUsage: {
|
|
inputTokens: 100,
|
|
outputTokens: 10,
|
|
inputTokenDetails: {
|
|
noCacheTokens: 20,
|
|
cacheReadTokens: 70,
|
|
cacheWriteTokens: 10,
|
|
},
|
|
},
|
|
}),
|
|
],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
const done = events.at(-1)
|
|
assert.equal(done?.type, "done")
|
|
if (done?.type !== "done") throw new Error("expected done")
|
|
assert.equal(done.message.usage.input, 20)
|
|
assert.equal(done.message.usage.cacheRead, 70)
|
|
assert.equal(done.message.usage.cacheWrite, 10)
|
|
assert.equal(done.message.usage.totalTokens, 110)
|
|
})
|
|
|
|
it("ends on finish without waiting for an open upstream connection", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [
|
|
JSON.stringify({ type: "text-delta", text: "done" }),
|
|
JSON.stringify({ type: "finish", finishReason: "stop" }),
|
|
],
|
|
hangAfterLast: true,
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
500,
|
|
)
|
|
|
|
assert.equal(events.at(-1)?.type, "done")
|
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
assert.ok(server.responseClosedBeforeEnd(), "client should cancel the still-open response body")
|
|
})
|
|
|
|
it("emits reasoning and tool-call blocks in order", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [
|
|
JSON.stringify({ type: "reasoning-start" }),
|
|
JSON.stringify({ type: "reasoning-delta", text: "think" }),
|
|
JSON.stringify({ type: "reasoning-end" }),
|
|
JSON.stringify({ type: "text-delta", text: "Using tool" }),
|
|
JSON.stringify({
|
|
type: "tool-call",
|
|
toolCallId: "call_1",
|
|
toolName: "read_file",
|
|
input: JSON.stringify({ path: "/tmp/x" }),
|
|
}),
|
|
JSON.stringify({ type: "finish", finishReason: "tool-calls" }),
|
|
],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
assert.deepEqual(eventTypes(events), [
|
|
"start",
|
|
"thinking_start",
|
|
"thinking_delta",
|
|
"thinking_end",
|
|
"text_start",
|
|
"text_delta",
|
|
"text_end",
|
|
"toolcall_start",
|
|
"toolcall_end",
|
|
"done",
|
|
])
|
|
const done = events.at(-1)
|
|
if (done?.type !== "done") throw new Error("expected done")
|
|
assert.equal(done.reason, "toolUse")
|
|
assert.deepEqual(
|
|
done.message.content.map((content) => content.type),
|
|
["thinking", "text", "toolCall"],
|
|
)
|
|
const toolCall = done.message.content[2]
|
|
assert.equal(toolCall?.type === "toolCall" ? toolCall.name : "", "read_file")
|
|
})
|
|
|
|
it("flushes reasoning if finish arrives without reasoning-end", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [
|
|
JSON.stringify({ type: "reasoning-delta", text: "unfinished thought" }),
|
|
JSON.stringify({ type: "finish", finishReason: "stop" }),
|
|
],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
const done = events.at(-1)
|
|
if (done?.type !== "done") throw new Error("expected done")
|
|
assert.equal(done.message.content[0]?.type, "thinking")
|
|
})
|
|
|
|
it("closes thinking block before text when reasoning-end is missing", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [
|
|
JSON.stringify({ type: "reasoning-start" }),
|
|
JSON.stringify({ type: "reasoning-delta", text: "thinking" }),
|
|
JSON.stringify({ type: "text-delta", text: "answer" }),
|
|
JSON.stringify({ type: "finish", finishReason: "stop" }),
|
|
],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
assert.deepEqual(eventTypes(events), [
|
|
"start",
|
|
"thinking_start",
|
|
"thinking_delta",
|
|
"thinking_end",
|
|
"text_start",
|
|
"text_delta",
|
|
"text_end",
|
|
"done",
|
|
])
|
|
})
|
|
|
|
it("closes thinking block before tool-call when reasoning-end is missing", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [
|
|
JSON.stringify({ type: "reasoning-start" }),
|
|
JSON.stringify({ type: "reasoning-delta", text: "thinking" }),
|
|
JSON.stringify({
|
|
type: "tool-call",
|
|
toolCallId: "call_1",
|
|
toolName: "read_file",
|
|
input: JSON.stringify({ path: "/tmp/x" }),
|
|
}),
|
|
JSON.stringify({ type: "finish", finishReason: "tool-calls" }),
|
|
],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
assert.deepEqual(eventTypes(events), [
|
|
"start",
|
|
"thinking_start",
|
|
"thinking_delta",
|
|
"thinking_end",
|
|
"toolcall_start",
|
|
"toolcall_end",
|
|
"done",
|
|
])
|
|
})
|
|
})
|
|
|
|
describe("streamCommandCode — request serialization", () => {
|
|
it("rejects image input before sending a lossy request", async () => {
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
const events = await collectEvents(
|
|
streamCommandCode(
|
|
makeModel(),
|
|
makeContext({
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: [{ type: "image", data: "base64-data", mimeType: "image/png" }],
|
|
},
|
|
],
|
|
}),
|
|
{ apiKey: "mock-key" },
|
|
),
|
|
)
|
|
|
|
assert.deepEqual(eventTypes(events), ["start", "error"])
|
|
const lastEvent = events.at(-1)
|
|
assert.equal(lastEvent?.type, "error")
|
|
if (lastEvent?.type === "error") {
|
|
assert.match(lastEvent.error.errorMessage ?? "", /does not support image content/i)
|
|
}
|
|
assert.equal(server.requestCount(), 0)
|
|
})
|
|
it("sends the expected request body and default headers", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
const context = makeContext({
|
|
messages: [
|
|
{ role: "user", content: "first" },
|
|
{
|
|
role: "assistant",
|
|
content: [{ type: "text", text: "first response" }],
|
|
},
|
|
{ role: "user", content: "second" },
|
|
],
|
|
tools: [
|
|
{
|
|
name: "get_weather",
|
|
description: "Get weather",
|
|
parameters: {
|
|
kind: "object",
|
|
properties: { city: { kind: "string" } },
|
|
},
|
|
},
|
|
],
|
|
})
|
|
|
|
await collectEvents(
|
|
streamCommandCode(makeModel(), context, {
|
|
apiKey: "mock-key",
|
|
maxTokens: 500,
|
|
}),
|
|
)
|
|
|
|
const body = server.lastRequestBody()
|
|
assert.equal(objectAt(body, ["config", "workingDir"]), "/repo")
|
|
assert.equal(objectAt(body, ["config", "date"]), "2026-05-05")
|
|
assert.equal(objectAt(body, ["params", "model"]), "deepseek/deepseek-v4-flash")
|
|
assert.equal(objectAt(body, ["params", "stream"]), true)
|
|
assert.equal(objectAt(body, ["params", "max_tokens"]), 500)
|
|
assert.equal(objectAt(body, ["params", "reasoning_effort"]), undefined)
|
|
assert.equal(objectAt(body, ["params", "temperature"]), 0.3)
|
|
assert.equal(objectAt(body, ["params", "system"]), "You are a test assistant.")
|
|
assert.equal(objectAt(body, ["memory"]), null)
|
|
assert.equal(objectAt(body, ["taste"]), null)
|
|
assert.equal(objectAt(body, ["skills"]), null)
|
|
assert.equal(objectAt(body, ["permissionMode"]), undefined)
|
|
assert.equal(objectAt(body, ["threadId"]), "00000000-0000-4000-8000-000000000000")
|
|
assert.equal(
|
|
objectAt(body, ["params", "messages", "1", "content", "0", "text"]),
|
|
"first response",
|
|
)
|
|
assert.equal(objectAt(body, ["params", "tools", "0", "name"]), "get_weather")
|
|
|
|
const headers = server.lastRequestHeaders()
|
|
assert.equal(headers.authorization, "Bearer mock-key")
|
|
assert.equal(headers["x-command-code-version"], "1.32.1")
|
|
assert.equal(headers["x-project-slug"], "repo")
|
|
assert.equal(headers["x-taste-learning"], "true")
|
|
assert.equal(headers["x-co-flag"], "false")
|
|
assert.equal(headers["x-session-id"], undefined)
|
|
})
|
|
|
|
it("accepts the legacy OMP nested reasoning map", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
const model = makeModel({
|
|
id: "omp-compat-reasoning-model",
|
|
reasoning: true,
|
|
thinking: { effortMap: { high: "legacy-high" } },
|
|
})
|
|
|
|
await collectEvents(
|
|
streamCommandCode(model, makeContext(), { apiKey: "mock-key", reasoning: "high" }),
|
|
)
|
|
|
|
assert.equal(objectAt(server.lastRequestBody(), ["params", "reasoning_effort"]), "legacy-high")
|
|
})
|
|
|
|
it("forwards a supported Pi reasoning level as reasoning_effort", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
const model = makeModel({
|
|
id: "deepseek/deepseek-v4-flash",
|
|
reasoning: true,
|
|
thinkingLevelMap: thinkingLevelMapForEfforts(MODEL_EFFORTS["deepseek/deepseek-v4-flash"]),
|
|
})
|
|
|
|
await collectEvents(
|
|
streamCommandCode(model, makeContext(), { apiKey: "mock-key", reasoning: "max" }),
|
|
)
|
|
|
|
assert.equal(objectAt(server.lastRequestBody(), ["params", "reasoning_effort"]), "max")
|
|
})
|
|
|
|
it("omits reasoning_effort for off, unsupported, and unknown reasoning levels", async () => {
|
|
const model = makeModel({
|
|
id: "deepseek/deepseek-v4-flash",
|
|
reasoning: true,
|
|
thinkingLevelMap: thinkingLevelMapForEfforts(MODEL_EFFORTS["deepseek/deepseek-v4-flash"]),
|
|
})
|
|
|
|
for (const reasoning of ["off", "low"] as const) {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
await collectEvents(
|
|
streamCommandCode(model, makeContext(), { apiKey: "mock-key", reasoning }),
|
|
)
|
|
assert.equal(
|
|
objectAt(server.lastRequestBody(), ["params", "reasoning_effort"]),
|
|
undefined,
|
|
`${reasoning} should not be sent when it has no supported Command Code field`,
|
|
)
|
|
server.reset()
|
|
}
|
|
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
await collectEvents(
|
|
streamCommandCode(
|
|
makeModel({ id: "new-model-without-metadata", reasoning: false }),
|
|
makeContext(),
|
|
{ apiKey: "mock-key", reasoning: "high" },
|
|
),
|
|
)
|
|
assert.equal(objectAt(server.lastRequestBody(), ["params", "reasoning_effort"]), undefined)
|
|
})
|
|
|
|
it("caps maxTokens and passes custom headers", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
await collectEvents(
|
|
streamCommandCode(makeModel({ maxTokens: 500_000 }), makeContext(), {
|
|
apiKey: "mock-key",
|
|
maxTokens: 500_000,
|
|
headers: { "x-custom": "value" },
|
|
}),
|
|
)
|
|
|
|
assert.equal(objectAt(server.lastRequestBody(), ["params", "max_tokens"]), 64_000)
|
|
assert.equal(server.lastRequestHeaders()["x-custom"], "value")
|
|
})
|
|
|
|
it("caps default maxTokens by the selected model", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
await collectEvents(
|
|
streamCommandCode(makeModel({ maxTokens: 8_192 }), makeContext(), {
|
|
apiKey: "mock-key",
|
|
}),
|
|
)
|
|
|
|
assert.equal(objectAt(server.lastRequestBody(), ["params", "max_tokens"]), 8_192)
|
|
})
|
|
|
|
it("serializes OMP system prompt arrays as a string", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
await collectEvents(
|
|
streamCommandCode(
|
|
makeModel(),
|
|
makeContext({
|
|
systemPrompt: ["You are a test assistant.", "Use concise answers."] as unknown as string,
|
|
}),
|
|
{ apiKey: "mock-key" },
|
|
),
|
|
)
|
|
|
|
assert.equal(
|
|
objectAt(server.lastRequestBody(), ["params", "system"]),
|
|
"You are a test assistant.\n\nUse concise answers.",
|
|
)
|
|
})
|
|
|
|
it("times out a hung onResponse callback", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
const started = Date.now()
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), {
|
|
apiKey: "mock-key",
|
|
timeoutMs: 25,
|
|
onResponse: async () => new Promise<void>(() => {}),
|
|
}),
|
|
1_000,
|
|
)
|
|
|
|
assert.ok(Date.now() - started < 500)
|
|
assert.deepEqual(eventTypes(events), ["start", "error"])
|
|
const error = events.at(-1)
|
|
assert.equal(error?.type, "error")
|
|
if (error?.type !== "error") throw new Error("expected error")
|
|
assert.match(error.error.errorMessage ?? "", /timed out after 25ms/)
|
|
})
|
|
|
|
it("times out a hung onPayload callback", async () => {
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
const started = Date.now()
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), {
|
|
apiKey: "mock-key",
|
|
timeoutMs: 25,
|
|
onPayload: async () => new Promise<unknown>(() => {}),
|
|
}),
|
|
1_000,
|
|
)
|
|
|
|
assert.ok(Date.now() - started < 500)
|
|
assert.deepEqual(eventTypes(events), ["start", "error"])
|
|
const error = events.at(-1)
|
|
assert.equal(error?.type, "error")
|
|
if (error?.type !== "error") throw new Error("expected error")
|
|
assert.match(error.error.errorMessage ?? "", /timed out after 25ms/)
|
|
assert.equal(server.requestCount(), 0)
|
|
})
|
|
|
|
it("runs onPayload and onResponse hooks", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
let responseStatus = 0
|
|
|
|
await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), {
|
|
apiKey: "mock-key",
|
|
onPayload: () => ({ replaced: true }),
|
|
onResponse: (response) => {
|
|
responseStatus = response.status
|
|
},
|
|
}),
|
|
)
|
|
|
|
assert.equal(objectAt(server.lastRequestBody(), ["replaced"]), true)
|
|
assert.equal(responseStatus, 200)
|
|
})
|
|
})
|
|
|
|
describe("streamCommandCode — upstream errors and malformed streams", () => {
|
|
it("emits error for HTTP failures", async () => {
|
|
server.mockResponse({ type: "error", status: 429, body: "rate limited" })
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
assert.deepEqual(eventTypes(events), ["start", "error"])
|
|
const error = events.at(-1)
|
|
assert.equal(error?.type, "error")
|
|
if (error?.type !== "error") throw new Error("expected error")
|
|
assert.match(error.error.errorMessage ?? "", /429/)
|
|
})
|
|
|
|
it("emits error for provider error events", async () => {
|
|
server.mockResponse({
|
|
type: "success",
|
|
events: [
|
|
JSON.stringify({
|
|
type: "error",
|
|
error: { message: "provider failed" },
|
|
}),
|
|
],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
const error = events.at(-1)
|
|
assert.equal(error?.type, "error")
|
|
if (error?.type !== "error") throw new Error("expected error")
|
|
assert.equal(error.error.errorMessage, "provider failed")
|
|
})
|
|
|
|
it("handles SSE lines, malformed lines, split chunks, and final line without newline", async () => {
|
|
const textEvent = `data: ${JSON.stringify({ type: "text-delta", text: "split" })}\n`
|
|
const finishEvent = JSON.stringify({
|
|
type: "finish",
|
|
finishReason: "max_tokens",
|
|
})
|
|
server.mockResponse({
|
|
type: "success",
|
|
chunks: [
|
|
"not json\n",
|
|
textEvent.slice(0, 12),
|
|
textEvent.slice(12),
|
|
"event: ignored\n",
|
|
"data: [DONE]\n",
|
|
finishEvent,
|
|
],
|
|
})
|
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
|
|
|
const events = await collectEvents(
|
|
streamCommandCode(makeModel(), makeContext(), { apiKey: "mock-key" }),
|
|
)
|
|
|
|
const done = events.at(-1)
|
|
if (done?.type !== "done") throw new Error("expected done")
|
|
assert.equal(done.reason, "length")
|
|
assert.equal(
|
|
done.message.content[0]?.type === "text" ? done.message.content[0].text : "",
|
|
"split",
|
|
)
|
|
})
|
|
})
|