Compare commits

...

3 Commits

Author SHA1 Message Date
Xiangzhe
7b1d786bd1 test(services): align adoption tests with the opt-in contract from #11040
The #11040 merge changed decidePreSpawn() to opt-in adoption
(GHSA-wg9p-6m2g-4v27: a 2xx on the probed port cannot prove the listener
is this service), but the two integration tests in ServiceSupervisor.test.ts
still asserted adopt-by-default, leaving the release tip red:

- #6205: probeBeforeSpawn adopts a healthy existing instance (no spawn)
- adopted service resolves and records the real pid of the process holding the port

Both now set OMNIROUTE_ADOPT_EXISTING_SERVICE=1 (restored in finally) so the
adoption path they exercise stays covered under the new contract. Adds a new
default-deny case asserting that without the flag a healthy listener is NOT
adopted and the error names the opt-in escape hatch.

Verified against base tip 6cd4d38e21: file is 8/8 green,
ninerouter-embed-port-6205.test.ts still 9/9, eslint + prettier clean.
2026-08-22 13:03:36 -03:00
N123 Project
6cd4d38e21 fix(m365): BizChat invocation shape drift + HAR-import UX + Antigravity alias note (#11069)
5 — M365 Copilot (BizChat) individual/consumer path — 3 itens: (1) forma de invocação do #10718 derivou de novo (2026-08-21 capture): optionsSets 14→34, allowedMessageTypes 6→30, tone "magic"→"Magic", plugins []→[{BingWebSearch}], disconnectBehavior em todos os tiers, +8 keys de clientInfo; verificado contra conta real com round-trip WebSocket (ping-then-close → resposta real). (2) Aviso sobre o alias Antigravity gemini-3.1-pro-high ainda não publicado (3.8.49 pré-data). (3) Botão "Import .har file" no modal de credencial M365.

Conflito resolvido em copilot-m365-frames.ts (board vs release tip): mantive o forwarding de opts.plugins/toolChoice/customInstructions do HEAD com os NOVOS defaults da captura (BingWebSearch builtin, tone "Magic"). Alinhei 3 testes pré-existentes que afirmavam o contrato antigo (m365-bizchat-frames-4042 clientInfo, m365-tone-model-variants tone, copilot-m365-tool-calls plugins) — propagação de contrato, não mascaramento. Rebaselinei AddApiKeyModal 1073→1080 (crescimento próprio da parte 3, ~Har import button) com anotação.

Validação: typecheck limpo, 142/142 testes m365/copilot verdes, changelog-integrit/file-size/eslint OK.
2026-08-21 22:32:19 -03:00
Diego Rodrigues de Sa e Souza
b6412c6fed fix(command-code): use the documented /provider/v1 chat endpoint (#10265) (#11072)
5 — Fecha #10265: chat do command-code migra do endpoint CLI-only /alpha/generate (version-gated + proxy-blocked para callers externos) para o documentado /provider/v1/chat/completions (OpenAI format). Removido o envelope CLI reverse-engineered (config/memory/taste/skills + headers CLI-impersonation), substituído por passthrough OpenAI plano com normalização de model id vendor-prefixed (#10809), clamp de max_tokens (#5166), sanitização de reasoning_effort. commandCode.ts 1037→171 linhas.
Validado no worktree board sobre tip: typecheck:core limpo; 175/175 testes focados (command-code executor/vision/usage/maxtokens/user-array/validation-specialty/responses-handler/provider-models-scoping); changelog-integrity/file-size/complexity/cognitive todos OK. TDD RED→GREEN documentado.
2026-08-21 22:16:45 -03:00
29 changed files with 1212 additions and 2671 deletions

View File

@@ -0,0 +1 @@
- fix(command-code): route chat to the documented /provider/v1/chat/completions endpoint instead of the CLI-only /alpha/generate, which Command Code gates/blocks for external callers (#10265)

View File

@@ -443,7 +443,7 @@
"src/shared/components/ModelSelectModal.tsx": 1138, "src/shared/components/ModelSelectModal.tsx": 1138,
"src/shared/constants/providers/apikey/gateways.ts": 1250 "src/shared/constants/providers/apikey/gateways.ts": 1250
}, },
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1073, "src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1080,
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1051, "src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1051,
"src/shared/components/ModelSelectModal.tsx": 1138, "src/shared/components/ModelSelectModal.tsx": 1138,
"src/shared/constants/providers/apikey/gateways.ts": 1298, "src/shared/constants/providers/apikey/gateways.ts": 1298,
@@ -459,7 +459,8 @@
"open-sse/executors/commandCode.ts": 1059, "open-sse/executors/commandCode.ts": 1059,
"_rebaseline_2026_08_21_10859_vision_bridge_catalog": "#10859 own growth (Vision Bridge fixes #10808/#10809): src/lib/modelCapabilities.ts 1006->1016 (+10, cmd/gpt-5.3-codex* text-only capability resolution) and open-sse/executors/commandCode.ts 988->1023 (+35, Command Code wire-model normalization for bare ids + reasoning field fallback for opencode-routed gateways). Cohesive bug fixes at the existing capability-resolution / executor chokepoints; not extractable mid-fix. Covered by tests/unit/model-capabilities-command-code-codex-textonly-10703.test.ts, tests/unit/command-code-vision.test.ts, tests/unit/opencode-mimo-reasoning-details-nonstream.test.ts. Pushed directly to release (own-session miss: the original rebaseline was made in a throwaway validation worktree and never landed on the PR branch or the release before merge).", "_rebaseline_2026_08_21_10859_vision_bridge_catalog": "#10859 own growth (Vision Bridge fixes #10808/#10809): src/lib/modelCapabilities.ts 1006->1016 (+10, cmd/gpt-5.3-codex* text-only capability resolution) and open-sse/executors/commandCode.ts 988->1023 (+35, Command Code wire-model normalization for bare ids + reasoning field fallback for opencode-routed gateways). Cohesive bug fixes at the existing capability-resolution / executor chokepoints; not extractable mid-fix. Covered by tests/unit/model-capabilities-command-code-codex-textonly-10703.test.ts, tests/unit/command-code-vision.test.ts, tests/unit/opencode-mimo-reasoning-details-nonstream.test.ts. Pushed directly to release (own-session miss: the original rebaseline was made in a throwaway validation worktree and never landed on the PR branch or the release before merge).",
"_rebaseline_2026_08_21_10907_sticky_pin_clear": "#10907 own growth: open-sse/executors/commandCode.ts 1023->1038 (+15, effort-suffix sanitization threading for the sticky-pin-clear fix). Cohesive change at the existing executor chokepoint. Covered by tests/unit/command-code-executor.test.ts.", "_rebaseline_2026_08_21_10907_sticky_pin_clear": "#10907 own growth: open-sse/executors/commandCode.ts 1023->1038 (+15, effort-suffix sanitization threading for the sticky-pin-clear fix). Cohesive change at the existing executor chokepoint. Covered by tests/unit/command-code-executor.test.ts.",
"_rebaseline_2026_08_21_10986_reasoning_only_content": "#10986 own growth: open-sse/executors/commandCode.ts 1038->1059 (+21, reasoning-only content fallback — when upstream emits only reasoning-delta events and never a text-delta, surface the reasoning text as message.content in createJsonResponse and emit a synthetic content delta in createStreamResponse). Cohesive bug fix at the existing executor chokepoint (mirrors precedent style of #10907/#10859). Covered by tests/unit/command-code-executor.test.ts (2 new cases: non-stream + streaming)." "_rebaseline_2026_08_21_10986_reasoning_only_content": "#10986 own growth: open-sse/executors/commandCode.ts 1038->1059 (+21, reasoning-only content fallback — when upstream emits only reasoning-delta events and never a text-delta, surface the reasoning text as message.content in createJsonResponse and emit a synthetic content delta in createStreamResponse). Cohesive bug fix at the existing executor chokepoint (mirrors precedent style of #10907/#10859). Covered by tests/unit/command-code-executor.test.ts (2 new cases: non-stream + streaming).",
"_rebaseline_2026_08_21_11069_m365_har_import": "#11069 own growth: AddApiKeyModal.tsx 1073->1080 (+7 = Import .har file button for the copilot-m365-web credential modal — M365 is the only provider whose credential (access_token+chathubPath) must be extracted from a DevTools HAR WebSocket URL, added as a new modal affordance). Cohesive UI at the existing modal chokepoint; not extractable. Covered by tests/unit/m365-har-import*.test.ts."
}, },
"_rebaseline_base_2026_08_10_proxyfetch": "Base-red fix (green-prs sweep, issue #9985): open-sse/utils/proxyFetch.ts 1207 > cap 1000 — new proxied-TLS fetch helper introduced by the Fal reference-image work. Owner-authorized quick rebaseline to green; structural slim tracked for v3.9.0.", "_rebaseline_base_2026_08_10_proxyfetch": "Base-red fix (green-prs sweep, issue #9985): open-sse/utils/proxyFetch.ts 1207 > cap 1000 — new proxied-TLS fetch helper introduced by the Fal reference-image work. Owner-authorized quick rebaseline to green; structural slim tracked for v3.9.0.",
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).", "_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",

View File

@@ -8,7 +8,11 @@ export const command_codeProvider: RegistryEntry = {
format: "openai", format: "openai",
executor: "command-code", executor: "command-code",
baseUrl: "https://api.commandcode.ai", baseUrl: "https://api.commandcode.ai",
chatPath: "/alpha/generate", // Chat uses the documented /provider/v1/chat/completions (OpenAI-format)
// endpoint — NOT the CLI-only /alpha/generate endpoint, which Command Code
// version-gates and proxy-blocks for external callers (#10265). Discovery
// already targets the sibling /provider/v1/models endpoint.
chatPath: "/provider/v1/chat/completions",
modelsUrl: "https://api.commandcode.ai/provider/v1/models", modelsUrl: "https://api.commandcode.ai/provider/v1/models",
// The discovery response is a partial routing catalog; static registry // The discovery response is a partial routing catalog; static registry
// entries omitted from it can still be accepted by the gateway. // entries omitted from it can still be accepted by the gateway.

File diff suppressed because it is too large Load Diff

View File

@@ -30,17 +30,45 @@ export const HANDSHAKE_REQUEST = { protocol: "json", version: 1 } as const;
export const KEEPALIVE_PING = { type: 6 } as const; export const KEEPALIVE_PING = { type: 6 } as const;
/** /**
* Allowed message types observed in the 2026-08 recapture of the working * Allowed message types observed in a 2026-08-21 live capture of a working
* `m365.cloud.microsoft/chat` client (#10718). The old 11-entry list is no longer * `m365.cloud.microsoft/chat` session (issue: "Stream ended before producing a
* seen on the wire — the stale shape gets closed immediately after the type:4. * non-ping SSE event" on every individual/consumer M365 Copilot call). The
* #10718 6-entry shape above no longer produces a `type:1 target:"update"`
* frame at all — the socket only replies with SignalR keepalive pings and then
* closes, which is exactly what surfaces client-side as that generic stream
* error. 30 entries, up from 6.
*/ */
export const ALLOWED_MESSAGE_TYPES = [ export const ALLOWED_MESSAGE_TYPES = [
"Chat", "Chat",
"Suggestion", "Suggestion",
"InternalSearchQuery",
"Disengaged", "Disengaged",
"Progress",
"EndOfRequest",
"InternalLoaderMessage", "InternalLoaderMessage",
"Progress",
"GeneratedCode",
"RenderCardRequest",
"AdsQuery",
"SemanticSerp",
"GenerateContentQuery",
"GenerateGraphicArt",
"SearchQuery",
"ConfirmationCard",
"AuthError",
"DeveloperLogs",
"TriggerPlugin",
"HintInvocation",
"MemoryUpdate",
"EndOfRequest",
"TriggerConfirmation",
"ResumeInvokeAction",
"ResumeUserInputRequest",
"TriggerUserInputRequest",
"EscapeHatch",
"TriggerPluginAuth",
"ResumePluginAuth",
"SideBySide",
"ReferencesListComplete",
"SwitchRespondingEndpoint",
] as const; ] as const;
/** /**
@@ -78,19 +106,26 @@ export const M365_ENTERPRISE_EXTRA_MESSAGE_TYPES = [
] as const; ] as const;
/** /**
* Individual / EDU option sets from the 2026-08 recapture (#10718)14 entries. * Individual / EDU option sets from a 2026-08-21 live capture34 entries, up
* The previous 25-entry consumer/MSA set (enable_msa_user, pdnascan, cwc_code_*, * from the #10718 14-entry shape (which itself superseded an earlier 25-entry
* …) is no longer observed on the wire and belongs to the shape the substrate * shape). Each recapture so far has been additive/reshuffled rather than a
* now drops silently. * wholesale replacement — treat this as the protocol continuing to drift, not
* a one-time fix; a future capture may again need to update this list.
*/ */
export const M365_DEFAULT_OPTION_SETS = [ export const M365_DEFAULT_OPTION_SETS = [
"search_result_progress_messages_with_search_queries", "search_result_progress_messages_with_search_queries",
"update_textdoc_response_after_streaming", "update_textdoc_response_after_streaming",
"deepleo_networking_timeout_10minutes_canmore", "deepleo_networking_timeout_10minutes_canmore",
"cwc_flux_image", "cwc_flux_image",
"cwc_code_interpreter",
"cwc_code_interpreter_amsfix",
"cwcfluxgptv", "cwcfluxgptv",
"flux_v3_gptv_enable_upload_multi_image_in_turn_wo_ch", "flux_v3_gptv_enable_upload_multi_image_in_turn_wo_ch",
"gptvnorm2048", "gptvnorm2048",
"cwc_code_interpreter_citation_fix",
"code_interpreter_interactive_charts",
"cwc_code_interpreter_interactive_charts_inline_image",
"code_interpreter_matplotlib_patching",
"cwc_fileupload_odb", "cwc_fileupload_odb",
"update_memory_plugin", "update_memory_plugin",
"add_custom_instructions", "add_custom_instructions",
@@ -98,6 +133,20 @@ export const M365_DEFAULT_OPTION_SETS = [
"flux_v3_progress_messages", "flux_v3_progress_messages",
"enable_batch_token_processing", "enable_batch_token_processing",
"enable_gg_gpt", "enable_gg_gpt",
"async_client_interaction",
"flux_v3_references",
"flux_v3_references_entities",
"flux_v3_references_ci",
"add_filestore_filetype",
"cwc_code_interpreter_citation_sourceannotations",
"cdxcwc_code_interpreter_hallucinated_url_filter",
"flux_v3_image_gen_enable_dimensions",
"flux_v3_image_gen_enable_non_watermarked_storage",
"flux_v3_image_gen_enable_icon_dimensions",
"flux_v3_image_gen_enable_system_text_with_params",
"flux_v3_image_gen_enable_designer_dimensions_meta_prompting_in_system_prompts",
"flux_v3_image_gen_enable_story",
"rich_responses",
] as const; ] as const;
/** Append the record separator to a JSON-serializable frame. */ /** Append the record separator to a JSON-serializable frame. */
@@ -433,12 +482,14 @@ export function resolveChatInvocationOverrides(tier: string | undefined): {
} }
return { return {
optionsSets: [...M365_DEFAULT_OPTION_SETS], optionsSets: [...M365_DEFAULT_OPTION_SETS],
// #10718 — the 2026-08 recapture sends tone:"magic" (lowercase) on the // 2026-08-21 capture — the individual/consumer surface now sends "Magic"
// individual/EDU surface; the old "" default is part of the dropped shape. // (capitalized), matching the enterprise tone literal. The #10718
tone: "magic", // lowercase "magic" is part of the shape that gets silently dropped.
tone: "Magic",
allowedMessageTypes: ALLOWED_MESSAGE_TYPES, allowedMessageTypes: ALLOWED_MESSAGE_TYPES,
// Omitted entirely on the individual/EDU wire (see ChatInvocationOptions). // 2026-08-21 capture — disconnectBehavior:"continue" is now present on the
disconnectBehavior: undefined, // individual/consumer wire too, not just enterprise (see ChatInvocationOptions).
disconnectBehavior: "continue",
}; };
} }
@@ -467,16 +518,33 @@ export function resolveToneForModel(model: string | undefined): string | undefin
/** /**
* Build the `type:4` chat invocation frame body (not yet `\x1e`-terminated). * Build the `type:4` chat invocation frame body (not yet `\x1e`-terminated).
* Mirrors the argument shape recaptured from a working `m365.cloud.microsoft/chat` * Base shape from the #10718 recapture (populated `clientInfo` +
* client in 2026-08 (#10718). Notable differences from the pre-#10718 shape: a * `productThreadType:"Office"`, a `conversationId` matching the WS URL query, a
* populated `clientInfo` + `productThreadType:"Office"`, a `conversationId` * rich `message` object), extended per a 2026-08-21 live capture that found the
* matching the WS URL query, a rich `message` object, and no * #10718 shape alone no longer produces a `type:1 target:"update"` frame — the
* `spokenTextMode` / `extraExtensionParameters` / `isSbsSupported` / * socket only replies with keepalive pings and closes. The additions below
* `renderReferencesBehindEOS` / `disconnectBehavior` — none of those are still * (richer `clientInfo`, non-empty `plugins`, `extraExtensionParameters`,
* observed on the wire, and the stale shape gets closed immediately after the * `isSbsSupported`, `renderReferencesBehindEOS`,
* invocation. * `message.connectedFederatedConnections`, and `disconnectBehavior` on every
* tier) are exactly the fields the 2026-08-21 capture had that this shape was
* missing; the #10718 fields (`conversationId`, `productThreadType`,
* `toolChoice`, `message.attachments`) are kept as-is since removing them was
* not verified against a live socket.
*/ */
export function buildChatInvocation(opts: ChatInvocationOptions): Record<string, unknown> { export function buildChatInvocation(opts: ChatInvocationOptions): Record<string, unknown> {
const clientInfo = {
clientAppName: "Office",
clientPlatform: "mcmcopilot-web",
clientEntrypoint: "mcmcopilot-officeweb",
clientSessionId: opts.sessionId,
ProductCategory: "Chat",
clientAppType: "Web",
productEntryPoint: "ChatPanel",
deviceOS: "Windows",
deviceType: "Desktop",
clientPlatformVersion: "10",
};
return { return {
type: 4, type: 4,
target: "chat", target: "chat",
@@ -487,17 +555,17 @@ export function buildChatInvocation(opts: ChatInvocationOptions): Record<string,
? [...opts.allowedMessageTypes] ? [...opts.allowedMessageTypes]
: [...ALLOWED_MESSAGE_TYPES], : [...ALLOWED_MESSAGE_TYPES],
clientCorrelationId: opts.clientCorrelationId ?? opts.traceId, clientCorrelationId: opts.clientCorrelationId ?? opts.traceId,
clientInfo: { clientInfo,
clientAppName: "Office",
clientPlatform: "mcmcopilot-web",
},
conversationId: opts.conversationId, conversationId: opts.conversationId,
extraExtensionParameters: {},
isStartOfSession: opts.isStartOfSession ?? true, isStartOfSession: opts.isStartOfSession ?? true,
message: { message: {
adaptiveCards: [], adaptiveCards: [],
attachments: null, attachments: null,
author: "user", author: "user",
clientInfo,
clientPreferences: {}, clientPreferences: {},
connectedFederatedConnections: ["dummyId"],
entityAnnotationTypes: ["People", "File", "Event", "Email", "TeamsMessage"], entityAnnotationTypes: ["People", "File", "Event", "Email", "TeamsMessage"],
experienceType: "Default", experienceType: "Default",
inputMethod: "Keyboard", inputMethod: "Keyboard",
@@ -510,22 +578,27 @@ export function buildChatInvocation(opts: ChatInvocationOptions): Record<string,
requestId: opts.requestId, requestId: opts.requestId,
text: opts.text, text: opts.text,
}, },
isSbsSupported: true,
options: {}, options: {},
optionsSets: opts.optionsSets ?? [...M365_DEFAULT_OPTION_SETS], optionsSets: opts.optionsSets ?? [...M365_DEFAULT_OPTION_SETS],
plugins: opts.plugins ?? [], // 2026-08-21 capture (#11069): BingWebSearch is now the universal
// BuiltIn plugin on individual/consumer tier; keep an opt-out override.
plugins: opts.plugins ?? [{ Id: "BingWebSearch", Source: "BuiltIn" }],
...(opts.customInstructions ? { customInstructions: opts.customInstructions } : {}), ...(opts.customInstructions ? { customInstructions: opts.customInstructions } : {}),
productThreadType: "Office", productThreadType: "Office",
renderReferencesBehindEOS: true,
sessionId: opts.sessionId, sessionId: opts.sessionId,
sliceIds: [], sliceIds: [],
source: "officeweb", source: "officeweb",
streamingMode: "ConciseWithPadding", streamingMode: "ConciseWithPadding",
threadLevelGptId: {}, threadLevelGptId: {},
tone: opts.tone ?? "magic", // 2026-08-21 capture (#11069): tone is now capitalized "Magic" on both tiers.
tone: opts.tone ?? "Magic",
toolChoice: opts.toolChoice ?? null, toolChoice: opts.toolChoice ?? null,
traceId: opts.traceId, traceId: opts.traceId,
// #8971 keeps "continue" for the enterprise tier; the individual/EDU wire // 2026-08-21 capture — disconnectBehavior:"continue" is sent on every
// omits the key, so only include it when actually set (#10718). // tier now, not gated to enterprise as the #8971 comment described.
...(opts.disconnectBehavior ? { disconnectBehavior: opts.disconnectBehavior } : {}), disconnectBehavior: opts.disconnectBehavior ?? "continue",
}, },
], ],
}; };

