mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-23 15:42:12 +03:00
Compare commits
3 Commits
fix/11095-
...
fix/10265-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97fa38799e | ||
|
|
b45aa9eb19 | ||
|
|
690f5739ad |
@@ -1 +0,0 @@
|
||||
- fix(install): make the ONNX dependency chain optional so Termux/Android installs succeed again (#11095)
|
||||
@@ -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": 1080,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1073,
|
||||
"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,8 +459,7 @@
|
||||
"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_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_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_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).",
|
||||
|
||||
@@ -30,45 +30,17 @@ export const HANDSHAKE_REQUEST = { protocol: "json", version: 1 } as const;
|
||||
export const KEEPALIVE_PING = { type: 6 } as const;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
export const ALLOWED_MESSAGE_TYPES = [
|
||||
"Chat",
|
||||
"Suggestion",
|
||||
"InternalSearchQuery",
|
||||
"Disengaged",
|
||||
"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",
|
||||
"InternalLoaderMessage",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
@@ -106,26 +78,19 @@ export const M365_ENTERPRISE_EXTRA_MESSAGE_TYPES = [
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Individual / EDU option sets from a 2026-08-21 live capture — 34 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.
|
||||
* 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.
|
||||
*/
|
||||
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",
|
||||
@@ -133,20 +98,6 @@ 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. */
|
||||
@@ -482,14 +433,12 @@ export function resolveChatInvocationOverrides(tier: string | undefined): {
|
||||
}
|
||||
return {
|
||||
optionsSets: [...M365_DEFAULT_OPTION_SETS],
|
||||
// 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",
|
||||
// #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",
|
||||
allowedMessageTypes: ALLOWED_MESSAGE_TYPES,
|
||||
// 2026-08-21 capture — disconnectBehavior:"continue" is now present on the
|
||||
// individual/consumer wire too, not just enterprise (see ChatInvocationOptions).
|
||||
disconnectBehavior: "continue",
|
||||
// Omitted entirely on the individual/EDU wire (see ChatInvocationOptions).
|
||||
disconnectBehavior: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -518,33 +467,16 @@ export function resolveToneForModel(model: string | undefined): string | undefin
|
||||
|
||||
/**
|
||||
* Build the `type:4` chat invocation frame body (not yet `\x1e`-terminated).
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
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",
|
||||
@@ -555,17 +487,17 @@ export function buildChatInvocation(opts: ChatInvocationOptions): Record<string,
|
||||
? [...opts.allowedMessageTypes]
|
||||
: [...ALLOWED_MESSAGE_TYPES],
|
||||
clientCorrelationId: opts.clientCorrelationId ?? opts.traceId,
|
||||
clientInfo,
|
||||
clientInfo: {
|
||||
clientAppName: "Office",
|
||||
clientPlatform: "mcmcopilot-web",
|
||||
},
|
||||
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",
|
||||
@@ -578,27 +510,22 @@ export function buildChatInvocation(opts: ChatInvocationOptions): Record<string,
|
||||
requestId: opts.requestId,
|
||||
text: opts.text,
|
||||
},
|
||||
isSbsSupported: true,
|
||||
options: {},
|
||||
optionsSets: opts.optionsSets ?? [...M365_DEFAULT_OPTION_SETS],
|
||||
// 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" }],
|
||||
plugins: opts.plugins ?? [],
|
||||
...(opts.customInstructions ? { customInstructions: opts.customInstructions } : {}),
|
||||
productThreadType: "Office",
|
||||
renderReferencesBehindEOS: true,
|
||||
sessionId: opts.sessionId,
|
||||
sliceIds: [],
|
||||
source: "officeweb",
|
||||
streamingMode: "ConciseWithPadding",
|
||||
threadLevelGptId: {},
|
||||
// 2026-08-21 capture (#11069): tone is now capitalized "Magic" on both tiers.
|
||||
tone: opts.tone ?? "Magic",
|
||||
tone: opts.tone ?? "magic",
|
||||
toolChoice: opts.toolChoice ?? null,
|
||||
traceId: opts.traceId,
|
||||
// 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",
|
||||
// #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 } : {}),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
72
package-lock.json
generated
72
package-lock.json
generated
@@ -18,6 +18,7 @@
|
||||
"@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",
|
||||
@@ -60,6 +61,7 @@
|
||||
"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",
|
||||
@@ -154,11 +156,9 @@
|
||||
},
|
||||
"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,7 +4510,6 @@
|
||||
"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"
|
||||
}
|
||||
@@ -4519,15 +4518,13 @@
|
||||
"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",
|
||||
"optional": true
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"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",
|
||||
@@ -9486,35 +9483,30 @@
|
||||
"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"
|
||||
@@ -9524,28 +9516,24 @@
|
||||
"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": {
|
||||
@@ -12749,7 +12737,6 @@
|
||||
"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"
|
||||
@@ -14011,7 +13998,6 @@
|
||||
"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"
|
||||
}
|
||||
@@ -14985,8 +14971,7 @@
|
||||
"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",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bottleneck": {
|
||||
"version": "2.19.5",
|
||||
@@ -17950,7 +17935,6 @@
|
||||
"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",
|
||||
@@ -17980,7 +17964,6 @@
|
||||
"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",
|
||||
@@ -18081,8 +18064,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
@@ -18926,8 +18908,7 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es6-promisify": {
|
||||
"version": "7.0.0",
|
||||
@@ -20419,8 +20400,7 @@
|
||||
"version": "25.9.23",
|
||||
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz",
|
||||
"integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.4.2",
|
||||
@@ -21246,7 +21226,6 @@
|
||||
"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",
|
||||
@@ -21264,7 +21243,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -21313,7 +21291,6 @@
|
||||
"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",
|
||||
@@ -21668,8 +21645,7 @@
|
||||
"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",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hachure-fill": {
|
||||
"version": "0.5.2",
|
||||
@@ -21703,7 +21679,6 @@
|
||||
"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"
|
||||
@@ -24815,8 +24790,7 @@
|
||||
"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",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
@@ -26680,7 +26654,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -29452,7 +29425,6 @@
|
||||
"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"
|
||||
@@ -29660,8 +29632,7 @@
|
||||
"version": "1.24.3",
|
||||
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
|
||||
"integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/onnxruntime-node": {
|
||||
"version": "1.24.3",
|
||||
@@ -29669,7 +29640,6 @@
|
||||
"integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32",
|
||||
"darwin",
|
||||
@@ -29686,7 +29656,6 @@
|
||||
"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",
|
||||
@@ -29700,15 +29669,13 @@
|
||||
"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",
|
||||
"optional": true
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"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",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/open": {
|
||||
"version": "11.0.1",
|
||||
@@ -30939,8 +30906,7 @@
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
|
||||
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.62.1",
|
||||
@@ -31895,7 +31861,6 @@
|
||||
"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": {
|
||||
@@ -31919,7 +31884,6 @@
|
||||
"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": {
|
||||
@@ -33316,7 +33280,6 @@
|
||||
"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",
|
||||
@@ -33692,8 +33655,7 @@
|
||||
"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",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "1.2.1",
|
||||
@@ -33726,7 +33688,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -33742,7 +33703,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -34514,8 +34474,7 @@
|
||||
"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",
|
||||
"optional": true
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/sql.js": {
|
||||
"version": "1.14.2",
|
||||
@@ -36228,7 +36187,6 @@
|
||||
"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": {
|
||||
|
||||
@@ -265,6 +265,7 @@
|
||||
"@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",
|
||||
@@ -307,6 +308,7 @@
|
||||
"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",
|
||||
@@ -341,11 +343,9 @@
|
||||
},
|
||||
"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"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
name: omni-webhooks
|
||||
description: Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, request.failed, quota.exceeded, etc.) and manage delivery retries.
|
||||
description: Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, provider.error, budget.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, request.failed, quota.exceeded, etc.) and manage delivery retries.
|
||||
Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, provider.error, budget.exceeded, etc.) and manage delivery retries.
|
||||
|
||||
## Authentication
|
||||
|
||||
|
||||
@@ -12,13 +12,8 @@ import {
|
||||
} from "@/lib/combos/intelligentRouting";
|
||||
import { AI_PROVIDERS } from "@/shared/constants/providers";
|
||||
|
||||
function getI18nOrFallback(
|
||||
t: any,
|
||||
key: string,
|
||||
fallback: string,
|
||||
values?: Record<string, unknown>
|
||||
) {
|
||||
if (typeof t?.has === "function" && t.has(key)) return t(key, values);
|
||||
function getI18nOrFallback(t: any, key: string, fallback: string) {
|
||||
if (typeof t?.has === "function" && t.has(key)) return t(key);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -99,9 +94,10 @@ export default function IntelligentComboPanel({
|
||||
const updatedCombo = await response.json();
|
||||
onComboUpdated?.(updatedCombo);
|
||||
notify.success(
|
||||
getI18nOrFallback(t, "modePackUpdated", "Mode pack updated to {pack}.", {
|
||||
pack: modePackId,
|
||||
}).replace("{pack}", modePackId)
|
||||
getI18nOrFallback(t, "modePackUpdated", "Mode pack updated to {pack}.").replace(
|
||||
"{pack}",
|
||||
modePackId
|
||||
)
|
||||
);
|
||||
} catch (error: any) {
|
||||
notify.error(error?.message || "Failed to update mode pack.");
|
||||
@@ -188,9 +184,10 @@ export default function IntelligentComboPanel({
|
||||
</div>
|
||||
{savingModePack && (
|
||||
<span className="text-[11px] text-text-muted">
|
||||
{getI18nOrFallback(t, "savingModePack", "Saving {pack}…", {
|
||||
pack: savingModePack,
|
||||
}).replace("{pack}", savingModePack)}
|
||||
{getI18nOrFallback(t, "savingModePack", "Saving {pack}…").replace(
|
||||
"{pack}",
|
||||
savingModePack
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -533,9 +533,9 @@ function getStrategyBadgeClass(strategy) {
|
||||
return "bg-blue-500/15 text-blue-600 dark:text-blue-400";
|
||||
}
|
||||
|
||||
function getI18nOrFallback(t, key, fallback, values) {
|
||||
function getI18nOrFallback(t, key, fallback) {
|
||||
try {
|
||||
if (typeof t.has === "function" && t.has(key)) return t(key, values);
|
||||
if (typeof t.has === "function" && t.has(key)) return t(key);
|
||||
} catch {}
|
||||
return fallback;
|
||||
}
|
||||
@@ -1565,8 +1565,7 @@ function StrategyRecommendationsPanel({ strategy, onApply, showNudge }) {
|
||||
{getI18nOrFallback(
|
||||
t,
|
||||
"recommendationsUpdated",
|
||||
"Recommendations updated for {strategy}.",
|
||||
{ strategy: strategyLabel }
|
||||
"Recommendations updated for {strategy}."
|
||||
).replace("{strategy}", strategyLabel)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
@@ -31,7 +31,6 @@ 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";
|
||||
@@ -210,13 +209,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,
|
||||
@@ -751,12 +750,6 @@ export default function AddApiKeyModal({
|
||||
t={t}
|
||||
/>
|
||||
)}
|
||||
{provider && (
|
||||
<HarImportButton
|
||||
provider={provider}
|
||||
onImport={(apiKey) => setFormData({ ...formData, apiKey })}
|
||||
/>
|
||||
)}
|
||||
{!isNoAuthWebSessionCredential && (
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
|
||||
@@ -49,7 +49,6 @@ 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";
|
||||
@@ -910,12 +909,6 @@ export default function EditConnectionModal({
|
||||
t={t}
|
||||
/>
|
||||
)}
|
||||
{provider && (
|
||||
<HarImportButton
|
||||
provider={provider}
|
||||
onImport={(apiKey) => setFormData({ ...formData, apiKey })}
|
||||
/>
|
||||
)}
|
||||
{!isNoAuthWebSessionCredential && (
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
|
||||
@@ -6269,20 +6269,6 @@
|
||||
"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.",
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/**
|
||||
* 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 };
|
||||
}
|
||||
@@ -9,52 +9,40 @@ function readJson<T = Record<string, unknown>>(relPath: string): T {
|
||||
return JSON.parse(readFileSync(join(repoRoot, relPath), "utf8")) as T;
|
||||
}
|
||||
|
||||
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).
|
||||
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.
|
||||
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 an optionalDependency"
|
||||
);
|
||||
assert.equal(
|
||||
pkg.dependencies?.["onnxruntime-node"],
|
||||
undefined,
|
||||
"onnxruntime-node must NOT be a hard dependency (fatal EBADPLATFORM on Android)"
|
||||
);
|
||||
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)"
|
||||
"transformers must be a regular dependency (never optional) so npm ci cannot skip it"
|
||||
);
|
||||
assert.equal(pkg.optionalDependencies?.["@huggingface/transformers"], undefined);
|
||||
});
|
||||
|
||||
test("lockfile marks the whole ONNX chain optional", () => {
|
||||
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)"
|
||||
);
|
||||
assert.equal(pkg.optionalDependencies?.["onnxruntime-node"], undefined);
|
||||
|
||||
const lock = readJson<{
|
||||
packages: Record<
|
||||
string,
|
||||
@@ -63,61 +51,26 @@ test("lockfile marks the whole ONNX chain optional", () => {
|
||||
dependencies?: Record<string, string>;
|
||||
optionalDependencies?: Record<string, string>;
|
||||
}
|
||||
>;
|
||||
>;
|
||||
}>("package-lock.json");
|
||||
|
||||
assert.equal(
|
||||
lock.packages[""]?.optionalDependencies?.["@huggingface/transformers"],
|
||||
lock.packages[""]?.dependencies?.["@huggingface/transformers"],
|
||||
"^4.2.0",
|
||||
"root lock optionalDependencies must hold transformers"
|
||||
"root lock dependencies must hold transformers as a regular (non-optional) dep"
|
||||
);
|
||||
assert.equal(
|
||||
lock.packages[""]?.optionalDependencies?.["onnxruntime-node"],
|
||||
"1.24.3",
|
||||
"root lock optionalDependencies must hold onnxruntime-node"
|
||||
// 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.ok(
|
||||
lock.packages["node_modules/@huggingface/transformers"]?.optional,
|
||||
"transformers must be marked optional in the lockfile"
|
||||
!lock.packages["node_modules/onnxruntime-node"]?.optional,
|
||||
"onnxruntime-node must not be marked optional in the lockfile"
|
||||
);
|
||||
assert.ok(
|
||||
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"
|
||||
!lock.packages["node_modules/onnxruntime-common"]?.optional,
|
||||
"onnxruntime-common must not be marked optional in the lockfile"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,10 +15,7 @@ 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"));
|
||||
}
|
||||
@@ -147,55 +144,30 @@ test("#7870: enterprise-tier chat invocation defaults tone to Magic", async () =
|
||||
assert.equal(invocationArgs.tone, "Magic");
|
||||
});
|
||||
|
||||
test("2026-08-21: individual (no tier) chat invocation carries the recaptured shape", async () => {
|
||||
test("#10718: individual (no tier) chat invocation carries the recaptured 2026-08 shape", async () => {
|
||||
const invocationArgs = await sendChatInvocation(undefined);
|
||||
const optionsSets = invocationArgs.optionsSets as string[];
|
||||
// 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.
|
||||
// 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.
|
||||
assert.ok(!optionsSets.includes("enable_msa_user"));
|
||||
assert.ok(optionsSets.includes("enable_gg_gpt"));
|
||||
// The individual/consumer surface now sends tone:"Magic" (capitalized) —
|
||||
// the #10718 lowercase "magic" is stale.
|
||||
assert.equal(invocationArgs.tone, "Magic");
|
||||
// The browser sends tone:"magic" (lowercase) on the individual/EDU surface.
|
||||
assert.equal(invocationArgs.tone, "magic");
|
||||
assert.deepEqual(invocationArgs.allowedMessageTypes, [
|
||||
"Chat",
|
||||
"Suggestion",
|
||||
"InternalSearchQuery",
|
||||
"Disengaged",
|
||||
"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",
|
||||
"InternalLoaderMessage",
|
||||
]);
|
||||
});
|
||||
|
||||
test("2026-08-21: EDU-tier chat invocation carries the same recaptured shape", async () => {
|
||||
test("#10718: 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 () => {
|
||||
@@ -207,11 +179,11 @@ test("#8971: enterprise-tier chat invocation must send disconnectBehavior=contin
|
||||
);
|
||||
});
|
||||
|
||||
test("2026-08-21: individual (no tier) chat invocation also sends disconnectBehavior=continue (now on every tier)", async () => {
|
||||
test("#8971/#10718: individual (no tier) chat invocation omits disconnectBehavior (not on the 2026-08 wire)", async () => {
|
||||
const invocationArgs = await sendChatInvocation(undefined);
|
||||
assert.equal(
|
||||
invocationArgs.disconnectBehavior,
|
||||
"continue",
|
||||
`individual-tier invocation must carry disconnectBehavior="continue"; got ${JSON.stringify(invocationArgs.disconnectBehavior)}`
|
||||
undefined,
|
||||
`individual-tier invocation must omit disconnectBehavior; got ${JSON.stringify(invocationArgs.disconnectBehavior)}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ test("#10718: metricsFrame emits the exact bytes observed in the browser capture
|
||||
|
||||
// ── Recaptured invocation shape ────────────────────────────────────────────
|
||||
|
||||
test("2026-08-21: buildChatInvocation matches the recaptured arguments[0] key set", () => {
|
||||
test("#10718: buildChatInvocation matches the recaptured arguments[0] key set", () => {
|
||||
const arg = buildChatInvocation({
|
||||
text: "Say OK in one word.",
|
||||
traceId: "11111111-1111-1111-1111-111111111111",
|
||||
@@ -50,25 +50,19 @@ test("2026-08-21: buildChatInvocation matches the recaptured arguments[0] key se
|
||||
conversationId: "44444444-4444-4444-4444-444444444444",
|
||||
}).arguments[0] as Record<string, unknown>;
|
||||
|
||||
// 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.
|
||||
// Exact key set from the capture — additions AND omissions are both pinned,
|
||||
// because the stale keys are exactly what got the shape dropped.
|
||||
assert.deepEqual(Object.keys(arg).sort(), [
|
||||
"allowedMessageTypes",
|
||||
"clientCorrelationId",
|
||||
"clientInfo",
|
||||
"conversationId",
|
||||
"disconnectBehavior",
|
||||
"extraExtensionParameters",
|
||||
"isSbsSupported",
|
||||
"isStartOfSession",
|
||||
"message",
|
||||
"options",
|
||||
"optionsSets",
|
||||
"plugins",
|
||||
"productThreadType",
|
||||
"renderReferencesBehindEOS",
|
||||
"sessionId",
|
||||
"sliceIds",
|
||||
"source",
|
||||
@@ -79,32 +73,13 @@ test("2026-08-21: buildChatInvocation matches the recaptured arguments[0] key se
|
||||
"traceId",
|
||||
]);
|
||||
assert.equal(arg.productThreadType, "Office");
|
||||
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.deepEqual(arg.clientInfo, { clientAppName: "Office", clientPlatform: "mcmcopilot-web" });
|
||||
assert.equal(arg.conversationId, "44444444-4444-4444-4444-444444444444");
|
||||
assert.equal(arg.toolChoice, null);
|
||||
// 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");
|
||||
assert.equal(arg.tone, "magic");
|
||||
});
|
||||
|
||||
test("2026-08-21: the message object carries the recaptured rich shape", () => {
|
||||
test("#10718: the message object carries the recaptured rich shape", () => {
|
||||
const arg = buildChatInvocation({
|
||||
text: "Say OK in one word.",
|
||||
traceId: "t",
|
||||
@@ -118,9 +93,7 @@ test("2026-08-21: the message object carries the recaptured rich shape", () => {
|
||||
"adaptiveCards",
|
||||
"attachments",
|
||||
"author",
|
||||
"clientInfo",
|
||||
"clientPreferences",
|
||||
"connectedFederatedConnections",
|
||||
"entityAnnotationTypes",
|
||||
"experienceType",
|
||||
"inputMethod",
|
||||
@@ -134,41 +107,26 @@ test("2026-08-21: 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("2026-08-21: default tier lists are the recaptured 34-entry optionsSets / 30-entry allowedMessageTypes", () => {
|
||||
test("#10718: default tier lists are the recaptured 14-entry optionsSets / 6-entry allowedMessageTypes", () => {
|
||||
const overrides = resolveChatInvocationOverrides(undefined);
|
||||
assert.equal(overrides.optionsSets.length, 34);
|
||||
assert.equal(overrides.allowedMessageTypes.length, 30);
|
||||
assert.equal(overrides.tone, "Magic");
|
||||
assert.equal(overrides.disconnectBehavior, "continue");
|
||||
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.
|
||||
const optionSets = M365_DEFAULT_OPTION_SETS as readonly string[];
|
||||
const messageTypes = ALLOWED_MESSAGE_TYPES as readonly string[];
|
||||
// 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 ["enable_msa_user", "pdnascan", "cwc_code_interpreter", "rich_responses"]) {
|
||||
assert.ok(!optionSets.includes(stale), `${stale} must not be in the default option sets`);
|
||||
}
|
||||
for (const present of ["InternalSearchQuery", "GeneratedCode", "AuthError", "TriggerPlugin"]) {
|
||||
assert.ok(messageTypes.includes(present), `${present} must be in allowedMessageTypes`);
|
||||
for (const stale of ["InternalSearchQuery", "GeneratedCode", "RenderCardRequest", "AdsQuery", "SemanticSerp", "GenerateContentQuery"]) {
|
||||
assert.ok(!messageTypes.includes(stale), `${stale} must not be in allowedMessageTypes`);
|
||||
}
|
||||
// Entries the #10718 capture showed and this capture still confirms.
|
||||
// Entries the capture showed and the old lists lacked.
|
||||
assert.ok(optionSets.includes("cwcfluxgptv"));
|
||||
assert.ok(messageTypes.includes("EndOfRequest"));
|
||||
});
|
||||
@@ -176,7 +134,8 @@ test("2026-08-21: default tier lists are the recaptured 34-entry optionsSets / 3
|
||||
// ── 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`;
|
||||
}
|
||||
|
||||
|
||||
@@ -182,9 +182,7 @@ test("buildChatInvocation defaults stay backward compatible", () => {
|
||||
requestId: "r",
|
||||
conversationId: "c",
|
||||
}).arguments[0] as Record<string, unknown>;
|
||||
// 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.deepEqual(arg.plugins, []);
|
||||
assert.equal(arg.toolChoice, null);
|
||||
assert.equal(arg.customInstructions, undefined);
|
||||
});
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
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");
|
||||
});
|
||||
@@ -131,22 +131,7 @@ 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");
|
||||
// 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.deepEqual(arg.clientInfo, { clientAppName: "Office", clientPlatform: "mcmcopilot-web" });
|
||||
assert.equal(arg.isStartOfSession, true);
|
||||
assert.ok(Array.isArray(arg.optionsSets));
|
||||
assert.ok((arg.optionsSets as string[]).includes("enable_gg_gpt"));
|
||||
|
||||
@@ -30,11 +30,10 @@ 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 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).
|
||||
// enterprise tier default tone is Magic; individual/EDU sends "magic" (#10718
|
||||
// recapture — the old "" default was part of the silently-dropped shape)
|
||||
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) =>
|
||||
@@ -44,9 +43,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 (both "Magic" post-#11069)
|
||||
// the bare id keeps whatever the tier resolved
|
||||
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", () => {
|
||||
|
||||
Reference in New Issue
Block a user