Compare commits

...

4 Commits

Author SHA1 Message Date
Xiangzhe
cf1d564e0a chore(skills): regenerate omni-webhooks after dependency changes (#11095) 2026-08-22 14:13:57 -03:00
Xiangzhe
7fb921e28e fix(install): make ONNX chain optional for Android/Termux installs (#11095) 2026-08-22 13:57:59 -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
33 changed files with 1313 additions and 2723 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

@@ -0,0 +1 @@
- fix(install): make the ONNX dependency chain optional so Termux/Android installs succeed again (#11095)

View File

@@ -443,7 +443,7 @@
"src/shared/components/ModelSelectModal.tsx": 1138,
"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/shared/components/ModelSelectModal.tsx": 1138,
"src/shared/constants/providers/apikey/gateways.ts": 1298,
@@ -459,7 +459,8 @@
"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_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_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",
executor: "command-code",
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",
// The discovery response is a partial routing catalog; static registry
// 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;
/**
* Allowed message types observed in the 2026-08 recapture of the working
* `m365.cloud.microsoft/chat` client (#10718). The old 11-entry list is no longer
* seen on the wire — the stale shape gets closed immediately after the type:4.
* Allowed message types observed in a 2026-08-21 live capture of a working
* `m365.cloud.microsoft/chat` session (issue: "Stream ended before producing a
* 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 = [
"Chat",
"Suggestion",
"InternalSearchQuery",
"Disengaged",
"Progress",
"EndOfRequest",
"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;
/**
@@ -78,19 +106,26 @@ export const M365_ENTERPRISE_EXTRA_MESSAGE_TYPES = [
] as const;
/**
* Individual / EDU option sets from the 2026-08 recapture (#10718)14 entries.
* The previous 25-entry consumer/MSA set (enable_msa_user, pdnascan, cwc_code_*,
* …) is no longer observed on the wire and belongs to the shape the substrate
* now drops silently.
* Individual / EDU option sets from a 2026-08-21 live capture34 entries, up
* from the #10718 14-entry shape (which itself superseded an earlier 25-entry
* shape). Each recapture so far has been additive/reshuffled rather than a
* 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 = [
"search_result_progress_messages_with_search_queries",
"update_textdoc_response_after_streaming",
"deepleo_networking_timeout_10minutes_canmore",
"cwc_flux_image",
"cwc_code_interpreter",
"cwc_code_interpreter_amsfix",
"cwcfluxgptv",
"flux_v3_gptv_enable_upload_multi_image_in_turn_wo_ch",
"gptvnorm2048",
"cwc_code_interpreter_citation_fix",
"code_interpreter_interactive_charts",
"cwc_code_interpreter_interactive_charts_inline_image",
"code_interpreter_matplotlib_patching",
"cwc_fileupload_odb",
"update_memory_plugin",
"add_custom_instructions",
@@ -98,6 +133,20 @@ export const M365_DEFAULT_OPTION_SETS = [
"flux_v3_progress_messages",
"enable_batch_token_processing",
"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;
/** Append the record separator to a JSON-serializable frame. */
@@ -433,12 +482,14 @@ export function resolveChatInvocationOverrides(tier: string | undefined): {
}
return {
optionsSets: [...M365_DEFAULT_OPTION_SETS],
// #10718 — the 2026-08 recapture sends tone:"magic" (lowercase) on the
// individual/EDU surface; the old "" default is part of the dropped shape.
tone: "magic",
// 2026-08-21 capture — the individual/consumer surface now sends "Magic"
// (capitalized), matching the enterprise tone literal. The #10718
// lowercase "magic" is part of the shape that gets silently dropped.
tone: "Magic",
allowedMessageTypes: ALLOWED_MESSAGE_TYPES,
// Omitted entirely on the individual/EDU wire (see ChatInvocationOptions).
disconnectBehavior: undefined,
// 2026-08-21 capture — disconnectBehavior:"continue" is now present on the
// 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).
* Mirrors the argument shape recaptured from a working `m365.cloud.microsoft/chat`
* client in 2026-08 (#10718). Notable differences from the pre-#10718 shape: a
* populated `clientInfo` + `productThreadType:"Office"`, a `conversationId`
* matching the WS URL query, a rich `message` object, and no
* `spokenTextMode` / `extraExtensionParameters` / `isSbsSupported` /
* `renderReferencesBehindEOS` / `disconnectBehavior` — none of those are still
* observed on the wire, and the stale shape gets closed immediately after the
* invocation.
* Base shape from the #10718 recapture (populated `clientInfo` +
* `productThreadType:"Office"`, a `conversationId` matching the WS URL query, a
* rich `message` object), extended per a 2026-08-21 live capture that found the
* #10718 shape alone no longer produces a `type:1 target:"update"` frame — the
* socket only replies with keepalive pings and closes. The additions below
* (richer `clientInfo`, non-empty `plugins`, `extraExtensionParameters`,
* `isSbsSupported`, `renderReferencesBehindEOS`,
* `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> {
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 {
type: 4,
target: "chat",
@@ -487,17 +555,17 @@ export function buildChatInvocation(opts: ChatInvocationOptions): Record<string,
? [...opts.allowedMessageTypes]
: [...ALLOWED_MESSAGE_TYPES],
clientCorrelationId: opts.clientCorrelationId ?? opts.traceId,
clientInfo: {
clientAppName: "Office",
clientPlatform: "mcmcopilot-web",
},
clientInfo,
conversationId: opts.conversationId,
extraExtensionParameters: {},
isStartOfSession: opts.isStartOfSession ?? true,
message: {
adaptiveCards: [],
attachments: null,
author: "user",
clientInfo,
clientPreferences: {},
connectedFederatedConnections: ["dummyId"],
entityAnnotationTypes: ["People", "File", "Event", "Email", "TeamsMessage"],
experienceType: "Default",
inputMethod: "Keyboard",
@@ -510,22 +578,27 @@ export function buildChatInvocation(opts: ChatInvocationOptions): Record<string,
requestId: opts.requestId,
text: opts.text,
},
isSbsSupported: true,
options: {},
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 } : {}),
productThreadType: "Office",
renderReferencesBehindEOS: true,
sessionId: opts.sessionId,
sliceIds: [],
source: "officeweb",
streamingMode: "ConciseWithPadding",
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,
traceId: opts.traceId,
// #8971 keeps "continue" for the enterprise tier; the individual/EDU wire
// omits the key, so only include it when actually set (#10718).
...(opts.disconnectBehavior ? { disconnectBehavior: opts.disconnectBehavior } : {}),
// 2026-08-21 capture — disconnectBehavior:"continue" is sent on every
// tier now, not gated to enterprise as the #8971 comment described.
disconnectBehavior: opts.disconnectBehavior ?? "continue",
},
],
};

72
package-lock.json generated
View File

@@ -18,7 +18,6 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@huggingface/transformers": "^4.2.0",
"@lobehub/icons": "^5.16.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"@monaco-editor/react": "^4.7.0",
@@ -61,7 +60,6 @@
"next-themes": "^0.4.6",
"node-machine-id": "^1.1.12",
"omniglyph": "^1.4.0",
"onnxruntime-node": "1.24.3",
"open": "^11.0.1",
"ora": "^9.4.1",
"parse5": "^8.0.1",
@@ -156,9 +154,11 @@
},
"optionalDependencies": {
"@atjsh/llmlingua-2": "3.0.0",
"@huggingface/transformers": "^4.2.0",
"better-sqlite3": "^13.0.2",
"js-tiktoken": "^1.0.20",
"keytar": "^7.9.0",
"onnxruntime-node": "1.24.3",
"sqlite-vec": "^0.1.9",
"tls-client-node": "^0.2.0",
"wreq-js": "^3.0.0"
@@ -4510,6 +4510,7 @@
"resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.9.tgz",
"integrity": "sha512-uWTG+l3VJRsl7EXxYizuL3P+cCPoc3cRqbWWRcQN0FhejRfbdq0RNhCmbY/YDtnTcz9icdLYuLDjsnz4d8JMuw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=18"
}
@@ -4518,13 +4519,15 @@
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@huggingface/tokenizers/-/tokenizers-0.1.3.tgz",
"integrity": "sha512-8rF/RRT10u+kn7YuUbUg0OF30K8rjTc78aHpxT+qJ1uWSqxT1MHi8+9ltwYfkFYJzT/oS+qw3JVfHtNMGAdqyA==",
"license": "Apache-2.0"
"license": "Apache-2.0",
"optional": true
},
"node_modules/@huggingface/transformers": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-4.2.0.tgz",
"integrity": "sha512-8BRCoBMH0XsWaEIamuR0LrJGAfftgHAfb2Vrffy0VKlSAE/MnUJ5/h/zTfEP3fDIft+nk7TqB8xXEyABGitBjQ==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@huggingface/jinja": "^0.5.6",
"@huggingface/tokenizers": "^0.1.3",
@@ -9483,30 +9486,35 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
"devOptional": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1"
@@ -9516,24 +9524,28 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
"devOptional": true,
"license": "BSD-3-Clause"
},
"node_modules/@radix-ui/number": {
@@ -12737,6 +12749,7 @@
"version": "26.2.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
"integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
@@ -13998,6 +14011,7 @@
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz",
"integrity": "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14.0"
}
@@ -14971,7 +14985,8 @@
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/bottleneck": {
"version": "2.19.5",
@@ -17935,6 +17950,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
@@ -17964,6 +17980,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
@@ -18064,7 +18081,8 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/detect-node-es": {
"version": "1.1.0",
@@ -18908,7 +18926,8 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/es6-promisify": {
"version": "7.0.0",
@@ -20400,7 +20419,8 @@
"version": "25.9.23",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz",
"integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==",
"license": "Apache-2.0"
"license": "Apache-2.0",
"optional": true
},
"node_modules/flatted": {
"version": "3.4.2",
@@ -21226,6 +21246,7 @@
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"es6-error": "^4.1.1",
@@ -21243,6 +21264,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
"optional": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -21291,6 +21313,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"define-properties": "^1.2.1",
@@ -21645,7 +21668,8 @@
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
"license": "ISC"
"license": "ISC",
"optional": true
},
"node_modules/hachure-fill": {
"version": "0.5.2",
@@ -21679,6 +21703,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
@@ -24790,7 +24815,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"license": "ISC"
"license": "ISC",
"optional": true
},
"node_modules/json5": {
"version": "2.2.3",
@@ -26654,6 +26680,7 @@
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"license": "MIT",
"optional": true,
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
@@ -29425,6 +29452,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -29632,7 +29660,8 @@
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
"integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/onnxruntime-node": {
"version": "1.24.3",
@@ -29640,6 +29669,7 @@
"integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"win32",
"darwin",
@@ -29656,6 +29686,7 @@
"resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.26.0-dev.20260416-b7804b056c.tgz",
"integrity": "sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==",
"license": "MIT",
"optional": true,
"dependencies": {
"flatbuffers": "^25.1.24",
"guid-typescript": "^1.0.9",
@@ -29669,13 +29700,15 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
"license": "Apache-2.0",
"optional": true
},
"node_modules/onnxruntime-web/node_modules/onnxruntime-common": {
"version": "1.24.0-dev.20251116-b39e144322",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.0-dev.20251116-b39e144322.tgz",
"integrity": "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/open": {
"version": "11.0.1",
@@ -30906,7 +30939,8 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/playwright": {
"version": "1.62.1",
@@ -31861,6 +31895,7 @@
"version": "7.6.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
"integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
"devOptional": true,
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -31884,6 +31919,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/proxy-addr": {
@@ -33280,6 +33316,7 @@
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"detect-node": "^2.0.4",
@@ -33655,7 +33692,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/send": {
"version": "1.2.1",
@@ -33688,6 +33726,7 @@
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"license": "MIT",
"optional": true,
"dependencies": {
"type-fest": "^0.13.1"
},
@@ -33703,6 +33742,7 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"license": "(MIT OR CC0-1.0)",
"optional": true,
"engines": {
"node": ">=10"
},
@@ -34474,7 +34514,8 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause"
"license": "BSD-3-Clause",
"optional": true
},
"node_modules/sql.js": {
"version": "1.14.2",
@@ -36187,6 +36228,7 @@
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
"devOptional": true,
"license": "MIT"
},
"node_modules/unicode-emoji-modifier-base": {

View File

@@ -265,7 +265,6 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@huggingface/transformers": "^4.2.0",
"@lobehub/icons": "^5.16.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"@monaco-editor/react": "^4.7.0",
@@ -308,7 +307,6 @@
"next-themes": "^0.4.6",
"node-machine-id": "^1.1.12",
"omniglyph": "^1.4.0",
"onnxruntime-node": "1.24.3",
"open": "^11.0.1",
"ora": "^9.4.1",
"parse5": "^8.0.1",
@@ -343,9 +341,11 @@
},
"optionalDependencies": {
"@atjsh/llmlingua-2": "3.0.0",
"@huggingface/transformers": "^4.2.0",
"better-sqlite3": "^13.0.2",
"js-tiktoken": "^1.0.20",
"keytar": "^7.9.0",
"onnxruntime-node": "1.24.3",
"sqlite-vec": "^0.1.9",
"tls-client-node": "^0.2.0",
"wreq-js": "^3.0.0"

View File

@@ -1,12 +1,12 @@
---
name: omni-webhooks
description: Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, provider.error, budget.exceeded, etc.) and manage delivery retries.
description: Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, request.failed, quota.exceeded, etc.) and manage delivery retries.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, provider.error, budget.exceeded, etc.) and manage delivery retries.
Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, request.failed, quota.exceeded, etc.) and manage delivery retries.
## Authentication

View File

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

View File

@@ -533,9 +533,9 @@ function getStrategyBadgeClass(strategy) {
return "bg-blue-500/15 text-blue-600 dark:text-blue-400";
}
function getI18nOrFallback(t, key, fallback) {
function getI18nOrFallback(t, key, fallback, values) {
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 {}
return fallback;
}
@@ -1565,7 +1565,8 @@ function StrategyRecommendationsPanel({ strategy, onApply, showNudge }) {
{getI18nOrFallback(
t,
"recommendationsUpdated",
"Recommendations updated for {strategy}."
"Recommendations updated for {strategy}.",
{ strategy: strategyLabel }
).replace("{strategy}", strategyLabel)}
</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 { useOpenRouterPresetControl } from "../OpenRouterPresetInput";
import WebSessionCredentialGuide from "../WebSessionCredentialGuide";
import HarImportButton from "../HarImportButton";
import CcCompatibleRequestDefaultsFields from "./CcCompatibleRequestDefaultsFields";
import { buildAddProviderSpecificData } from "./connectionProviderSpecificData";
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."
: isWebSessionCredential
? getWebSessionCredentialHint(t, webSessionCredential, providerDisplayName, false)
: isLocalSelfHostedProvider
? t("localProviderApiKeyOptionalHint", {
provider: localProviderMetadata?.name || providerName || provider || "",
})
: apiKeyOptional
? t("apiKeyOptionalHint")
: undefined;
: isLocalSelfHostedProvider
? t("localProviderApiKeyOptionalHint", {
provider: localProviderMetadata?.name || providerName || provider || "",
})
: apiKeyOptional
? t("apiKeyOptionalHint")
: undefined;
const credentialValidationFailedMessage = isWebSessionCredential
? providerText(
t,
@@ -750,6 +751,12 @@ export default function AddApiKeyModal({
t={t}
/>
)}
{provider && (
<HarImportButton
provider={provider}
onImport={(apiKey) => setFormData({ ...formData, apiKey })}
/>
)}
{!isNoAuthWebSessionCredential && (
<div className="flex gap-2">
<Input

View File

@@ -49,6 +49,7 @@ import {
import { getWebSessionCredentialRequirement } from "../../webSessionCredentials";
import { useOpenRouterPresetControl } from "../OpenRouterPresetInput";
import WebSessionCredentialGuide from "../WebSessionCredentialGuide";
import HarImportButton from "../HarImportButton";
import CcCompatibleRequestDefaultsFields from "./CcCompatibleRequestDefaultsFields";
import { CodexConnectionFields } from "./CodexFingerprintFields";
import { assignEditApiKeyProviderSpecificData } from "./connectionProviderSpecificData";
@@ -909,6 +910,12 @@ export default function EditConnectionModal({
t={t}
/>
)}
{provider && (
<HarImportButton
provider={provider}
onImport={(apiKey) => setFormData({ ...formData, apiKey })}
/>
)}
{!isNoAuthWebSessionCredential && (
<div className="flex gap-2">
<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:.",
"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.",
"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.",
"webNoAuthGuideTitle": "No credential required",
"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).
// Extracted from validation.ts (god-file decomposition) — top-level functions; behavior is
// byte-identical to the original inline defs.
import { randomUUID } from "node:crypto";
import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry.ts";
import {
discoverBedrockNativeModels,
@@ -196,13 +195,12 @@ export async function validateOpenAILikeProvider({
export async function validateCommandCodeProvider({ apiKey, providerSpecificData = {} }: any) {
const entry = getRegistryEntry("command-code");
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 validationModelId =
providerSpecificData?.validationModelId ||
entry?.models?.find((model) => model.id === "deepseek/deepseek-v4-flash")?.id ||
"deepseek/deepseek-v4-flash";
const { COMMAND_CODE_VERSION } = await import("@omniroute/open-sse/executors/commandCode.ts");
return validateDirectChatProvider({
url,
@@ -210,37 +208,13 @@ export async function validateCommandCodeProvider({ apiKey, providerSpecificData
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"x-command-code-version": COMMAND_CODE_VERSION,
"x-cli-environment": "external",
"x-project-slug": "pi-cc",
"x-taste-learning": "false",
"x-co-flag": "false",
"x-session-id": randomUUID(),
Accept: "text/event-stream",
},
body: {
config: {
workingDir: "/workspace",
date: new Date().toISOString().slice(0, 10),
environment: "external",
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,
},
model: validationModelId,
messages: [{ role: "user", content: "test" }],
stream: true,
max_tokens: 1,
},
});
}

View File

@@ -83,7 +83,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
textIcon: "CC",
website: "https://commandcode.ai/",
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.",
},
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

@@ -9,40 +9,52 @@ function readJson<T = Record<string, unknown>>(relPath: string): T {
return JSON.parse(readFileSync(join(repoRoot, relPath), "utf8")) as T;
}
test("@huggingface/transformers is a regular dependency so npm ci never skips it", () => {
// #9962 deliberately moved @huggingface/transformers out of optionalDependencies:
// as an optional dep, npm silently skipped the whole subtree on Node 24/26 (old
// pin dragged onnxruntime-node@1.21.0 whose NAN build no longer compiles), which
// broke `npm ci`/`next build` with "Can't resolve @huggingface/transformers"
// (lazy import in src/lib/memory/embedding/transformersLocal.ts). As a regular
// dep with onnxruntime-node@~1.24.3 (napi prebuilds, no node-gyp) it stays
// installable and the memory embedding path requires() cleanly.
test("ONNX chain (@huggingface/transformers + onnxruntime-node) stays optional so Termux/Android installs succeed", () => {
// #11095: onnxruntime-node declares os ["win32","darwin","linux"], so while
// these lived in `dependencies` every npm install on Android/Termux aborted
// with a fatal EBADPLATFORM. As optionalDependencies npm skips only the
// unsupported-platform subtree (with a warning) and installs normally
// everywhere else. This deliberately reverses the MECHANISM of #9962 while
// keeping its goal: #9962's skip happened because the old onnxruntime-node@
// 1.21.0 pin built from source (NAN) and failed to compile on Node 24/26;
// the current 1.24.3 pin ships napi prebuilds, so on supported platforms the
// chain always installs and `npm ci`/`next build` keep resolving it. On
// platforms where it IS skipped, both consumers degrade gracefully via lazy/
// dynamic imports (asserted below).
const pkg = readJson<{
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
overrides?: Record<string, string>;
}>("package.json");
assert.equal(
pkg.dependencies?.["@huggingface/transformers"],
undefined,
"transformers must NOT be a hard dependency (fatal EBADPLATFORM on Android)"
);
assert.equal(
pkg.optionalDependencies?.["@huggingface/transformers"],
"^4.2.0",
"transformers must be a regular dependency (never optional) so npm ci cannot skip it"
"transformers must be an optionalDependency"
);
assert.equal(pkg.optionalDependencies?.["@huggingface/transformers"], undefined);
});
test("transformers + onnxruntime-node are regular dependencies (not optional)", () => {
const pkg = readJson<{
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
}>("package.json");
assert.equal(
pkg.dependencies?.["onnxruntime-node"],
"1.24.3",
"onnxruntime-node is a regular dep (napi prebuilds, installable on Node 24/26)"
undefined,
"onnxruntime-node must NOT be a hard dependency (fatal EBADPLATFORM on Android)"
);
assert.equal(pkg.optionalDependencies?.["onnxruntime-node"], undefined);
assert.equal(
pkg.optionalDependencies?.["onnxruntime-node"],
"1.24.3",
"onnxruntime-node must be an optionalDependency pinned in lockstep with the overrides pin"
);
assert.equal(
pkg.overrides?.["onnxruntime-node"],
"1.24.3",
"the overrides pin must stay aligned with @huggingface/transformers' own pin (single-copy invariant)"
);
});
test("lockfile marks the whole ONNX chain optional", () => {
const lock = readJson<{
packages: Record<
string,
@@ -51,26 +63,61 @@ test("transformers + onnxruntime-node are regular dependencies (not optional)",
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
}
>;
>;
}>("package-lock.json");
assert.equal(
lock.packages[""]?.dependencies?.["@huggingface/transformers"],
lock.packages[""]?.optionalDependencies?.["@huggingface/transformers"],
"^4.2.0",
"root lock dependencies must hold transformers as a regular (non-optional) dep"
"root lock optionalDependencies must hold transformers"
);
// Optional flag is only written `true` for genuinely optional packages;
// regular deps leave it absent/null. Assert each is NOT optional.
assert.ok(
!lock.packages["node_modules/@huggingface/transformers"]?.optional,
"transformers must not be marked optional in the lockfile"
assert.equal(
lock.packages[""]?.optionalDependencies?.["onnxruntime-node"],
"1.24.3",
"root lock optionalDependencies must hold onnxruntime-node"
);
assert.ok(
!lock.packages["node_modules/onnxruntime-node"]?.optional,
"onnxruntime-node must not be marked optional in the lockfile"
lock.packages["node_modules/@huggingface/transformers"]?.optional,
"transformers must be marked optional in the lockfile"
);
assert.ok(
!lock.packages["node_modules/onnxruntime-common"]?.optional,
"onnxruntime-common must not be marked optional in the lockfile"
lock.packages["node_modules/onnxruntime-node"]?.optional,
"onnxruntime-node must be marked optional in the lockfile"
);
assert.ok(
lock.packages["node_modules/onnxruntime-common"]?.optional,
"onnxruntime-common must be marked optional in the lockfile"
);
});
test("every @huggingface/transformers consumer loads it lazily so absent installs degrade gracefully", () => {
// If any module ever switches to a STATIC import of the optional chain,
// startup crashes on platforms where npm skipped it (Android/Termux).
// transformersLocal.ts must keep its lazy await import() (D8/D25);
// onnxWorker.ts must keep its runtime-variable dynamicImport indirection.
const embeddingSrc = readFileSync(
join(repoRoot, "src/lib/memory/embedding/transformersLocal.ts"),
"utf8"
);
assert.doesNotMatch(
embeddingSrc,
/^\s*import\s+(?:[^'"]*?\s+from\s+)?["']@huggingface\/transformers["']/m,
"transformersLocal.ts must not statically import @huggingface/transformers"
);
assert.match(
embeddingSrc,
/await import\(["']@huggingface\/transformers["']\)/,
"transformersLocal.ts must load @huggingface/transformers via await import()"
);
const workerSrc = readFileSync(
join(repoRoot, "open-sse/services/compression/engines/llmlingua/onnxWorker.ts"),
"utf8"
);
assert.doesNotMatch(
workerSrc,
/^\s*import\s+(?:[^'"]*?\s+from\s+)?["']@huggingface\/transformers["']/m,
"onnxWorker.ts must not statically import @huggingface/transformers"
);
});

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;
const { REGISTRY, getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts");
const { CommandCodeExecutor, COMMAND_CODE_VERSION } =
await import("../../open-sse/executors/commandCode.ts");
const { CommandCodeExecutor } = await import("../../open-sse/executors/commandCode.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 originalFetch = globalThis.fetch;
type JsonRecord = Record<string, unknown>;
type ResponsesEvent = {
event: string;
data: { response: JsonRecord & { usage?: unknown; output?: JsonRecord[] } };
};
type FetchCall = { url: string; init: Record<string, unknown>; body?: Record<string, unknown> };
const PINNED_COMMAND_CODE_MODELS = [
"claude-opus-4-7",
@@ -44,20 +37,7 @@ const PINNED_COMMAND_CODE_MODELS = [
"Qwen/Qwen3.6-Plus",
];
function commandCodeStream(lines: unknown[], { sse = false } = {}) {
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)]));
}
const CHAT_URL = "https://api.commandcode.ai/provider/v1/chat/completions";
function parseSsePayloads(sse: string) {
return sse
@@ -68,25 +48,21 @@ function parseSsePayloads(sse: string) {
.map((line) => JSON.parse(line));
}
async function responsesFromChatSse(sse: string): Promise<ResponsesEvent[]> {
const input = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new TextEncoder().encode(sse));
controller.close();
},
});
const transformed = await new Response(
input.pipeThrough(createResponsesApiTransformStream(null, 60_000))
).text();
function openAiSse(obj: unknown): string {
return `data: ${JSON.stringify(obj)}\n\n`;
}
return transformed
.split("\n\n")
.map((part) => {
const event = part.match(/^event:\s*(.+)$/m)?.[1];
const data = part.match(/^data:\s*(.+)$/m)?.[1];
return event && data ? ({ event, data: JSON.parse(data) } as ResponsesEvent) : null;
})
.filter((entry): entry is ResponsesEvent => entry !== null);
function captureFetch(body: Record<string, unknown>) {
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({
url: String(url),
init,
body: JSON.parse(String(init.body)),
});
return new Response(JSON.stringify(body), { status: 200 });
};
return calls;
}
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.executor, "command-code");
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(
entry.models.map((model) => model.id),
PINNED_COMMAND_CODE_MODELS
@@ -119,17 +97,10 @@ test("getExecutor returns the specialized Command Code executor", () => {
assert.ok(getExecutor("cmd") instanceof CommandCodeExecutor);
});
type FetchCall = { url: string; init: Record<string, unknown>; body?: unknown };
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" }]);
};
test("Command Code executor posts a flat OpenAI body + standard headers to /provider/v1/chat/completions (#10265)", async () => {
const calls = captureFetch({});
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",
stream: false,
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[0].url, "https://api.commandcode.ai/alpha/generate");
assert.equal(calls[0].url, CHAT_URL);
assert.equal(calls[0].init.method, "POST");
assert.equal(headers.Authorization, "Bearer cc_test_key");
assert.equal(headers["x-command-code-version"], COMMAND_CODE_VERSION);
assert.equal(headers["x-cli-environment"], "external");
assert.equal(headers["x-project-slug"], "pi-cc");
assert.equal(headers["x-taste-learning"], "false");
assert.equal(headers["x-co-flag"], "false");
assert.equal(typeof headers["x-session-id"], "string");
// No CLI-impersonation headers.
assert.equal(headers["x-command-code-version"], undefined);
assert.equal(headers["x-cli-environment"], undefined);
assert.equal(headers["x-project-slug"], undefined);
const posted = JSON.parse(String(calls[0].init.body));
assert.deepEqual(posted, transformedBody);
for (const key of ["config", "memory", "taste", "skills", "permissionMode", "params"]) {
assert.ok(key in posted, `missing ${key}`);
}
assert.equal(posted.skills, "");
assert.equal(posted.params.model, "gpt-5.4-mini");
assert.equal(posted.params.stream, true);
assert.equal(posted.params.system, "You are concise.");
assert.equal(posted.params.messages[0].role, "user");
assert.equal(posted.params.tools[0].name, "lookup");
const posted = calls[0].body as Record<string, unknown>;
// No CLI envelope.
assert.equal(posted.config, undefined, "CLI envelope config must not be sent");
assert.equal(posted.params, undefined, "CLI envelope params wrapper must not be sent");
assert.equal(posted.model, "gpt-5.4-mini");
assert.equal(posted.stream, false);
assert.equal((posted.messages as Array<{ role: string }>)[0].role, "system");
const tool = (posted.tools as Array<{ function: { name: string } }>)[0];
assert.equal(tool.function.name, "lookup", "tools in OpenAI shape (function.name)");
assert.equal(posted.max_tokens, 42);
// The upstream OpenAI JSON passes through untouched.
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 () => {
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
test("Command Code executor passes reasoning/thinking fields through at the top level of the OpenAI body", async () => {
const calls = captureFetch({});
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro",
stream: false,
@@ -189,30 +153,19 @@ test("Command Code executor passes reasoning/thinking fields through to params (
reasoning_effort: "high",
thinking: { type: "enabled" },
effort: "high",
output_config: { effort: "high" },
extra_body: { enable_thinking: true },
},
});
const posted = JSON.parse(String(calls[0].init.body));
assert.equal(posted.params.reasoning_effort, "high");
assert.deepEqual(posted.params.thinking, { type: "enabled" });
assert.equal(posted.params.effort, "high");
assert.deepEqual(posted.params.output_config, { effort: "high" });
assert.deepEqual(posted.params.extra_body, { enable_thinking: true });
const posted = calls[0].body as Record<string, unknown>;
assert.equal(posted.reasoning_effort, "high");
assert.deepEqual(posted.thinking, { type: "enabled" });
assert.equal(posted.effort, "high");
assert.deepEqual(posted.extra_body, { enable_thinking: true });
});
test("Command Code executor honors body.model rewrite from payload rules", async () => {
const calls: FetchCall[] = [];
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).
const calls = captureFetch({});
await getExecutor("command-code").execute({
model: "deepseek-v4-pro-max",
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));
assert.equal(posted.params.model, "deepseek/deepseek-v4-pro");
assert.equal(posted.params.reasoning_effort, "max");
const posted = calls[0].body as Record<string, unknown>;
assert.equal(posted.model, "deepseek/deepseek-v4-pro");
assert.equal(posted.reasoning_effort, "max");
});
test("Command Code executor maps unsupported minimal reasoning_effort to low (upstream 400 regression)", async () => {
const calls: FetchCall[] = [];
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\"" —
const calls = captureFetch({});
// `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.
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));
assert.equal(posted.params.reasoning_effort, "low", "minimal must map to low");
const posted = calls[0].body as Record<string, unknown>;
assert.equal(posted.reasoning_effort, "low", "minimal must map to low");
});
test("Command Code raw NDJSON stream becomes OpenAI chat SSE chunks", async () => {
const calls: FetchCall[] = [];
test("Command Code executor passes the upstream OpenAI SSE stream through untouched", async () => {
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 = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([
{ type: "text-delta", text: "Hello" },
{ type: "reasoning-delta", text: "thinking" },
{ type: "tool-call", toolCallId: "call_1", toolName: "search", input: { q: "docs" } },
{ type: "finish", finishReason: "tool-calls" },
]);
capturedStreamFlag = JSON.parse(String(init.body)).stream;
return new Response(sse, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
};
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" }] },
});
assert.equal(calls[0].body.params.stream, true);
assert.equal(response.headers.get("Content-Type"), "text/event-stream; charset=utf-8");
const sse = await response.text();
assert.match(sse, /data: \[DONE\]/);
const chunks = parseSsePayloads(sse);
assert.equal(chunks[0].object, "chat.completion.chunk");
assert.deepEqual(chunks[0].choices[0].delta, { role: "assistant" });
assert.equal(capturedStreamFlag, true, "stream flag forwarded to upstream");
const text = await response.text();
assert.equal(text, sse, "OpenAI SSE stream passed through byte-for-byte");
assert.ok(text.includes("data: [DONE]"));
const chunks = parseSsePayloads(text);
assert.equal(chunks[0].choices[0].delta.role, "assistant");
assert.equal(chunks[1].choices[0].delta.content, "Hello");
assert.equal(chunks[2].choices[0].delta.reasoning_content, "thinking");
assert.equal(chunks[3].choices[0].delta.tool_calls[0].function.name, "search");
assert.equal(chunks.at(-1).choices[0].finish_reason, "tool_calls");
assert.equal(chunks[2].choices[0].finish_reason, "stop");
});
test("Command Code data: SSE lines aggregate into non-stream ChatCompletion JSON", async () => {
globalThis.fetch = async () =>
commandCodeStream(
[
{ type: "text-delta", text: "Hel" },
{ type: "text-delta", text: "lo" },
{ type: "reasoning-delta", text: "because" },
{ type: "tool-call", id: "call_2", name: "lookup", arguments: { id: 7 } },
{
type: "finish",
finishReason: "max_tokens",
totalUsage: {
inputTokens: 3,
inputTokenDetails: { cacheReadTokens: 2 },
outputTokens: 5,
},
},
],
{ sse: true }
);
test("Command Code executor passes the upstream OpenAI JSON through untouched (non-stream)", async () => {
const upstreamJson = {
id: "chatcmpl-1",
object: "chat.completion",
model: "gpt-5.4-mini",
choices: [{ index: 0, message: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
usage: { prompt_tokens: 3, completion_tokens: 2, total_tokens: 5 },
};
let capturedStreamFlag: unknown = null;
globalThis.fetch = async (url, init = {}) => {
capturedStreamFlag = JSON.parse(String(init.body)).stream;
return new Response(JSON.stringify(upstreamJson), {
status: 200,
headers: { "Content-Type": "application/json" },
});
};
const { response } = await getExecutor("command-code").execute({
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" }] },
});
assert.equal(response.headers.get("Content-Type"), "application/json");
const json = await response.json();
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,
});
assert.equal(capturedStreamFlag, false, "stream flag forwarded as false for non-stream");
assert.deepEqual(await response.json(), upstreamJson);
});
test("Command Code reasoning-only output falls back to reasoning as content (non-stream)", async () => {
globalThis.fetch = async () =>
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" });
test("Command Code executor surfaces upstream errors", async () => {
globalThis.fetch = async () => new Response("bad key", { status: 401, statusText: "Unauthorized" });
const upstreamFailure = await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
stream: false,
@@ -406,124 +287,68 @@ test("Command Code executor surfaces upstream and streamed errors", async () =>
});
assert.equal(upstreamFailure.response.status, 401);
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 () => {
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" }]);
};
// No client max_tokens: we must NOT fabricate one. Omitting the field lets
// Command Code's upstream apply the model's own native default.
test("Command Code executor omits max_tokens when the client does not supply one", async () => {
const calls = captureFetch({});
await getExecutor("command-code").execute({
model: "zai-org/GLM-5.1",
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 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));
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 clamps an oversized client-supplied max_tokens to the endpoint ceiling", 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" }]);
};
// A client asking for more than the 200000 endpoint ceiling is clamped down
// (not 400'd), mirroring the provider-driven clamp in antigravity.ts.
const calls = captureFetch({});
// A client asking for more than the 200000 endpoint ceiling is clamped down.
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
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 () => {
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" }]);
};
test("Command Code executor honors a smaller client-provided max_tokens", async () => {
const calls = captureFetch({});
await getExecutor("command-code").execute({
model: "zai-org/GLM-5.1",
stream: false,
credentials: { apiKey: "cc_test_key" },
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 () => {
globalThis.fetch = async () =>
new Response(
`${JSON.stringify({ type: "text-delta", text: "Hello" })}\n${JSON.stringify({
type: "error",
error: { message: "boom" },
})}`,
{ status: 200, headers: { "Content-Type": "application/x-ndjson" } }
);
await assert.rejects(async () => {
await getExecutor("command-code").execute({
test("Command Code stream preserves the upstream OpenAI usage chunk (passthrough)", async () => {
const sse =
openAiSse({
id: "c1",
object: "chat.completion.chunk",
model: "gpt-5.4-mini",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
}, /boom/);
});
test("Command Code usage chunk surfaces cache_read and no_cache for the stream pipeline", async () => {
globalThis.fetch = async () =>
commandCodeStream([
{ type: "text-delta", text: "Hi" },
{
type: "finish",
finishReason: "stop",
totalUsage: {
inputTokens: 10,
inputTokenDetails: { noCacheTokens: 6, cacheReadTokens: 4 },
outputTokens: 6,
},
choices: [{ index: 0, delta: { content: "Hi" } }],
}) +
openAiSse({
id: "c1",
object: "chat.completion.chunk",
model: "gpt-5.4-mini",
choices: [],
usage: {
prompt_tokens: 10,
prompt_tokens_details: { cached_tokens: 4 },
completion_tokens: 6,
completion_tokens_details: { reasoning_tokens: 1 },
total_tokens: 16,
},
]);
}) +
"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({
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" }] },
});
const sse = await response.text();
const chunks = parseSsePayloads(sse);
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");
// 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,
});
});
const text = await response.text();
// The upstream OpenAI usage chunk passes through unchanged, including the
// standard OpenAI usage shape the stream pipeline already understands.
assert.ok(text.includes('"prompt_tokens":10'));
assert.ok(text.includes('"cached_tokens":4'));
assert.ok(text.includes('"reasoning_tokens":1'));
assert.ok(text.includes("data: [DONE]"));
});

View File

@@ -34,7 +34,7 @@ test.after(() => {
core.resetDbInstance();
});
async function captureParams(body: Record<string, unknown>): Promise<FetchCall> {
async function captureBody(body: Record<string, unknown>): Promise<FetchCall> {
const calls: FetchCall[] = [];
globalThis.fetch = async (url: any, init: any = {}) => {
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 () => {
const call = await captureParams({ max_tokens: -1 });
const call = await captureBody({ max_tokens: -1 });
assert.ok(
!("max_tokens" in call.body.params),
`max_tokens:-1 must be omitted, got params.max_tokens=${call.body.params.max_tokens}`
!("max_tokens" in call.body),
`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 () => {
const call = await captureParams({ max_completion_tokens: -1 });
const call = await captureBody({ max_completion_tokens: -1 });
assert.ok(
!("max_tokens" in call.body.params),
`max_completion_tokens:-1 must be omitted, got params.max_tokens=${call.body.params.max_tokens}`
!("max_tokens" in call.body),
`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 () => {
const call = await captureParams({ max_tokens: 0 });
assert.ok(!("max_tokens" in call.body.params), "max_tokens:0 must be omitted");
const call = await captureBody({ max_tokens: 0 });
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 () => {
const call = await captureParams({ max_tokens: 2048 });
assert.equal(call.body.params.max_tokens, 2048);
});
const call = await captureBody({ 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
* (e.g. [{type:"text",text:"Hello"},{type:"text",text:"World"}]), the raw array
* must NOT reach the Command Code upstream — it requires user content to be a plain
* string. The executor must normalise the array to a string before posting.
*
* NOTE: this file covers ONLY the user-content-array/400 symptom of #5166.
* The 0-output-token symptom on mimo-v2.5-pro (reasoning-only models) is tracked
* separately and is NOT addressed here.
* The original regression was that a user message whose `content` was an array of
* content parts reached the CLI-only /alpha/generate endpoint, which required
* user content to be a plain string. Since #10265 the executor posts to the
* documented /provider/v1/chat/completions endpoint, which natively speaks the
* OpenAI chat.completions format — array content (text + image_url parts) is
* valid there and passes through unchanged. These tests pin that OpenAI-shaped
* passthrough.
*/
import test from "node:test";
import assert from "node:assert/strict";
@@ -26,9 +25,8 @@ const core = await import("../../src/lib/db/core.ts");
const originalFetch = globalThis.fetch;
function commandCodeStream(lines: unknown[]) {
const text = lines.map((l) => JSON.stringify(l)).join("\n") + "\n";
return new Response(text, { status: 200, headers: { "Content-Type": "application/x-ndjson" } });
function okResponse() {
return new Response("{}", { status: 200, headers: { "Content-Type": "application/json" } });
}
test.after(() => {
@@ -41,155 +39,100 @@ test.afterEach(() => {
globalThis.fetch = originalFetch;
});
// ── helpers ────────────────────────────────────────────────────────────────────
// ── helpers ────────────────────────────────────────────────────────────
type FetchCall = { url: string; init: Record<string, unknown>; body: Record<string, unknown> };
function captureFetch(response: Response) {
const calls: FetchCall[] = [];
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 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(
"#5166 user message with multi-part array content is flattened to a string (#5166)",
async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
const userMsg = (calls[0].body.messages as Record<string, unknown>[])[0];
// OpenAI array content is valid on /provider/v1 — forwarded as-is.
assert.ok(Array.isArray(userMsg.content), "array content forwarded (no CLI flattening)");
const parts = userMsg.content as Record<string, unknown>[];
assert.equal(parts.length, 2);
assert.equal(parts[0].text, "Hello");
assert.equal(parts[1].text, "World");
});
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("#5166 user message with single text-part array passes through", 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: "Hi there" }] }],
},
});
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;
const userMsg = (posted.params as Record<string, unknown[]>).messages[0] as Record<
string,
unknown
>;
test("#5166 user message with plain string content passes through unchanged", 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: "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.
assert.equal(
typeof userMsg.content,
"string",
`user message content must be a string, got ${typeof userMsg.content}`
);
// Joined text parts with "\n"
assert.equal(userMsg.content, "Hello\nWorld");
}
);
test(
"#5166 user message with single text-part array is flattened to a plain string",
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: "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:");
}
);
test("#5166 user message with mixed parts (text + image_url) keeps all parts", 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: "Describe this:" },
{ type: "image_url", image_url: { url: "https://example.com/img.png" } },
],
},
],
},
});
const userMsg = (calls[0].body.messages as Record<string, unknown>[])[0];
const parts = userMsg.content as Record<string, unknown>[];
assert.equal(parts.length, 2, "text + image both preserved");
assert.equal(parts[0].text, "Describe this:");
assert.equal(parts[1].type, "image_url");
});