View File

@@ -12,8 +12,13 @@ import {
} from "@/lib/combos/intelligentRouting"; } from "@/lib/combos/intelligentRouting";
import { AI_PROVIDERS } from "@/shared/constants/providers"; import { AI_PROVIDERS } from "@/shared/constants/providers";
function getI18nOrFallback(t: any, key: string, fallback: string) { function getI18nOrFallback(
if (typeof t?.has === "function" && t.has(key)) return t(key); t: any,
key: string,
fallback: string,
values?: Record<string, unknown>
) {
if (typeof t?.has === "function" && t.has(key)) return t(key, values);
return fallback; return fallback;
} }
@@ -94,10 +99,9 @@ export default function IntelligentComboPanel({
const updatedCombo = await response.json(); const updatedCombo = await response.json();
onComboUpdated?.(updatedCombo); onComboUpdated?.(updatedCombo);
notify.success( notify.success(
getI18nOrFallback(t, "modePackUpdated", "Mode pack updated to {pack}.").replace( getI18nOrFallback(t, "modePackUpdated", "Mode pack updated to {pack}.", {
"{pack}", pack: modePackId,
modePackId }).replace("{pack}", modePackId)
)
); );
} catch (error: any) { } catch (error: any) {
notify.error(error?.message || "Failed to update mode pack."); notify.error(error?.message || "Failed to update mode pack.");
@@ -184,10 +188,9 @@ export default function IntelligentComboPanel({
</div> </div>
{savingModePack && ( {savingModePack && (
<span className="text-[11px] text-text-muted"> <span className="text-[11px] text-text-muted">
{getI18nOrFallback(t, "savingModePack", "Saving {pack}…").replace( {getI18nOrFallback(t, "savingModePack", "Saving {pack}…", {
"{pack}", pack: savingModePack,
savingModePack }).replace("{pack}", savingModePack)}
)}
</span> </span>
)} )}
</div> </div>

View File

@@ -533,9 +533,9 @@ function getStrategyBadgeClass(strategy) {
return "bg-blue-500/15 text-blue-600 dark:text-blue-400"; return "bg-blue-500/15 text-blue-600 dark:text-blue-400";
} }
function getI18nOrFallback(t, key, fallback) { function getI18nOrFallback(t, key, fallback, values) {
try { try {
if (typeof t.has === "function" && t.has(key)) return t(key); if (typeof t.has === "function" && t.has(key)) return t(key, values);
} catch {} } catch {}
return fallback; return fallback;
} }
@@ -1565,7 +1565,8 @@ function StrategyRecommendationsPanel({ strategy, onApply, showNudge }) {
{getI18nOrFallback( {getI18nOrFallback(
t, t,
"recommendationsUpdated", "recommendationsUpdated",
"Recommendations updated for {strategy}." "Recommendations updated for {strategy}.",
{ strategy: strategyLabel }
).replace("{strategy}", strategyLabel)} ).replace("{strategy}", strategyLabel)}
</div> </div>
)} )}

View File

@@ -0,0 +1,165 @@
"use client";
import { useRef, useState } from "react";
import { useTranslations } from "next-intl";
import {
extractM365CredentialFromHar,
describeHarImportExpiry,
type M365HarImportResult,
} from "@/shared/utils/m365HarImport";
import { providerText, type ProviderMessageTranslator } from "../providerPageHelpers";
type HarImporter = (text: string) => M365HarImportResult;
// One entry per web-session provider that can offer HAR import. Add a new
// key here (and its own extractor in src/shared/utils/) to support another
// provider — the button renders nothing for any provider not listed.
const HAR_IMPORTERS: Record<string, HarImporter> = {
"copilot-m365-web": extractM365CredentialFromHar,
};
const ERROR_MESSAGE_KEYS: Record<string, [string, string]> = {
notJson: ["harImportErrorNotJson", "That file isn't valid JSON — is it really a .har export?"],
noEntries: ["harImportErrorNoEntries", "This HAR has no network entries recorded."],
noChathubUrl: [
"harImportErrorNoChathubUrl",
"No Copilot chat connection found in this HAR. Send at least one chat message in m365.cloud.microsoft before exporting.",
],
unparsableUrl: [
"harImportErrorUnparsableUrl",
"Found the chat connection, but couldn't read its URL.",
],
missingFields: [
"harImportErrorMissingFields",
"Found the chat connection, but the token was missing from it.",
],
};
export interface HarImportButtonProps {
provider: string;
onImport: (apiKey: string) => void;
}
export default function HarImportButton({ provider, onImport }: HarImportButtonProps) {
const t = useTranslations("providers") as ProviderMessageTranslator;
const importer = HAR_IMPORTERS[provider];
const fileInputRef = useRef<HTMLInputElement>(null);
const [state, setState] = useState<
| { phase: "idle" }
| { phase: "reading" }
| { phase: "error"; message: string }
| { phase: "success"; expiresAt: number | null }
>({ phase: "idle" });
if (!importer) return null;
async function handleFile(file: File | undefined) {
if (!file) return;
setState({ phase: "reading" });
let text: string;
try {
text = await file.text();
} catch {
setState({
phase: "error",
message: providerText(t, "harImportErrorReadFailed", "Couldn't read that file."),
});
return;
}
const result = importer(text);
if (!result.ok) {
const [key, fallback] = ERROR_MESSAGE_KEYS[result.error] ?? [
"harImportErrorUnknown",
"Couldn't extract a credential from that HAR file.",
];
setState({ phase: "error", message: providerText(t, key, fallback) });
return;
}
onImport(result.apiKey);
setState({ phase: "success", expiresAt: result.expiresAt });
}
const expiry = state.phase === "success" ? describeHarImportExpiry(state.expiresAt) : null;
const expiryText =
expiry?.tone === "unknown"
? providerText(t, "harImportStatusUnknownExpiry", "Imported. Couldn't read its expiry.")
: expiry?.tone === "bad"
? providerText(
t,
"harImportStatusExpired",
"Imported, but this token already expired ({minutes}m ago) — export a fresh HAR.",
{ minutes: Math.abs(expiry.minutesRemaining ?? 0) }
)
: expiry?.tone === "warn"
? providerText(
t,
"harImportStatusExpiringSoon",
"Imported — valid for only ~{minutes}m more.",
{ minutes: expiry.minutesRemaining ?? 0 }
)
: expiry?.tone === "ok"
? providerText(t, "harImportStatusValid", "Imported — valid for ~{minutes}m.", {
minutes: expiry.minutesRemaining ?? 0,
})
: null;
return (
<div className="flex flex-col gap-1.5">
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => fileInputRef.current?.click()}
disabled={state.phase === "reading"}
data-testid="har-import-button"
className="inline-flex items-center gap-1.5 rounded border border-border px-2.5 py-1.5 text-xs font-medium text-text-main hover:bg-surface-hover disabled:opacity-50"
>
<span className="material-symbols-outlined text-[16px]" aria-hidden="true">
upload_file
</span>
{state.phase === "reading"
? providerText(t, "harImportButtonBusy", "Importing…")
: providerText(t, "harImportButtonLabel", "Import .har file")}
</button>
<span className="text-xs text-text-muted">
{providerText(
t,
"harImportButtonHint",
"Export from DevTools Network tab after sending at least one chat message."
)}
</span>
<input
ref={fileInputRef}
type="file"
accept=".har,application/json"
data-testid="har-import-input"
className="hidden"
onChange={(event) => {
void handleFile(event.target.files?.[0]);
event.target.value = "";
}}
/>
</div>
{state.phase === "error" && (
<p className="text-xs text-red-600 dark:text-red-400" data-testid="har-import-error">
{state.message}
</p>
)}
{state.phase === "success" && expiryText && (
<p
className={
expiry?.tone === "bad"
? "text-xs text-red-600 dark:text-red-400"
: expiry?.tone === "warn"
? "text-xs text-amber-700 dark:text-amber-300"
: "text-xs text-emerald-700 dark:text-emerald-300"
}
data-testid="har-import-status"
>
{expiryText}
</p>
)}
</div>
);
}

View File

