Files
OmniRoute/tests/unit/opencode-free-tier-request-contract.test.ts
Dizzle 0349627c86 fix(opencode): match the upstream free-tier request contract (#14013)
Match the upstream OpenCode free-tier request contract (issue #13935): canonical
ses_/msg_ identity ids, versioned User-Agent, and the measured body requirements
(stream:true + non-empty tools) with a learn-and-reuse tool-name cache, so
no-auth oc/* requests stop being refused with 403 FreeTierError.

Supersedes #13937 (session regex and minimum-version rule kept, credited below).
Complements #14011 (refusal classification) and #13819 (stream_options strip),
both already merged.

Closes #13935

Co-authored-by: AStupidBear <16422976+AStupidBear@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-09-18 12:57:10 -03:00

753 lines
28 KiB
TypeScript

/**
* OpenCode Zen free-tier request contract.
*
* Since 2026-09-17 the free tier answers 403 FreeTierError unless the request carries
* all four of: `stream: true`, a non-empty `tools` array, an `x-opencode-session` (or
* `x-session-id`) shaped `ses_` + 12 hex + 14 base62, and a `User-Agent` containing
* `opencode/<version >= 1.17>`. Removing any one of the four turns a 200 into a 403, and
* a version below 1.17 answers 426 instead.
*
* Measured again on 2026-09-18, and the tools condition is narrower than it first looked:
* the upstream inspects which names are declared. One made-up name, twelve made-up names
* and twelve plausible names borrowed from another vocabulary are all refused, while six,
* nine and twelve names from the official client pass; three of those same names are
* refused. What the upstream takes also differs by model and moves between days, which is
* why the placeholder is resolved at runtime rather than pinned here.
*
* `tool_choice` is never imposed on any surface: the upstream rejects every value but
* "auto" with a 400 invalid_request_error (`only "auto" is supported for tool_choice`).
*
* Paid models on the same host are not gated (one without tools answers 401), so the
* request contract applies to free-tier models only.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import {
applyFreeTierRequestContract,
configuredPlaceholderToolNames,
noteFreeTierOutcome,
prepareFreeTierRequest,
isGatedFreeTierRequest,
requiresFreeTierRequestContract,
surfaceFromBaseUrl,
} from "../../open-sse/executors/opencodeFreeTierContract.ts";
import {
_resetToolObservationForTests,
getObservedToolNames,
} from "../../open-sse/executors/opencodeToolObservation.ts";
import {
DEFAULT_OPENCODE_USER_AGENT,
OPENCODE_SESSION_PATTERN,
clientSuppliedOpencodeSession,
forwardOpencodeClientHeaders,
satisfiesOpencodeUserAgentContract,
} from "../../open-sse/utils/opencodeHeaders.ts";
import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts";
const CHAT_BODY = () => ({
model: "nemotron-3.5-lightning-free",
messages: [{ role: "user", content: "hi" }],
});
const RESPONSES_BODY = () => ({
model: "muse-spark-1.3-contributor-free",
input: [{ role: "user", content: "hi" }],
});
test("the contract applies to free-tier models on the gated surface only", () => {
assert.equal(
requiresFreeTierRequestContract("zen", "opencode", "nemotron-3.5-lightning-free"),
true
);
assert.equal(requiresFreeTierRequestContract("zen", "opencode-zen", "big-pickle"), true);
assert.equal(
requiresFreeTierRequestContract("zen", "opencode-zen", "muse-spark-1.3-contributor-free"),
true
);
assert.equal(requiresFreeTierRequestContract("zen", "opencode-zen", "gpt-5.6-luna"), false);
// The other surface refuses a request that carries tools rather than one that does not
// (upstream anomalyco/opencode#44300 and #44382), so it is never brought up to this
// contract — including for the free model its registry declares.
assert.equal(requiresFreeTierRequestContract("go", "opencode-go", "big-pickle"), false);
assert.equal(requiresFreeTierRequestContract("go", "opencode-go", "ox-alpha-free"), false);
assert.equal(requiresFreeTierRequestContract("other", "groq", "big-pickle"), false);
});
test("the surface is told apart by base url, so the `oc` alias needs no special case", () => {
assert.equal(surfaceFromBaseUrl("https://opencode.ai/zen/v1"), "zen");
assert.equal(surfaceFromBaseUrl("https://opencode.ai/zen/go/v1"), "go");
assert.equal(surfaceFromBaseUrl("https://api.groq.com/openai/v1"), "other");
assert.equal(surfaceFromBaseUrl(undefined), "other");
});
test("chat completions: the contract adds streaming and a placeholder tool, and no tool_choice", () => {
const body = applyFreeTierRequestContract(CHAT_BODY(), "openai") as Record<string, unknown>;
assert.equal(body.stream, true);
const tools = body.tools as Array<{ type: string; function: { name: string } }>;
assert.equal(tools.length, 1);
assert.equal(tools[0].type, "function");
assert.equal(tools[0].function.name, "_noop");
// The upstream answers 400 `only "auto" is supported for tool_choice` (measured
// 2026-09-18 on the Chat Completions surface), so none is imposed here either.
assert.equal("tool_choice" in body, false);
});
test("the placeholder carries the names it is given, one entry each", () => {
const body = applyFreeTierRequestContract(CHAT_BODY(), "openai", [
"glob",
"grep",
"read",
]) as Record<string, unknown>;
const tools = body.tools as Array<{ function: { name: string; parameters: object } }>;
assert.deepEqual(
tools.map((t) => t.function.name),
["glob", "grep", "read"]
);
// Names only: an empty parameter object, so a borrowed name is a list entry rather than
// a tool the model could usefully call.
assert.deepEqual(tools[0].function.parameters, { type: "object", properties: {} });
});
test("responses: several placeholder names keep the flat shape and no tool_choice", () => {
const body = applyFreeTierRequestContract(RESPONSES_BODY(), "openai-responses", [
"glob",
"grep",
]) as Record<string, unknown>;
const tools = body.tools as Array<{ type: string; name: string }>;
assert.deepEqual(
tools.map((t) => t.name),
["glob", "grep"]
);
assert.equal("tool_choice" in body, false);
});
test("an empty tools array counts as no tools", () => {
// It is the exact shape the upstream refuses — the official client sends it on its own
// compaction path (upstream anomalyco/opencode#49433).
const body = applyFreeTierRequestContract({ ...CHAT_BODY(), tools: [] }, "openai") as Record<
string,
unknown
>;
assert.equal((body.tools as unknown[]).length, 1);
});
test("responses: the placeholder tool is flat and tool_choice stays absent", () => {
// The upstream Responses surface rejects tool_choice with a 400 invalid_request_error
// (measured on both "none" and {type:"none"}), so the contract must not send it there.
const body = applyFreeTierRequestContract(RESPONSES_BODY(), "openai-responses") as Record<
string,
unknown
>;
assert.equal(body.stream, true);
const tools = body.tools as Array<{ type: string; name: string }>;
assert.equal(tools.length, 1);
assert.equal(tools[0].type, "function");
assert.equal(tools[0].name, "_noop");
assert.equal("tool_choice" in body, false);
});
test("client-supplied tools are never replaced, and no tool_choice is imposed", () => {
const clientTools = [
{ type: "function", function: { name: "search", parameters: { type: "object" } } },
];
const body = applyFreeTierRequestContract(
{ ...CHAT_BODY(), tools: clientTools },
"openai"
) as Record<string, unknown>;
assert.deepEqual(body.tools, clientTools);
assert.equal("tool_choice" in body, false);
assert.equal(body.stream, true);
});
test("a client tool_choice is preserved", () => {
const body = applyFreeTierRequestContract(
{ ...CHAT_BODY(), tool_choice: "auto" },
"openai"
) as Record<string, unknown>;
assert.equal(body.tool_choice, "auto");
});
test("applying the contract twice does not duplicate the placeholder tool", () => {
const once = applyFreeTierRequestContract(CHAT_BODY(), "openai");
const twice = applyFreeTierRequestContract(once, "openai") as Record<string, unknown>;
assert.equal((twice.tools as unknown[]).length, 1);
});
test("an unknown body format only gets the streaming flag", () => {
const body = applyFreeTierRequestContract(CHAT_BODY(), "claude") as Record<string, unknown>;
assert.equal(body.stream, true);
assert.equal("tools" in body, false);
assert.equal("tool_choice" in body, false);
});
test("the user-agent contract accepts a versioned opencode client and rejects the rest", () => {
assert.equal(satisfiesOpencodeUserAgentContract(DEFAULT_OPENCODE_USER_AGENT), true);
assert.equal(satisfiesOpencodeUserAgentContract("opencode/1.17.0"), true);
assert.equal(satisfiesOpencodeUserAgentContract("opencode/beta/1.18.31/cli"), true);
assert.equal(
satisfiesOpencodeUserAgentContract("opencode/1.18.14 ai-sdk/provider-utils/4.0.23"),
true
);
// Measured: 1.16.0 answers 426 UpgradeRequired, so it does not satisfy the contract.
assert.equal(satisfiesOpencodeUserAgentContract("opencode/1.16.0"), false);
assert.equal(satisfiesOpencodeUserAgentContract("opencode-cli/1.0.0"), false);
assert.equal(satisfiesOpencodeUserAgentContract("opencode"), false);
assert.equal(satisfiesOpencodeUserAgentContract("curl/8.5.0"), false);
assert.equal(satisfiesOpencodeUserAgentContract(undefined), false);
});
test("synthesized identity headers carry a canonical session and request id", () => {
const headers: Record<string, string> = {};
forwardOpencodeClientHeaders(
headers,
{},
{
cliDefaults: {
userAgent: DEFAULT_OPENCODE_USER_AGENT,
client: "desktop",
project: "global",
},
sessionBody: CHAT_BODY(),
}
);
assert.equal(headers["User-Agent"], DEFAULT_OPENCODE_USER_AGENT);
assert.match(headers["x-opencode-session"] ?? "", OPENCODE_SESSION_PATTERN);
assert.match(headers["x-opencode-request"] ?? "", /^msg_[0-9a-f]{12}[0-9A-Za-z]{14}$/);
});
test("the same conversation keeps the same session id, a different one does not", () => {
const sessionFor = (body: Record<string, unknown>) => {
const headers: Record<string, string> = {};
forwardOpencodeClientHeaders(
headers,
{},
{
cliDefaults: {
userAgent: DEFAULT_OPENCODE_USER_AGENT,
client: "desktop",
project: "global",
},
sessionBody: body,
}
);
return headers["x-opencode-session"];
};
const first = sessionFor(CHAT_BODY());
assert.equal(sessionFor(CHAT_BODY()), first, "conversation-stable, so upstream caching hits");
assert.notEqual(
sessionFor({ ...CHAT_BODY(), messages: [{ role: "user", content: "other" }] }),
first
);
});
test("a client session already in the canonical shape is preserved", () => {
const canonical = "ses_0123456789abABCDEFGHIJKLMN";
const headers: Record<string, string> = {};
forwardOpencodeClientHeaders(
headers,
{ "x-opencode-session": canonical },
{
cliDefaults: {
userAgent: DEFAULT_OPENCODE_USER_AGENT,
client: "desktop",
project: "global",
},
}
);
assert.equal(headers["x-opencode-session"], canonical);
});
test("a foreign client session is translated into the canonical shape, deterministically", () => {
const translate = () => {
const headers: Record<string, string> = {};
forwardOpencodeClientHeaders(
headers,
{ "x-opencode-session": "conv-42" },
{
cliDefaults: {
userAgent: DEFAULT_OPENCODE_USER_AGENT,
client: "desktop",
project: "global",
},
}
);
return headers["x-opencode-session"];
};
const translated = translate();
assert.match(translated ?? "", OPENCODE_SESSION_PATTERN);
assert.equal(translate(), translated, "same client session, same upstream session");
});
test("buildHeaders: a free-tier request accepts an event stream even when the client wants JSON", () => {
const executor = new OpencodeExecutor("opencode-zen");
const headers = executor.buildHeaders(null, false, null, "nemotron-3.5-lightning-free");
assert.equal(headers["Accept"], "text/event-stream");
assert.equal(headers["User-Agent"], DEFAULT_OPENCODE_USER_AGENT);
assert.match(headers["x-opencode-session"] ?? "", OPENCODE_SESSION_PATTERN);
});
test("buildHeaders: a paid model keeps the client's non-streaming expectation", () => {
const executor = new OpencodeExecutor("opencode-zen");
const headers = executor.buildHeaders(null, false, null, "gpt-5.6-luna");
assert.equal(headers["Accept"], undefined);
});
test("transformRequest: the contract is applied for a free model and skipped for a paid one", () => {
const executor = new OpencodeExecutor("opencode-zen");
const free = executor.transformRequest(
"nemotron-3.5-lightning-free",
CHAT_BODY(),
false,
null as never
) as Record<string, unknown>;
assert.equal(free.stream, true);
assert.equal((free.tools as unknown[]).length, 1);
const paid = executor.transformRequest(
"gpt-5.6-luna",
{ model: "gpt-5.6-luna", messages: [{ role: "user", content: "hi" }] },
false,
null as never
) as Record<string, unknown>;
assert.equal("tools" in paid, false);
assert.equal(paid.stream, undefined);
});
test("a JSON caller gets a JSON body back even though the upstream request was streamed", async () => {
// The contract forces streaming, so the executor owes every caller that asked for JSON a
// JSON body — handleChatCore can buffer an event stream, but the other consumers
// (compression judge, token counting) call execute() and read response.json() directly.
const executor = new OpencodeExecutor("opencode-zen");
const originalFetch = globalThis.fetch;
const seen: Array<Record<string, unknown>> = [];
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
seen.push(JSON.parse(String(init?.body ?? "{}")));
const sse =
'data: {"id":"gen-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"hi"},"finish_reason":null}]}\n\n' +
'data: {"id":"gen-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n' +
"data: [DONE]\n\n";
return new Response(sse, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}) as typeof globalThis.fetch;
try {
const result = (await executor.execute({
model: "nemotron-3.5-lightning-free",
body: { model: "nemotron-3.5-lightning-free", messages: [{ role: "user", content: "hi" }] },
stream: false,
signal: null,
credentials: { apiKey: "k", accessToken: null, connectionId: "c" },
log: { debug() {}, info() {}, warn() {}, error() {} },
})) as { response: Response };
assert.equal(seen[0]?.stream, true, "the upstream request was streamed");
assert.equal((seen[0]?.tools as unknown[]).length, 1, "and carried the placeholder tool");
assert.match(result.response.headers.get("content-type") ?? "", /application\/json/);
const json = (await result.response.json()) as {
choices?: Array<{ message?: { content?: string } }>;
};
assert.equal(json.choices?.[0]?.message?.content, "hi");
} finally {
globalThis.fetch = originalFetch;
}
});
test("a streaming caller still receives the event stream untouched", async () => {
const executor = new OpencodeExecutor("opencode-zen");
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () =>
new Response('data: {"choices":[{"delta":{"content":"hi"}}]}\n\n', {
status: 200,
headers: { "Content-Type": "text/event-stream" },
})) as typeof globalThis.fetch;
try {
const result = (await executor.execute({
model: "nemotron-3.5-lightning-free",
body: {
model: "nemotron-3.5-lightning-free",
messages: [{ role: "user", content: "hi" }],
stream: true,
},
stream: true,
signal: null,
credentials: { apiKey: "k", accessToken: null, connectionId: "c" },
log: { debug() {}, info() {}, warn() {}, error() {} },
})) as { response: Response };
assert.match(result.response.headers.get("content-type") ?? "", /text\/event-stream/);
await result.response.body?.cancel();
} finally {
globalThis.fetch = originalFetch;
}
});
test("a gated request repairs a stale configured user-agent, a non-gated one keeps it", () => {
// An operator still carrying the previous default in OPENCODE_USER_AGENT would otherwise
// keep sending `opencode`, which the free tier refuses. Outside the gate the configured
// value is still honoured verbatim (#5997).
const saved = process.env.OPENCODE_USER_AGENT;
process.env.OPENCODE_USER_AGENT = "opencode";
try {
const executor = new OpencodeExecutor("opencode-zen");
const gated = executor.buildHeaders(null, true, null, "nemotron-3.5-lightning-free");
assert.equal(gated["User-Agent"], DEFAULT_OPENCODE_USER_AGENT);
assert.ok(satisfiesOpencodeUserAgentContract(gated["User-Agent"]));
const paid = executor.buildHeaders(null, true, null, "gpt-5.6-luna");
assert.equal(paid["User-Agent"], "opencode");
} finally {
if (saved === undefined) delete process.env.OPENCODE_USER_AGENT;
else process.env.OPENCODE_USER_AGENT = saved;
}
});
test("responses: two conversations on the same model get different session ids", () => {
const executor = new OpencodeExecutor("opencode");
executor._requestFormat = "openai-responses";
const sessionFor = (text: string) =>
executor.buildHeaders(null, true, null, "muse-spark-1.3-contributor-free", undefined, {
model: "muse-spark-1.3-contributor-free",
input: [{ role: "user", content: text }],
})["x-opencode-session"];
const first = sessionFor("first conversation");
assert.match(first ?? "", OPENCODE_SESSION_PATTERN);
assert.equal(sessionFor("first conversation"), first, "same conversation, same session");
assert.notEqual(
sessionFor("a completely different opening message"),
first,
"the Responses surface carries the conversation under input, not messages"
);
});
// ── The placeholder is resolved from what the upstream is currently accepting ──────────
test("the opt-out switches the body half off and leaves the scope predicate alone", () => {
const previous = process.env.OPENCODE_FREE_TIER_REQUEST_CONTRACT;
try {
process.env.OPENCODE_FREE_TIER_REQUEST_CONTRACT = "off";
assert.equal(
requiresFreeTierRequestContract("zen", "opencode", "nemotron-3.5-lightning-free"),
false
);
assert.equal(isGatedFreeTierRequest("zen", "opencode", "nemotron-3.5-lightning-free"), true);
const { body, attempt } = prepareFreeTierRequest(
CHAT_BODY(),
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free"
);
assert.equal(attempt, null);
assert.equal("tools" in (body as Record<string, unknown>), false);
assert.equal("stream" in (body as Record<string, unknown>), false);
} finally {
if (previous === undefined) delete process.env.OPENCODE_FREE_TIER_REQUEST_CONTRACT;
else process.env.OPENCODE_FREE_TIER_REQUEST_CONTRACT = previous;
}
});
test("configured placeholder names are parsed, bad entries dropped, unset stays empty", () => {
const previous = process.env.OPENCODE_FREE_TIER_PLACEHOLDER_TOOLS;
try {
delete process.env.OPENCODE_FREE_TIER_PLACEHOLDER_TOOLS;
assert.deepEqual(configuredPlaceholderToolNames(), []);
process.env.OPENCODE_FREE_TIER_PLACEHOLDER_TOOLS = " glob , grep ,,9bad, glob ,ok-name";
assert.deepEqual(configuredPlaceholderToolNames(), ["glob", "grep", "ok-name"]);
} finally {
if (previous === undefined) delete process.env.OPENCODE_FREE_TIER_PLACEHOLDER_TOOLS;
else process.env.OPENCODE_FREE_TIER_PLACEHOLDER_TOOLS = previous;
}
});
test("with nothing observed and nothing configured, the built-in placeholder is used", () => {
_resetToolObservationForTests();
const { body, attempt } = prepareFreeTierRequest(
CHAT_BODY(),
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free"
);
const tools = (body as Record<string, unknown>).tools as Array<{ function: { name: string } }>;
assert.deepEqual(
tools.map((t) => t.function.name),
["_noop"]
);
// Nothing was borrowed, so a refusal here must not be charged against the store.
assert.equal(attempt?.borrowed, false);
});
test("an accepted request teaches the names it carried, and a later bare request borrows them", () => {
_resetToolObservationForTests();
const withTools = {
...CHAT_BODY(),
tools: [
{ type: "function", function: { name: "glob" } },
{ type: "function", function: { name: "grep" } },
],
};
const first = prepareFreeTierRequest(
withTools,
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free"
);
assert.deepEqual(first.attempt?.clientToolNames, ["glob", "grep"]);
noteFreeTierOutcome(first.attempt, true);
assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), [
"glob",
"grep",
]);
const second = prepareFreeTierRequest(
CHAT_BODY(),
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free"
);
const tools = (second.body as Record<string, unknown>).tools as Array<{
function: { name: string };
}>;
assert.deepEqual(
tools.map((t) => t.function.name),
["glob", "grep"]
);
assert.equal(second.attempt?.borrowed, true);
});
test("what one model learns stays with that model", () => {
// Asserts an absence, so it stays green if the store is removed entirely — it guards
// against cross-model leakage, not against the mechanism disappearing.
_resetToolObservationForTests();
const learned = prepareFreeTierRequest(
{ ...CHAT_BODY(), tools: [{ type: "function", function: { name: "glob" } }] },
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free"
);
noteFreeTierOutcome(learned.attempt, true);
assert.equal(getObservedToolNames("opencode", "big-pickle"), null);
assert.equal(getObservedToolNames("opencode-zen", "nemotron-3.5-lightning-free"), null);
});
test("a borrowed set is dropped after three refusals in a row, not after one", () => {
_resetToolObservationForTests();
const learned = prepareFreeTierRequest(
{ ...CHAT_BODY(), tools: [{ type: "function", function: { name: "glob" } }] },
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free"
);
noteFreeTierOutcome(learned.attempt, true);
const borrow = () =>
prepareFreeTierRequest(CHAT_BODY(), "openai", "zen", "opencode", "nemotron-3.5-lightning-free")
.attempt;
// One refusal is not proof: the same body was refused and then accepted minutes apart on
// a free model (measured 2026-09-18), so a single verdict must not clear the entry.
noteFreeTierOutcome(borrow(), false);
assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]);
noteFreeTierOutcome(borrow(), false);
assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]);
noteFreeTierOutcome(borrow(), false);
assert.equal(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), null);
});
test("an acceptance resets the refusal streak", () => {
_resetToolObservationForTests();
const withTools = {
...CHAT_BODY(),
tools: [{ type: "function", function: { name: "glob" } }],
};
const learned = prepareFreeTierRequest(
withTools,
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free"
);
noteFreeTierOutcome(learned.attempt, true);
const borrow = () =>
prepareFreeTierRequest(CHAT_BODY(), "openai", "zen", "opencode", "nemotron-3.5-lightning-free")
.attempt;
noteFreeTierOutcome(borrow(), false);
noteFreeTierOutcome(borrow(), false);
noteFreeTierOutcome(
prepareFreeTierRequest(withTools, "openai", "zen", "opencode", "nemotron-3.5-lightning-free")
.attempt,
true
);
noteFreeTierOutcome(borrow(), false);
assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]);
});
test("a refusal on the caller's own tools never touches the store", () => {
_resetToolObservationForTests();
const withTools = {
...CHAT_BODY(),
tools: [{ type: "function", function: { name: "glob" } }],
};
noteFreeTierOutcome(
prepareFreeTierRequest(withTools, "openai", "zen", "opencode", "nemotron-3.5-lightning-free")
.attempt,
true
);
for (let i = 0; i < 5; i++) {
const own = prepareFreeTierRequest(
withTools,
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free"
);
assert.equal(own.attempt?.borrowed, false);
noteFreeTierOutcome(own.attempt, false);
}
assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]);
});
test("the other surface is left alone entirely", () => {
// Scope guard only. That `/zen/go/v1` refuses a request carrying tools comes from
// upstream anomalyco/opencode#44300 and #44382, not from a measurement here: this
// installation exposes no model on that surface.
_resetToolObservationForTests();
const { body, attempt } = prepareFreeTierRequest(
CHAT_BODY(),
"openai",
"go",
"opencode-go",
"ox-alpha-free"
);
assert.equal(attempt, null);
assert.equal("tools" in (body as Record<string, unknown>), false);
assert.equal("stream" in (body as Record<string, unknown>), false);
});
// ── A conversation repairs its own service requests ───────────────────────────────────
const SES_A = "ses_0123456789abABCDEFGHIJKLMN";
const SES_B = "ses_fedcba987654ZYXWVUTSRQPONM";
test("a session's service request borrows the list its own build request declared", () => {
_resetToolObservationForTests();
// The official client sends its build requests with a full tool list and its title and
// compaction requests with none, on the same session (upstream
// anomalyco/opencode#49433). The second gets back what the first declared.
const build = prepareFreeTierRequest(
{
...CHAT_BODY(),
tools: [
{ type: "function", function: { name: "glob" } },
{ type: "function", function: { name: "grep" } },
],
},
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free",
SES_A
);
noteFreeTierOutcome(build.attempt, true);
const title = prepareFreeTierRequest(
CHAT_BODY(),
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free",
SES_A
);
const tools = (title.body as Record<string, unknown>).tools as Array<{
function: { name: string };
}>;
assert.deepEqual(
tools.map((t) => t.function.name),
["glob", "grep"]
);
assert.equal(title.attempt?.borrowed, true);
});
test("a session's own list wins over the shared one", () => {
_resetToolObservationForTests();
// Order matters: the session entry is recorded FIRST and the shared entry overwritten
// afterwards by another conversation, so a store keyed by model alone would answer
// "shared" here and this case would fail.
const own = prepareFreeTierRequest(
{ ...CHAT_BODY(), tools: [{ type: "function", function: { name: "own" } }] },
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free",
SES_A
);
noteFreeTierOutcome(own.attempt, true);
const shared = prepareFreeTierRequest(
{ ...CHAT_BODY(), tools: [{ type: "function", function: { name: "shared" } }] },
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free",
SES_B
);
noteFreeTierOutcome(shared.attempt, true);
const borrowed = prepareFreeTierRequest(
CHAT_BODY(),
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free",
SES_A
);
const tools = (borrowed.body as Record<string, unknown>).tools as Array<{
function: { name: string };
}>;
assert.deepEqual(
tools.map((t) => t.function.name),
["own"]
);
});
test("a session that has declared nothing yet falls back to the shared entry", () => {
_resetToolObservationForTests();
noteFreeTierOutcome(
prepareFreeTierRequest(
{ ...CHAT_BODY(), tools: [{ type: "function", function: { name: "shared" } }] },
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free",
SES_A
).attempt,
true
);
const fresh = prepareFreeTierRequest(
CHAT_BODY(),
"openai",
"zen",
"opencode",
"nemotron-3.5-lightning-free",
SES_B
);
const tools = (fresh.body as Record<string, unknown>).tools as Array<{
function: { name: string };
}>;
assert.deepEqual(
tools.map((t) => t.function.name),
["shared"]
);
});
test("a client session id is read case-insensitively, a synthesized one is not borrowed from", () => {
assert.equal(clientSuppliedOpencodeSession({ "X-OpenCode-Session": SES_A }), SES_A);
assert.equal(clientSuppliedOpencodeSession({ "x-session-id": SES_B }), SES_B);
assert.equal(clientSuppliedOpencodeSession({ "x-opencode-session": " " }), undefined);
assert.equal(clientSuppliedOpencodeSession({}), undefined);
assert.equal(clientSuppliedOpencodeSession(undefined), undefined);
});