Files
OmniRoute/tests/unit/zed-hosted-think-close-marker.test.ts
Arnav Rastogi 48124fca5a fix(zed-hosted): send the provider wire values cloud.zed.dev accepts (#10051)
Every zed-hosted completion failed with

  500 {"error":{"message":"[500]: An internal server error occurred."}}

for every model id, including deliberately invalid ones.

Root cause: ZED_PROVIDER held display-cased names ("Anthropic", "OpenAi",
"Google", "XAi"), and normalizeZedProvider's return value is serialized
straight into the `provider` field of the POST /completions envelope. Zed
matches that field exactly and fails the request before looking at the model,
which is why the model id never mattered.

Verified live against cloud.zed.dev with an otherwise identical request:

  {"provider":"anthropic",...} -> 200
  {"provider":"Anthropic",...} -> 500 {"message":"An internal server error occurred."}
  {"provider":"open_ai",...}   -> reaches the OpenAI request parser
  {"provider":"openai",...}    -> 500 (same internal error)

The spellings now follow Zed's own GET /models catalog, which reports
`anthropic`, `open_ai` and `google`. That also makes normalizeZedProvider
identity on catalog values instead of corrupting a value Zed just supplied —
previously it accepted the correct lowercase input and re-cased it into the
form that 500s.

`x_ai` follows the same underscore convention; this account's catalog exposes
no xAI models, so that one spelling is by convention rather than observation.

The constant is module-local and every branch compares against it, so internal
dispatch (initProviderState / convertProviderEvent / buildProviderRequest) is
unaffected. Two existing tests asserted the display-cased values and one passed
"Anthropic" to wrapZedCompletionStream directly; all are updated to the wire
values the executor now produces.

Co-authored-by: root <root@srv1710948.hstgr.cloud>
2026-08-13 07:52:27 -03:00

73 lines
2.9 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
const { __test__ } = await import("../../open-sse/executors/zed-hosted.ts");
const { wrapZedCompletionStream } = __test__;
// zed-hosted's Anthropic backend converts Claude events to OpenAI chunks
// inside the executor (wrapZedCompletionStream → claudeToOpenAIResponse),
// bypassing chatCore's suppressThinkClose wiring. Responses API clients
// receive reasoning as structured items, so the textual `</think>` close
// marker must be suppressed on that path (same policy as chatCore / GLM).
function buildZedAnthropicNdjson(): string {
const lines = [
{ event: { type: "message_start", message: { id: "msg_zed", model: "claude-test" } } },
{ event: { type: "content_block_start", index: 0, content_block: { type: "thinking" } } },
{
event: {
type: "content_block_delta",
index: 0,
delta: { type: "thinking_delta", thinking: "plan" },
},
},
{ event: { type: "content_block_stop", index: 0 } },
{ event: { type: "content_block_start", index: 1, content_block: { type: "text", text: "" } } },
{
event: { type: "content_block_delta", index: 1, delta: { type: "text_delta", text: "Hi" } },
},
{ event: { type: "content_block_stop", index: 1 } },
{
event: {
type: "message_delta",
delta: { stop_reason: "end_turn" },
usage: { output_tokens: 3 },
},
},
{ event: { type: "message_stop" } },
];
return lines.map((l) => JSON.stringify(l)).join("\n") + "\n";
}
async function readAll(stream: ReadableStream<Uint8Array>): Promise<string> {
const reader = stream.getReader();
const decoder = new TextDecoder();
let out = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
out += decoder.decode(value, { stream: true });
}
return out;
}
function wrapAnthropic(options?: Record<string, unknown>): Promise<string> {
const response = new Response(buildZedAnthropicNdjson(), { status: 200 });
// "anthropic" is the wire value normalizeZedProvider now returns (and the one
// cloud.zed.dev accepts); the capitalized spelling 500s upstream.
const wrapped = wrapZedCompletionStream(response, "anthropic", "claude-test", options);
return readAll(wrapped.body as ReadableStream<Uint8Array>);
}
test("zed anthropic stream keeps the close marker by default (#4633)", async () => {
const out = await wrapAnthropic();
assert.ok(out.includes('"content":"</think>"'), "expected default marker emission");
});
test("zed anthropic stream suppresses the close marker when asked", async () => {
const out = await wrapAnthropic({ suppressThinkClose: true });
assert.ok(!out.includes("</think>"), "marker must not leak into output");
assert.ok(out.includes('"content":"Hi"'), "text content still flows");
assert.ok(out.includes('"reasoning_content":"plan"'), "reasoning still flows");
});