@@ -31,6 +31,7 @@ import {
import { getWebSessionCredentialRequirement } from "../../webSessionCredentials"; import { getWebSessionCredentialRequirement } from "../../webSessionCredentials";
import { useOpenRouterPresetControl } from "../OpenRouterPresetInput"; import { useOpenRouterPresetControl } from "../OpenRouterPresetInput";
import WebSessionCredentialGuide from "../WebSessionCredentialGuide"; import WebSessionCredentialGuide from "../WebSessionCredentialGuide";
import HarImportButton from "../HarImportButton";
import CcCompatibleRequestDefaultsFields from "./CcCompatibleRequestDefaultsFields"; import CcCompatibleRequestDefaultsFields from "./CcCompatibleRequestDefaultsFields";
import { buildAddProviderSpecificData } from "./connectionProviderSpecificData"; import { buildAddProviderSpecificData } from "./connectionProviderSpecificData";
import { getCommandCodeAuthPhaseLabel } from "./commandCodeAuthPhase"; import { getCommandCodeAuthPhaseLabel } from "./commandCodeAuthPhase";
@@ -209,13 +210,13 @@ export default function AddApiKeyModal({
? "Freebuff uses an authentic CLI auth token obtained via codebuff CLI login or automated harvester." ? "Freebuff uses an authentic CLI auth token obtained via codebuff CLI login or automated harvester."
: isWebSessionCredential : isWebSessionCredential
? getWebSessionCredentialHint(t, webSessionCredential, providerDisplayName, false) ? getWebSessionCredentialHint(t, webSessionCredential, providerDisplayName, false)
: isLocalSelfHostedProvider : isLocalSelfHostedProvider
? t("localProviderApiKeyOptionalHint", { ? t("localProviderApiKeyOptionalHint", {
provider: localProviderMetadata?.name || providerName || provider || "", provider: localProviderMetadata?.name || providerName || provider || "",
}) })
: apiKeyOptional : apiKeyOptional
? t("apiKeyOptionalHint") ? t("apiKeyOptionalHint")
: undefined; : undefined;
const credentialValidationFailedMessage = isWebSessionCredential const credentialValidationFailedMessage = isWebSessionCredential
? providerText( ? providerText(
t, t,
@@ -750,6 +751,12 @@ export default function AddApiKeyModal({
t={t} t={t}
/> />
)} )}
{provider && (
<HarImportButton
provider={provider}
onImport={(apiKey) => setFormData({ ...formData, apiKey })}
/>
)}
{!isNoAuthWebSessionCredential && ( {!isNoAuthWebSessionCredential && (
<div className="flex gap-2"> <div className="flex gap-2">
<Input <Input

View File

@@ -49,6 +49,7 @@ import {
import { getWebSessionCredentialRequirement } from "../../webSessionCredentials"; import { getWebSessionCredentialRequirement } from "../../webSessionCredentials";
import { useOpenRouterPresetControl } from "../OpenRouterPresetInput"; import { useOpenRouterPresetControl } from "../OpenRouterPresetInput";
import WebSessionCredentialGuide from "../WebSessionCredentialGuide"; import WebSessionCredentialGuide from "../WebSessionCredentialGuide";
import HarImportButton from "../HarImportButton";
import CcCompatibleRequestDefaultsFields from "./CcCompatibleRequestDefaultsFields"; import CcCompatibleRequestDefaultsFields from "./CcCompatibleRequestDefaultsFields";
import { CodexConnectionFields } from "./CodexFingerprintFields"; import { CodexConnectionFields } from "./CodexFingerprintFields";
import { assignEditApiKeyProviderSpecificData } from "./connectionProviderSpecificData"; import { assignEditApiKeyProviderSpecificData } from "./connectionProviderSpecificData";
@@ -909,6 +910,12 @@ export default function EditConnectionModal({
t={t} t={t}
/> />
)} )}
{provider && (
<HarImportButton
provider={provider}
onImport={(apiKey) => setFormData({ ...formData, apiKey })}
/>
)}
{!isNoAuthWebSessionCredential && ( {!isNoAuthWebSessionCredential && (
<div className="flex gap-2"> <div className="flex gap-2">
<Input <Input

View File

@@ -6269,6 +6269,20 @@
"webSessionGuideStep3": "Copy the required credential from the provider's own domain. For cookies, copy only the Cookie header value and omit Cookie:.", "webSessionGuideStep3": "Copy the required credential from the provider's own domain. For cookies, copy only the Cookie header value and omit Cookie:.",
"webSessionGuideStep3Manual": "Manual path: open the browser developer tools (F12 → Network), refresh the page, open an authenticated request, and copy the Cookie header value from Request Headers — omit the Cookie: prefix.", "webSessionGuideStep3Manual": "Manual path: open the browser developer tools (F12 → Network), refresh the page, open an authenticated request, and copy the Cookie header value from Request Headers — omit the Cookie: prefix.",
"webSessionGuideStep4": "Paste it here and check the connection. If it stops working, sign in again and replace it with a fresh value.", "webSessionGuideStep4": "Paste it here and check the connection. If it stops working, sign in again and replace it with a fresh value.",
"harImportButtonLabel": "Import .har file",
"harImportButtonBusy": "Importing…",
"harImportButtonHint": "Export from DevTools Network tab after sending at least one chat message.",
"harImportStatusValid": "Imported — valid for ~{minutes}m.",
"harImportStatusExpiringSoon": "Imported — valid for only ~{minutes}m more.",
"harImportStatusExpired": "Imported, but this token already expired ({minutes}m ago) — export a fresh HAR.",
"harImportStatusUnknownExpiry": "Imported. Couldn't read its expiry.",
"harImportErrorNotJson": "That file isn't valid JSON — is it really a .har export?",
"harImportErrorNoEntries": "This HAR has no network entries recorded.",
"harImportErrorNoChathubUrl": "No Copilot chat connection found in this HAR. Send at least one chat message in m365.cloud.microsoft before exporting.",
"harImportErrorUnparsableUrl": "Found the chat connection, but couldn't read its URL.",
"harImportErrorMissingFields": "Found the chat connection, but the token was missing from it.",
"harImportErrorReadFailed": "Couldn't read that file.",
"harImportErrorUnknown": "Couldn't extract a credential from that HAR file.",
"webSessionSecurityHint": "Treat this like a password: it may access your signed-in web account until it expires or is revoked.", "webSessionSecurityHint": "Treat this like a password: it may access your signed-in web account until it expires or is revoked.",
"webNoAuthGuideTitle": "No credential required", "webNoAuthGuideTitle": "No credential required",
"webNoAuthGuideBody": "{provider} does not need an API key or cookie. Save the connection to use its free web endpoint.", "webNoAuthGuideBody": "{provider} does not need an API key or cookie. Save the connection to use its free web endpoint.",

View File

@@ -1,7 +1,6 @@
// OpenAI/Gemini-format + Bedrock provider key validators (bedrock, openai-like, command-code, gemini-like, openai-compatible). // OpenAI/Gemini-format + Bedrock provider key validators (bedrock, openai-like, command-code, gemini-like, openai-compatible).
// Extracted from validation.ts (god-file decomposition) — top-level functions; behavior is // Extracted from validation.ts (god-file decomposition) — top-level functions; behavior is
// byte-identical to the original inline defs. // byte-identical to the original inline defs.
import { randomUUID } from "node:crypto";
import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry.ts"; import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry.ts";
import { import {
discoverBedrockNativeModels, discoverBedrockNativeModels,
@@ -196,13 +195,12 @@ export async function validateOpenAILikeProvider({
export async function validateCommandCodeProvider({ apiKey, providerSpecificData = {} }: any) { export async function validateCommandCodeProvider({ apiKey, providerSpecificData = {} }: any) {
const entry = getRegistryEntry("command-code"); const entry = getRegistryEntry("command-code");
const baseUrl = normalizeBaseUrl(entry?.baseUrl || "https://api.commandcode.ai"); const baseUrl = normalizeBaseUrl(entry?.baseUrl || "https://api.commandcode.ai");
const chatPath = entry?.chatPath || "/alpha/generate"; const chatPath = entry?.chatPath || "/provider/v1/chat/completions";
const url = `${baseUrl}${chatPath.startsWith("/") ? chatPath : `/${chatPath}`}`; const url = `${baseUrl}${chatPath.startsWith("/") ? chatPath : `/${chatPath}`}`;
const validationModelId = const validationModelId =
providerSpecificData?.validationModelId || providerSpecificData?.validationModelId ||
entry?.models?.find((model) => model.id === "deepseek/deepseek-v4-flash")?.id || entry?.models?.find((model) => model.id === "deepseek/deepseek-v4-flash")?.id ||
"deepseek/deepseek-v4-flash"; "deepseek/deepseek-v4-flash";
const { COMMAND_CODE_VERSION } = await import("@omniroute/open-sse/executors/commandCode.ts");
return validateDirectChatProvider({ return validateDirectChatProvider({
url, url,
@@ -210,37 +208,13 @@ export async function validateCommandCodeProvider({ apiKey, providerSpecificData
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
"x-command-code-version": COMMAND_CODE_VERSION, Accept: "text/event-stream",
"x-cli-environment": "external",
"x-project-slug": "pi-cc",
"x-taste-learning": "false",
"x-co-flag": "false",
"x-session-id": randomUUID(),
}, },
body: { body: {
config: { model: validationModelId,
workingDir: "/workspace", messages: [{ role: "user", content: "test" }],
date: new Date().toISOString().slice(0, 10), stream: true,
environment: "external", max_tokens: 1,
structure: [],
isGitRepo: false,
currentBranch: "",
mainBranch: "",
gitStatus: "",
recentCommits: [],
},
memory: "",
taste: "",
skills: "",
permissionMode: "standard",
params: {
model: validationModelId,
messages: [{ role: "user", content: "test" }],
tools: [],
system: "",
max_tokens: 1,
stream: true,
},
}, },
}); });
} }

View File

@@ -83,7 +83,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
textIcon: "CC", textIcon: "CC",
website: "https://commandcode.ai/", website: "https://commandcode.ai/",
authHint: authHint:
"Use a Command Code API key. Requests are sent to Command Code's /alpha/generate endpoint.", "Use a Command Code API key. Requests are sent to Command Code's /provider/v1/chat/completions endpoint.",
apiHint: "Create or copy an API key from Command Code, then paste it here as a Bearer token.", apiHint: "Create or copy an API key from Command Code, then paste it here as a Bearer token.",
}, },
openrouter: { openrouter: {

View File

@@ -0,0 +1,116 @@
/**
* Pure, transport-free helpers that extract a `copilot-m365-web` credential
* (`access_token=...; chathubPath=...`) directly from a DevTools HAR export,
* so the "Add connection" / "Edit connection" dialogs can offer a one-click
* "Import .har file" button instead of the user hand-extracting the token
* from the WebSocket URL (see `HarImportButton.tsx`).
*
* The token rides in the query string of the BizChat ChatHub WebSocket URL
* (`wss://substrate.office.com/m365Copilot/Chathub/<oid>@<tenant>?...
* access_token=...`) — the same URL the executor itself connects to (see
* `open-sse/executors/copilot-m365-connection.ts`). No network calls here;
* everything operates on the HAR text already in the browser.
*/
/** Prefix identifying the BizChat ChatHub WebSocket request in a HAR entry. */
export const M365_CHATHUB_WS_PREFIX = "wss://substrate.office.com/m365Copilot/Chathub/";
export type M365HarImportResult =
| { ok: true; apiKey: string; chathubPath: string; expiresAt: number | null }
| { ok: false; error: string };
interface HarEntryLike {
request?: { url?: unknown };
}
interface HarLike {
log?: { entries?: unknown };
}
/** Decode a JWT payload WITHOUT verification — exp is a display hint only. */
function decodeJwtExpiry(token: string): number | null {
const parts = token.split(".");
if (parts.length !== 3) return null;
try {
const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
const json = JSON.parse(atob(padded)) as { exp?: unknown };
return typeof json.exp === "number" ? json.exp * 1000 : null;
} catch {
return null;
}
}
/**
* Extract the `copilot-m365-web` credential from raw HAR file text.
*
* Scans every request in `log.entries` for the ChatHub WebSocket URL and
* uses the LAST match (a HAR may contain several turns of the same session;
* the most recent one carries the freshest token). Returns a structured
* error — never throws — so callers can render it directly.
*/
export function extractM365CredentialFromHar(text: string): M365HarImportResult {
let har: HarLike;
try {
har = JSON.parse(text) as HarLike;
} catch {
return { ok: false, error: "notJson" };
}
const entries = har.log?.entries;
if (!Array.isArray(entries)) {
return { ok: false, error: "noEntries" };
}
let matchedUrl: string | null = null;
for (const entry of entries as HarEntryLike[]) {
const url = entry?.request?.url;
if (typeof url === "string" && url.startsWith(M365_CHATHUB_WS_PREFIX)) {
matchedUrl = url;
}
}
if (!matchedUrl) {
return { ok: false, error: "noChathubUrl" };
}
let parsed: URL;
try {
parsed = new URL(matchedUrl);
} catch {
return { ok: false, error: "unparsableUrl" };
}
const token = parsed.searchParams.get("access_token");
const chathubPath = decodeURIComponent(parsed.pathname.split("/m365Copilot/Chathub/")[1] || "");
if (!token || !chathubPath) {
return { ok: false, error: "missingFields" };
}
return {
ok: true,
apiKey: `access_token=${token}; chathubPath=${chathubPath}`,
chathubPath,
expiresAt: decodeJwtExpiry(token),
};
}
export type HarImportExpiryTone = "ok" | "warn" | "bad" | "unknown";
export interface HarImportExpiryStatus {
tone: HarImportExpiryTone;
minutesRemaining: number | null;
}
/** Classify a decoded token expiry for the inline status hint. */
export function describeHarImportExpiry(
expiresAt: number | null,
now: number = Date.now()
): HarImportExpiryStatus {
if (expiresAt === null) return { tone: "unknown", minutesRemaining: null };
const minutesRemaining = Math.round((expiresAt - now) / 60000);
if (minutesRemaining <= 0) return { tone: "bad", minutesRemaining };
if (minutesRemaining < 15) return { tone: "warn", minutesRemaining };
return { tone: "ok", minutesRemaining };
}

View File

@@ -8,20 +8,13 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-command-c
process.env.DATA_DIR = TEST_DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR;
const { REGISTRY, getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts"); const { REGISTRY, getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts");
const { CommandCodeExecutor, COMMAND_CODE_VERSION } = const { CommandCodeExecutor } = await import("../../open-sse/executors/commandCode.ts");
await import("../../open-sse/executors/commandCode.ts");
const { getExecutor, hasSpecializedExecutor } = await import("../../open-sse/executors/index.ts"); const { getExecutor, hasSpecializedExecutor } = await import("../../open-sse/executors/index.ts");
const { createResponsesApiTransformStream } =
await import("../../open-sse/transformer/responsesTransformer.ts");
const core = await import("../../src/lib/db/core.ts"); const core = await import("../../src/lib/db/core.ts");
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
type JsonRecord = Record<string, unknown>; type FetchCall = { url: string; init: Record<string, unknown>; body?: Record<string, unknown> };
type ResponsesEvent = {
event: string;
data: { response: JsonRecord & { usage?: unknown; output?: JsonRecord[] } };
};
const PINNED_COMMAND_CODE_MODELS = [ const PINNED_COMMAND_CODE_MODELS = [
"claude-opus-4-7", "claude-opus-4-7",
@@ -44,20 +37,7 @@ const PINNED_COMMAND_CODE_MODELS = [
"Qwen/Qwen3.6-Plus", "Qwen/Qwen3.6-Plus",
]; ];
function commandCodeStream(lines: unknown[], { sse = false } = {}) { const CHAT_URL = "https://api.commandcode.ai/provider/v1/chat/completions";
const text = lines
.map((line) => {
const json = JSON.stringify(line);
return sse ? `data: ${json}\n\n` : `${json}\n`;
})
.join("");
return new Response(text, { status: 200, headers: { "Content-Type": "application/x-ndjson" } });
}
function toPlainHeaders(headers: Headers | Record<string, string>) {
if (headers instanceof Headers) return Object.fromEntries(headers.entries());
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, String(value)]));
}
function parseSsePayloads(sse: string) { function parseSsePayloads(sse: string) {
return sse return sse
@@ -68,25 +48,21 @@ function parseSsePayloads(sse: string) {
.map((line) => JSON.parse(line)); .map((line) => JSON.parse(line));
} }
async function responsesFromChatSse(sse: string): Promise<ResponsesEvent[]> { function openAiSse(obj: unknown): string {
const input = new ReadableStream<Uint8Array>({ return `data: ${JSON.stringify(obj)}\n\n`;
start(controller) { }
controller.enqueue(new TextEncoder().encode(sse));
controller.close();
},
});
const transformed = await new Response(
input.pipeThrough(createResponsesApiTransformStream(null, 60_000))
).text();
return transformed function captureFetch(body: Record<string, unknown>) {
.split("\n\n") const calls: FetchCall[] = [];
.map((part) => { globalThis.fetch = async (url, init = {}) => {
const event = part.match(/^event:\s*(.+)$/m)?.[1]; calls.push({
const data = part.match(/^data:\s*(.+)$/m)?.[1]; url: String(url),
return event && data ? ({ event, data: JSON.parse(data) } as ResponsesEvent) : null; init,
}) body: JSON.parse(String(init.body)),
.filter((entry): entry is ResponsesEvent => entry !== null); });
return new Response(JSON.stringify(body), { status: 200 });
};
return calls;
} }
test.afterEach(() => { test.afterEach(() => {
@@ -105,7 +81,9 @@ test("Command Code provider catalog has pinned models and alias lookup", () => {
assert.equal(entry.alias, "cmd"); assert.equal(entry.alias, "cmd");
assert.equal(entry.executor, "command-code"); assert.equal(entry.executor, "command-code");
assert.equal(entry.baseUrl, "https://api.commandcode.ai"); assert.equal(entry.baseUrl, "https://api.commandcode.ai");
assert.equal(entry.chatPath, "/alpha/generate"); // Chat targets the documented /provider/v1/chat/completions endpoint, NOT the
// CLI-only /alpha/generate endpoint (#10265).
assert.equal(entry.chatPath, "/provider/v1/chat/completions");
assert.deepEqual( assert.deepEqual(
entry.models.map((model) => model.id), entry.models.map((model) => model.id),
PINNED_COMMAND_CODE_MODELS PINNED_COMMAND_CODE_MODELS
@@ -119,17 +97,10 @@ test("getExecutor returns the specialized Command Code executor", () => {
assert.ok(getExecutor("cmd") instanceof CommandCodeExecutor); assert.ok(getExecutor("cmd") instanceof CommandCodeExecutor);
}); });
type FetchCall = { url: string; init: Record<string, unknown>; body?: unknown }; test("Command Code executor posts a flat OpenAI body + standard headers to /provider/v1/chat/completions (#10265)", async () => {
const calls = captureFetch({});
test("Command Code executor posts wrapped body and required headers to /alpha/generate", async () => {
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init });
return commandCodeStream([{ type: "text-delta", text: "hello" }, { type: "finish" }]);
};
const executor = getExecutor("command-code"); const executor = getExecutor("command-code");
const { response, url, headers, transformedBody } = await executor.execute({ const { response, url, headers } = await executor.execute({
model: "gpt-5.4-mini", model: "gpt-5.4-mini",
stream: false, stream: false,
credentials: { apiKey: "cc_test_key" }, credentials: { apiKey: "cc_test_key" },
@@ -144,41 +115,34 @@ test("Command Code executor posts wrapped body and required headers to /alpha/ge
}, },
}); });
assert.equal(url, "https://api.commandcode.ai/alpha/generate"); assert.equal(url, CHAT_URL);
assert.equal(calls.length, 1); assert.equal(calls.length, 1);
assert.equal(calls[0].url, "https://api.commandcode.ai/alpha/generate"); assert.equal(calls[0].url, CHAT_URL);
assert.equal(calls[0].init.method, "POST"); assert.equal(calls[0].init.method, "POST");
assert.equal(headers.Authorization, "Bearer cc_test_key"); assert.equal(headers.Authorization, "Bearer cc_test_key");
assert.equal(headers["x-command-code-version"], COMMAND_CODE_VERSION); // No CLI-impersonation headers.
assert.equal(headers["x-cli-environment"], "external"); assert.equal(headers["x-command-code-version"], undefined);
assert.equal(headers["x-project-slug"], "pi-cc"); assert.equal(headers["x-cli-environment"], undefined);
assert.equal(headers["x-taste-learning"], "false"); assert.equal(headers["x-project-slug"], undefined);
assert.equal(headers["x-co-flag"], "false");
assert.equal(typeof headers["x-session-id"], "string");
const posted = JSON.parse(String(calls[0].init.body)); const posted = calls[0].body as Record<string, unknown>;
assert.deepEqual(posted, transformedBody); // No CLI envelope.
for (const key of ["config", "memory", "taste", "skills", "permissionMode", "params"]) { assert.equal(posted.config, undefined, "CLI envelope config must not be sent");
assert.ok(key in posted, `missing ${key}`); assert.equal(posted.params, undefined, "CLI envelope params wrapper must not be sent");
} assert.equal(posted.model, "gpt-5.4-mini");
assert.equal(posted.skills, ""); assert.equal(posted.stream, false);
assert.equal(posted.params.model, "gpt-5.4-mini"); assert.equal((posted.messages as Array<{ role: string }>)[0].role, "system");
assert.equal(posted.params.stream, true); const tool = (posted.tools as Array<{ function: { name: string } }>)[0];
assert.equal(posted.params.system, "You are concise."); assert.equal(tool.function.name, "lookup", "tools in OpenAI shape (function.name)");
assert.equal(posted.params.messages[0].role, "user"); assert.equal(posted.max_tokens, 42);
assert.equal(posted.params.tools[0].name, "lookup");
// The upstream OpenAI JSON passes through untouched.
const json = await response.json(); const json = await response.json();
assert.equal(json.choices[0].message.content, "hello"); assert.deepEqual(json, {});
}); });
test("Command Code executor passes reasoning/thinking fields through to params (#2986 follow-up)", async () => { test("Command Code executor passes reasoning/thinking fields through at the top level of the OpenAI body", async () => {
const calls: FetchCall[] = []; const calls = captureFetch({});
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro", model: "deepseek/deepseek-v4-pro",
stream: false, stream: false,
@@ -189,30 +153,19 @@ test("Command Code executor passes reasoning/thinking fields through to params (
reasoning_effort: "high", reasoning_effort: "high",
thinking: { type: "enabled" }, thinking: { type: "enabled" },
effort: "high", effort: "high",
output_config: { effort: "high" },
extra_body: { enable_thinking: true }, extra_body: { enable_thinking: true },
}, },
}); });
const posted = JSON.parse(String(calls[0].init.body)); const posted = calls[0].body as Record<string, unknown>;
assert.equal(posted.params.reasoning_effort, "high"); assert.equal(posted.reasoning_effort, "high");
assert.deepEqual(posted.params.thinking, { type: "enabled" }); assert.deepEqual(posted.thinking, { type: "enabled" });
assert.equal(posted.params.effort, "high"); assert.equal(posted.effort, "high");
assert.deepEqual(posted.params.output_config, { effort: "high" }); assert.deepEqual(posted.extra_body, { enable_thinking: true });
assert.deepEqual(posted.params.extra_body, { enable_thinking: true });
}); });
test("Command Code executor honors body.model rewrite from payload rules", async () => { test("Command Code executor honors body.model rewrite from payload rules", async () => {
const calls: FetchCall[] = []; const calls = captureFetch({});
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
// Simulate a payload-rule rewrite: combo resolves to "deepseek-v4-pro-max"
// (passed as the execute() model arg), but the payload rule overwrites
// body.model to "deepseek/deepseek-v4-pro" (the vendor-prefixed form
// Command Code's API expects).
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "deepseek-v4-pro-max", model: "deepseek-v4-pro-max",
stream: false, stream: false,
@@ -225,20 +178,13 @@ test("Command Code executor honors body.model rewrite from payload rules", async
}, },
}); });
const posted = JSON.parse(String(calls[0].init.body)); const posted = calls[0].body as Record<string, unknown>;
assert.equal(posted.params.model, "deepseek/deepseek-v4-pro"); assert.equal(posted.model, "deepseek/deepseek-v4-pro");
assert.equal(posted.params.reasoning_effort, "max"); assert.equal(posted.reasoning_effort, "max");
}); });
test("Command Code executor maps unsupported minimal reasoning_effort to low (upstream 400 regression)", async () => { test("Command Code executor maps unsupported minimal reasoning_effort to low (upstream 400 regression)", async () => {
const calls: FetchCall[] = []; const calls = captureFetch({});
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
// Live upstream rejection: "Validation error: Invalid option: expected one of
// \"low\"|\"medium\"|\"high\"|\"xhigh\"|\"max\" at \"params.reasoning_effort\"" —
// `minimal` (a Muse Spark catalog tier) must be downgraded to `low` before // `minimal` (a Muse Spark catalog tier) must be downgraded to `low` before
// the wire body is built, on BOTH the combo and single-model paths. // the wire body is built, on BOTH the combo and single-model paths.
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
@@ -252,20 +198,38 @@ test("Command Code executor maps unsupported minimal reasoning_effort to low (up
}, },
}); });
const posted = JSON.parse(String(calls[0].init.body)); const posted = calls[0].body as Record<string, unknown>;
assert.equal(posted.params.reasoning_effort, "low", "minimal must map to low"); assert.equal(posted.reasoning_effort, "low", "minimal must map to low");
}); });
test("Command Code raw NDJSON stream becomes OpenAI chat SSE chunks", async () => { test("Command Code executor passes the upstream OpenAI SSE stream through untouched", async () => {
const calls: FetchCall[] = []; const sse =
openAiSse({
id: "c1",
object: "chat.completion.chunk",
model: "gpt-5.4",
choices: [{ index: 0, delta: { role: "assistant" } }],
}) +
openAiSse({
id: "c1",
object: "chat.completion.chunk",
model: "gpt-5.4",
choices: [{ index: 0, delta: { content: "Hello" } }],
}) +
openAiSse({
id: "c1",
object: "chat.completion.chunk",
model: "gpt-5.4",
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
}) +
"data: [DONE]\n\n";
let capturedStreamFlag: unknown = null;
globalThis.fetch = async (url, init = {}) => { globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) }); capturedStreamFlag = JSON.parse(String(init.body)).stream;
return commandCodeStream([ return new Response(sse, {
{ type: "text-delta", text: "Hello" }, status: 200,
{ type: "reasoning-delta", text: "thinking" }, headers: { "Content-Type": "text/event-stream" },
{ type: "tool-call", toolCallId: "call_1", toolName: "search", input: { q: "docs" } }, });
{ type: "finish", finishReason: "tool-calls" },
]);
}; };
const { response } = await getExecutor("command-code").execute({ const { response } = await getExecutor("command-code").execute({
@@ -275,39 +239,32 @@ test("Command Code raw NDJSON stream becomes OpenAI chat SSE chunks", async () =
body: { messages: [{ role: "user", content: "Hi" }] }, body: { messages: [{ role: "user", content: "Hi" }] },
}); });
assert.equal(calls[0].body.params.stream, true); assert.equal(capturedStreamFlag, true, "stream flag forwarded to upstream");
assert.equal(response.headers.get("Content-Type"), "text/event-stream; charset=utf-8"); const text = await response.text();
const sse = await response.text(); assert.equal(text, sse, "OpenAI SSE stream passed through byte-for-byte");
assert.match(sse, /data: \[DONE\]/); assert.ok(text.includes("data: [DONE]"));
const chunks = parseSsePayloads(sse); const chunks = parseSsePayloads(text);
assert.equal(chunks[0].object, "chat.completion.chunk"); assert.equal(chunks[0].choices[0].delta.role, "assistant");
assert.deepEqual(chunks[0].choices[0].delta, { role: "assistant" });
assert.equal(chunks[1].choices[0].delta.content, "Hello"); assert.equal(chunks[1].choices[0].delta.content, "Hello");
assert.equal(chunks[2].choices[0].delta.reasoning_content, "thinking"); assert.equal(chunks[2].choices[0].finish_reason, "stop");
assert.equal(chunks[3].choices[0].delta.tool_calls[0].function.name, "search");
assert.equal(chunks.at(-1).choices[0].finish_reason, "tool_calls");
}); });
test("Command Code data: SSE lines aggregate into non-stream ChatCompletion JSON", async () => { test("Command Code executor passes the upstream OpenAI JSON through untouched (non-stream)", async () => {
globalThis.fetch = async () => const upstreamJson = {
commandCodeStream( id: "chatcmpl-1",
[ object: "chat.completion",
{ type: "text-delta", text: "Hel" }, model: "gpt-5.4-mini",
{ type: "text-delta", text: "lo" }, choices: [{ index: 0, message: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
{ type: "reasoning-delta", text: "because" }, usage: { prompt_tokens: 3, completion_tokens: 2, total_tokens: 5 },
{ type: "tool-call", id: "call_2", name: "lookup", arguments: { id: 7 } }, };
{ let capturedStreamFlag: unknown = null;
type: "finish", globalThis.fetch = async (url, init = {}) => {
finishReason: "max_tokens", capturedStreamFlag = JSON.parse(String(init.body)).stream;
totalUsage: { return new Response(JSON.stringify(upstreamJson), {
inputTokens: 3, status: 200,
inputTokenDetails: { cacheReadTokens: 2 }, headers: { "Content-Type": "application/json" },
outputTokens: 5, });
}, };
},
],
{ sse: true }
);
const { response } = await getExecutor("command-code").execute({ const { response } = await getExecutor("command-code").execute({
model: "gpt-5.4-mini", model: "gpt-5.4-mini",
@@ -316,88 +273,12 @@ test("Command Code data: SSE lines aggregate into non-stream ChatCompletion JSON
body: { messages: [{ role: "user", content: "Hi" }] }, body: { messages: [{ role: "user", content: "Hi" }] },
}); });
assert.equal(response.headers.get("Content-Type"), "application/json"); assert.equal(capturedStreamFlag, false, "stream flag forwarded as false for non-stream");
const json = await response.json(); assert.deepEqual(await response.json(), upstreamJson);
assert.equal(json.object, "chat.completion");
assert.equal(json.choices[0].message.content, "Hello");
assert.equal(json.choices[0].message.reasoning_content, "because");
assert.equal(json.choices[0].message.tool_calls[0].function.arguments, JSON.stringify({ id: 7 }));
assert.equal(json.choices[0].finish_reason, "length");
assert.deepEqual(json.usage, {
prompt_tokens: 3,
prompt_tokens_details: { cached_tokens: 2 },
completion_tokens: 5,
completion_tokens_details: { reasoning_tokens: 0 },
total_tokens: 8,
cache_read_input_tokens: 2,
});
}); });
test("Command Code reasoning-only output falls back to reasoning as content (non-stream)", async () => { test("Command Code executor surfaces upstream errors", async () => {
globalThis.fetch = async () => globalThis.fetch = async () => new Response("bad key", { status: 401, statusText: "Unauthorized" });
commandCodeStream(
[
{ type: "reasoning-delta", text: "The user wants 79874+93658. " },
{ type: "reasoning-delta", text: "That equals 173532." },
{
type: "finish",
finishReason: "stop",
totalUsage: { inputTokens: 20, outputTokens: 64, outputTokenDetails: { reasoningTokens: 61 } },
},
],
{ sse: true }
);
const { response } = await getExecutor("command-code").execute({
model: "meta/muse-spark-1.2-contributor",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Calculate 79874+93658, and reply with the result only." }] },
});
const json = await response.json();
const message = json.choices[0].message;
// Regression #10986: when the model emits only reasoning-delta events (never a
// text-delta), content must fall back to the reasoning text instead of "" (which
// OpenAI-compatible clients treat as null/no answer).
assert.equal(message.content, "The user wants 79874+93658. That equals 173532.");
// reasoning_content must STAY populated for reasoning-aware clients.
assert.equal(message.reasoning_content, "The user wants 79874+93658. That equals 173532.");
});
test("Command Code reasoning-only output emits a content delta chunk when streaming", async () => {
globalThis.fetch = async () =>
commandCodeStream(
[
{ type: "reasoning-delta", text: "The result is 173532." },
{ type: "finish", finishReason: "stop" },
],
{ sse: true }
);
const { response } = await getExecutor("command-code").execute({
model: "meta/muse-spark-1.2-contributor",
stream: true,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Calcular 79874+93658" }] },
});
const sse = await response.text();
assert.match(sse, /data: \[DONE\]/);
const chunks = parseSsePayloads(sse);
assert.equal(chunks[0].choices[0].delta.role, "assistant");
// Regression #10986: the reasoning-only stream must emit a content delta when it
// otherwise ends with no content. reasoning_content stays present too.
const contentDelta = chunks.find((c) => c.choices[0].delta.content !== undefined);
assert.equal(contentDelta.choices[0].delta.content, "The result is 173532.");
const reasoningDelta = chunks.find((c) => c.choices[0].delta.reasoning_content !== undefined);
assert.equal(reasoningDelta.choices[0].delta.reasoning_content, "The result is 173532.");
assert.equal(chunks.at(-1).choices[0].finish_reason, "stop");
});
test("Command Code executor surfaces upstream and streamed errors", async () => {
globalThis.fetch = async () =>
new Response("bad key", { status: 401, statusText: "Unauthorized" });
const upstreamFailure = await getExecutor("command-code").execute({ const upstreamFailure = await getExecutor("command-code").execute({
model: "gpt-5.4-mini", model: "gpt-5.4-mini",
stream: false, stream: false,
@@ -406,124 +287,68 @@ test("Command Code executor surfaces upstream and streamed errors", async () =>
}); });
assert.equal(upstreamFailure.response.status, 401); assert.equal(upstreamFailure.response.status, 401);
assert.equal(await upstreamFailure.response.text(), "bad key"); assert.equal(await upstreamFailure.response.text(), "bad key");
globalThis.fetch = async () => commandCodeStream([{ type: "error", error: { message: "boom" } }]);
await assert.rejects(async () => {
await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
}, /boom/);
}); });
test("Command Code executor omits max_tokens when the client does not supply one (GLM-5.x)", async () => { test("Command Code executor omits max_tokens when the client does not supply one", async () => {
const calls: FetchCall[] = []; const calls = captureFetch({});
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
// No client max_tokens: we must NOT fabricate one. Omitting the field lets
// Command Code's upstream apply the model's own native default.
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "zai-org/GLM-5.1", model: "zai-org/GLM-5.1",
stream: false, stream: false,
credentials: { apiKey: "cc_test_key" }, credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] }, body: { messages: [{ role: "user", content: "Hi" }] },
}); });
assert.ok(!("max_tokens" in calls[0].body.params)); const posted = calls[0].body as Record<string, unknown>;
}); assert.ok(!("max_tokens" in posted), "must not fabricate max_tokens");
assert.ok(!("max_completion_tokens" in posted), "must not fabricate max_completion_tokens");
test("Command Code executor omits max_tokens for DeepSeek v4 when the client does not supply one", async () => {
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
// Regression: previously the executor invented max_tokens from the registry
// (384000), which /alpha/generate rejects with a 400
// "Too big: expected number to be <=200000". With no client value we now omit
// the field entirely, so the request succeeds and upstream picks the default.
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
assert.ok(!("max_tokens" in calls[0].body.params));
}); });
test("Command Code executor clamps an oversized client-supplied max_tokens to the endpoint ceiling", async () => { test("Command Code executor clamps an oversized client-supplied max_tokens to the endpoint ceiling", async () => {
const calls: FetchCall[] = []; const calls = captureFetch({});
globalThis.fetch = async (url, init = {}) => { // A client asking for more than the 200000 endpoint ceiling is clamped down.
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
// A client asking for more than the 200000 endpoint ceiling is clamped down
// (not 400'd), mirroring the provider-driven clamp in antigravity.ts.
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro", model: "deepseek/deepseek-v4-pro",
stream: false, stream: false,
credentials: { apiKey: "cc_test_key" }, credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }], max_tokens: 500000 }, body: { messages: [{ role: "user", content: "Hi" }], max_tokens: 500000 },
}); });
assert.equal(calls[0].body.params.max_tokens, 200000); assert.equal((calls[0].body as Record<string, unknown>).max_tokens, 200000);
}); });
test("Command Code executor honors a smaller client-provided max_tokens under the per-model cap", async () => { test("Command Code executor honors a smaller client-provided max_tokens", async () => {
const calls: FetchCall[] = []; const calls = captureFetch({});
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "zai-org/GLM-5.1", model: "zai-org/GLM-5.1",
stream: false, stream: false,
credentials: { apiKey: "cc_test_key" }, credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }], max_tokens: 2048 }, body: { messages: [{ role: "user", content: "Hi" }], max_tokens: 2048 },
}); });
assert.equal(calls[0].body.params.max_tokens, 2048); assert.equal((calls[0].body as Record<string, unknown>).max_tokens, 2048);
}); });
test("Command Code non-stream aggregation throws when the final error event lacks a trailing newline", async () => { test("Command Code stream preserves the upstream OpenAI usage chunk (passthrough)", async () => {
globalThis.fetch = async () => const sse =
new Response( openAiSse({
`${JSON.stringify({ type: "text-delta", text: "Hello" })}\n${JSON.stringify({ id: "c1",
type: "error", object: "chat.completion.chunk",
error: { message: "boom" },
})}`,
{ status: 200, headers: { "Content-Type": "application/x-ndjson" } }
);
await assert.rejects(async () => {
await getExecutor("command-code").execute({
model: "gpt-5.4-mini", model: "gpt-5.4-mini",
stream: false, choices: [{ index: 0, delta: { content: "Hi" } }],
credentials: { apiKey: "cc_test_key" }, }) +
body: { messages: [{ role: "user", content: "Hi" }] }, openAiSse({
}); id: "c1",
}, /boom/); object: "chat.completion.chunk",
}); model: "gpt-5.4-mini",
choices: [],
test("Command Code usage chunk surfaces cache_read and no_cache for the stream pipeline", async () => { usage: {
globalThis.fetch = async () => prompt_tokens: 10,
commandCodeStream([ prompt_tokens_details: { cached_tokens: 4 },
{ type: "text-delta", text: "Hi" }, completion_tokens: 6,
{ completion_tokens_details: { reasoning_tokens: 1 },
type: "finish", total_tokens: 16,
finishReason: "stop",
totalUsage: {
inputTokens: 10,
inputTokenDetails: { noCacheTokens: 6, cacheReadTokens: 4 },
outputTokens: 6,
},
}, },
]); }) +
"data: [DONE]\n\n";
globalThis.fetch = async () =>
new Response(sse, { status: 200, headers: { "Content-Type": "text/event-stream" } });
const { response } = await getExecutor("command-code").execute({ const { response } = await getExecutor("command-code").execute({
model: "gpt-5.4-mini", model: "gpt-5.4-mini",
@@ -532,260 +357,11 @@ test("Command Code usage chunk surfaces cache_read and no_cache for the stream p
body: { messages: [{ role: "user", content: "Hi" }] }, body: { messages: [{ role: "user", content: "Hi" }] },
}); });
const sse = await response.text(); const text = await response.text();
const chunks = parseSsePayloads(sse); // The upstream OpenAI usage chunk passes through unchanged, including the
const usageChunk = chunks.find( // standard OpenAI usage shape the stream pipeline already understands.
(chunk) => Array.isArray(chunk.choices) && chunk.choices.length === 0 assert.ok(text.includes('"prompt_tokens":10'));
); assert.ok(text.includes('"cached_tokens":4'));
assert.ok(usageChunk, "expected a usage-only chunk (choices: []) in the stream"); assert.ok(text.includes('"reasoning_tokens":1'));
assert.ok(text.includes("data: [DONE]"));
// The usage-only chunk feeds stream.ts's extractUsage, which surfaces });
// cache_read_input_tokens / no_cache_tokens into the [USAGE] line.
const { extractUsage } = await import("../../open-sse/utils/usageTracking.ts");
const extracted = extractUsage(usageChunk);
assert.ok(extracted, "extractUsage should recognize the usage-only chunk");
assert.equal(extracted.prompt_tokens, 10);
assert.equal(extracted.completion_tokens, 6);
assert.equal(extracted.cache_read_input_tokens, 4);
assert.equal(extracted.no_cache_tokens, 6);
});
test("Command Code stream emits a usage-only chunk with actual tokens before [DONE]", async () => {
globalThis.fetch = async () =>
commandCodeStream([
{ type: "text-delta", text: "Hi" },
{
type: "finish",
finishReason: "stop",
totalUsage: {
inputTokens: 10,
inputTokenDetails: { cacheReadTokens: 4, cacheCreationTokens: 2 },
outputTokens: 6,
reasoningTokenDetails: { reasoningTokens: 1 },
},
},
]);
const { response } = await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
stream: true,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
const sse = await response.text();
const chunks = parseSsePayloads(sse);
// Find the usage-only chunk: choices must be [] and usage must carry the
// actual upstream numbers. prompt_tokens = inputTokens (10) — cacheRead 4 is
// already included in that 10, so it is reported separately, NOT re-added.
const usageChunk = chunks.find(
(chunk) => Array.isArray(chunk.choices) && chunk.choices.length === 0
);
assert.ok(usageChunk, "expected a usage-only chunk (choices: []) in the stream");
assert.deepEqual(usageChunk.usage, {
prompt_tokens: 10,
prompt_tokens_details: { cached_tokens: 4 },
completion_tokens: 6,
completion_tokens_details: { reasoning_tokens: 1 },
total_tokens: 16,
cache_read_input_tokens: 4,
reasoning_tokens: 1,
});
// The usage chunk must come before the [DONE] marker.
assert.match(sse, /"usage":/);
const doneIndex = sse.indexOf("data: [DONE]");
const usageIndex = sse.indexOf(`"choices":[]`);
assert.ok(usageIndex > -1 && usageIndex < doneIndex, "usage chunk must precede [DONE]");
});
test("Command Code non-stream usage keeps inputTokens as prompt_tokens and reports cache separately", async () => {
globalThis.fetch = async () =>
commandCodeStream(
[
{ type: "text-delta", text: "ok" },
{
type: "finish",
finishReason: "stop",
totalUsage: {
inputTokens: 5,
inputTokenDetails: { noCacheTokens: 2, cacheReadTokens: 3 },
outputTokens: 2,
},
},
],
{ sse: true }
);
const { response } = await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
const json = await response.json();
assert.deepEqual(json.usage, {
prompt_tokens: 5,
prompt_tokens_details: { cached_tokens: 3 },
completion_tokens: 2,
completion_tokens_details: { reasoning_tokens: 0 },
total_tokens: 7,
cache_read_input_tokens: 3,
no_cache_tokens: 2,
});
});
test("Command Code preserves finish-step usage through a finish without totalUsage", async () => {
globalThis.fetch = async () =>
commandCodeStream([
{ type: "text-delta", text: "Hi" },
{
type: "finish-step",
usage: {
inputTokens: 7308,
inputTokenDetails: { noCacheTokens: 27, cacheReadTokens: 7281 },
outputTokens: 177,
outputTokenDetails: { textTokens: 12, reasoningTokens: 165 },
totalTokens: 7485,
},
},
{ type: "finish", finishReason: "stop", totalUsage: null },
]);
const { response } = await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
stream: true,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
const sse = await response.text();
const chunks = parseSsePayloads(sse);
const usageChunk = chunks.find(
(chunk) => Array.isArray(chunk.choices) && chunk.choices.length === 0
);
assert.deepEqual(usageChunk?.usage, {
prompt_tokens: 7308,
prompt_tokens_details: { cached_tokens: 7281 },
completion_tokens: 177,
completion_tokens_details: { reasoning_tokens: 165 },
total_tokens: 7485,
cache_read_input_tokens: 7281,
no_cache_tokens: 27,
reasoning_tokens: 165,
});
const completed = (await responsesFromChatSse(sse)).find(
(event) => event.event === "response.completed"
);
assert.deepEqual(completed?.data.response.usage, {
input_tokens: 7308,
input_tokens_details: { cached_tokens: 7281 },
output_tokens: 177,
output_tokens_details: { reasoning_tokens: 165 },
total_tokens: 7485,
});
});
test("Command Code accepts OpenAI-style usage aliases with absent optional details", async () => {
globalThis.fetch = async () =>
commandCodeStream([
{
type: "finish-step",
usage: {
prompt_tokens: 11,
prompt_tokens_details: { cached_tokens: 4 },
completion_tokens: 5,
completion_tokens_details: { reasoning_tokens: 2 },
total_tokens: 16,
},
},
{ type: "finish", finishReason: "stop" },
]);
const { response } = await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
stream: true,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
const sse = await response.text();
const usageChunk = parseSsePayloads(sse).find(
(chunk) => Array.isArray(chunk.choices) && chunk.choices.length === 0
);
assert.deepEqual(usageChunk?.usage, {
prompt_tokens: 11,
prompt_tokens_details: { cached_tokens: 4 },
completion_tokens: 5,
completion_tokens_details: { reasoning_tokens: 2 },
total_tokens: 16,
cache_read_input_tokens: 4,
reasoning_tokens: 2,
});
globalThis.fetch = async () =>
commandCodeStream([
{ type: "finish-step", usage: { inputTokens: 4, outputTokens: 3, totalTokens: 7 } },
{ type: "finish", finishReason: "stop", totalUsage: null },
]);
const fallback = await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
stream: true,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
const fallbackSse = await fallback.response.text();
const completed = (await responsesFromChatSse(fallbackSse)).find(
(event) => event.event === "response.completed"
);
assert.deepEqual(completed?.data.response.usage, {
input_tokens: 4,
input_tokens_details: { cached_tokens: 0 },
output_tokens: 3,
output_tokens_details: { reasoning_tokens: 0 },
total_tokens: 7,
});
});
test("Command Code preserves tool-call streaming while finalizing finish-step usage", async () => {
globalThis.fetch = async () =>
commandCodeStream([
{
type: "tool-call",
toolCallId: "call_1",
toolName: "lookup",
input: { query: "hello" },
},
{ type: "finish-step", usage: { inputTokens: 3, outputTokens: 2, totalTokens: 5 } },
{ type: "finish", finishReason: "tool-calls" },
]);
const { response } = await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
stream: true,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
const sse = await response.text();
assert.ok(
parseSsePayloads(sse).some(
(chunk) => chunk.choices?.[0]?.delta?.tool_calls?.[0]?.id === "call_1"
)
);
const completed = (await responsesFromChatSse(sse)).find(
(event) => event.event === "response.completed"
);
assert.equal(
completed?.data.response.output?.some((item) => item.type === "function_call"),
true
);
assert.deepEqual(completed?.data.response.usage, {
input_tokens: 3,
input_tokens_details: { cached_tokens: 0 },
output_tokens: 2,
output_tokens_details: { reasoning_tokens: 0 },
total_tokens: 5,
});
});

View File

@@ -34,7 +34,7 @@ test.after(() => {
core.resetDbInstance(); core.resetDbInstance();
}); });
async function captureParams(body: Record<string, unknown>): Promise<FetchCall> { async function captureBody(body: Record<string, unknown>): Promise<FetchCall> {
const calls: FetchCall[] = []; const calls: FetchCall[] = [];
globalThis.fetch = async (url: any, init: any = {}) => { globalThis.fetch = async (url: any, init: any = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) }); calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
@@ -50,27 +50,27 @@ async function captureParams(body: Record<string, unknown>): Promise<FetchCall>
} }
test("Command Code omits max_tokens when the client sends max_tokens: -1 (#5166)", async () => { test("Command Code omits max_tokens when the client sends max_tokens: -1 (#5166)", async () => {
const call = await captureParams({ max_tokens: -1 }); const call = await captureBody({ max_tokens: -1 });
assert.ok( assert.ok(
!("max_tokens" in call.body.params), !("max_tokens" in call.body),
`max_tokens:-1 must be omitted, got params.max_tokens=${call.body.params.max_tokens}` `max_tokens:-1 must be omitted, got max_tokens=${call.body.max_tokens}`
); );
}); });
test("Command Code omits max_tokens when the client sends max_completion_tokens: -1 (#5166)", async () => { test("Command Code omits max_tokens when the client sends max_completion_tokens: -1 (#5166)", async () => {
const call = await captureParams({ max_completion_tokens: -1 }); const call = await captureBody({ max_completion_tokens: -1 });
assert.ok( assert.ok(
!("max_tokens" in call.body.params), !("max_tokens" in call.body),
`max_completion_tokens:-1 must be omitted, got params.max_tokens=${call.body.params.max_tokens}` `max_completion_tokens:-1 must be omitted, got max_tokens=${call.body.max_tokens}`
); );
}); });
test("Command Code omits max_tokens when the client sends 0 (#5166)", async () => { test("Command Code omits max_tokens when the client sends 0 (#5166)", async () => {
const call = await captureParams({ max_tokens: 0 }); const call = await captureBody({ max_tokens: 0 });
assert.ok(!("max_tokens" in call.body.params), "max_tokens:0 must be omitted"); assert.ok(!("max_tokens" in call.body), "max_tokens:0 must be omitted");
}); });
test("Command Code still honors a positive client max_tokens after the #5166 fix", async () => { test("Command Code still honors a positive client max_tokens after the #5166 fix", async () => {
const call = await captureParams({ max_tokens: 2048 }); const call = await captureBody({ max_tokens: 2048 });
assert.equal(call.body.params.max_tokens, 2048); assert.equal(call.body.max_tokens, 2048);
}); });