View File

@@ -1,9 +1,13 @@
/**
* 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.)
* receive image parts in Command Code CLI format, while text-only
* models strip images as before (no regression).
* Since #10265 the executor posts to the documented /provider/v1/chat/completions
* endpoint, which speaks the standard OpenAI chat.completions format. User image
* 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 assert from "node:assert/strict";
@@ -19,9 +23,8 @@ const core = await import("../../src/lib/db/core.ts");
const originalFetch = globalThis.fetch;
function commandCodeStream(lines: unknown[]) {
const text = lines.map((l) => JSON.stringify(l)).join("\n") + "\n";
return new Response(text, { status: 200, headers: { "Content-Type": "application/x-ndjson" } });
function okResponse() {
return new Response("{}", { status: 200, headers: { "Content-Type": "application/json" } });
}
test.after(() => {
@@ -52,44 +55,28 @@ function captureFetch(response: Response) {
}
function userContent(calls: FetchCall[]): unknown {
return (
(calls[0].body.params as Record<string, unknown[]>).messages as Record<string, unknown>[]
)[0].content;
return (calls[0].body.messages as Record<string, unknown>[])[0].content;
}
function wireModel(calls: FetchCall[]): string {
return calls[0].body.model as string;
}
// ── 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 () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "command-code/mimo-v2.5",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
model: "command-code/mimo-v2.5",
messages: [{ role: "user", content: "hi" }],
},
body: { model: "command-code/mimo-v2.5", messages: [{ role: "user", content: "hi" }] },
});
assert.equal(wireModel(calls), "xiaomi/mimo-v2.5");
});
test("#10809: cmd/mimo-v2.5 (alias prefix) is also normalized", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "cmd/mimo-v2.5",
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 () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "command-code/deepseek/deepseek-v4-pro",
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");
});
// ── vision models: image parts preserved in CC CLI format ─────────────
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" }])
);
// ── image content passthrough (OpenAI /provider/v1 surface) ──────────
test("image_url parts pass through unchanged (text + image preserved)", async () => {
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "MiniMaxAI/MiniMax-M3",
stream: false,
@@ -132,451 +114,25 @@ test("vision model minimax-m3 preserves image_url part as CC CLI {type:image}",
role: "user",
content: [
{ 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);
assert.ok(Array.isArray(content), "vision model user content must be an array");
const parts = content as Record<string, unknown>[];
assert.equal(parts.length, 2);
// Text part preserved
assert.equal(parts[0].type, "text");
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=");
const content = userContent(calls) as Record<string, unknown>[];
assert.equal(content.length, 2);
assert.equal(content[0].type, "text");
assert.equal(content[1].type, "image_url", "image_url part preserved as-is");
assert.equal(
(content[1].image_url as { url: string }).url,
"data:image/png;base64,iVBORw0KGgo="
);
});
test("vision model minimax-m3 preserves image_url with HTTP URL", 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: "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" }])
);
test("Anthropic Messages-style source image blocks pass through unchanged", async () => {
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "xiaomi/mimo-v2.5",
stream: false,
@@ -601,24 +157,15 @@ test("vision model mimo-v2.5 preserves Anthropic source.base64 image block", asy
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content), "user content must be an array");
const parts = content as Record<string, unknown>[];
assert.equal(parts.length, 2, "text + image parts preserved");
assert.equal(parts[0].type, "text");
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"
);
const content = userContent(calls) as Record<string, unknown>[];
assert.equal(content.length, 2, "text + image parts preserved");
assert.equal(content[1].type, "image");
assert.equal((content[1].source as { type: string }).type, "base64");
assert.equal((content[1].source as { data: string }).data, "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==");
});
test("vision model preserves Anthropic source.url image block", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
test("Anthropic source.url image block passes through unchanged", async () => {
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "xiaomi/mimo-v2.5",
stream: false,
@@ -629,31 +176,23 @@ test("vision model preserves Anthropic source.url image block", async () => {
role: "user",
content: [
{ 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);
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");
const content = userContent(calls) as Record<string, unknown>[];
assert.equal(content.length, 2);
assert.equal(content[1].type, "image");
assert.deepEqual(content[1].source, { type: "url", url: "https://example.com/img.png" });
});
test("text-only model deepseek-v4-flash strips Anthropic source.base64 image block", async () => {
const calls = captureFetch(
commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }])
);
test("multiple image parts are all preserved", async () => {
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-flash",
model: "minimax-m3",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
@@ -661,44 +200,41 @@ test("text-only model deepseek-v4-flash strips Anthropic source.base64 image blo
{
role: "user",
content: [
{ type: "text", text: "Text only" },
{
type: "image",
source: {
type: "base64",
media_type: "image/png",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
},
},
{ 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) 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);
// Text-only model: content flattened to plain string, image stripped.
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) ─────
//
// These two ids stay INSIDE the `/gpt-5/` vision family: both accept image
// input on the OpenAI API, and there is no verified Command Code backend data
// 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" }])
);
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
// executor forwards content untouched, so there is no text-only stripping.
const calls = captureFetch(okResponse());
await getExecutor("command-code").execute({
model: "gpt-5.4-mini",
model: "deepseek/deepseek-v4-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: {
@@ -706,54 +242,15 @@ test("gpt-5.4-mini keeps image parts (conservative vision family lock)", async (
{
role: "user",
content: [
{ type: "text", text: "What's in this?" },
{
type: "image_url",
image_url: { url: "https://example.com/img.png" },
},
{ type: "text", text: "Hello" },
{ type: "image_url", image_url: { url: "https://example.com/img.png" } },
],
},
],
},
});
const content = userContent(calls);
assert.ok(Array.isArray(content), "gpt-5.4-mini 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");
});
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");
});
const content = userContent(calls) as Record<string, unknown>[];
assert.equal(content.length, 2, "content array forwarded unchanged");
assert.equal(content[1].type, "image_url");
});

View File

@@ -15,7 +15,10 @@ class MockM365WebSocket {
closed = false;
listeners = new Map<string, Listener[]>();
constructor(public url: string, public options: unknown) {
constructor(
public url: string,
public options: unknown
) {
MockM365WebSocket.instances.push(this);
queueMicrotask(() => this.emit("open"));
}
@@ -144,30 +147,55 @@ test("#7870: enterprise-tier chat invocation defaults tone to Magic", async () =
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 optionsSets = invocationArgs.optionsSets as string[];
// The 25-entry consumer/MSA set (enable_msa_user, pdnascan, …) is gone from
// the wire — the stale set was part of the silently-dropped shape.
// The pre-#10718 25-entry consumer/MSA set (enable_msa_user, pdnascan, …) is
// 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_gg_gpt"));
// The browser sends tone:"magic" (lowercase) on the individual/EDU surface.
assert.equal(invocationArgs.tone, "magic");
// The individual/consumer surface now sends tone:"Magic" (capitalized) —
// the #10718 lowercase "magic" is stale.
assert.equal(invocationArgs.tone, "Magic");
assert.deepEqual(invocationArgs.allowedMessageTypes, [
"Chat",
"Suggestion",
"InternalSearchQuery",
"Disengaged",
"Progress",
"EndOfRequest",
"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 optionsSets = invocationArgs.optionsSets as string[];
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 () => {
@@ -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);
assert.equal(
invocationArgs.disconnectBehavior,
undefined,
`individual-tier invocation must omit disconnectBehavior; got ${JSON.stringify(invocationArgs.disconnectBehavior)}`
"continue",
`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 ────────────────────────────────────────────
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({
text: "Say OK in one word.",
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",
}).arguments[0] as Record<string, unknown>;
// Exact key set from the capture — additions AND omissions are both pinned,
// because the stale keys are exactly what got the shape dropped.
// Exact key set from the 2026-08-21 capture (issue: individual/consumer M365
// 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(), [
"allowedMessageTypes",
"clientCorrelationId",
"clientInfo",
"conversationId",
"disconnectBehavior",
"extraExtensionParameters",
"isSbsSupported",
"isStartOfSession",
"message",
"options",
"optionsSets",
"plugins",
"productThreadType",
"renderReferencesBehindEOS",
"sessionId",
"sliceIds",
"source",
@@ -73,13 +79,32 @@ test("#10718: buildChatInvocation matches the recaptured arguments[0] key set",
"traceId",
]);
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.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({
text: "Say OK in one word.",
traceId: "t",
@@ -93,7 +118,9 @@ test("#10718: the message object carries the recaptured rich shape", () => {
"adaptiveCards",
"attachments",
"author",
"clientInfo",
"clientPreferences",
"connectedFederatedConnections",
"entityAnnotationTypes",
"experienceType",
"inputMethod",
@@ -107,26 +134,41 @@ test("#10718: the message object carries the recaptured rich shape", () => {
assert.equal(message.messageType, "Chat");
assert.equal(message.requestId, "r");
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.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);
assert.equal(overrides.optionsSets.length, 14);
assert.equal(overrides.allowedMessageTypes.length, 6);
assert.equal(overrides.tone, "magic");
// The pre-#10718 consumer/MSA flags are gone from the wire.
assert.equal(overrides.optionsSets.length, 34);
assert.equal(overrides.allowedMessageTypes.length, 30);
assert.equal(overrides.tone, "Magic");
assert.equal(overrides.disconnectBehavior, "continue");
const optionSets = M365_DEFAULT_OPTION_SETS as readonly string[];
const messageTypes = ALLOWED_MESSAGE_TYPES as readonly string[];
for (const stale of ["enable_msa_user", "pdnascan", "cwc_code_interpreter", "rich_responses"]) {
assert.ok(!optionSets.includes(stale), `${stale} must not be in the default option sets`);
// Entries the 2026-08-21 capture showed that the #10718 lists lacked.
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"]) {
assert.ok(!messageTypes.includes(stale), `${stale} must not be in allowedMessageTypes`);
for (const present of ["InternalSearchQuery", "GeneratedCode", "AuthError", "TriggerPlugin"]) {
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(messageTypes.includes("EndOfRequest"));
});
@@ -134,8 +176,7 @@ test("#10718: default tier lists are the recaptured 14-entry optionsSets / 6-ent
// ── refresh_token helpers ──────────────────────────────────────────────────
function fakeJwt(claims: Record<string, unknown>): string {
const b64 = (value: unknown) =>
Buffer.from(JSON.stringify(value)).toString("base64url");
const b64 = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url");
return `${b64({ alg: "none" })}.${b64(claims)}.sig`;
}

View File

@@ -182,7 +182,9 @@ test("buildChatInvocation defaults stay backward compatible", () => {
requestId: "r",
conversationId: "c",
}).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.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);
});
it("buildUrl returns a string", () => {
it("buildUrl targets the documented /provider/v1/chat/completions endpoint (#10265)", () => {
const executor = new mod.CommandCodeExecutor();
const url = executor.buildUrl();
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 () => {
@@ -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 originalFetch = globalThis.fetch;
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
@@ -70,182 +74,8 @@ describe("CommandCodeExecutor", () => {
}) as typeof fetch;
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 = {
model: "gpt-5.4",
messages: [
{ role: "user", content: "hi" },
{
@@ -253,29 +83,19 @@ describe("CommandCodeExecutor", () => {
content: "",
tool_calls: [
{
id: "call_00_AAAAAAAAAAAAAAAAA",
id: "call_1",
type: "function",
function: { name: "tool_search", arguments: '{"query":"x"}' },
},
{
id: "call_01_BBBBBBBBBBBBBBBBB",
type: "function",
function: { name: "lookup", arguments: '{"q":"1"}' },
function: { name: "lookup", arguments: '{"q":"docs"}' },
},
// 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_01_BBBBBBBBBBBBBBBBB", content: "r2" },
{ role: "tool", tool_call_id: "call_1", content: "r1" },
{ role: "tool", tool_call_id: "call_2", content: "r2" },
],
tools: [
{
type: "function",
function: {
name: "tool_search",
description: "search tools",
parameters: { type: "object", properties: { query: { type: "string" } } },
},
},
{
type: "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 {
result = await executor.execute({
model: "test",
await executor.execute({
model: "gpt-5.4",
body,
stream: false,
credentials: { apiKey: "fake-key" },
@@ -321,46 +119,85 @@ describe("CommandCodeExecutor", () => {
globalThis.fetch = originalFetch;
}
const sentBody = calls[0].body as {
params: {
messages: Array<{ role: string; content: unknown }>;
tools: Array<{ name: string }>;
};
};
const toolDefNames = sentBody.params.tools.map((t) => t.name);
assert.equal(calls.length, 1, "exactly one upstream call");
assert.ok(
toolDefNames.includes("omniroute_tool_search"),
"colliding tool def renamed on the wire"
calls[0].url.includes("/provider/v1/chat/completions"),
`expected documented provider endpoint, got: ${calls[0].url}`
);
assert.ok(toolDefNames.includes("lookup"), "non-colliding tool def untouched");
const assistant = sentBody.params.messages.find((m) => m.role === "assistant");
const toolCallParts = (assistant?.content as Array<Record<string, unknown>>).filter(
(p) => p.type === "tool-call"
const sent = calls[0].body as Record<string, unknown>;
// No CLI envelope.
assert.equal(sent.config, undefined, "CLI envelope `config` must not be sent");
assert.equal(sent.params, undefined, "CLI envelope `params` wrapper must not be sent");
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(toolSearchCall, "assistant tool-call part renamed on the wire");
const lookupCall = toolCallParts.find((p) => p.toolName === "lookup");
assert.ok(lookupCall, "non-colliding tool-call part untouched");
const toolMsgs = sentBody.params.messages.filter((m) => m.role === "tool");
const toolSearchResult = toolMsgs.find(
(m) => (m.content as Array<Record<string, unknown>>)[0]?.toolName === "omniroute_tool_search"
assert.ok(assistant, "assistant turn present");
const toolCalls = assistant?.tool_calls as Array<{
id: string;
function: { name: string; arguments?: string };
}>;
assert.equal(toolCalls.length, 2, "both tool calls pass through untouched");
assert.equal(toolCalls[0].function.name, "lookup");
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");
// 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(toolMsg?.tool_call_id, "call_1");
assert.equal(
toolCalls[0].function.name,
"tool_search",
"renamed wire name un-renamed for the client"
(sent.tools as Array<{ function: { name: string } }>)[0].function.name,
"lookup",
"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.conversationId, "conversation-id");
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.ok(Array.isArray(arg.optionsSets));
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 individual = resolveChatInvocationOverrides(undefined);
// enterprise tier default tone is Magic; individual/EDU sends "magic" (#10718
// recapture — the old "" default was part of the silently-dropped shape)
// enterprise tier default tone is Magic; individual/EDU now also sends
// "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(individual.tone, "magic");
assert.equal(individual.tone, "Magic");
// precedence: resolveToneForModel(model) ?? overrides.tone (mirrors the executor wiring)
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", 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", individual.tone), "magic");
assert.equal(toneFor("copilot-m365", individual.tone), "Magic");
});
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
// the provider's own catalog lacks the model id, importing the DECLARING
// 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: []`
// upstream — upstream rejected with "Invalid prompt: messages must not be empty"
// (502). Model-level targetFormat is provider-scoped: it must not leak.
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);
// raw provider id form behaves identically (alias resolution)
assert.equal(getModelTargetFormat("command-code", "gpt-5.6-luna"), null);

View File

@@ -16,7 +16,6 @@ const { __setTlsFetchOverrideForTesting: __setPplxTlsFetchOverride } =
const { __setTlsFetchOverrideForTesting: __setGrokTlsFetchOverride } =
await import("../../open-sse/services/grokTlsClient.ts");
const { COMMAND_CODE_VERSION } = await import("../../open-sse/executors/commandCode.ts");
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 () => {
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>;
assert.equal(headers.Authorization, "Bearer cc-key");
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 });
};
@@ -239,7 +238,7 @@ test("validateCommandCodeProvider ignores caller baseUrl and chatPath overrides"
test("validateCommandCodeProvider defaults probe model to DeepSeek flash", async () => {
globalThis.fetch = async (_url, init = {}) => {
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 });
};
@@ -2285,7 +2284,7 @@ test("specialty validator rejects invalid Runway credentials", async () => {
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<{
url: 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.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].headers.Authorization, "Bearer cc_test_key");
assert.equal(calls[0].headers["Content-Type"], "application/json");
assert.equal(calls[0].headers["x-command-code-version"], COMMAND_CODE_VERSION);
assert.equal(calls[0].headers["x-cli-environment"], "external");
assert.equal(calls[0].headers["x-project-slug"], "pi-cc");
assert.equal(calls[0].headers["x-taste-learning"], "false");
assert.equal(calls[0].headers["x-co-flag"], "false");
assert.equal(typeof calls[0].headers["x-session-id"], "string");
assert.equal(calls[0].body.config.environment, "external");
assert.equal(calls[0].body.permissionMode, "standard");
assert.equal(calls[0].body.skills, "");
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);
// No CLI-impersonation headers.
assert.equal(calls[0].headers["x-command-code-version"], undefined);
assert.equal(calls[0].headers["x-cli-environment"], undefined);
assert.equal(calls[0].headers["x-project-slug"], undefined);
// Flat OpenAI chat.completions body (no CLI wrapper).
assert.equal(calls[0].body.params, undefined, "CLI envelope params wrapper must not be sent");
assert.equal(calls[0].body.model, "gpt-5.4-mini");
assert.equal(calls[0].body.stream, true);
assert.equal(calls[0].body.max_tokens, 1);
});
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 { handleResponsesCore } = await import("../../open-sse/handlers/responsesHandler.ts");
const { COMMAND_CODE_VERSION } = await import("../../open-sse/executors/commandCode.ts");
const originalFetch = globalThis.fetch;
@@ -354,26 +353,31 @@ test("handleResponsesCore transforms Command Code executor SSE through Responses
input: "hello command code",
},
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(
[
`data: ${JSON.stringify({ type: "text-delta", text: "command" })}`,
"",
`data: ${JSON.stringify({ type: "reasoning-delta", text: "thinking" })}`,
"",
`data: ${JSON.stringify({ type: "finish", finishReason: "stop" })}`,
"",
].join("\n"),
{ status: 200, headers: { "Content-Type": "application/x-ndjson" } }
chunk({ role: "assistant" }),
chunk({ content: "command" }),
chunk({}),
].join("") + "data: [DONE]\n\n",
{ status: 200, headers: { "Content-Type": "text/event-stream" } }
);
},
});
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["x-command-code-version"], COMMAND_CODE_VERSION);
assert.equal(call.body.params.model, "gpt-5.4-mini");
assert.equal(call.body.params.stream, true);
assert.equal(call.headers["x-command-code-version"], undefined);
assert.equal(call.body.model, "gpt-5.4-mini");
assert.equal(call.body.stream, true);
const sse = await result.response.text();
assert.match(sse, /event: response\.created/);