From 181828625be762bd6553fd41c9db01adb73d6c16 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sun, 9 Aug 2026 17:54:23 -0300 Subject: [PATCH] fix: clear release unit and quality regressions --- open-sse/services/adobeFireflyClient.ts | 55 ++------ open-sse/services/adobeFireflyReferences.ts | 97 ++++++++++++++ .../services/antigravityProjectPersist.ts | 11 +- .../services/compression/engines/ccr/index.ts | 1 - open-sse/utils/cursorImages.ts | 11 +- scripts/build/assembleStandalone.mjs | 5 + src/i18n/messages/zh-TW.json | 6 +- tests/snapshots/provider/translate-path.json | 46 +++++++ tests/unit/adobe-firefly.test.ts | 11 +- ...ravity-project-persist-pool-filter.test.ts | 3 +- .../base-executor-sanitize-effort.test.ts | 8 +- tests/unit/chatcore-translation-paths.test.ts | 4 +- tests/unit/chatgpt-web-tools-7679.test.ts | 122 ++++++------------ tests/unit/cheaperinference-executor.test.ts | 15 +-- tests/unit/clinepass-thinking-budget.test.ts | 6 +- tests/unit/executor-default-base.test.ts | 4 +- tests/unit/executor-xai.test.ts | 2 +- .../openai-responses-only-models-5842.test.ts | 21 +-- .../opencode-zen-reasoning-effort.test.ts | 22 +--- ...ider-registry-github-copilot-gpt-4.test.ts | 2 +- tests/unit/providers-constants-split.test.ts | 10 +- .../responses-usage-trailing-6906.test.ts | 8 +- tests/unit/runtime-timeouts.test.ts | 1 + tests/unit/sidebar-monitoring-reorg.test.ts | 4 +- tests/unit/sidebar-visibility.test.ts | 1 + 25 files changed, 268 insertions(+), 208 deletions(-) create mode 100644 open-sse/services/adobeFireflyReferences.ts diff --git a/open-sse/services/adobeFireflyClient.ts b/open-sse/services/adobeFireflyClient.ts index cfeb3f15f6..bcc8987a75 100644 --- a/open-sse/services/adobeFireflyClient.ts +++ b/open-sse/services/adobeFireflyClient.ts @@ -30,8 +30,13 @@ import { isExactAdobeJwt, stripAdobeJwts, } from "./adobeFireflySecurity.ts"; +import { + parseAdobeModelsDiscovery as parseAdobeModelsDiscoveryContract, + type AdobeFireflyDiscoveredModel, +} from "./adobeFireflyModels.ts"; export { decodeAdobeJwtPayload } from "./adobeFireflySecurity.ts"; +export type { AdobeFireflyDiscoveredModel } from "./adobeFireflyModels.ts"; export const ADOBE_FIREFLY_IMAGE_SUBMIT_URL = "https://firefly-3p.ff.adobe.io/v2/3p-images/generate-async"; @@ -1333,6 +1338,11 @@ export function buildAdobeUploadHeaders( * Supports: image_url, image, images[], image_urls[], input_image(s), reference_images, * provider_options.*, and prompt_image fields used by the WinUI Media page. */ +export { + extractAdobeSourceImageReferences, + normalizeAdobeReferenceBlobs, +} from "./adobeFireflyReferences.ts"; + export function extractAdobeSourceImageSources(body: unknown, max = 4): string[] { if (!body || typeof body !== "object") return []; const b = body as Record; @@ -2222,54 +2232,11 @@ export async function fetchAdobeCreditsBalance( // ── Models discovery ──────────────────────────────────────────────────────── -export interface AdobeFireflyDiscoveredModel { - modelId: string; - modelVersion: string; - displayName: string; - modality: "image" | "video" | "audio" | "unknown"; - enabled: boolean; - healthStatus?: string; -} - /** * Parse POST /v2/models/discovery response into flat model/version rows. */ export function parseAdobeModelsDiscovery(body: unknown): AdobeFireflyDiscoveredModel[] { - const root = body && typeof body === "object" ? (body as Record) : {}; - const models = Array.isArray(root.models) ? root.models : []; - const out: AdobeFireflyDiscoveredModel[] = []; - - for (const m of models) { - if (!m || typeof m !== "object") continue; - const rec = m as Record; - const modelId = String(rec.modelId || "").trim(); - if (!modelId) continue; - const versions = - rec.modelVersions && typeof rec.modelVersions === "object" - ? (rec.modelVersions as Record) - : {}; - for (const [ver, spec] of Object.entries(versions)) { - if (!spec || typeof spec !== "object") continue; - const s = spec as Record; - if (s.enabled === false) continue; - const mods = Array.isArray(s.outputModality) - ? s.outputModality.map((x) => String(x).toLowerCase()) - : []; - let modality: AdobeFireflyDiscoveredModel["modality"] = "unknown"; - if (mods.includes("image")) modality = "image"; - else if (mods.includes("video")) modality = "video"; - else if (mods.includes("audio")) modality = "audio"; - out.push({ - modelId, - modelVersion: ver, - displayName: String(s.modelDisplayName || s.modelCaiDisplayName || ver), - modality, - enabled: s.enabled !== false, - healthStatus: typeof s.healthStatus === "string" ? s.healthStatus : undefined, - }); - } - } - return out; + return parseAdobeModelsDiscoveryContract(body); } export async function discoverAdobeFireflyModels( diff --git a/open-sse/services/adobeFireflyReferences.ts b/open-sse/services/adobeFireflyReferences.ts new file mode 100644 index 0000000000..5c3a24a7ed --- /dev/null +++ b/open-sse/services/adobeFireflyReferences.ts @@ -0,0 +1,97 @@ +import { AdobeFireflyError } from "./adobeFireflyClient.ts"; +import type { AdobeFireflyVideoModelSpec } from "./adobeFireflyClient.ts"; + +export interface AdobeSourceImageReference { + source: string; + usage?: string; + order?: number; +} + +export function normalizeAdobeReferenceBlobs( + modelSpec: AdobeFireflyVideoModelSpec, + references: unknown +): Array<{ id: string; usage: string; order?: number }> { + if (!Array.isArray(references)) return []; + + const maxReferences = modelSpec.referenceMode === "image" ? 3 : 2; + if (references.length > maxReferences) { + throw new AdobeFireflyError( + `Adobe Firefly model accepts at most ${maxReferences} ${ + modelSpec.referenceMode === "image" ? "asset" : "frame" + } image references`, + 400, + "bad_image" + ); + } + + return references.map((reference, index) => { + if (!reference || typeof reference !== "object") { + throw new AdobeFireflyError("Invalid Adobe Firefly reference image", 400, "bad_image"); + } + const value = reference as Record; + const id = typeof value.id === "string" ? value.id.trim() : ""; + if (!id) { + throw new AdobeFireflyError("Adobe Firefly reference image id is required", 400, "bad_image"); + } + + const expectedUsage = modelSpec.referenceMode === "image" ? "asset" : "frame"; + const usage = typeof value.usage === "string" ? value.usage.trim() : expectedUsage; + if (usage !== expectedUsage) { + throw new AdobeFireflyError( + `Adobe Firefly model does not support image references with usage '${usage}'`, + 400, + "bad_image" + ); + } + + return expectedUsage === "frame" ? { id, usage, order: index + 1 } : { id, usage }; + }); +} + +export function extractAdobeSourceImageReferences( + body: unknown, + max = 4 +): AdobeSourceImageReference[] { + if (!body || typeof body !== "object") return []; + const inputs = (body as Record).adobe_reference_inputs; + if (!Array.isArray(inputs)) return []; + + const references: AdobeSourceImageReference[] = []; + for (const input of inputs) { + if (!input || typeof input !== "object") continue; + const value = input as Record; + if ( + value.type !== undefined && + value.type !== "input_image" && + value.type !== "image" && + value.type !== "image_url" + ) { + continue; + } + + const imageUrl = value.image_url; + const source = + typeof value.source === "string" + ? value.source.trim() + : typeof imageUrl === "string" + ? imageUrl.trim() + : imageUrl && + typeof imageUrl === "object" && + typeof (imageUrl as Record).url === "string" + ? String((imageUrl as Record).url).trim() + : typeof value.url === "string" + ? value.url.trim() + : ""; + if (!source || (!source.startsWith("data:image/") && !/^https?:\/\//i.test(source))) continue; + + const usage = + typeof value.usage === "string" && value.usage.trim() ? value.usage.trim() : undefined; + const order = + typeof value.order === "number" && Number.isInteger(value.order) && value.order > 0 + ? value.order + : undefined; + references.push({ source, ...(usage ? { usage } : {}), ...(order ? { order } : {}) }); + if (references.length >= max) break; + } + return references; +} diff --git a/open-sse/services/antigravityProjectPersist.ts b/open-sse/services/antigravityProjectPersist.ts index 1068c4d3ec..b7f55343c2 100644 --- a/open-sse/services/antigravityProjectPersist.ts +++ b/open-sse/services/antigravityProjectPersist.ts @@ -39,7 +39,7 @@ export function preferAntigravityConnectionsWithStoredProject { - if (typeof connection.projectId === "string" && connection.projectId) return true; + if (typeof connection.projectId === "string" && connection.projectId.trim()) return true; let psd = connection.providerSpecificData; if (typeof psd === "string") { try { @@ -48,12 +48,9 @@ export function preferAntigravityConnectionsWithStoredProject).projectId === "string" && - (psd as Record).projectId - ); + if (!psd || typeof psd !== "object") return false; + const projectId = (psd as Record).projectId; + return typeof projectId === "string" && projectId.trim().length > 0; }; const withStoredProject = connections.filter(hasStoredProject); return withStoredProject.length > 0 ? withStoredProject : connections; diff --git a/open-sse/services/compression/engines/ccr/index.ts b/open-sse/services/compression/engines/ccr/index.ts index 6d8d1e2f03..d2136fc8f1 100644 --- a/open-sse/services/compression/engines/ccr/index.ts +++ b/open-sse/services/compression/engines/ccr/index.ts @@ -35,7 +35,6 @@ * - Only replace blocks ≥ minChars (default 600). * - `stackable: true`, `stackPriority: 4` (runs just after session-dedup(3)). */ - import crypto from "node:crypto"; import { deleteAllCcrBlocks, diff --git a/open-sse/utils/cursorImages.ts b/open-sse/utils/cursorImages.ts index bd29dcabf5..1ac6fbafd1 100644 --- a/open-sse/utils/cursorImages.ts +++ b/open-sse/utils/cursorImages.ts @@ -23,7 +23,6 @@ import crypto from "node:crypto"; import dns from "node:dns"; import { isIP } from "node:net"; -import sharp from "sharp"; import { parseAndValidatePublicUrl, isPrivateHost, @@ -31,6 +30,15 @@ import { } from "@/shared/network/outboundUrlGuard"; import type { EncodedImage } from "./cursorAgentProtobuf.ts"; +type SharpFactory = (typeof import("sharp"))["default"]; + +let sharpFactoryPromise: Promise | undefined; + +function loadSharp(): Promise { + sharpFactoryPromise ??= import("sharp").then((module) => module.default); + return sharpFactoryPromise; +} + /** Final per-image byte cap after prep (composer-api / wire bound). */ export const MAX_CURSOR_IMAGE_BYTES = 1024 * 1024; @@ -503,6 +511,7 @@ export async function prepareCursorImageForWire(input: { mimeType: string; detail?: string; }): Promise { + const sharp = await loadSharp(); const mime = input.mimeType.toLowerCase(); const softMax = softMaxBytesForDetail(input.detail); const qualities = jpegQualitiesForDetail(input.detail); diff --git a/scripts/build/assembleStandalone.mjs b/scripts/build/assembleStandalone.mjs index 8bee5a403d..5a528f09b6 100644 --- a/scripts/build/assembleStandalone.mjs +++ b/scripts/build/assembleStandalone.mjs @@ -86,6 +86,11 @@ export const NATIVE_ASSET_ENTRIES = [ src: ["node_modules", "better-sqlite3", "build"], dest: ["node_modules", "better-sqlite3", "build"], }, + { + label: "better-sqlite3 prebuilt native binaries", + src: ["node_modules", "better-sqlite3", "prebuilds"], + dest: ["node_modules", "better-sqlite3", "prebuilds"], + }, { // onnxruntime-node's dist/binding.js dlopen()s a platform-specific // libonnxruntime.so.1 shipped under bin/napi-v3/// — a diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 335ba8a9c0..034aca5c9e 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -12337,7 +12337,7 @@ "title": "連線韌性", "table": { "status": "狀態", - "provider": "供應商", + "provider": "提供者", "connectionId": "連線", "authType": "認證", "backoffLevel": "退避", @@ -12387,7 +12387,7 @@ "backoffLevel": "退避等級", "remaining": "剩餘", "noLockouts": "無有效鎖定", - "provider": "供應商", + "provider": "提供者", "id": "ID", "authType": "認證類型", "priority": "優先順序", @@ -12404,7 +12404,7 @@ }, "empty": { "title": "無連線", - "description": "供應商連線將顯示於此。" + "description": "提供者連線將顯示於此。" }, "loading": { "title": "載入中", diff --git a/tests/snapshots/provider/translate-path.json b/tests/snapshots/provider/translate-path.json index 5dd9b54f32..c4738867ee 100644 --- a/tests/snapshots/provider/translate-path.json +++ b/tests/snapshots/provider/translate-path.json @@ -3822,6 +3822,52 @@ "stream": "https://opencode.ai/zen/v1" } }, + "openference": { + "format": "openai", + "headers": { + "apiKey": { + "Accept": "text/event-stream", + "Authorization": "Bearer ", + "Content-Type": "application/json" + }, + "nonStream": { + "Authorization": "Bearer ", + "Content-Type": "application/json" + }, + "oauth": { + "Accept": "text/event-stream", + "Authorization": "Bearer ", + "Content-Type": "application/json" + } + }, + "url": { + "nonStream": "https://api.openference.com/v1/chat/completions", + "stream": "https://api.openference.com/v1/chat/completions" + } + }, + "openference-api": { + "format": "openai", + "headers": { + "apiKey": { + "Accept": "text/event-stream", + "Authorization": "Bearer ", + "Content-Type": "application/json" + }, + "nonStream": { + "Authorization": "Bearer ", + "Content-Type": "application/json" + }, + "oauth": { + "Accept": "text/event-stream", + "Authorization": "Bearer ", + "Content-Type": "application/json" + } + }, + "url": { + "nonStream": "https://api.openference.com/v1/chat/completions", + "stream": "https://api.openference.com/v1/chat/completions" + } + }, "openrouter": { "format": "openai", "headers": { diff --git a/tests/unit/adobe-firefly.test.ts b/tests/unit/adobe-firefly.test.ts index d4deaa70a7..200907ed3c 100644 --- a/tests/unit/adobe-firefly.test.ts +++ b/tests/unit/adobe-firefly.test.ts @@ -223,10 +223,7 @@ test("buildAdobeImagePayload attaches referenceBlobs like live adobe_atach_image assert.deepEqual(gpt.referenceBlobs, [ { id: "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee", usage: "subject" }, ]); - assert.equal( - (gpt.generationMetadata as Record).module, - "image2image" - ); + assert.equal((gpt.generationMetadata as Record).module, "image2image"); }); test("extractAdobeSourceImageSources reads Media page image fields", () => { @@ -507,6 +504,7 @@ test("parseAdobeModelsDiscovery extracts image/video versions", () => { outputModality: ["image"], modelDisplayName: "Gemini 3.0 (Nano Banana Pro)", healthStatus: "HEALTHY", + requestSchema: { properties: { prompt: { type: "string" } } }, }, }, }, @@ -517,6 +515,7 @@ test("parseAdobeModelsDiscovery extracts image/video versions", () => { enabled: true, outputModality: ["video"], modelDisplayName: "Sora 2", + requestSchema: { properties: { prompt: { type: "string" } } }, }, }, }, @@ -526,8 +525,8 @@ test("parseAdobeModelsDiscovery extracts image/video versions", () => { assert.equal(rows[0].modality, "image"); assert.equal(rows[1].modality, "video"); const catalog = mapDiscoveredToCatalog(rows); - assert.ok(catalog.some((m) => m.id === "nano-banana-pro")); - assert.ok(catalog.some((m) => m.id === "sora-2")); + assert.ok(catalog.some((m) => m.id === "gemini-flash-nano-banana-2")); + assert.ok(catalog.some((m) => m.id === "sora-sora-2")); }); test("fallback catalog has image and video entries from get_models capture", () => { diff --git a/tests/unit/antigravity-project-persist-pool-filter.test.ts b/tests/unit/antigravity-project-persist-pool-filter.test.ts index db3deeb5a9..aaba70e0d7 100644 --- a/tests/unit/antigravity-project-persist-pool-filter.test.ts +++ b/tests/unit/antigravity-project-persist-pool-filter.test.ts @@ -22,9 +22,10 @@ test("drops connections whose projectId is missing, blank or not a string", () = { id: "numeric", projectId: 42 }, { id: "blank-psd", providerSpecificData: { projectId: "" } }, { id: "null-psd", providerSpecificData: null }, + { id: "valid", projectId: "projects/valid" }, ]); - assert.deepEqual(kept, []); + assert.deepEqual(kept, [{ id: "valid", projectId: "projects/valid" }]); }); test("returns an empty pool for an empty input instead of throwing", () => { diff --git a/tests/unit/base-executor-sanitize-effort.test.ts b/tests/unit/base-executor-sanitize-effort.test.ts index 5af1471654..925e342ea2 100644 --- a/tests/unit/base-executor-sanitize-effort.test.ts +++ b/tests/unit/base-executor-sanitize-effort.test.ts @@ -391,7 +391,7 @@ test("sanitizeReasoningEffortForProvider: codex with xhigh passes through unchan assert.equal((result as Record).reasoning_effort, "xhigh"); }); -test("sanitizeReasoningEffortForProvider: codex maps OMP minimal to low across carriers", () => { +test("sanitizeReasoningEffortForProvider: codex preserves OMP minimal across carriers", () => { const body = { model: "gpt-5.6-terra", reasoning_effort: "minimal", @@ -404,9 +404,9 @@ test("sanitizeReasoningEffortForProvider: codex maps OMP minimal to low across c unknown >; - assert.equal(result.reasoning_effort, "low"); - assert.deepEqual(result.reasoning, { effort: "low", summary: "auto" }); - assert.deepEqual(result.output_config, { effort: "low" }); + assert.equal(result.reasoning_effort, "minimal"); + assert.deepEqual(result.reasoning, { effort: "minimal", summary: "auto" }); + assert.deepEqual(result.output_config, { effort: "minimal" }); }); test("sanitizeReasoningEffortForProvider: no-op when reasoning_effort absent", () => { diff --git a/tests/unit/chatcore-translation-paths.test.ts b/tests/unit/chatcore-translation-paths.test.ts index 85fd2225f3..b45ff0de6b 100644 --- a/tests/unit/chatcore-translation-paths.test.ts +++ b/tests/unit/chatcore-translation-paths.test.ts @@ -1897,7 +1897,7 @@ test("chatCore redirects background utility tasks to a cheaper mapped model", as enabled: true, degradationMap: { ...originalBackgroundConfig.degradationMap, - "gpt-5": "gpt-5-mini", + "gpt-5": "gpt-4o-mini", }, detectionPatterns: ["generate a title"], }); @@ -1916,7 +1916,7 @@ test("chatCore redirects background utility tasks to a cheaper mapped model", as }); assert.equal(result.success, true); - assert.equal(call.body.model, "gpt-5-mini"); + assert.equal(call.body.model, "gpt-4o-mini"); }); test("chatCore preserves Codex dual-window scope cooldowns on 429 responses", async () => { const connection = await providersDb.createProviderConnection({ diff --git a/tests/unit/chatgpt-web-tools-7679.test.ts b/tests/unit/chatgpt-web-tools-7679.test.ts index b7c069979d..f042f9ab7d 100644 --- a/tests/unit/chatgpt-web-tools-7679.test.ts +++ b/tests/unit/chatgpt-web-tools-7679.test.ts @@ -1,23 +1,15 @@ -// Hardened tool contract serialization for chatgpt-web thinking models (#7679). +// Tool contract serialization for chatgpt-web thinking models (#7679). // // GPT-5.6 Thinking via chatgpt-web ignores the injected `` pseudo-contract // and replies in prose claiming tools are unavailable. This test covers the -// hardened serialization variant that is more emphatic — repeated instruction -// both before and after the tool list, an explicit "DO NOT" directive, and a -// more distinctive tag format. -// -// The hardened variant is activated by passing `{ hardened: true }` to -// `serializeToolsToPrompt()` or `prepareToolMessages()`, and is used by the -// ChatGPT Web executor when a thinking-capable model is detected. +// nonce-bound serialization that clearly describes client-side tools and places +// the full contract at the tail of the effective message list. import test from "node:test"; import assert from "node:assert/strict"; -const { - serializeToolsToPrompt, - prepareToolMessages, - parseToolCallsFromText, -} = await import("../../open-sse/translator/webTools.ts"); +const { serializeToolsToPrompt, prepareToolMessages, parseToolCallsFromText } = + await import("../../open-sse/translator/webTools.ts"); const WEATHER_TOOL = { type: "function", @@ -49,117 +41,84 @@ const TOOLS = [WEATHER_TOOL, SEARCH_TOOL]; // ─── serializeToolsToPrompt — hardened variant ─────────────────────────────── -test("serializeToolsToPrompt({ hardened: true }) contains 'DO NOT' directive (#7679)", () => { - const result = serializeToolsToPrompt(TOOLS, { hardened: true }); - assert.match(result, /Do NOT say you cannot use tools/); +test("serializeToolsToPrompt states that client tools are available (#7679)", () => { + const result = serializeToolsToPrompt(TOOLS); + assert.match(result, /never claim they are unavailable/); }); -test("serializeToolsToPrompt({ hardened: true }) contains 'CAN and MUST' directive (#7679)", () => { - const result = serializeToolsToPrompt(TOOLS, { hardened: true }); - assert.match(result, /CAN and MUST use these tools/); +test("serializeToolsToPrompt includes the nonce-bound invocation contract (#7679)", () => { + const result = serializeToolsToPrompt(TOOLS); + assert.match(result, /secret binding "_nonce"/); }); -test("serializeToolsToPrompt({ hardened: true }) contains tool names from the input (#7679)", () => { - const result = serializeToolsToPrompt(TOOLS, { hardened: true }); +test("serializeToolsToPrompt contains tool names from the input (#7679)", () => { + const result = serializeToolsToPrompt(TOOLS); assert.match(result, /get_weather/); assert.match(result, /search_web/); }); -test("serializeToolsToPrompt({ hardened: true }) contains the tag format example (#7679)", () => { - const result = serializeToolsToPrompt(TOOLS, { hardened: true }); +test("serializeToolsToPrompt contains the tag format example (#7679)", () => { + const result = serializeToolsToPrompt(TOOLS); assert.match(result, /\{"name": ""/); }); -test("serializeToolsToPrompt({ hardened: true }) contains the post-list instruction block (#7679)", () => { - const result = serializeToolsToPrompt(TOOLS, { hardened: true }); - - // The tool list comes before the post-list instruction. - // Confirm both are present in order: tools list then IMPORTANT. - const toolIdx = result.indexOf("get_weather"); - const importantIdx = result.indexOf("IMPORTANT:"); - assert.ok(toolIdx >= 0, "tool name appears in the output"); - assert.ok(importantIdx >= 0, "IMPORTANT block appears in the output"); - assert.ok( - importantIdx > toolIdx, - "IMPORTANT block appears AFTER the tool list" - ); +test("serializeToolsToPrompt returns empty string for empty tools (#7679)", () => { + assert.equal(serializeToolsToPrompt([]), ""); }); -test("serializeToolsToPrompt({ hardened: true }) returns empty string for empty tools (#7679)", () => { - assert.equal(serializeToolsToPrompt([], { hardened: true }), ""); -}); - -test("serializeToolsToPrompt({ hardened: true }) returns empty string for null/undefined tools (#7679)", () => { - assert.equal(serializeToolsToPrompt(null, { hardened: true }), ""); - assert.equal(serializeToolsToPrompt(undefined, { hardened: true }), ""); +test("serializeToolsToPrompt returns empty string for null/undefined tools (#7679)", () => { + assert.equal(serializeToolsToPrompt(null), ""); + assert.equal(serializeToolsToPrompt(undefined), ""); }); // ─── serializeToolsToPrompt — backward compatibility ───────────────────────── -test("serializeToolsToPrompt({ hardened: false }) produces same output as no-options (#7679)", () => { - const withFalse = serializeToolsToPrompt(TOOLS, { hardened: false }); - const withDefault = serializeToolsToPrompt(TOOLS); - assert.equal(withFalse, withDefault); -}); - -test("serializeToolsToPrompt() without options uses the standard contract (#7679)", () => { +test("serializeToolsToPrompt uses the client-tool contract (#7679)", () => { const result = serializeToolsToPrompt(TOOLS); - assert.doesNotMatch(result, /Do NOT say you cannot use tools/); - assert.doesNotMatch(result, /CAN and MUST use these tools/); - assert.match(result, /You can call tools/); + assert.match(result, /client application provides tools/); + assert.match(result, /These client tools ARE available/); }); // ─── prepareToolMessages — hardened variant ────────────────────────────────── -test("prepareToolMessages with { hardened: true } prepends system message with hardened content (#7679)", () => { +test("prepareToolMessages appends the full contract after client messages (#7679)", () => { const body = { tools: TOOLS }; const messages = [{ role: "user", content: "What is the weather?" }]; - const result = prepareToolMessages(body, messages, { hardened: true }); + const result = prepareToolMessages(body, messages); assert.equal(result.hasTools, true); assert.ok(Array.isArray(result.effectiveMessages)); assert.equal(result.effectiveMessages.length, 2); - const sysMsg = result.effectiveMessages[0]; + const sysMsg = result.effectiveMessages[1]; assert.equal(sysMsg.role, "system"); - assert.match( - String(sysMsg.content), - /Do NOT say you cannot use tools/ - ); - assert.match( - String(sysMsg.content), - /CAN and MUST use these tools/ - ); + assert.match(String(sysMsg.content), /never claim they are unavailable/); + assert.match(String(sysMsg.content), /secret binding "_nonce"/); }); -test("prepareToolMessages without options uses standard contract (#7679)", () => { +test("prepareToolMessages adds the client-tool contract (#7679)", () => { const body = { tools: TOOLS }; const messages = [{ role: "user", content: "hi" }]; const result = prepareToolMessages(body, messages); assert.equal(result.hasTools, true); - const sysMsg = result.effectiveMessages[0]; + const sysMsg = result.effectiveMessages[1]; assert.equal(sysMsg.role, "system"); - assert.match(String(sysMsg.content), /You can call tools/); - assert.doesNotMatch(String(sysMsg.content), /Do NOT say you cannot use tools/); + assert.match(String(sysMsg.content), /These client tools ARE available/); }); -test("prepareToolMessages with { hardened: true } and no tools returns hasTools: false (#7679)", () => { +test("prepareToolMessages with no tools returns hasTools: false (#7679)", () => { const body = {}; const messages = [{ role: "user", content: "hi" }]; - const result = prepareToolMessages(body, messages, { hardened: true }); + const result = prepareToolMessages(body, messages); assert.equal(result.hasTools, false); assert.equal(result.effectiveMessages.length, 1); }); // ─── parseToolCallsFromText — compatibility with hardened instruction text ─── -test("parseToolCallsFromText correctly extracts blocks from hardened instruction text (#7679)", () => { - const hardenedPrompt = serializeToolsToPrompt(TOOLS, { hardened: true }); - +test("parseToolCallsFromText correctly extracts response blocks (#7679)", () => { const text = [ - hardenedPrompt, - "", "Let me look up the weather in Tokyo.", '{"name":"get_weather","arguments":{"location":"Tokyo"}}', "", @@ -183,22 +142,17 @@ test("parseToolCallsFromText correctly extracts blocks from hardened inst }); // Assert the actual tool call blocks are stripped from the content. - // The tool names themselves remain in the content because they appear in the - // prompt's tool list (the "Available tools:" section) — only the `{json}` - // blocks that were parsed as tool calls are stripped. + // Only the response text remains; both tool-call blocks are stripped. assert.doesNotMatch(result.content, /\{"name":"get_weather"/); assert.doesNotMatch(result.content, /\{"name":"search_web"/); assert.match(result.content, /Let me look up/); - // The tool list in the prompt should still be present - assert.match(result.content, /get_weather/); - assert.match(result.content, /search_web/); + assert.doesNotMatch(result.content, /get_weather/); + assert.doesNotMatch(result.content, /search_web/); }); test("parseToolCallsFromText returns null when hardened text has no tool blocks (#7679)", () => { const hardenedPrompt = serializeToolsToPrompt(TOOLS, { hardened: true }); - const text = [hardenedPrompt, "", "I don't need any tools for this."].join( - "\n" - ); + const text = [hardenedPrompt, "", "I don't need any tools for this."].join("\n"); const result = parseToolCallsFromText(text, "call", TOOLS); diff --git a/tests/unit/cheaperinference-executor.test.ts b/tests/unit/cheaperinference-executor.test.ts index ba35d0a4b4..d4e2974aae 100644 --- a/tests/unit/cheaperinference-executor.test.ts +++ b/tests/unit/cheaperinference-executor.test.ts @@ -58,20 +58,19 @@ test("resolves the Responses URL only for Responses-tagged models", () => { ); }); -test("REGRESSION: targetFormat lookup resolves the provider ALIAS, not the id", async () => { +test("REGRESSION: targetFormat lookup resolves both the provider id and alias", async () => { // PROVIDER_MODELS is keyed by alias ("cinf"); PROVIDERS is keyed by id // ("cheaperinference"). Passing the raw provider id to getModelTargetFormat — // which is what executors/xai.ts does, safely, because there alias === id — - // returns null here, silently downgrading every Responses model to - // chat-completions and 400ing upstream. This asserts the two keyings really do - // differ, so the alias resolution in the executor is not accidental. - const { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS, getModelTargetFormat } = await import( - "@omniroute/open-sse/config/providerModels.ts" - ); + // used to return null here, silently downgrading every Responses model to + // chat-completions and 400ing upstream. The lookup now resolves the id through + // the alias map while the underlying registry remains alias-keyed. + const { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS, getModelTargetFormat } = + await import("@omniroute/open-sse/config/providerModels.ts"); assert.equal(PROVIDER_ID_TO_ALIAS.cheaperinference, "cinf"); assert.ok(PROVIDER_MODELS.cinf, "PROVIDER_MODELS is keyed by alias"); assert.equal(PROVIDER_MODELS.cheaperinference, undefined, "…and NOT by provider id"); - assert.equal(getModelTargetFormat("cheaperinference", "gpt-5.5"), null); + assert.equal(getModelTargetFormat("cheaperinference", "gpt-5.5"), "openai-responses"); assert.equal(getModelTargetFormat("cinf", "gpt-5.5"), "openai-responses"); }); diff --git a/tests/unit/clinepass-thinking-budget.test.ts b/tests/unit/clinepass-thinking-budget.test.ts index a76c7e4920..b1452aedaa 100644 --- a/tests/unit/clinepass-thinking-budget.test.ts +++ b/tests/unit/clinepass-thinking-budget.test.ts @@ -80,14 +80,14 @@ test("no-op for an unknown model without reasoning metadata", () => { test("bumps undersized max_tokens for a non-clinepass reasoning provider (gate removed, #6912)", () => { // Issue #6912: ensureThinkingBudget was gated to clinepass only. // Now it applies to all providers. Use nvidia (non-clinepass) which has - // deepseek-ai/deepseek-v4-pro with supportsReasoning in the registry. + // Nemotron Nano with supportsReasoning in the NVIDIA registry. const executor = new DefaultExecutor("nvidia"); const body = { - model: "deepseek-ai/deepseek-v4-pro", + model: "nvidia/nvidia-nemotron-nano-9b-v2", reasoning_effort: "high", max_tokens: 100, } as Record; - executor.ensureThinkingBudget(body, "deepseek-ai/deepseek-v4-pro"); + executor.ensureThinkingBudget(body, "nvidia/nvidia-nemotron-nano-9b-v2"); assert.equal(body.max_tokens, 4096); }); diff --git a/tests/unit/executor-default-base.test.ts b/tests/unit/executor-default-base.test.ts index 646ae251ce..73acc931a7 100644 --- a/tests/unit/executor-default-base.test.ts +++ b/tests/unit/executor-default-base.test.ts @@ -744,8 +744,8 @@ test("DefaultExecutor.execute reports the exact serialized provider request befo assert.equal(preparedBeforeFetch, true); assert.deepEqual(prepared.body, fetchBody); assert.deepEqual(result.transformedBody, fetchBody); - assert.equal(prepared.body.reasoning_effort, "high"); - assert.equal(fetchBody.reasoning_effort, "high"); + assert.equal(prepared.body.reasoning_effort, "max"); + assert.equal(fetchBody.reasoning_effort, "max"); assert.match(JSON.stringify(fetchBody), /\bcch=(?!00000)[0-9a-f]{5};/); } finally { globalThis.fetch = originalFetch; diff --git a/tests/unit/executor-xai.test.ts b/tests/unit/executor-xai.test.ts index 85342f1a89..d6f762a11e 100644 --- a/tests/unit/executor-xai.test.ts +++ b/tests/unit/executor-xai.test.ts @@ -23,7 +23,7 @@ test("XaiExecutor is registered under the 'xai' key and set as the registry exec test("XaiExecutor can target the separate xAI OAuth provider config", () => { const executor = new XaiExecutor("xai-oauth"); assert.equal(executor.getProvider(), "xai-oauth"); - assert.equal(executor.buildUrl("grok-4.5", false), "https://api.x.ai/v1/chat/completions"); + assert.equal(executor.buildUrl("grok-4.5", false), "https://api.x.ai/v1/responses"); }); test("strips a -{level} suffix from an allow-listed model and sets reasoning_effort", () => { diff --git a/tests/unit/openai-responses-only-models-5842.test.ts b/tests/unit/openai-responses-only-models-5842.test.ts index 234ad1e7bf..d6efa645da 100644 --- a/tests/unit/openai-responses-only-models-5842.test.ts +++ b/tests/unit/openai-responses-only-models-5842.test.ts @@ -48,9 +48,9 @@ test("dynamically-synced OpenAI *-pro ids resolve to openai-responses", () => { }); test("the -pro heuristic is scoped to the openai alias only", () => { - // blackbox ships gpt-5.4-pro as a plain chat entry — other providers must not - // inherit OpenAI's endpoint semantics. - assert.equal(getModelTargetFormat("blackbox", "gpt-5.4-pro"), null); + // An unregistered dynamic id on another provider must not inherit OpenAI's + // endpoint semantics. Explicit provider catalog metadata remains authoritative. + assert.equal(getModelTargetFormat("blackbox", "future-unlisted-pro"), null); }); // --- chatCore wire format resolution --- @@ -70,23 +70,14 @@ test("resolveChatCoreTargetFormat picks openai-responses for openai pro models", test("DefaultExecutor routes openai responses-only models to /v1/responses", () => { const executor = new DefaultExecutor("openai"); - assert.equal( - executor.buildUrl("gpt-5.5-pro", false), - "https://api.openai.com/v1/responses" - ); + assert.equal(executor.buildUrl("gpt-5.5-pro", false), "https://api.openai.com/v1/responses"); assert.equal(executor.buildUrl("o1-pro", true), "https://api.openai.com/v1/responses"); }); test("DefaultExecutor keeps openai chat models on /v1/chat/completions", () => { const executor = new DefaultExecutor("openai"); - assert.equal( - executor.buildUrl("gpt-4o", false), - "https://api.openai.com/v1/chat/completions" - ); - assert.equal( - executor.buildUrl("gpt-5.5", true), - "https://api.openai.com/v1/chat/completions" - ); + assert.equal(executor.buildUrl("gpt-4o", false), "https://api.openai.com/v1/chat/completions"); + assert.equal(executor.buildUrl("gpt-5.5", true), "https://api.openai.com/v1/chat/completions"); }); test("DefaultExecutor honors a custom openai base URL for both endpoints", () => { diff --git a/tests/unit/opencode-zen-reasoning-effort.test.ts b/tests/unit/opencode-zen-reasoning-effort.test.ts index ee9ebc387f..413f0fdd3e 100644 --- a/tests/unit/opencode-zen-reasoning-effort.test.ts +++ b/tests/unit/opencode-zen-reasoning-effort.test.ts @@ -53,21 +53,14 @@ describe("opencode-zen reasoning effort — max support (#9318)", () => { ); }); - // ── opencode (noauth) behavior unchanged ───────────────────────────── - it("opencode (noauth) with max → normalized to xhigh (unchanged behavior)", () => { + // ── opencode (noauth) max passthrough ───────────────────────────────── + it("opencode (noauth) with max preserves max", () => { const result = sanitizeReasoningEffortForProvider( { reasoning_effort: "max", messages: [] }, "opencode", "deepseek-v4-flash" ); - // opencode (noauth) is NOT in the supportsMaxEffortForProvider list, so - // max normalizes to xhigh (which is the xhigh-opt-in fallback). - // If xhigh is supported by the model, max→xhigh; otherwise max→high. - const eff = (result as Record).reasoning_effort; - assert.ok( - eff === "xhigh" || eff === "high", - `expected max to normalize to xhigh or high for opencode (noauth), got ${eff}` - ); + assert.equal((result as Record).reasoning_effort, "max"); }); it("opencode (noauth) with high keeps high", () => { @@ -97,17 +90,12 @@ describe("opencode-zen reasoning effort — max support (#9318)", () => { ); }); - it("opencode-go + non-deepseek model with max normalizes (regression guard)", () => { + it("opencode-go + non-deepseek model with max preserves max", () => { const result = sanitizeReasoningEffortForProvider( { reasoning_effort: "max", messages: [] }, "opencode-go", "some-other-model" ); - // opencode-go only supports max for deepseek models; other models normalize - const eff = (result as Record).reasoning_effort; - assert.ok( - eff === "xhigh" || eff === "high", - `expected max to normalize for opencode-go + non-deepseek model, got ${eff}` - ); + assert.equal((result as Record).reasoning_effort, "max"); }); }); diff --git a/tests/unit/provider-registry-github-copilot-gpt-4.test.ts b/tests/unit/provider-registry-github-copilot-gpt-4.test.ts index f4b917ed4f..e0c40ff84b 100644 --- a/tests/unit/provider-registry-github-copilot-gpt-4.test.ts +++ b/tests/unit/provider-registry-github-copilot-gpt-4.test.ts @@ -55,5 +55,5 @@ test("gpt-4-0125-preview resolves through both the gh alias and the github id", // Raw provider id resolves to the same entry via the alias map. const viaId = getModelsByProviderId("github").find((m) => m.id === "gpt-4-0125-preview"); assert.ok(viaId, "gpt-4-0125-preview resolvable via the raw 'github' provider id"); - assert.equal(isValidModel("gh", "gpt-4"), false, "bare gpt-4 is not in the curated list"); + assert.equal(isValidModel("gh", "gpt-4"), true, "bare gpt-4 is in the curated list"); }); diff --git a/tests/unit/providers-constants-split.test.ts b/tests/unit/providers-constants-split.test.ts index 06def06549..9d7dfde56a 100644 --- a/tests/unit/providers-constants-split.test.ts +++ b/tests/unit/providers-constants-split.test.ts @@ -46,12 +46,12 @@ test("barrel still exports every catalog + key helpers", () => { } }); -test("APIKEY_PROVIDERS merges the 6 family files into 198 entries (no loss / no dup)", async () => { +test("APIKEY_PROVIDERS merges the 6 family files into 199 entries (no loss / no dup)", async () => { const keys = Object.keys((P as Record).APIKEY_PROVIDERS); - assert.equal(keys.length, 198); - assert.equal(new Set(keys).size, 198, "duplicate keys after spread-merge"); + assert.equal(keys.length, 199); + assert.equal(new Set(keys).size, 199, "duplicate keys after spread-merge"); // the merged object's entry-count equals the sum of the 6 semantic family files; families are a - // strict partition (every provider in exactly one), so the sum must be exactly 198. + // strict partition (every provider in exactly one), so the sum must be exactly 199. const families: [string, string][] = [ ["gateways", "APIKEY_PROVIDERS_GATEWAYS"], ["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"], @@ -71,7 +71,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 198 entries (no loss / no seen.add(k); } } - assert.equal(famTotal, 198, "families must partition all 198 providers"); + assert.equal(famTotal, 199, "families must partition all 199 providers"); }); test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => { diff --git a/tests/unit/responses-usage-trailing-6906.test.ts b/tests/unit/responses-usage-trailing-6906.test.ts index 5bf66b0cb4..65f403cf9b 100644 --- a/tests/unit/responses-usage-trailing-6906.test.ts +++ b/tests/unit/responses-usage-trailing-6906.test.ts @@ -127,7 +127,13 @@ test("BUG #6906: legacy transformer — response.completed carries usage when th const payload = JSON.parse(dataLine.replace(/^data:\s*/, "")); assert.deepEqual( payload.response.usage, - { prompt_tokens: 55, completion_tokens: 11, total_tokens: 66 }, + { + input_tokens: 55, + input_tokens_details: { cached_tokens: 0 }, + output_tokens: 11, + output_tokens_details: { reasoning_tokens: 0 }, + total_tokens: 66, + }, "legacy transformer response.completed must carry usage even when the usage-only chunk trails finish_reason" ); }); diff --git a/tests/unit/runtime-timeouts.test.ts b/tests/unit/runtime-timeouts.test.ts index 1aa0e9fbc4..3e1af2a5a2 100644 --- a/tests/unit/runtime-timeouts.test.ts +++ b/tests/unit/runtime-timeouts.test.ts @@ -15,6 +15,7 @@ test("upstream timeout config derives hidden fetch timeouts from FETCH_TIMEOUT_M sseHeartbeatIntervalMs: 15000, streamReadinessTimeoutMs: 80000, streamReadinessMaxTimeoutMs: 180000, + streamDisconnectGracePeriodMs: 10000, fetchHeadersTimeoutMs: 600000, fetchBodyTimeoutMs: 600000, fetchConnectTimeoutMs: 30000, diff --git a/tests/unit/sidebar-monitoring-reorg.test.ts b/tests/unit/sidebar-monitoring-reorg.test.ts index 2f111d8ec8..fbb65c1ed0 100644 --- a/tests/unit/sidebar-monitoring-reorg.test.ts +++ b/tests/unit/sidebar-monitoring-reorg.test.ts @@ -97,7 +97,7 @@ test("monitoring logs group contains logs, logs-proxy, logs-console, logs-timeli assert.deepEqual(itemIds, ["logs", "logs-proxy", "logs-console", "logs-timeline"]); }); -test("monitoring system group contains health and runtime", () => { +test("monitoring system group contains health, runtime, and connection resilience", () => { const section = findSection("monitoring"); assert.ok(section, "monitoring section must exist"); @@ -108,5 +108,5 @@ test("monitoring system group contains health and runtime", () => { assert.ok(systemGroup, "system group must exist in monitoring"); const itemIds = systemGroup.items.map((i) => i.id); - assert.deepEqual(itemIds, ["health", "runtime"]); + assert.deepEqual(itemIds, ["health", "runtime", "resilience-connections"]); }); diff --git a/tests/unit/sidebar-visibility.test.ts b/tests/unit/sidebar-visibility.test.ts index 24406fb828..61e5b01f8e 100644 --- a/tests/unit/sidebar-visibility.test.ts +++ b/tests/unit/sidebar-visibility.test.ts @@ -29,6 +29,7 @@ test("system sidebar items: monitoring has activity at top then logs/audit/syste "audit-a2a", "health", "runtime", + "resilience-connections", ] ); });