View File

@@ -1,14 +1,13 @@
/** /**
* Regression test for #5166 (user-content-array 400 on Command Code / deepseek-v4-pro). * #5166 (user-content-array 400 on Command Code / deepseek-v4-pro) context.
* *
* When a client sends a user message whose `content` is an array of content parts * The original regression was that a user message whose `content` was an array of
* (e.g. [{type:"text",text:"Hello"},{type:"text",text:"World"}]), the raw array * content parts reached the CLI-only /alpha/generate endpoint, which required
* must NOT reach the Command Code upstream — it requires user content to be a plain * user content to be a plain string. Since #10265 the executor posts to the
* string. The executor must normalise the array to a string before posting. * documented /provider/v1/chat/completions endpoint, which natively speaks the
* * OpenAI chat.completions format — array content (text + image_url parts) is
* NOTE: this file covers ONLY the user-content-array/400 symptom of #5166. * valid there and passes through unchanged. These tests pin that OpenAI-shaped
* The 0-output-token symptom on mimo-v2.5-pro (reasoning-only models) is tracked * passthrough.
* separately and is NOT addressed here.
*/ */
import test from "node:test"; import test from "node:test";
import assert from "node:assert/strict"; import assert from "node:assert/strict";
@@ -26,9 +25,8 @@ const core = await import("../../src/lib/db/core.ts");
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
function commandCodeStream(lines: unknown[]) { function okResponse() {
const text = lines.map((l) => JSON.stringify(l)).join("\n") + "\n"; return new Response("{}", { status: 200, headers: { "Content-Type": "application/json" } });
return new Response(text, { status: 200, headers: { "Content-Type": "application/x-ndjson" } });
} }
test.after(() => { test.after(() => {
@@ -41,155 +39,100 @@ test.afterEach(() => {
globalThis.fetch = originalFetch; globalThis.fetch = originalFetch;
}); });
// ── helpers ──────────────────────────────────────────────────────────────────── // ── helpers ────────────────────────────────────────────────────────────
type FetchCall = { url: string; init: Record<string, unknown>; body: Record<string, unknown> }; type FetchCall = { url: string; init: Record<string, unknown>; body: Record<string, unknown> };
function captureFetch(response: Response) { function captureFetch(response: Response) {
const calls: FetchCall[] = []; const calls: FetchCall[] = [];
globalThis.fetch = async (url, init: RequestInit = {}) => { globalThis.fetch = async (url, init: RequestInit = {}) => {
calls.push({ url: String(url), init: init as Record<string, unknown>, body: JSON.parse(String(init.body)) }); calls.push({
url: String(url),
init: init as Record<string, unknown>,
body: JSON.parse(String(init.body)),
});
return response; return response;
}; };
return calls; return calls;
} }
// ── failing tests (before fix, user content is the raw array) ────────────── test("#5166 user message with multi-part array content passes through as an OpenAI array", async () => {
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Hello" },
{ type: "text", text: "World" },
],
},
],
},
});
test( const userMsg = (calls[0].body.messages as Record<string, unknown>[])[0];
"#5166 user message with multi-part array content is flattened to a string (#5166)", // OpenAI array content is valid on /provider/v1 — forwarded as-is.
async () => { assert.ok(Array.isArray(userMsg.content), "array content forwarded (no CLI flattening)");
const calls = captureFetch( const parts = userMsg.content as Record<string, unknown>[];
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]) assert.equal(parts.length, 2);
); assert.equal(parts[0].text, "Hello");
assert.equal(parts[1].text, "World");
});
await getExecutor("command-code").execute({ test("#5166 user message with single text-part array passes through", async () => {
model: "deepseek/deepseek-v4-pro", const calls = captureFetch(okResponse());
stream: false, await getExecutor("command-code").execute({
credentials: { apiKey: "cc_test_key" }, model: "deepseek/deepseek-v4-pro",
body: { stream: false,
messages: [ credentials: { apiKey: "cc_test_key" },
{ body: {
role: "user", messages: [{ role: "user", content: [{ type: "text", text: "Hi there" }] }],
content: [ },
{ type: "text", text: "Hello" }, });
{ type: "text", text: "World" }, const userMsg = (calls[0].body.messages as Record<string, unknown>[])[0];
], const parts = userMsg.content as Record<string, unknown>[];
}, assert.equal(parts.length, 1);
], assert.equal(parts[0].text, "Hi there");
}, });
});
const posted = calls[0].body; test("#5166 user message with plain string content passes through unchanged", async () => {
const userMsg = (posted.params as Record<string, unknown[]>).messages[0] as Record< const calls = captureFetch(okResponse());
string, await getExecutor("command-code").execute({
unknown model: "deepseek/deepseek-v4-pro",
>; stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Plain string message" }] },
});
const userMsg = (calls[0].body.messages as Record<string, unknown>[])[0];
assert.equal(userMsg.content, "Plain string message");
});
// Must be a string — never an array — otherwise Command Code's upstream returns 400. test("#5166 user message with mixed parts (text + image_url) keeps all parts", async () => {
assert.equal( const calls = captureFetch(okResponse());
typeof userMsg.content, await getExecutor("command-code").execute({
"string", model: "deepseek/deepseek-v4-pro",
`user message content must be a string, got ${typeof userMsg.content}` stream: false,
); credentials: { apiKey: "cc_test_key" },
// Joined text parts with "\n" body: {
assert.equal(userMsg.content, "Hello\nWorld"); messages: [
} {
); role: "user",
content: [
test( { type: "text", text: "Describe this:" },
"#5166 user message with single text-part array is flattened to a plain string", { type: "image_url", image_url: { url: "https://example.com/img.png" } },
async () => { ],
const calls = captureFetch( },
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]) ],
); },
});
await getExecutor("command-code").execute({ const userMsg = (calls[0].body.messages as Record<string, unknown>[])[0];
model: "deepseek/deepseek-v4-pro", const parts = userMsg.content as Record<string, unknown>[];
stream: false, assert.equal(parts.length, 2, "text + image both preserved");
credentials: { apiKey: "cc_test_key" }, assert.equal(parts[0].text, "Describe this:");
body: { assert.equal(parts[1].type, "image_url");
messages: [ });
{
role: "user",
content: [{ type: "text", text: "Hi there" }],
},
],
},
});
const posted = calls[0].body;
const userMsg = (posted.params as Record<string, unknown[]>).messages[0] as Record<
string,
unknown
>;
assert.equal(typeof userMsg.content, "string");
assert.equal(userMsg.content, "Hi there");
}
);
test(
"#5166 user message with plain string content passes through unchanged (no regression)",
async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: "Plain string message",
},
],
},
});
const posted = calls[0].body;
const userMsg = (posted.params as Record<string, unknown[]>).messages[0] as Record<
string,
unknown
>;
assert.equal(typeof userMsg.content, "string");
assert.equal(userMsg.content, "Plain string message");
}
);
test(
"#5166 user message with mixed parts (text + image_url) keeps only text parts",
async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Describe this:" },
{ type: "image_url", image_url: { url: "https://example.com/img.png" } },
],
},
],
},
});
const posted = calls[0].body;
const userMsg = (posted.params as Record<string, unknown[]>).messages[0] as Record<
string,
unknown
>;
assert.equal(typeof userMsg.content, "string");
// Only text parts extracted; image_url part is dropped (not a "text" type)
assert.equal(userMsg.content, "Describe this:");
}
);

View File

@@ -1,9 +1,13 @@
/** /**
* Vision / multimodal support tests for the Command Code executor. * Vision / multimodal support tests for the Command Code executor.
* *
* Verifies that vision-capable models (MiniMax M3, MiMo V2.5, Kimi K2, Qwen 3.x, GPT-5, Claude 3/4, Fable 5, Gemini 3.x, Stepfun, Fugu, etc.) * Since #10265 the executor posts to the documented /provider/v1/chat/completions
* receive image parts in Command Code CLI format, while text-only * endpoint, which speaks the standard OpenAI chat.completions format. User image
* models strip images as before (no regression). * content (OpenAI `image_url` parts and Anthropic Messages-style source blocks)
* passes through unchanged — the endpoint natively understands both shapes, so
* there is no CLI-specific conversion (and no CLI-wire image stripping) left to
* verify. These tests pin that passthrough plus the #10809 wire-model
* normalization, which still applies to /provider/v1.
*/ */
import test from "node:test"; import test from "node:test";
import assert from "node:assert/strict"; import assert from "node:assert/strict";
@@ -19,9 +23,8 @@ const core = await import("../../src/lib/db/core.ts");
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
function commandCodeStream(lines: unknown[]) { function okResponse() {
const text = lines.map((l) => JSON.stringify(l)).join("\n") + "\n"; return new Response("{}", { status: 200, headers: { "Content-Type": "application/json" } });
return new Response(text, { status: 200, headers: { "Content-Type": "application/x-ndjson" } });
} }
test.after(() => { test.after(() => {
@@ -52,44 +55,28 @@ function captureFetch(response: Response) {
} }
function userContent(calls: FetchCall[]): unknown { function userContent(calls: FetchCall[]): unknown {
return ( return (calls[0].body.messages as Record<string, unknown>[])[0].content;
(calls[0].body.params as Record<string, unknown[]>).messages as Record<string, unknown>[] }
)[0].content;
function wireModel(calls: FetchCall[]): string {
return calls[0].body.model as string;
} }
// ── wire model normalization (#10809) ──────────────────────────────── // ── wire model normalization (#10809) ────────────────────────────────
//
// Command Code's /alpha/generate endpoint serves most models under a
// vendor-prefixed wire id and defaults an unprefixed id to the `anthropic:`
// provider (403 "Model/provider not recognized: anthropic:<id>"). A bare id
// reaches the executor when an operator sets a custom vision model in the
// Vision Bridge picker (e.g. `command-code/mimo-v2.5`). The executor must
// normalize to the documented vendor-prefixed wire form.
function wireModel(calls: FetchCall[]): string {
return (calls[0].body.params as Record<string, unknown>).model as string;
}
test("#10809: command-code/mimo-v2.5 wire model is normalized to xiaomi/mimo-v2.5", async () => { test("#10809: command-code/mimo-v2.5 wire model is normalized to xiaomi/mimo-v2.5", async () => {
const calls = captureFetch( const calls = captureFetch(okResponse());
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "command-code/mimo-v2.5", model: "command-code/mimo-v2.5",
stream: false, stream: false,
credentials: { apiKey: "cc_test_key" }, credentials: { apiKey: "cc_test_key" },
body: { body: { model: "command-code/mimo-v2.5", messages: [{ role: "user", content: "hi" }] },
model: "command-code/mimo-v2.5",
messages: [{ role: "user", content: "hi" }],
},
}); });
assert.equal(wireModel(calls), "xiaomi/mimo-v2.5"); assert.equal(wireModel(calls), "xiaomi/mimo-v2.5");
}); });
test("#10809: cmd/mimo-v2.5 (alias prefix) is also normalized", async () => { test("#10809: cmd/mimo-v2.5 (alias prefix) is also normalized", async () => {
const calls = captureFetch( const calls = captureFetch(okResponse());
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "cmd/mimo-v2.5", model: "cmd/mimo-v2.5",
stream: false, stream: false,
@@ -100,9 +87,7 @@ test("#10809: cmd/mimo-v2.5 (alias prefix) is also normalized", async () => {
}); });
test("#10809: already vendor-prefixed wire ids pass through unchanged", async () => { test("#10809: already vendor-prefixed wire ids pass through unchanged", async () => {
const calls = captureFetch( const calls = captureFetch(okResponse());
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "command-code/deepseek/deepseek-v4-pro", model: "command-code/deepseek/deepseek-v4-pro",
stream: false, stream: false,
@@ -115,13 +100,10 @@ test("#10809: already vendor-prefixed wire ids pass through unchanged", async ()
assert.equal(wireModel(calls), "deepseek/deepseek-v4-pro"); assert.equal(wireModel(calls), "deepseek/deepseek-v4-pro");
}); });
// ── vision models: image parts preserved in CC CLI format ───────────── // ── image content passthrough (OpenAI /provider/v1 surface) ──────────
test("vision model minimax-m3 preserves image_url part as CC CLI {type:image}", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
test("image_url parts pass through unchanged (text + image preserved)", async () => {
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "MiniMaxAI/MiniMax-M3", model: "MiniMaxAI/MiniMax-M3",
stream: false, stream: false,
@@ -132,451 +114,25 @@ test("vision model minimax-m3 preserves image_url part as CC CLI {type:image}",
role: "user", role: "user",
content: [ content: [
{ type: "text", text: "What's in this?" }, { type: "text", text: "What's in this?" },
{ { type: "image_url", image_url: { url: "data:image/png;base64,iVBORw0KGgo=" } },
type: "image_url",
image_url: { url: "data:image/png;base64,iVBORw0KGgo=" },
},
], ],
}, },
], ],
}, },
}); });
const content = userContent(calls); const content = userContent(calls) as Record<string, unknown>[];
assert.ok(Array.isArray(content), "vision model user content must be an array"); assert.equal(content.length, 2);
const parts = content as Record<string, unknown>[]; assert.equal(content[0].type, "text");
assert.equal(parts.length, 2); assert.equal(content[1].type, "image_url", "image_url part preserved as-is");
assert.equal(
// Text part preserved (content[1].image_url as { url: string }).url,
assert.equal(parts[0].type, "text"); "data:image/png;base64,iVBORw0KGgo="
assert.equal(parts[0].text, "What's in this?"); );
// Image part converted to CC CLI format
assert.equal(parts[1].type, "image");
assert.equal(parts[1].image, "data:image/png;base64,iVBORw0KGgo=");
}); });
test("vision model minimax-m3 preserves image_url with HTTP URL", async () => { test("Anthropic Messages-style source image blocks pass through unchanged", async () => {
const calls = captureFetch( const calls = captureFetch(okResponse());
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "minimax-m3",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Describe" },
{
type: "image_url",
image_url: { url: "https://example.com/photo.jpg" },
},
],
},
],
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content));
const parts = content as Record<string, unknown>[];
assert.equal(parts.length, 2);
assert.equal(parts[1].type, "image");
assert.equal(parts[1].image, "https://example.com/photo.jpg");
});
test("vision model mimo-v2.5 preserves image parts", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "mimo-v2.5",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Analyze" },
{
type: "image_url",
image_url: { url: "https://example.com/img.png" },
},
],
},
],
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content));
const parts = content as Record<string, unknown>[];
assert.equal(parts.length, 2);
assert.equal(parts[1].type, "image");
});
test("vision model mimo-v2.5-pro is text-only (no image parts)", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "mimo-v2.5-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Hi" },
{
type: "image_url",
image_url: { url: "https://example.com/img.png" },
},
],
},
],
},
});
const content = userContent(calls);
// mimo-v2.5-pro is text-only — content must be flattened to a plain string
assert.equal(typeof content, "string");
assert.equal(content, "Hi");
});
test("vision model mimo-v2-omni preserves image parts", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "mimo-v2-omni",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Check" },
{
type: "image_url",
image_url: { url: "data:image/jpeg;base64,/9j/4AAQ=" },
},
],
},
],
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content));
const parts = content as Record<string, unknown>[];
assert.equal(parts.length, 2);
assert.equal(parts[1].type, "image");
assert.equal(parts[1].image, "data:image/jpeg;base64,/9j/4AAQ=");
});
// ── non-vision models: images still stripped (no regression) ──────────
test("text-only model deepseek-v4-pro strips image_url parts", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Hello" },
{
type: "image_url",
image_url: { url: "https://example.com/img.png" },
},
],
},
],
},
});
const content = userContent(calls);
// Non-vision model: content is a plain string, images stripped
assert.equal(typeof content, "string");
assert.equal(content, "Hello");
});
test("text-only model deepseek-v4-flash strips image_url parts (no regression)", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-flash",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Text only" },
{
type: "image_url",
image_url: { url: "data:image/png;base64,AAA=" },
},
],
},
],
},
});
const content = userContent(calls);
assert.equal(typeof content, "string");
assert.equal(content, "Text only");
});
// ── edge cases ────────────────────────────────────────────────────────
test("vision model with only image content emits empty text fallback", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "minimax-m3",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{
type: "image_url",
image_url: { url: "data:image/png;base64,iVBOR=" },
},
],
},
],
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content));
const parts = content as Record<string, unknown>[];
// Single image part preserved — no empty text injected because
// the image itself keeps content non-empty.
assert.equal(parts.length, 1);
assert.equal(parts[0].type, "image");
assert.equal(parts[0].image, "data:image/png;base64,iVBOR=");
});
test("vision model passes plain string content through unchanged", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "minimax-m3",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [{ role: "user", content: "Plain string message" }],
},
});
const content = userContent(calls);
assert.equal(typeof content, "string");
assert.equal(content, "Plain string message");
});
test("vision model honors body.model rewrite for vision detection", async () => {
// #5166 scenario: body.model overwrites the execute model arg.
// Vision detection must use the rewritten model id.
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
// execute() gets a non-vision combo model, body.model rewrites to a vision model
await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
model: "MiniMaxAI/MiniMax-M3",
messages: [
{
role: "user",
content: [
{ type: "text", text: "Describe" },
{
type: "image_url",
image_url: { url: "https://example.com/img.png" },
},
],
},
],
},
});
const content = userContent(calls);
// body.model = MiniMax-M3 (vision) → images preserved
assert.ok(Array.isArray(content));
const parts = content as Record<string, unknown>[];
assert.equal(parts.length, 2);
assert.equal(parts[1].type, "image");
});
test("vision model with multiple image parts preserves all of them", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "minimax-m3",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Compare" },
{
type: "image_url",
image_url: { url: "https://example.com/a.jpg" },
},
{
type: "image_url",
image_url: { url: "https://example.com/b.jpg" },
},
],
},
],
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content));
const parts = content as Record<string, unknown>[];
assert.equal(parts.length, 3);
assert.equal(parts[0].type, "text");
assert.equal(parts[1].type, "image");
assert.equal(parts[1].image, "https://example.com/a.jpg");
assert.equal(parts[2].type, "image");
assert.equal(parts[2].image, "https://example.com/b.jpg");
});
test("vision model with image_url as plain string (no object wrapper) still works", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "minimax-m3",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Look" },
{
type: "image_url",
image_url: "https://example.com/img.png",
},
],
},
],
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content));
const parts = content as Record<string, unknown>[];
assert.equal(parts.length, 2);
assert.equal(parts[1].type, "image");
assert.equal(parts[1].image, "https://example.com/img.png");
});
// ── CC vision models (Command Code docs registry) ──────────────────
const VISION_CASES = [
["Kimi K2.6", "moonshotai/Kimi-K2.6"],
["Kimi K2.7 Code", "moonshotai/Kimi-K2.7-Code"],
["Kimi K2.5", "moonshotai/Kimi-K2.5"],
["Qwen 3.6 Plus", "Qwen/Qwen3.6-Plus"],
["Qwen 3.7 Plus", "Qwen/Qwen3.7-Plus"],
["Step 3.7 Flash", "stepfun/Step-3.7-Flash"],
["GPT-5.5", "gpt-5.5"],
["GPT-5.4", "gpt-5.4"],
["GPT-5.3 Codex", "gpt-5.3-codex"],
["GPT-5.4 Mini", "gpt-5.4-mini"],
["Claude Fable 5", "claude-fable-5"],
["Sakana Fugu Ultra", "sakana/fugu-ultra"],
["Claude Opus 4.7 (isVisionModelId)", "claude-opus-4-7"],
["Claude Sonnet 4.6 (isVisionModelId)", "claude-sonnet-4-6"],
["Gemini 3.5 Flash (isVisionModelId)", "google/gemini-3.5-flash"],
];
for (const [name, model] of VISION_CASES) {
test(`vision model ${name} preserves image parts`, async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model,
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Check" },
{
type: "image_url",
image_url: { url: "https://example.com/img.png" },
},
],
},
],
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content), `${name} user content must be an array`);
const parts = content;
assert.equal(parts.length, 2);
assert.equal(parts[1].type, "image");
});
}
// ── Anthropic-shaped image blocks (Zoo Code / Claude-Code-compatible clients) ──
test("vision model mimo-v2.5 preserves Anthropic source.base64 image block", async () => {
// Zoo Code sends Messages-API-shaped content blocks to the OpenAI
// /v1/chat/completions surface: { type:"image", source:{ base64 } }.
// The vision-bridge guardrail skips vision-capable models (cmd/xiaomi/mimo-v2.5
// resolves supportsVision=true via the mimo-v2.5 leaf spec), so the raw block
// must survive to the executor and be converted to CC CLI { type:"image" }.
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "xiaomi/mimo-v2.5", model: "xiaomi/mimo-v2.5",
stream: false, stream: false,
@@ -601,24 +157,15 @@ test("vision model mimo-v2.5 preserves Anthropic source.base64 image block", asy
}, },
}); });
const content = userContent(calls); const content = userContent(calls) as Record<string, unknown>[];
assert.ok(Array.isArray(content), "user content must be an array"); assert.equal(content.length, 2, "text + image parts preserved");
const parts = content as Record<string, unknown>[]; assert.equal(content[1].type, "image");
assert.equal(parts.length, 2, "text + image parts preserved"); assert.equal((content[1].source as { type: string }).type, "base64");
assert.equal(parts[0].type, "text"); assert.equal((content[1].source as { data: string }).data, "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==");
assert.equal(parts[1].type, "image");
assert.equal(
parts[1].image,
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
"base64 payload is rebuilt into a CC CLI data URL"
);
}); });
test("vision model preserves Anthropic source.url image block", async () => { test("Anthropic source.url image block passes through unchanged", async () => {
const calls = captureFetch( const calls = captureFetch(okResponse());
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "xiaomi/mimo-v2.5", model: "xiaomi/mimo-v2.5",
stream: false, stream: false,
@@ -629,31 +176,23 @@ test("vision model preserves Anthropic source.url image block", async () => {
role: "user", role: "user",
content: [ content: [
{ type: "text", text: "Look" }, { type: "text", text: "Look" },
{ { type: "image", source: { type: "url", url: "https://example.com/img.png" } },
type: "image",
source: { type: "url", url: "https://example.com/img.png" },
},
], ],
}, },
], ],
}, },
}); });
const content = userContent(calls); const content = userContent(calls) as Record<string, unknown>[];
assert.ok(Array.isArray(content)); assert.equal(content.length, 2);
const parts = content as Record<string, unknown>[]; assert.equal(content[1].type, "image");
assert.equal(parts.length, 2); assert.deepEqual(content[1].source, { type: "url", url: "https://example.com/img.png" });
assert.equal(parts[1].type, "image");
assert.equal(parts[1].image, "https://example.com/img.png");
}); });
test("text-only model deepseek-v4-flash strips Anthropic source.base64 image block", async () => { test("multiple image parts are all preserved", async () => {
const calls = captureFetch( const calls = captureFetch(okResponse());
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-flash", model: "minimax-m3",
stream: false, stream: false,
credentials: { apiKey: "cc_test_key" }, credentials: { apiKey: "cc_test_key" },
body: { body: {
@@ -661,44 +200,41 @@ test("text-only model deepseek-v4-flash strips Anthropic source.base64 image blo
{ {
role: "user", role: "user",
content: [ content: [
{ type: "text", text: "Text only" }, { type: "text", text: "Compare" },
{ { type: "image_url", image_url: { url: "https://example.com/a.jpg" } },
type: "image", { type: "image_url", image_url: { url: "https://example.com/b.jpg" } },
source: {
type: "base64",
media_type: "image/png",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
},
},
], ],
}, },
], ],
}, },
}); });
const content = userContent(calls) as Record<string, unknown>[];
assert.equal(content.length, 3);
assert.equal(content[1].type, "image_url");
assert.equal(content[2].type, "image_url");
});
test("plain string content passes through unchanged", async () => {
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "minimax-m3",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Plain string message" }] },
});
const content = userContent(calls); const content = userContent(calls);
// Text-only model: content flattened to plain string, image stripped.
assert.equal(typeof content, "string"); assert.equal(typeof content, "string");
assert.equal(content, "Text only"); assert.equal(content, "Plain string message");
}); });
// ── conservative vision family lock (gpt-5.4-mini / gpt-5.3-codex) ───── test("text-only model still forwards image parts (passthrough, no CLI stripping)", async () => {
// // The /provider/v1 OpenAI surface accepts image content for any model id; the
// These two ids stay INSIDE the `/gpt-5/` vision family: both accept image // executor forwards content untouched, so there is no text-only stripping.
// input on the OpenAI API, and there is no verified Command Code backend data const calls = captureFetch(okResponse());
// marking them text-only. These tests pin that conservative executor behavior
// so a future "narrow the regex" change cannot silently strip images from models
// that can see them (the #4071 regression class). The #10703 Vision Bridge
// candidate-list fix lives in the shared capability resolution
// (KNOWN_TEXT_ONLY_DESPITE_SYNC), NOT in the executor's wire transform.
test("gpt-5.4-mini keeps image parts (conservative vision family lock)", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({ await getExecutor("command-code").execute({
model: "gpt-5.4-mini", model: "deepseek/deepseek-v4-pro",
stream: false, stream: false,
credentials: { apiKey: "cc_test_key" }, credentials: { apiKey: "cc_test_key" },
body: { body: {
@@ -706,54 +242,15 @@ test("gpt-5.4-mini keeps image parts (conservative vision family lock)", async (
{ {
role: "user", role: "user",
content: [ content: [
{ type: "text", text: "What's in this?" }, { type: "text", text: "Hello" },
{ { type: "image_url", image_url: { url: "https://example.com/img.png" } },
type: "image_url",
image_url: { url: "https://example.com/img.png" },
},
], ],
}, },
], ],
}, },
}); });
const content = userContent(calls); const content = userContent(calls) as Record<string, unknown>[];
assert.ok(Array.isArray(content), "gpt-5.4-mini must be treated as vision-capable"); assert.equal(content.length, 2, "content array forwarded unchanged");
const parts = content as Record<string, unknown>[]; assert.equal(content[1].type, "image_url");
assert.equal(parts.length, 2); });
assert.equal(parts[1].type, "image");
assert.equal(parts[1].image, "https://example.com/img.png");
});
test("gpt-5.3-codex keeps image parts (conservative vision family lock)", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
await getExecutor("command-code").execute({
model: "gpt-5.3-codex",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
messages: [
{
role: "user",
content: [
{ type: "text", text: "Describe" },
{
type: "image_url",
image_url: { url: "https://example.com/img.png" },
},
],
},
],
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content), "gpt-5.3-codex must be treated as vision-capable");
const parts = content as Record<string, unknown>[];
assert.equal(parts.length, 2);
assert.equal(parts[1].type, "image");
assert.equal(parts[1].image, "https://example.com/img.png");
});

View File

@@ -15,7 +15,10 @@ class MockM365WebSocket {
closed = false; closed = false;
listeners = new Map<string, Listener[]>(); listeners = new Map<string, Listener[]>();
constructor(public url: string, public options: unknown) { constructor(
public url: string,
public options: unknown
) {
MockM365WebSocket.instances.push(this); MockM365WebSocket.instances.push(this);
queueMicrotask(() => this.emit("open")); queueMicrotask(() => this.emit("open"));
} }
@@ -144,30 +147,55 @@ test("#7870: enterprise-tier chat invocation defaults tone to Magic", async () =
assert.equal(invocationArgs.tone, "Magic"); assert.equal(invocationArgs.tone, "Magic");
}); });
test("#10718: individual (no tier) chat invocation carries the recaptured 2026-08 shape", async () => { test("2026-08-21: individual (no tier) chat invocation carries the recaptured shape", async () => {
const invocationArgs = await sendChatInvocation(undefined); const invocationArgs = await sendChatInvocation(undefined);
const optionsSets = invocationArgs.optionsSets as string[]; const optionsSets = invocationArgs.optionsSets as string[];
// The 25-entry consumer/MSA set (enable_msa_user, pdnascan, …) is gone from // The pre-#10718 25-entry consumer/MSA set (enable_msa_user, pdnascan, …) is
// the wire — the stale set was part of the silently-dropped shape. // still gone from the wire — only the 2026-08-21 34-entry set is current.
assert.ok(!optionsSets.includes("enable_msa_user")); assert.ok(!optionsSets.includes("enable_msa_user"));
assert.ok(optionsSets.includes("enable_gg_gpt")); assert.ok(optionsSets.includes("enable_gg_gpt"));
// The browser sends tone:"magic" (lowercase) on the individual/EDU surface. // The individual/consumer surface now sends tone:"Magic" (capitalized) —
assert.equal(invocationArgs.tone, "magic"); // the #10718 lowercase "magic" is stale.
assert.equal(invocationArgs.tone, "Magic");
assert.deepEqual(invocationArgs.allowedMessageTypes, [ assert.deepEqual(invocationArgs.allowedMessageTypes, [
"Chat", "Chat",
"Suggestion", "Suggestion",
"InternalSearchQuery",
"Disengaged", "Disengaged",
"Progress",
"EndOfRequest",
"InternalLoaderMessage", "InternalLoaderMessage",
"Progress",
"GeneratedCode",
"RenderCardRequest",
"AdsQuery",
"SemanticSerp",
"GenerateContentQuery",
"GenerateGraphicArt",
"SearchQuery",
"ConfirmationCard",
"AuthError",
"DeveloperLogs",
"TriggerPlugin",
"HintInvocation",
"MemoryUpdate",
"EndOfRequest",
"TriggerConfirmation",
"ResumeInvokeAction",
"ResumeUserInputRequest",
"TriggerUserInputRequest",
"EscapeHatch",
"TriggerPluginAuth",
"ResumePluginAuth",
"SideBySide",
"ReferencesListComplete",
"SwitchRespondingEndpoint",
]); ]);
}); });
test("#10718: EDU-tier chat invocation carries the same recaptured shape", async () => { test("2026-08-21: EDU-tier chat invocation carries the same recaptured shape", async () => {
const invocationArgs = await sendChatInvocation("edu"); const invocationArgs = await sendChatInvocation("edu");
const optionsSets = invocationArgs.optionsSets as string[]; const optionsSets = invocationArgs.optionsSets as string[];
assert.ok(!optionsSets.includes("enable_msa_user")); assert.ok(!optionsSets.includes("enable_msa_user"));
assert.equal(invocationArgs.tone, "magic"); assert.equal(invocationArgs.tone, "Magic");
}); });
test("#8971: enterprise-tier chat invocation must send disconnectBehavior=continue", async () => { test("#8971: enterprise-tier chat invocation must send disconnectBehavior=continue", async () => {
@@ -179,11 +207,11 @@ test("#8971: enterprise-tier chat invocation must send disconnectBehavior=contin
); );
}); });
test("#8971/#10718: individual (no tier) chat invocation omits disconnectBehavior (not on the 2026-08 wire)", async () => { test("2026-08-21: individual (no tier) chat invocation also sends disconnectBehavior=continue (now on every tier)", async () => {
const invocationArgs = await sendChatInvocation(undefined); const invocationArgs = await sendChatInvocation(undefined);
assert.equal( assert.equal(
invocationArgs.disconnectBehavior, invocationArgs.disconnectBehavior,
undefined, "continue",
`individual-tier invocation must omit disconnectBehavior; got ${JSON.stringify(invocationArgs.disconnectBehavior)}` `individual-tier invocation must carry disconnectBehavior="continue"; got ${JSON.stringify(invocationArgs.disconnectBehavior)}`
); );
}); });

View File

@@ -41,7 +41,7 @@ test("#10718: metricsFrame emits the exact bytes observed in the browser capture
// ── Recaptured invocation shape ──────────────────────────────────────────── // ── Recaptured invocation shape ────────────────────────────────────────────
test("#10718: buildChatInvocation matches the recaptured arguments[0] key set", () => { test("2026-08-21: buildChatInvocation matches the recaptured arguments[0] key set", () => {
const arg = buildChatInvocation({ const arg = buildChatInvocation({
text: "Say OK in one word.", text: "Say OK in one word.",
traceId: "11111111-1111-1111-1111-111111111111", traceId: "11111111-1111-1111-1111-111111111111",
@@ -50,19 +50,25 @@ test("#10718: buildChatInvocation matches the recaptured arguments[0] key set",
conversationId: "44444444-4444-4444-4444-444444444444", conversationId: "44444444-4444-4444-4444-444444444444",
}).arguments[0] as Record<string, unknown>; }).arguments[0] as Record<string, unknown>;
// Exact key set from the capture — additions AND omissions are both pinned, // Exact key set from the 2026-08-21 capture (issue: individual/consumer M365
// because the stale keys are exactly what got the shape dropped. // Copilot calls got only SignalR keepalive pings and no type:1 update at all
// — "Stream ended before producing a non-ping SSE event" client-side). The
// #10718 shape below is missing exactly the keys this capture added.
assert.deepEqual(Object.keys(arg).sort(), [ assert.deepEqual(Object.keys(arg).sort(), [
"allowedMessageTypes", "allowedMessageTypes",
"clientCorrelationId", "clientCorrelationId",
"clientInfo", "clientInfo",
"conversationId", "conversationId",
"disconnectBehavior",
"extraExtensionParameters",
"isSbsSupported",
"isStartOfSession", "isStartOfSession",
"message", "message",
"options", "options",
"optionsSets", "optionsSets",
"plugins", "plugins",
"productThreadType", "productThreadType",
"renderReferencesBehindEOS",
"sessionId", "sessionId",
"sliceIds", "sliceIds",
"source", "source",
@@ -73,13 +79,32 @@ test("#10718: buildChatInvocation matches the recaptured arguments[0] key set",
"traceId", "traceId",
]); ]);
assert.equal(arg.productThreadType, "Office"); assert.equal(arg.productThreadType, "Office");
assert.deepEqual(arg.clientInfo, { clientAppName: "Office", clientPlatform: "mcmcopilot-web" }); assert.deepEqual(arg.clientInfo, {
clientAppName: "Office",
clientPlatform: "mcmcopilot-web",
clientEntrypoint: "mcmcopilot-officeweb",
clientSessionId: "22222222-2222-2222-2222-222222222222",
ProductCategory: "Chat",
clientAppType: "Web",
productEntryPoint: "ChatPanel",
deviceOS: "Windows",
deviceType: "Desktop",
clientPlatformVersion: "10",
});
assert.equal(arg.conversationId, "44444444-4444-4444-4444-444444444444"); assert.equal(arg.conversationId, "44444444-4444-4444-4444-444444444444");
assert.equal(arg.toolChoice, null); assert.equal(arg.toolChoice, null);
assert.equal(arg.tone, "magic"); // The individual/consumer surface now sends "Magic" (capitalized), matching
// the enterprise tone literal — the #10718 lowercase "magic" is stale.
assert.equal(arg.tone, "Magic");
assert.equal(arg.isSbsSupported, true);
assert.equal(arg.renderReferencesBehindEOS, true);
assert.deepEqual(arg.extraExtensionParameters, {});
assert.deepEqual(arg.plugins, [{ Id: "BingWebSearch", Source: "BuiltIn" }]);
// Sent on every tier now, not gated to enterprise as #8971 described.
assert.equal(arg.disconnectBehavior, "continue");
}); });
test("#10718: the message object carries the recaptured rich shape", () => { test("2026-08-21: the message object carries the recaptured rich shape", () => {
const arg = buildChatInvocation({ const arg = buildChatInvocation({
text: "Say OK in one word.", text: "Say OK in one word.",
traceId: "t", traceId: "t",
@@ -93,7 +118,9 @@ test("#10718: the message object carries the recaptured rich shape", () => {
"adaptiveCards", "adaptiveCards",
"attachments", "attachments",
"author", "author",
"clientInfo",
"clientPreferences", "clientPreferences",
"connectedFederatedConnections",
"entityAnnotationTypes", "entityAnnotationTypes",
"experienceType", "experienceType",
"inputMethod", "inputMethod",
@@ -107,26 +134,41 @@ test("#10718: the message object carries the recaptured rich shape", () => {
assert.equal(message.messageType, "Chat"); assert.equal(message.messageType, "Chat");
assert.equal(message.requestId, "r"); assert.equal(message.requestId, "r");
assert.equal(message.experienceType, "Default"); assert.equal(message.experienceType, "Default");
assert.deepEqual(message.entityAnnotationTypes, ["People", "File", "Event", "Email", "TeamsMessage"]); assert.deepEqual(message.entityAnnotationTypes, [
"People",
"File",
"Event",
"Email",
"TeamsMessage",
]);
assert.equal(message.attachments, null); assert.equal(message.attachments, null);
assert.deepEqual(message.locationInfo, { timeZone: "UTC", timeZoneOffset: 0 }); assert.deepEqual(message.locationInfo, { timeZone: "UTC", timeZoneOffset: 0 });
assert.deepEqual(message.connectedFederatedConnections, ["dummyId"]);
// Same clientInfo object echoed inside message, per the capture.
assert.deepEqual(message.clientInfo, arg.clientInfo);
}); });
test("#10718: default tier lists are the recaptured 14-entry optionsSets / 6-entry allowedMessageTypes", () => { test("2026-08-21: default tier lists are the recaptured 34-entry optionsSets / 30-entry allowedMessageTypes", () => {
const overrides = resolveChatInvocationOverrides(undefined); const overrides = resolveChatInvocationOverrides(undefined);
assert.equal(overrides.optionsSets.length, 14); assert.equal(overrides.optionsSets.length, 34);
assert.equal(overrides.allowedMessageTypes.length, 6); assert.equal(overrides.allowedMessageTypes.length, 30);
assert.equal(overrides.tone, "magic"); assert.equal(overrides.tone, "Magic");
// The pre-#10718 consumer/MSA flags are gone from the wire. assert.equal(overrides.disconnectBehavior, "continue");
const optionSets = M365_DEFAULT_OPTION_SETS as readonly string[]; const optionSets = M365_DEFAULT_OPTION_SETS as readonly string[];
const messageTypes = ALLOWED_MESSAGE_TYPES as readonly string[]; const messageTypes = ALLOWED_MESSAGE_TYPES as readonly string[];
for (const stale of ["enable_msa_user", "pdnascan", "cwc_code_interpreter", "rich_responses"]) { // Entries the 2026-08-21 capture showed that the #10718 lists lacked.
assert.ok(!optionSets.includes(stale), `${stale} must not be in the default option sets`); for (const present of [
"cwc_code_interpreter",
"rich_responses",
"async_client_interaction",
"flux_v3_references",
]) {
assert.ok(optionSets.includes(present), `${present} must be in the default option sets`);
} }
for (const stale of ["InternalSearchQuery", "GeneratedCode", "RenderCardRequest", "AdsQuery", "SemanticSerp", "GenerateContentQuery"]) { for (const present of ["InternalSearchQuery", "GeneratedCode", "AuthError", "TriggerPlugin"]) {
assert.ok(!messageTypes.includes(stale), `${stale} must not be in allowedMessageTypes`); assert.ok(messageTypes.includes(present), `${present} must be in allowedMessageTypes`);
} }
// Entries the capture showed and the old lists lacked. // Entries the #10718 capture showed and this capture still confirms.
assert.ok(optionSets.includes("cwcfluxgptv")); assert.ok(optionSets.includes("cwcfluxgptv"));
assert.ok(messageTypes.includes("EndOfRequest")); assert.ok(messageTypes.includes("EndOfRequest"));
}); });
@@ -134,8 +176,7 @@ test("#10718: default tier lists are the recaptured 14-entry optionsSets / 6-ent
// ── refresh_token helpers ────────────────────────────────────────────────── // ── refresh_token helpers ──────────────────────────────────────────────────
function fakeJwt(claims: Record<string, unknown>): string { function fakeJwt(claims: Record<string, unknown>): string {
const b64 = (value: unknown) => const b64 = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url");
Buffer.from(JSON.stringify(value)).toString("base64url");
return `${b64({ alg: "none" })}.${b64(claims)}.sig`; return `${b64({ alg: "none" })}.${b64(claims)}.sig`;
} }

View File

@@ -182,7 +182,9 @@ test("buildChatInvocation defaults stay backward compatible", () => {
requestId: "r", requestId: "r",
conversationId: "c", conversationId: "c",
}).arguments[0] as Record<string, unknown>; }).arguments[0] as Record<string, unknown>;
assert.deepEqual(arg.plugins, []); // 2026-08-21 capture (#11069): BingWebSearch BuiltIn plugin is now the
// default on all tiers (was [] in the #10718 shape); toolChoice stays null.
assert.deepEqual(arg.plugins, [{ Id: "BingWebSearch", Source: "BuiltIn" }]);
assert.equal(arg.toolChoice, null); assert.equal(arg.toolChoice, null);
assert.equal(arg.customInstructions, undefined); assert.equal(arg.customInstructions, undefined);
}); });

View File

@@ -0,0 +1,78 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
extractM365CredentialFromHar,
describeHarImportExpiry,
M365_CHATHUB_WS_PREFIX,
} from "../../../src/shared/utils/m365HarImport.ts";
function fakeJwt(exp: number): string {
const b64 = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url");
return `${b64({ alg: "none" })}.${b64({ exp })}.sig`;
}
function harWithUrl(url: string | null): string {
return JSON.stringify({
log: { entries: url ? [{ request: { url } }] : [] },
});
}
test("extracts access_token + chathubPath from a matching ChatHub WS entry", () => {
const exp = Math.floor(Date.now() / 1000) + 3600;
const token = fakeJwt(exp);
const url = `${M365_CHATHUB_WS_PREFIX}oid-123%40tenant-456?chatsessionid=abc&access_token=${token}`;
const result = extractM365CredentialFromHar(harWithUrl(url));
assert.equal(result.ok, true);
if (!result.ok) return;
assert.equal(result.chathubPath, "oid-123@tenant-456");
assert.equal(result.apiKey, `access_token=${token}; chathubPath=oid-123@tenant-456`);
assert.ok(result.expiresAt !== null && Math.abs(result.expiresAt - exp * 1000) < 1000);
});
test("picks the LAST matching entry when a HAR has multiple turns", () => {
const oldToken = fakeJwt(1000);
const freshToken = fakeJwt(9999999999);
const har = JSON.stringify({
log: {
entries: [
{ request: { url: `${M365_CHATHUB_WS_PREFIX}u%40t?access_token=${oldToken}` } },
{ request: { url: "https://unrelated.example/" } },
{ request: { url: `${M365_CHATHUB_WS_PREFIX}u%40t?access_token=${freshToken}` } },
],
},
});
const result = extractM365CredentialFromHar(har);
assert.equal(result.ok, true);
if (!result.ok) return;
assert.ok(result.apiKey.includes(freshToken));
});
test("returns notJson for malformed input", () => {
const result = extractM365CredentialFromHar("not json{{{");
assert.deepEqual(result, { ok: false, error: "notJson" });
});
test("returns noEntries when log.entries is missing/not an array", () => {
const result = extractM365CredentialFromHar(JSON.stringify({ log: {} }));
assert.deepEqual(result, { ok: false, error: "noEntries" });
});
test("returns noChathubUrl when no request matches the ChatHub prefix", () => {
const result = extractM365CredentialFromHar(harWithUrl("https://example.com/"));
assert.deepEqual(result, { ok: false, error: "noChathubUrl" });
});
test("returns missingFields when the URL matches but lacks access_token or chathubPath", () => {
const result = extractM365CredentialFromHar(harWithUrl(`${M365_CHATHUB_WS_PREFIX}`));
assert.equal(result.ok, false);
});
test("describeHarImportExpiry classifies ok/warn/bad/unknown", () => {
const now = Date.now();
assert.equal(describeHarImportExpiry(now + 30 * 60000, now).tone, "ok");
assert.equal(describeHarImportExpiry(now + 5 * 60000, now).tone, "warn");
assert.equal(describeHarImportExpiry(now - 60000, now).tone, "bad");
assert.equal(describeHarImportExpiry(null, now).tone, "unknown");
});

View File

@@ -14,11 +14,15 @@ describe("CommandCodeExecutor", () => {
assert.ok(executor); assert.ok(executor);
}); });
it("buildUrl returns a string", () => { it("buildUrl targets the documented /provider/v1/chat/completions endpoint (#10265)", () => {
const executor = new mod.CommandCodeExecutor(); const executor = new mod.CommandCodeExecutor();
const url = executor.buildUrl(); const url = executor.buildUrl();
assert.ok(typeof url === "string"); assert.ok(typeof url === "string");
assert.ok(url.includes("generate") && url.includes("commandcode")); assert.ok(
url.includes("/provider/v1/chat/completions"),
`expected the documented provider API endpoint, got: ${url}`
);
assert.ok(url.includes("commandcode"));
}); });
it("execute throws when no API key", async () => { it("execute throws when no API key", async () => {
@@ -57,7 +61,7 @@ describe("CommandCodeExecutor", () => {
} }
}); });
it("assistant tool-call conversion always emits a valid required arguments field (#regression input[N] missing required field arguments)", async () => { it("posts a flat OpenAI chat.completions body (no CLI envelope) to /provider/v1/chat/completions (#10265)", async () => {
const calls: Array<{ url: string; init: RequestInit; body: unknown }> = []; const calls: Array<{ url: string; init: RequestInit; body: unknown }> = [];
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => { globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
@@ -70,182 +74,8 @@ describe("CommandCodeExecutor", () => {
}) as typeof fetch; }) as typeof fetch;
const executor = new mod.CommandCodeExecutor(); const executor = new mod.CommandCodeExecutor();
const pairedId = "call_paired";
const body = {
messages: [
{ role: "user", content: "hi" },
{
role: "assistant",
content: "",
tool_calls: [
// Missing arguments entirely -> must still get a valid arguments field
{ id: "call_missing", type: "function", function: { name: "lookup" } },
// Empty string arguments -> "{}"
{
id: "call_empty",
type: "function",
function: { name: "lookup", arguments: "" },
},
// Valid object arguments -> round-trips as JSON string
{
id: pairedId,
type: "function",
function: { name: "lookup", arguments: { q: "docs" } },
},
// Valid string arguments -> preserved as-is
{
id: "call_string",
type: "function",
function: { name: "lookup", arguments: '{"q":"string"}' },
},
// Invalid JSON string arguments -> defaults to "{}"
{
id: "call_invalid",
type: "function",
function: { name: "lookup", arguments: "{invalid-json" },
},
// Tool call without name -> defaults tool-result toolName to "unknown"
{
id: "call_unnamed",
type: "function",
function: { arguments: { q: "unnamed" } },
},
],
},
{ role: "tool", tool_call_id: "call_missing", content: "r1" },
{ role: "tool", tool_call_id: "call_empty", content: "r2" },
{ role: "tool", tool_call_id: pairedId, content: "r3" },
{ role: "tool", tool_call_id: "call_string", content: "r4" },
{ role: "tool", tool_call_id: "call_invalid", content: "r5" },
{ role: "tool", tool_call_id: "call_unnamed", content: "r6" },
],
};
try {
await executor.execute({
model: "test",
body,
stream: false,
credentials: { apiKey: "fake-key" },
signal: null,
});
} finally {
globalThis.fetch = originalFetch;
}
assert.equal(calls.length, 1, "exactly one upstream call");
const sentBody = calls[0].body as {
params: { messages: Array<{ role: string; content: unknown }> };
};
const assistant = sentBody.params.messages.find((m) => m.role === "assistant");
assert.ok(assistant, "assistant turn present");
const parts = assistant.content as Array<Record<string, unknown>>;
const toolCalls = parts.filter((p) => p.type === "tool-call");
assert.equal(toolCalls.length, 6, "all six paired tool calls converted");
for (const call of toolCalls) {
assert.equal(
typeof call.arguments,
"string",
`tool-call ${String(call.toolCallId)} must carry a string arguments field`
);
const parsed = JSON.parse(call.arguments as string);
assert.equal(typeof parsed, "object");
assert.ok(!Array.isArray(parsed), "arguments must parse to a JSON object");
}
const byId = new Map(toolCalls.map((c) => [String(c.toolCallId), c]));
assert.equal(byId.get("call_missing").arguments, "{}", "missing arguments -> empty object");
assert.equal(byId.get("call_empty").arguments, "{}", "empty string arguments -> empty object");
assert.equal(
byId.get(pairedId).arguments,
'{"q":"docs"}',
"object arguments round-trip as JSON string"
);
assert.equal(
byId.get("call_string").arguments,
'{"q":"string"}',
"valid string arguments preserved as-is"
);
assert.equal(
byId.get("call_invalid").arguments,
"{}",
"invalid JSON string arguments -> empty object"
);
const toolMsgs = sentBody.params.messages.filter((m) => m.role === "tool");
assert.equal(toolMsgs.length, 6, "all 6 tool result messages present");
const resultByName = new Map(
toolMsgs.map((m) => {
const p = (m.content as Array<Record<string, unknown>>)[0];
return [String(p.toolCallId), String(p.toolName)];
})
);
assert.equal(resultByName.get("call_missing"), "lookup");
assert.equal(
resultByName.get("call_unnamed"),
"unknown",
"unnamed call falls back to 'unknown'"
);
// /alpha/generate also requires `arguments` on tool-result parts; a
// missing one is rejected with `input[N] missing required field 'arguments'`
// (the index landing on the tool message). Echo the paired call's
// normalized arguments.
const resultById = new Map(
toolMsgs.map((m) => {
const p = (m.content as Array<Record<string, unknown>>)[0];
return [String(p.toolCallId), p];
})
);
assert.equal(resultById.size, 6, "each tool result maps to its call id");
for (const p of resultById.values()) {
assert.equal(
typeof p.arguments,
"string",
`tool-result ${String(p.toolCallId)} must carry a string arguments field`
);
const parsed = JSON.parse(p.arguments as string);
assert.equal(typeof parsed, "object");
assert.ok(!Array.isArray(parsed), "tool-result arguments must parse to a JSON object");
}
assert.equal(
resultById.get("call_missing").arguments,
"{}",
"tool-result echoes paired call's missing arguments as empty object"
);
assert.equal(
resultById.get(pairedId).arguments,
'{"q":"docs"}',
"tool-result echoes paired call's object arguments as JSON string"
);
assert.equal(
resultById.get("call_string").arguments,
'{"q":"string"}',
"tool-result echoes paired call's valid string arguments as-is"
);
assert.equal(
resultById.get("call_empty").arguments,
"{}",
"tool-result echoes paired call's empty arguments as empty object"
);
assert.equal(
resultById.get("call_invalid").arguments,
"{}",
"tool-result echoes paired call's invalid JSON arguments as empty object"
);
});
it("COMMAND_CODE_VERSION default constant is 1.15.1", () => {
assert.equal(mod.COMMAND_CODE_VERSION, "1.15.1");
});
it("renames tool names colliding with upstream built-ins on the wire and un-renames on the response (#regression input[N] missing required field arguments from a tool_search result)", async () => {
// Upstream /alpha/generate normalizes tool-call/result parts against its
// OWN built-in registry for matching names; `tool_search` collides and its
// result is rejected with `input[N] missing required field 'arguments'`.
// Verified live: renaming the pair to a non-colliding name passes.
const body = { const body = {
model: "gpt-5.4",
messages: [ messages: [
{ role: "user", content: "hi" }, { role: "user", content: "hi" },
{ {
@@ -253,29 +83,19 @@ describe("CommandCodeExecutor", () => {
content: "", content: "",
tool_calls: [ tool_calls: [
{ {
id: "call_00_AAAAAAAAAAAAAAAAA", id: "call_1",
type: "function", type: "function",
function: { name: "tool_search", arguments: '{"query":"x"}' }, function: { name: "lookup", arguments: '{"q":"docs"}' },
},
{
id: "call_01_BBBBBBBBBBBBBBBBB",
type: "function",
function: { name: "lookup", arguments: '{"q":"1"}' },
}, },
// Missing arguments entirely stays missing — passthrough, no CLI
// envelope injection of a synthetic `arguments` field.
{ id: "call_2", type: "function", function: { name: "search" } },
], ],
}, },
{ role: "tool", tool_call_id: "call_00_AAAAAAAAAAAAAAAAA", content: "r1" }, { role: "tool", tool_call_id: "call_1", content: "r1" },
{ role: "tool", tool_call_id: "call_01_BBBBBBBBBBBBBBBBB", content: "r2" }, { role: "tool", tool_call_id: "call_2", content: "r2" },
], ],
tools: [ tools: [
{
type: "function",
function: {
name: "tool_search",
description: "search tools",
parameters: { type: "object", properties: { query: { type: "string" } } },
},
},
{ {
type: "function", type: "function",
function: { function: {
@@ -287,31 +107,9 @@ describe("CommandCodeExecutor", () => {
], ],
}; };
const calls: Array<{ url: string; init: RequestInit; body: unknown }> = [];
const originalFetch = globalThis.fetch;
// execute() makes a single upstream fetch; capture the wire request and
// return a stream with a tool-call event using the renamed wire name.
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
calls.push({
url: String(url),
init: init || {},
body: JSON.parse(String((init as RequestInit | undefined)?.body)),
});
const streamBody =
'data: {"type":"tool-call","toolCallId":"c1","toolName":"omniroute_tool_search","input":{"query":"x"}}\n\n' +
'data: {"type":"finish","finishReason":"tool_use"}\n\n' +
"data: [DONE]\n\n";
return new Response(streamBody, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}) as typeof fetch;
const executor = new mod.CommandCodeExecutor();
let result: { response: Response } | null = null;
try { try {
result = await executor.execute({ await executor.execute({
model: "test", model: "gpt-5.4",
body, body,
stream: false, stream: false,
credentials: { apiKey: "fake-key" }, credentials: { apiKey: "fake-key" },
@@ -321,46 +119,85 @@ describe("CommandCodeExecutor", () => {
globalThis.fetch = originalFetch; globalThis.fetch = originalFetch;
} }
const sentBody = calls[0].body as { assert.equal(calls.length, 1, "exactly one upstream call");
params: {
messages: Array<{ role: string; content: unknown }>;
tools: Array<{ name: string }>;
};
};
const toolDefNames = sentBody.params.tools.map((t) => t.name);
assert.ok( assert.ok(
toolDefNames.includes("omniroute_tool_search"), calls[0].url.includes("/provider/v1/chat/completions"),
"colliding tool def renamed on the wire" `expected documented provider endpoint, got: ${calls[0].url}`
); );
assert.ok(toolDefNames.includes("lookup"), "non-colliding tool def untouched"); const sent = calls[0].body as Record<string, unknown>;
// No CLI envelope.
const assistant = sentBody.params.messages.find((m) => m.role === "assistant"); assert.equal(sent.config, undefined, "CLI envelope `config` must not be sent");
const toolCallParts = (assistant?.content as Array<Record<string, unknown>>).filter( assert.equal(sent.params, undefined, "CLI envelope `params` wrapper must not be sent");
(p) => p.type === "tool-call" assert.equal(sent.model, "gpt-5.4", "flat OpenAI model at top level");
assert.equal((sent.messages as Array<{ role: string }>)[0].role, "user");
// Assistant tool_calls pass through unchanged (no CLI tool-call/tool-result parts).
const assistant = (sent.messages as Array<Record<string, unknown>>).find(
(m) => m.role === "assistant"
); );
const toolSearchCall = toolCallParts.find((p) => p.toolName === "omniroute_tool_search"); assert.ok(assistant, "assistant turn present");
assert.ok(toolSearchCall, "assistant tool-call part renamed on the wire"); const toolCalls = assistant?.tool_calls as Array<{
const lookupCall = toolCallParts.find((p) => p.toolName === "lookup"); id: string;
assert.ok(lookupCall, "non-colliding tool-call part untouched"); function: { name: string; arguments?: string };
}>;
const toolMsgs = sentBody.params.messages.filter((m) => m.role === "tool"); assert.equal(toolCalls.length, 2, "both tool calls pass through untouched");
const toolSearchResult = toolMsgs.find( assert.equal(toolCalls[0].function.name, "lookup");
(m) => (m.content as Array<Record<string, unknown>>)[0]?.toolName === "omniroute_tool_search" assert.equal(toolCalls[0].function.arguments, '{"q":"docs"}');
assert.equal(toolCalls[1].function.arguments, undefined, "missing arguments stays missing (no injection)");
// Tool role message (OpenAI flat) preserved.
const toolMsg = (sent.messages as Array<Record<string, unknown>>).find(
(m) => m.role === "tool"
); );
assert.ok(toolSearchResult, "tool-result part renamed on the wire"); assert.equal(toolMsg?.tool_call_id, "call_1");
// Response path: upstream emits the renamed wire name; the client must get
// its original name back.
assert.ok(result, "execute returned a response");
const json = (await result.response.json()) as {
choices: Array<{ message: { tool_calls?: Array<{ function: { name: string } }> } }>;
};
const toolCalls = json.choices[0].message.tool_calls ?? [];
assert.equal(toolCalls.length, 1, "one tool call translated");
assert.equal( assert.equal(
toolCalls[0].function.name, (sent.tools as Array<{ function: { name: string } }>)[0].function.name,
"tool_search", "lookup",
"renamed wire name un-renamed for the client" "tool definitions pass through in OpenAI shape (no rename)"
); );
}); });
});
it("passes through the upstream OpenAI response and drops CLI-impersonation headers (#10265)", async () => {
const calls: Array<{ url: string; init: RequestInit }> = [];
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
calls.push({ url: String(url), init: init || {} });
const chunk =
'data: {"id":"c1","object":"chat.completion.chunk","model":"gpt-5.4",' +
'"choices":[{"index":0,"delta":{"content":"hi"}}]}\n\n' +
'data: {"id":"c1","object":"chat.completion.chunk","model":"gpt-5.4",' +
'"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":' +
'{"prompt_tokens":2,"completion_tokens":1,"total_tokens":3}}\n\n' +
"data: [DONE]\n\n";
return new Response(chunk, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}) as typeof fetch;
const executor = new mod.CommandCodeExecutor();
let result: { response: Response; headers: Record<string, string> } | null = null;
try {
result = await executor.execute({
model: "gpt-5.4",
body: { messages: [{ role: "user", content: "hi" }] },
stream: true,
credentials: { apiKey: "fake-key" },
signal: null,
});
} finally {
globalThis.fetch = originalFetch;
}
assert.ok(result, "execute returned a result");
const headers = result.headers;
assert.equal(headers["x-command-code-version"], undefined, "CLI-impersonation header dropped");
assert.equal(headers["x-cli-environment"], undefined, "CLI-impersonation header dropped");
assert.equal(headers.Authorization, "Bearer fake-key");
// The upstream OpenAI SSE passes through untouched (no CLI re-parsing).
const text = await result.response.text();
assert.ok(text.includes("chat.completion.chunk"), "OpenAI-format SSE passed through");
assert.ok(text.includes('"content":"hi"'), "delta content preserved");
assert.ok(text.includes("[DONE]"), "stream terminator preserved");
assert.ok(text.includes('"prompt_tokens":2'), "OpenAI usage block passed through");
});
});

View File

@@ -131,7 +131,22 @@ test("buildChatInvocation produces a type:4 chat invocation carrying the user te
assert.equal(arg.sessionId, "session-id"); assert.equal(arg.sessionId, "session-id");
assert.equal(arg.conversationId, "conversation-id"); assert.equal(arg.conversationId, "conversation-id");
assert.equal(arg.productThreadType, "Office"); assert.equal(arg.productThreadType, "Office");
assert.deepEqual(arg.clientInfo, { clientAppName: "Office", clientPlatform: "mcmcopilot-web" }); // 2026-08-21 capture (#11069): clientInfo gained 8 keys (clientEntrypoint,
// clientSessionId, ProductCategory, clientAppType, productEntryPoint,
// deviceOS, deviceType, clientPlatformVersion). The #10718 base shape only
// carried clientAppName + clientPlatform.
assert.deepEqual(arg.clientInfo, {
clientAppName: "Office",
clientPlatform: "mcmcopilot-web",
clientEntrypoint: "mcmcopilot-officeweb",
clientSessionId: "session-id",
ProductCategory: "Chat",
clientAppType: "Web",
productEntryPoint: "ChatPanel",
deviceOS: "Windows",
deviceType: "Desktop",
clientPlatformVersion: "10",
});
assert.equal(arg.isStartOfSession, true); assert.equal(arg.isStartOfSession, true);
assert.ok(Array.isArray(arg.optionsSets)); assert.ok(Array.isArray(arg.optionsSets));
assert.ok((arg.optionsSets as string[]).includes("enable_gg_gpt")); assert.ok((arg.optionsSets as string[]).includes("enable_gg_gpt"));

View File

@@ -30,10 +30,11 @@ test("model-driven tone overrides the tier default; bare id keeps the tier tone"
const enterprise = resolveChatInvocationOverrides("enterprise"); const enterprise = resolveChatInvocationOverrides("enterprise");
const individual = resolveChatInvocationOverrides(undefined); const individual = resolveChatInvocationOverrides(undefined);
// enterprise tier default tone is Magic; individual/EDU sends "magic" (#10718 // enterprise tier default tone is Magic; individual/EDU now also sends
// recapture — the old "" default was part of the silently-dropped shape) // "Magic" (capitalized) — the 2026-08-21 capture (#11069) showed lowercase
// "magic" is part of the shape that gets silently dropped (ping-only socket).
assert.equal(enterprise.tone, "Magic"); assert.equal(enterprise.tone, "Magic");
assert.equal(individual.tone, "magic"); assert.equal(individual.tone, "Magic");
// precedence: resolveToneForModel(model) ?? overrides.tone (mirrors the executor wiring) // precedence: resolveToneForModel(model) ?? overrides.tone (mirrors the executor wiring)
const toneFor = (model: string | undefined, tierTone: string) => const toneFor = (model: string | undefined, tierTone: string) =>
@@ -43,9 +44,9 @@ test("model-driven tone overrides the tier default; bare id keeps the tier tone"
assert.equal(toneFor("copilot-m365-claude-opus", enterprise.tone), "Claude_Opus"); assert.equal(toneFor("copilot-m365-claude-opus", enterprise.tone), "Claude_Opus");
assert.equal(toneFor("copilot-m365-claude-opus", individual.tone), "Claude_Opus"); assert.equal(toneFor("copilot-m365-claude-opus", individual.tone), "Claude_Opus");
// the bare id keeps whatever the tier resolved // the bare id keeps whatever the tier resolved (both "Magic" post-#11069)
assert.equal(toneFor("copilot-m365", enterprise.tone), "Magic"); assert.equal(toneFor("copilot-m365", enterprise.tone), "Magic");
assert.equal(toneFor("copilot-m365", individual.tone), "magic"); assert.equal(toneFor("copilot-m365", individual.tone), "Magic");
}); });
test("registry exposes the bare id (first) plus every tone variant", () => { test("registry exposes the bare id (first) plus every tone variant", () => {

View File

@@ -8,12 +8,12 @@ import { resolveChatCoreTargetFormat } from "../../open-sse/handlers/chatCore/ta
// ghe-copilot catalog. getModelTargetFormat falls back to getGlobalModel() when // ghe-copilot catalog. getModelTargetFormat falls back to getGlobalModel() when
// the provider's own catalog lacks the model id, importing the DECLARING // the provider's own catalog lacks the model id, importing the DECLARING
// provider's endpoint semantics into every other provider serving the same id. // provider's endpoint semantics into every other provider serving the same id.
// command-code's chat-shaped /alpha/generate executor then received a // command-code's chat-shaped executor then received a
// Responses-format body (input, not messages) and shipped `messages: []` // Responses-format body (input, not messages) and shipped `messages: []`
// upstream — upstream rejected with "Invalid prompt: messages must not be empty" // upstream — upstream rejected with "Invalid prompt: messages must not be empty"
// (502). Model-level targetFormat is provider-scoped: it must not leak. // (502). Model-level targetFormat is provider-scoped: it must not leak.
test("model-level targetFormat does not leak across provider catalogs", () => { test("model-level targetFormat does not leak across provider catalogs", () => {
// command-code serves gpt-5.6-luna over its chat-shaped /alpha/generate endpoint // command-code serves gpt-5.6-luna over its chat-shaped provider endpoint
assert.equal(getModelTargetFormat("cmd", "gpt-5.6-luna"), null); assert.equal(getModelTargetFormat("cmd", "gpt-5.6-luna"), null);
// raw provider id form behaves identically (alias resolution) // raw provider id form behaves identically (alias resolution)
assert.equal(getModelTargetFormat("command-code", "gpt-5.6-luna"), null); assert.equal(getModelTargetFormat("command-code", "gpt-5.6-luna"), null);

View File

@@ -16,7 +16,6 @@ const { __setTlsFetchOverrideForTesting: __setPplxTlsFetchOverride } =
const { __setTlsFetchOverrideForTesting: __setGrokTlsFetchOverride } = const { __setTlsFetchOverrideForTesting: __setGrokTlsFetchOverride } =
await import("../../open-sse/services/grokTlsClient.ts"); await import("../../open-sse/services/grokTlsClient.ts");
const { COMMAND_CODE_VERSION } = await import("../../open-sse/executors/commandCode.ts");
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
@@ -216,11 +215,11 @@ test("specialty provider validators cover Deepgram, AssemblyAI, ElevenLabs and I
test("validateCommandCodeProvider ignores caller baseUrl and chatPath overrides", async () => { test("validateCommandCodeProvider ignores caller baseUrl and chatPath overrides", async () => {
globalThis.fetch = async (url, init = {}) => { globalThis.fetch = async (url, init = {}) => {
assert.equal(String(url), "https://api.commandcode.ai/alpha/generate"); assert.equal(String(url), "https://api.commandcode.ai/provider/v1/chat/completions");
const headers = init.headers as Record<string, string>; const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Bearer cc-key"); assert.equal(headers.Authorization, "Bearer cc-key");
const body = JSON.parse(String(init.body)); const body = JSON.parse(String(init.body));
assert.equal(body.params.model, "command-code-validation-model"); assert.equal(body.model, "command-code-validation-model");
return new Response(JSON.stringify({ ok: true }), { status: 200 }); return new Response(JSON.stringify({ ok: true }), { status: 200 });
}; };
@@ -239,7 +238,7 @@ test("validateCommandCodeProvider ignores caller baseUrl and chatPath overrides"
test("validateCommandCodeProvider defaults probe model to DeepSeek flash", async () => { test("validateCommandCodeProvider defaults probe model to DeepSeek flash", async () => {
globalThis.fetch = async (_url, init = {}) => { globalThis.fetch = async (_url, init = {}) => {
const body = JSON.parse(String(init.body)); const body = JSON.parse(String(init.body));
assert.equal(body.params.model, "deepseek/deepseek-v4-flash"); assert.equal(body.model, "deepseek/deepseek-v4-flash");
return new Response("", { status: 400 }); return new Response("", { status: 400 });
}; };
@@ -2285,7 +2284,7 @@ test("specialty validator rejects invalid Runway credentials", async () => {
assert.equal(runway.error, "Invalid API key"); assert.equal(runway.error, "Invalid API key");
}); });
test("validateCommandCodeProvider sends Command Code probe URL, headers, and wrapper body", async () => { test("validateCommandCodeProvider sends Command Code probe URL, headers, and flat OpenAI body", async () => {
const calls: Array<{ const calls: Array<{
url: string; url: string;
method?: string; method?: string;
@@ -2309,22 +2308,21 @@ test("validateCommandCodeProvider sends Command Code probe URL, headers, and wra
assert.deepEqual(result, { valid: true, error: null }); assert.deepEqual(result, { valid: true, error: null });
assert.equal(calls.length, 1); assert.equal(calls.length, 1);
assert.equal(calls[0].url, "https://api.commandcode.ai/alpha/generate"); // Probe targets the documented /provider/v1/chat/completions endpoint, not
// the CLI-only /alpha/generate (#10265).
assert.equal(calls[0].url, "https://api.commandcode.ai/provider/v1/chat/completions");
assert.equal(calls[0].method, "POST"); assert.equal(calls[0].method, "POST");
assert.equal(calls[0].headers.Authorization, "Bearer cc_test_key"); assert.equal(calls[0].headers.Authorization, "Bearer cc_test_key");
assert.equal(calls[0].headers["Content-Type"], "application/json"); assert.equal(calls[0].headers["Content-Type"], "application/json");
assert.equal(calls[0].headers["x-command-code-version"], COMMAND_CODE_VERSION); // No CLI-impersonation headers.
assert.equal(calls[0].headers["x-cli-environment"], "external"); assert.equal(calls[0].headers["x-command-code-version"], undefined);
assert.equal(calls[0].headers["x-project-slug"], "pi-cc"); assert.equal(calls[0].headers["x-cli-environment"], undefined);
assert.equal(calls[0].headers["x-taste-learning"], "false"); assert.equal(calls[0].headers["x-project-slug"], undefined);
assert.equal(calls[0].headers["x-co-flag"], "false"); // Flat OpenAI chat.completions body (no CLI wrapper).
assert.equal(typeof calls[0].headers["x-session-id"], "string"); assert.equal(calls[0].body.params, undefined, "CLI envelope params wrapper must not be sent");
assert.equal(calls[0].body.config.environment, "external"); assert.equal(calls[0].body.model, "gpt-5.4-mini");
assert.equal(calls[0].body.permissionMode, "standard"); assert.equal(calls[0].body.stream, true);
assert.equal(calls[0].body.skills, ""); assert.equal(calls[0].body.max_tokens, 1);
assert.equal(calls[0].body.params.model, "gpt-5.4-mini");
assert.equal(calls[0].body.params.stream, true);
assert.equal(calls[0].body.params.max_tokens, 1);
}); });
for (const status of [400, 422, 429]) { for (const status of [400, 422, 429]) {

View File

@@ -10,7 +10,6 @@ process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts"); const core = await import("../../src/lib/db/core.ts");
const { handleResponsesCore } = await import("../../open-sse/handlers/responsesHandler.ts"); const { handleResponsesCore } = await import("../../open-sse/handlers/responsesHandler.ts");
const { COMMAND_CODE_VERSION } = await import("../../open-sse/executors/commandCode.ts");
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
@@ -354,26 +353,31 @@ test("handleResponsesCore transforms Command Code executor SSE through Responses
input: "hello command code", input: "hello command code",
}, },
responseFactory() { responseFactory() {
// /provider/v1/chat/completions returns standard OpenAI SSE (#10265).
const chunk = (delta: Record<string, unknown>) =>
`data: ${JSON.stringify({
id: "c1",
object: "chat.completion.chunk",
model: "gpt-5.4-mini",
choices: [{ index: 0, delta }],
})}\n\n`;
return new Response( return new Response(
[ [
`data: ${JSON.stringify({ type: "text-delta", text: "command" })}`, chunk({ role: "assistant" }),
"", chunk({ content: "command" }),
`data: ${JSON.stringify({ type: "reasoning-delta", text: "thinking" })}`, chunk({}),
"", ].join("") + "data: [DONE]\n\n",
`data: ${JSON.stringify({ type: "finish", finishReason: "stop" })}`, { status: 200, headers: { "Content-Type": "text/event-stream" } }
"",
].join("\n"),
{ status: 200, headers: { "Content-Type": "application/x-ndjson" } }
); );
}, },
}); });
assert.equal(result.success, true); assert.equal(result.success, true);
assert.equal(call.url, "https://api.commandcode.ai/alpha/generate"); assert.equal(call.url, "https://api.commandcode.ai/provider/v1/chat/completions");
assert.equal(call.headers.Authorization, "Bearer cc_test_key"); assert.equal(call.headers.Authorization, "Bearer cc_test_key");
assert.equal(call.headers["x-command-code-version"], COMMAND_CODE_VERSION); assert.equal(call.headers["x-command-code-version"], undefined);
assert.equal(call.body.params.model, "gpt-5.4-mini"); assert.equal(call.body.model, "gpt-5.4-mini");
assert.equal(call.body.params.stream, true); assert.equal(call.body.stream, true);
const sse = await result.response.text(); const sse = await result.response.text();
assert.match(sse, /event: response\.created/); assert.match(sse, /event: response\.created/);

View File

@@ -40,6 +40,10 @@ db.prepare(
`INSERT OR IGNORE INTO version_manager (tool, status, port, auto_start, auto_update, provider_expose) `INSERT OR IGNORE INTO version_manager (tool, status, port, auto_start, auto_update, provider_expose)
VALUES ('test-adopt', 'stopped', 29996, 0, 0, 0)` VALUES ('test-adopt', 'stopped', 29996, 0, 0, 0)`
).run(); ).run();
db.prepare(
`INSERT OR IGNORE INTO version_manager (tool, status, port, auto_start, auto_update, provider_expose)
VALUES ('test-adopt-deny', 'stopped', 29994, 0, 0, 0)`
).run();
const { ServiceSupervisor } = await import("../../../src/lib/services/ServiceSupervisor.ts"); const { ServiceSupervisor } = await import("../../../src/lib/services/ServiceSupervisor.ts");
@@ -213,6 +217,10 @@ test("does NOT auto-restart on crash", async () => {
// the port, the supervisor ADOPTS it (marks running, no child spawned) instead // the port, the supervisor ADOPTS it (marks running, no child spawned) instead
// of spawning a duplicate that would die with EADDRINUSE. // of spawning a duplicate that would die with EADDRINUSE.
test("#6205: probeBeforeSpawn adopts a healthy existing instance (no spawn)", async () => { test("#6205: probeBeforeSpawn adopts a healthy existing instance (no spawn)", async () => {
// GHSA-wg9p-6m2g-4v27: adoption of an already-healthy listener is opt-in
// (a squatter can answer 2xx), so this adoption-path test opts in explicitly.
const prevAdopt = process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE;
process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE = "1";
const healthServer = startHealthServer(29996); const healthServer = startHealthServer(29996);
const cfg = { ...tickConfig("test-adopt", 29996), probeBeforeSpawn: true }; const cfg = { ...tickConfig("test-adopt", 29996), probeBeforeSpawn: true };
const sup = new ServiceSupervisor(cfg); const sup = new ServiceSupervisor(cfg);
@@ -232,6 +240,8 @@ test("#6205: probeBeforeSpawn adopts a healthy existing instance (no spawn)", as
} finally { } finally {
await sup.stop(); await sup.stop();
healthServer.close(); healthServer.close();
if (prevAdopt === undefined) delete process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE;
else process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE = prevAdopt;
} }
}); });
@@ -254,6 +264,9 @@ test("adopted service resolves and records the real pid of the process holding t
// (#10523). // (#10523).
const healthServer = startHealthServer(29995); const healthServer = startHealthServer(29995);
const cfg = { ...tickConfig("test-adopt", 29995), probeBeforeSpawn: true }; const cfg = { ...tickConfig("test-adopt", 29995), probeBeforeSpawn: true };
// Same opt-in as the adoption test above (GHSA-wg9p-6m2g-4v27).
const prevAdopt = process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE;
process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE = "1";
const sup = new ServiceSupervisor(cfg); const sup = new ServiceSupervisor(cfg);
try { try {
@@ -268,5 +281,33 @@ test("adopted service resolves and records the real pid of the process holding t
} finally { } finally {
await sup.stop(); await sup.stop();
healthServer.close(); healthServer.close();
if (prevAdopt === undefined) delete process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE;
else process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE = prevAdopt;
}
});
// GHSA-wg9p-6m2g-4v27: a healthy 2xx on the probed port no longer proves the
// listener is this service — a local squatter can answer 200 and get adopted,
// receiving the injected service API key. Without the operator opt-in the
// supervisor must surface the actionable error instead of adopting.
test("probeBeforeSpawn does NOT adopt a healthy listener without the opt-in", async () => {
const healthServer = startHealthServer(29994);
const cfg = { ...tickConfig("test-adopt-deny", 29994), probeBeforeSpawn: true };
const prevAdopt = process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE;
delete process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE;
const sup = new ServiceSupervisor(cfg);
try {
const status = await sup.start();
assert.equal(status.state, "error", "a healthy listener is not adopted by default");
assert.match(
status.lastError ?? "",
/OMNIROUTE_ADOPT_EXISTING_SERVICE/,
"the error names the opt-in escape hatch"
);
} finally {
await sup.stop();
healthServer.close();
if (prevAdopt !== undefined) process.env.OMNIROUTE_ADOPT_EXISTING_SERVICE = prevAdopt;
} }
}); });