diff --git a/open-sse/executors/antigravity.js b/open-sse/executors/antigravity.js index 52162586..e2158516 100644 --- a/open-sse/executors/antigravity.js +++ b/open-sse/executors/antigravity.js @@ -3,7 +3,7 @@ import { BaseExecutor } from "./base.js"; import { PROVIDERS } from "../config/providers.js"; import { OAUTH_ENDPOINTS, ANTIGRAVITY_HEADERS, INTERNAL_REQUEST_HEADER, AG_DEFAULT_TOOLS, AG_TOOL_SUFFIX } from "../config/appConstants.js"; import { HTTP_STATUS } from "../config/runtimeConfig.js"; -import { deriveSessionId } from "../utils/sessionManager.js"; +import { resolveSessionId } from "../utils/sessionManager.js"; import { proxyAwareFetch } from "../utils/proxyFetch.js"; import { cleanJSONSchemaForAntigravity } from "../translator/formats/gemini.js"; @@ -94,7 +94,7 @@ export class AntigravityExecutor extends BaseExecutor { generationConfig, ...(contents && { contents }), ...(tools && { tools }), - sessionId: body.request?.sessionId || deriveSessionId(credentials?.email || credentials?.connectionId), + sessionId: body.request?.sessionId || resolveSessionId({ headers: credentials?.rawHeaders, body, connectionId: credentials?.email || credentials?.connectionId, scope: "antigravity" }), safetySettings: undefined, ...(tools?.length > 0 && { toolConfig: { functionCallingConfig: { mode: "VALIDATED" } } }) }; diff --git a/open-sse/executors/codex.js b/open-sse/executors/codex.js index c5b9d09d..bfff245d 100644 --- a/open-sse/executors/codex.js +++ b/open-sse/executors/codex.js @@ -1,4 +1,3 @@ -import { createHash } from "crypto"; import { BaseExecutor } from "./base.js"; import { CODEX_DEFAULT_INSTRUCTIONS } from "../config/codexInstructions.js"; import { PROVIDERS } from "../config/providers.js"; @@ -9,18 +8,14 @@ import { import { normalizeResponsesInput } from "../translator/formats/responsesApi.js"; import { fetchImageAsBase64 } from "../translator/concerns/image.js"; import { getModelUpstreamId } from "../config/providerModels.js"; -import { getConsistentMachineId } from "../shared/machineId.js"; import { DEFAULT_RETRY_CONFIG, resolveRetryEntry } from "../config/runtimeConfig.js"; import { dbg } from "../utils/debugLog.js"; +import { resolveSessionId } from "../utils/sessionManager.js"; // SSE error patterns inside 200-OK body that should trigger retry as if 503 const CODEX_SSE_OVERLOADED_PATTERNS = ["server_is_overloaded", "service_unavailable_error"]; const CODEX_SSE_PEEK_BYTES = 4096; -// In-memory map: hash(machineId + first assistant content) → { sessionId, lastUsed } -const SESSION_TTL_MS = 60 * 60 * 1000; // 1 hour -const assistantSessionMap = new Map(); - // Server-generated item id prefixes that Codex /responses cannot resolve when store=false const SERVER_ID_PATTERN = /^(rs|fc|resp|msg)_/; @@ -104,86 +99,17 @@ function normalizeCodexTools(body) { } } -// Cache machine ID at module level (resolved once) -let cachedMachineId = null; -getConsistentMachineId().then(id => { cachedMachineId = id; }); - -function hashContent(text) { - return createHash("sha256").update(text).digest("hex").slice(0, 16); +// Resolve prompt-cache session id: client session → assistant-text-hash → workspaceId → connection +function resolveCacheSessionId(body, credentials) { + return resolveSessionId({ + headers: credentials?.rawHeaders, + body, + connectionId: credentials?.connectionId, + workspaceId: credentials?.providerSpecificData?.workspaceId, + scope: "codex" + }); } -function generateSessionId() { - return `sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`; -} - -// Extract text content from an input item -function extractItemText(item) { - if (!item) return ""; - if (typeof item.content === "string") return item.content; - if (Array.isArray(item.content)) { - return item.content.map(c => c.text || c.output || "").filter(Boolean).join(""); - } - return ""; -} - -// Normalize a session id candidate (trim, length cap) -function normalizeSessionId(value) { - if (typeof value !== "string") return null; - const v = value.trim(); - if (!v || v.length > 256) return null; - return v; -} - -// Resolve prompt-cache session id with priority: body → assistant-text-hash → workspaceId → machineId -function resolveCacheSessionId(body, credentials, machineId) { - // 1. Client-provided session/conversation id (highest priority — stable per conversation) - const fromBody = - normalizeSessionId(body?.prompt_cache_key) || - normalizeSessionId(body?.session_id) || - normalizeSessionId(body?.conversation_id); - if (fromBody) return fromBody; - - // 2. Hash accumulated assistant text (≥50 chars) — sticky session across turns - if (Array.isArray(body?.input) && body.input.length > 0) { - let text = ""; - const MIN_LEN = 50; - const CAP_LEN = 200; - for (const item of body.input) { - if (item?.role !== "assistant") continue; - const t = extractItemText(item); - if (!t) continue; - text += t; - if (text.length >= CAP_LEN) break; - } - if (text.length >= MIN_LEN) { - const hash = hashContent((machineId || "") + text.slice(0, CAP_LEN)); - const entry = assistantSessionMap.get(hash); - if (entry) { - entry.lastUsed = Date.now(); - return entry.sessionId; - } - const sessionId = generateSessionId(); - assistantSessionMap.set(hash, { sessionId, lastUsed: Date.now() }); - return sessionId; - } - } - - // 3. Account-wide fallback (workspaceId from connection) - const workspaceId = normalizeSessionId(credentials?.providerSpecificData?.workspaceId); - if (workspaceId) return workspaceId; - - // 4. Last resort — stable per-machine id - return machineId ? `sess_${hashContent(machineId)}` : generateSessionId(); -} - -// Cleanup expired entries periodically -setInterval(() => { - const now = Date.now(); - for (const [key, entry] of assistantSessionMap) { - if (now - entry.lastUsed > SESSION_TTL_MS) assistantSessionMap.delete(key); - } -}, 10 * 60 * 1000); - /** * Codex Executor - handles OpenAI Codex API (Responses API format) * Automatically injects default instructions if missing @@ -377,7 +303,7 @@ export class CodexExecutor extends BaseExecutor { this._isCompact = !!body._compact; delete body._compact; // Resolve conversation-stable session_id (priority: body → assistant-text → workspace → machine) - this._currentSessionId = resolveCacheSessionId(body, credentials, cachedMachineId); + this._currentSessionId = resolveCacheSessionId(body, credentials); // Convert string input to array format (Codex API requires input as array) const normalized = normalizeResponsesInput(body.input); if (normalized) body.input = normalized; diff --git a/open-sse/handlers/chatCore.js b/open-sse/handlers/chatCore.js index 02f92ca7..465be25c 100644 --- a/open-sse/handlers/chatCore.js +++ b/open-sse/handlers/chatCore.js @@ -88,6 +88,9 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred const clientTool = detectClientTool(clientRawRequest?.headers || {}, body); const passthrough = isNativePassthrough(clientTool, provider); + // Expose raw client headers to translators/executors for session-id resolution + if (credentials) credentials.rawHeaders = clientRawRequest?.headers || {}; + let translatedBody; let toolNameMap; if (passthrough) { diff --git a/open-sse/providers/capabilities.js b/open-sse/providers/capabilities.js new file mode 100644 index 00000000..e1e390c8 --- /dev/null +++ b/open-sse/providers/capabilities.js @@ -0,0 +1,218 @@ +// Model capabilities — what each model can read/do beyond plain text. +// +// Fallback order (first match wins), result merged over DEFAULT_CAPABILITIES: +// 1. PROVIDER_CAPABILITIES[provider][model] — provider-specific override +// 2. MODEL_CAPABILITIES[model] — canonical exact id (handles exceptions) +// 3. PATTERN_CAPABILITIES — glob match, ordered specific -> generic +// 4. DEFAULT_CAPABILITIES — safe floor (always returned) +// +// ── HOW TO ADD / UPDATE A MODEL ────────────────────────────────────── +// Authoritative data source: https://models.dev/api.json (145 providers, 4000+ +// models, MIT). Each model exposes the exact fields we map below: +// modalities.input ["text","image","pdf","audio","video"] -> vision / pdf / audioInput / videoInput +// modalities.output ["text","image","audio"] -> imageOutput / audioOutput +// reasoning -> reasoning tool_call -> tools +// limit.context -> contextWindow limit.output -> maxOutput +// Look up the model id, then: +// • If a PATTERN below already covers it correctly -> nothing to do. +// • If it is an exception (pattern would mis-match) -> add an exact entry to +// MODEL_CAPABILITIES (only the fields that differ from DEFAULT). +// • If a whole new family -> add an ordered PATTERN (specific before generic). +// NOTE: models.dev has NO "search" flag (web search is a runtime tool, not a +// model spec); set `search` from vendor docs (Claude 4.x+, GPT-5.x/4o, Gemini +// 2.0+, Grok, Perplexity). Verify with: curl -s https://models.dev/api.json + +import { matchPattern } from "./pricing.js"; + +/** + * Safe floor — every resolved result is merged over this so consumers + * never need null-checks. Most modern LLMs meet these limits. + */ +export const DEFAULT_CAPABILITIES = { + // input modalities + vision: false, // read images + pdf: false, // read PDF / documents + audioInput: false, // read audio + videoInput: false, // read video + // output modalities + imageOutput: false, // generate images + audioOutput: false, // generate audio + // features + search: false, // built-in web search tool / grounding + tools: true, // function / tool calling + reasoning: false, // thinking / reasoning + // limits (tokens) + contextWindow: 200000, + maxOutput: 64000, +}; + +/** + * Canonical exact-id overrides — used for exceptions that patterns would + * otherwise mis-match. Only declare deltas vs DEFAULT. + */ +export const MODEL_CAPABILITIES = { + // Claude 4.6/4.7 have 1M context (override generic claude pattern at 200k) + "claude-opus-4.6": { vision: true, reasoning: true, search: true, contextWindow: 1000000, maxOutput: 128000 }, + "claude-opus-4.7": { vision: true, reasoning: true, search: true, contextWindow: 1000000, maxOutput: 128000 }, + "claude-opus-4-6": { vision: true, reasoning: true, search: true, contextWindow: 1000000, maxOutput: 128000 }, + "claude-sonnet-4.6": { vision: true, reasoning: true, search: true, contextWindow: 1000000, maxOutput: 64000 }, + "claude-sonnet-4-6": { vision: true, reasoning: true, search: true, contextWindow: 1000000, maxOutput: 64000 }, + + // Gemini image-gen / OpenAI image / xai image variants + "gpt-image-1": { imageOutput: true, tools: false }, + + // GLM vision variant (text GLM has no vision) + "glm-4.6v": { vision: true, reasoning: true, contextWindow: 128000 }, + + // Qwen plain coder/text (no vision) — registry "vision-model" / "coder-model" aliases + "vision-model": { vision: true, reasoning: true, contextWindow: 1000000 }, + "coder-model": { reasoning: true, contextWindow: 1000000 }, +}; + +/** + * Provider-specific capability overrides. Keyed by provider alias/id. + */ +export const PROVIDER_CAPABILITIES = {}; + +/** + * Pattern fallback — glob (* = wildcard), matched case-insensitively and + * anchored (^...$) so a pattern must match the full model id. ORDER MATTERS: + * vision/specific variants first, text-only/generic families last, to avoid + * a broad family pattern swallowing an exception (e.g. glm-4.6v vs glm-5). + */ +export const PATTERN_CAPABILITIES = [ + // ── Claude (4.x+ = vision + thinking + web search) ─────────────── + { pattern: "*claude*opus*", caps: { vision: true, reasoning: true, search: true } }, + { pattern: "*claude*sonnet*", caps: { vision: true, reasoning: true, search: true } }, + { pattern: "*claude*haiku*", caps: { vision: true, reasoning: true, search: true } }, + { pattern: "*claude*fable*", caps: { vision: true, reasoning: true, search: true, contextWindow: 1000000, maxOutput: 128000 } }, + { pattern: "*claude*mythos*", caps: { vision: true, reasoning: true, search: true, contextWindow: 1000000, maxOutput: 128000 } }, + { pattern: "*claude-3*", caps: { vision: true } }, + { pattern: "*claude*", caps: { vision: true, reasoning: true, search: true } }, + + // ── Gemini (all 2.0+ multimodal + google_search grounding, 1M ctx) ─ + { pattern: "*gemini*image*", caps: { vision: true, imageOutput: true, contextWindow: 1048576 } }, + { pattern: "*gemini-3*pro*", caps: { vision: true, audioInput: true, videoInput: true, reasoning: true, search: true, contextWindow: 1048576, maxOutput: 65535 } }, + { pattern: "*gemini-3*", caps: { vision: true, audioInput: true, videoInput: true, search: true, contextWindow: 1048576, maxOutput: 65536 } }, + { pattern: "*gemini-2*", caps: { vision: true, audioInput: true, videoInput: true, search: true, contextWindow: 1048576, maxOutput: 65536 } }, + { pattern: "*gemini*", caps: { vision: true, search: true, contextWindow: 1048576 } }, + { pattern: "*gemma*", caps: { vision: true, contextWindow: 128000 } }, + { pattern: "*nanobanana*", caps: { vision: true, imageOutput: true } }, + + // ── OpenAI GPT-5.x (vision + thinking + web search) ────────────── + { pattern: "*gpt-5*image*", caps: { imageOutput: true } }, + { pattern: "*gpt-5*codex*", caps: { reasoning: true, search: true, contextWindow: 400000, maxOutput: 128000 } }, + { pattern: "*gpt-5*", caps: { vision: true, reasoning: true, search: true, contextWindow: 400000, maxOutput: 128000 } }, + { pattern: "*gpt-4o*", caps: { vision: true, search: true, contextWindow: 128000, maxOutput: 16384 } }, + { pattern: "*gpt-4.1*", caps: { vision: true, contextWindow: 1000000, maxOutput: 32768 } }, + { pattern: "*gpt-4-turbo*", caps: { vision: true, contextWindow: 128000 } }, + { pattern: "*gpt-4*", caps: { contextWindow: 128000 } }, + { pattern: "*gpt-3.5*", caps: { contextWindow: 16385, maxOutput: 4096 } }, + { pattern: "*gpt-oss*", caps: { reasoning: true, contextWindow: 128000 } }, + + // ── OpenAI o-series (reasoning, vision) ────────────────────────── + { pattern: "*o1-mini*", caps: { reasoning: true, contextWindow: 128000 } }, + { pattern: "*o1*", caps: { vision: true, reasoning: true, contextWindow: 200000, maxOutput: 100000 } }, + { pattern: "*o3*", caps: { vision: true, reasoning: true, contextWindow: 200000, maxOutput: 100000 } }, + { pattern: "*o4*", caps: { vision: true, reasoning: true, contextWindow: 200000, maxOutput: 100000 } }, + + // ── Grok (vision + Live Search) ────────────────────────────────── + { pattern: "*grok*image*", caps: { imageOutput: true } }, + { pattern: "*grok-code*", caps: { reasoning: true, contextWindow: 256000 } }, + { pattern: "*grok-4*", caps: { vision: true, reasoning: true, search: true, contextWindow: 256000 } }, + { pattern: "*grok-3*", caps: { vision: true, reasoning: true, search: true, contextWindow: 131072 } }, + { pattern: "*grok*", caps: { vision: true, reasoning: true, search: true, contextWindow: 256000 } }, + + // ── Qwen (VL = vision; max/plus = vision+1M; coder/text last) ───── + { pattern: "*qwen*vl*", caps: { vision: true, reasoning: true, contextWindow: 262144 } }, + { pattern: "*qwen*max*", caps: { vision: true, reasoning: true, contextWindow: 1000000, maxOutput: 65536 } }, + { pattern: "*qwen*plus*", caps: { vision: true, reasoning: true, contextWindow: 1000000, maxOutput: 65536 } }, + { pattern: "*qwen*235b*", caps: { reasoning: true, contextWindow: 262144 } }, + { pattern: "*qwen*coder*", caps: { reasoning: true, contextWindow: 1000000 } }, + { pattern: "*qwq*", caps: { reasoning: true, contextWindow: 131072 } }, + { pattern: "*qwen*", caps: { reasoning: true, contextWindow: 262144 } }, + + // ── Kimi (K2.x = vision + thinking, 262K) ──────────────────────── + { pattern: "*kimi*k2*", caps: { vision: true, reasoning: true, contextWindow: 262144, maxOutput: 262144 } }, + { pattern: "*kimi*", caps: { reasoning: true, contextWindow: 262144 } }, + + // ── GLM (4.6V vision handled by exact id; text GLM = reasoning) ─── + { pattern: "*glm-5*", caps: { reasoning: true, contextWindow: 200000, maxOutput: 128000 } }, + { pattern: "*glm-4.7*", caps: { reasoning: true, contextWindow: 200000, maxOutput: 128000 } }, + { pattern: "*glm-4*", caps: { reasoning: true, contextWindow: 200000 } }, + { pattern: "*glm*", caps: { reasoning: true, contextWindow: 200000 } }, + + // ── DeepSeek (NO vision; v4 = 1M ctx; r1/reasoner = thinking) ───── + { pattern: "*deepseek-v4*", caps: { reasoning: true, contextWindow: 1000000, maxOutput: 384000 } }, + { pattern: "*reasoner*", caps: { reasoning: true, contextWindow: 128000 } }, + { pattern: "*deepseek-r*", caps: { reasoning: true, contextWindow: 128000 } }, + { pattern: "*deepseek*", caps: { contextWindow: 128000 } }, + + // ── MiniMax (M3 = 1M/512K; M2.x = 200K) ────────────────────────── + { pattern: "*minimax*image*", caps: { imageOutput: true } }, + { pattern: "*minimax-m3*", caps: { reasoning: true, contextWindow: 1048576, maxOutput: 512000 } }, + { pattern: "*minimax-m2.7*", caps: { reasoning: true, contextWindow: 204800, maxOutput: 131072 } }, + { pattern: "*minimax*", caps: { reasoning: true, contextWindow: 200000, maxOutput: 131072 } }, + + // ── Xiaomi MiMo (vision, 1M / 262K ctx) ────────────────────────── + { pattern: "*mimo*v2.5*", caps: { vision: true, contextWindow: 1048576, maxOutput: 131072 } }, + { pattern: "*mimo*omni*", caps: { vision: true, audioInput: true, contextWindow: 262144, maxOutput: 131072 } }, + { pattern: "*mimo*", caps: { vision: true, contextWindow: 262144, maxOutput: 131072 } }, + + // ── Llama (4 = vision/1M; 3.x = text-only/128K) ────────────────── + { pattern: "*llama-4*", caps: { vision: true, contextWindow: 1000000 } }, + { pattern: "*llama*", caps: { contextWindow: 128000 } }, + + // ── Mistral (Large 3 = vision/256K; codestral text) ────────────── + { pattern: "*codestral*", caps: { contextWindow: 256000 } }, + { pattern: "*mistral-large*", caps: { vision: true, contextWindow: 256000 } }, + { pattern: "*mistral*", caps: { contextWindow: 128000 } }, + + // ── Cohere (Command A Vision = vision; others text) ────────────── + { pattern: "*command-a-vision*", caps: { vision: true, contextWindow: 128000 } }, + { pattern: "*command*", caps: { contextWindow: 128000 } }, + + // ── Perplexity (web search native) ─────────────────────────────── + { pattern: "*sonar*", caps: { search: true, contextWindow: 128000 } }, + { pattern: "*pplx*", caps: { search: true, contextWindow: 128000 } }, + { pattern: "*perplexity*", caps: { search: true, contextWindow: 128000 } }, + + // ── Others ─────────────────────────────────────────────────────── + { pattern: "*hunyuan*", caps: { reasoning: true, contextWindow: 262144, maxOutput: 262144 } }, + { pattern: "hy3*", caps: { reasoning: true, contextWindow: 262144, maxOutput: 262144 } }, + { pattern: "*step-*", caps: { reasoning: true, contextWindow: 128000 } }, + { pattern: "*nemotron*", caps: { reasoning: true, contextWindow: 128000 } }, + { pattern: "*ling-*", caps: { reasoning: true, contextWindow: 128000 } }, +]; + +/** + * Resolve capabilities for a model using the 4-step fallback chain, + * merged over DEFAULT_CAPABILITIES so the result is always complete. + * + * @param {string} provider + * @param {string} model + * @returns {object} full capabilities object + */ +export function getCapabilitiesForModel(provider, model) { + if (!model) return { ...DEFAULT_CAPABILITIES }; + + // 1. Provider-specific override + if (provider && PROVIDER_CAPABILITIES[provider]?.[model]) { + return { ...DEFAULT_CAPABILITIES, ...PROVIDER_CAPABILITIES[provider][model] }; + } + + // 2. Canonical exact (strip vendor prefix: "anthropic/claude-opus-4.7" -> "claude-opus-4.7") + const baseModel = model.includes("/") ? model.split("/").pop() : model; + if (MODEL_CAPABILITIES[baseModel]) return { ...DEFAULT_CAPABILITIES, ...MODEL_CAPABILITIES[baseModel] }; + if (MODEL_CAPABILITIES[model]) return { ...DEFAULT_CAPABILITIES, ...MODEL_CAPABILITIES[model] }; + + // 3. Pattern match (first match wins) + for (const { pattern, caps } of PATTERN_CAPABILITIES) { + if (matchPattern(pattern, baseModel) || matchPattern(pattern, model)) { + return { ...DEFAULT_CAPABILITIES, ...caps }; + } + } + + // 4. Floor + return { ...DEFAULT_CAPABILITIES }; +} diff --git a/src/shared/constants/pricing.js b/open-sse/providers/pricing.js similarity index 98% rename from src/shared/constants/pricing.js rename to open-sse/providers/pricing.js index e2b6c04e..9e767a80 100644 --- a/src/shared/constants/pricing.js +++ b/open-sse/providers/pricing.js @@ -207,10 +207,11 @@ export const PATTERN_PRICING = [ ]; /** - * Match a model ID against a glob pattern (* = wildcard). + * Match a model ID against a glob pattern (* = wildcard). Case-insensitive: + * registry ids mix casing (e.g. "MiniMax-M2.5" vs "minimax-m2.5"). */ -function matchPattern(pattern, model) { - const regex = new RegExp("^" + pattern.split("*").map(s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$"); +export function matchPattern(pattern, model) { + const regex = new RegExp("^" + pattern.split("*").map(s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$", "i"); return regex.test(model); } diff --git a/open-sse/providers/registry/aws-polly.js b/open-sse/providers/registry/aws-polly.js new file mode 100644 index 00000000..3e269bef --- /dev/null +++ b/open-sse/providers/registry/aws-polly.js @@ -0,0 +1,45 @@ +export default { + id: "aws-polly", + alias: "polly", + display: { + name: "AWS Polly", + icon: "record_voice_over", + color: "#FF9900", + textIcon: "PL", + website: "https://aws.amazon.com/polly/", + notice: { + text: "Use AWS Secret Access Key as API key; set providerSpecificData.accessKeyId and optional region.", + apiKeyUrl: "https://console.aws.amazon.com/iam/home#/security_credentials" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "tts" + ], + ttsConfig: { + baseUrl: "https://polly.{region}.amazonaws.com/v1/speech", + authType: "apikey", + authHeader: "aws-sigv4", + format: "aws-polly", + models: [ + { + id: "standard", + name: "Standard" + }, + { + id: "neural", + name: "Neural" + }, + { + id: "long-form", + name: "Long-form" + }, + { + id: "generative", + name: "Generative" + } + ] + }, + hasProviderSpecificData: true +}; diff --git a/open-sse/providers/registry/brave-search.js b/open-sse/providers/registry/brave-search.js new file mode 100644 index 00000000..6fcad119 --- /dev/null +++ b/open-sse/providers/registry/brave-search.js @@ -0,0 +1,35 @@ +export default { + id: "brave-search", + alias: "brave", + display: { + name: "Brave Search", + icon: "travel_explore", + color: "#FB542B", + textIcon: "BR", + website: "https://brave.com/search/api", + notice: { + apiKeyUrl: "https://api-dashboard.search.brave.com/app/keys" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webSearch" + ], + searchConfig: { + baseUrl: "https://api.search.brave.com/res/v1", + method: "GET", + authType: "apikey", + authHeader: "x-subscription-token", + costPerQuery: 0.005, + freeMonthlyQuota: 1000, + searchTypes: [ + "web", + "news" + ], + defaultMaxResults: 5, + maxMaxResults: 20, + timeoutMs: 10000, + cacheTTLMs: 300000 + } +}; diff --git a/open-sse/providers/registry/byteplus.js b/open-sse/providers/registry/byteplus.js index 5b891245..6440cc48 100644 --- a/open-sse/providers/registry/byteplus.js +++ b/open-sse/providers/registry/byteplus.js @@ -1,6 +1,6 @@ export default { id: "byteplus", - priority: 150, + priority: 70, alias: "byteplus", aliases: [ "bpm", diff --git a/open-sse/providers/registry/cartesia.js b/open-sse/providers/registry/cartesia.js new file mode 100644 index 00000000..411925c5 --- /dev/null +++ b/open-sse/providers/registry/cartesia.js @@ -0,0 +1,36 @@ +export default { + id: "cartesia", + alias: "cartesia", + display: { + name: "Cartesia", + icon: "spatial_audio", + color: "#FF4F8B", + textIcon: "CA", + website: "https://cartesia.ai", + notice: { + apiKeyUrl: "https://play.cartesia.ai/keys" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "tts" + ], + ttsConfig: { + baseUrl: "https://api.cartesia.ai/tts/bytes", + authType: "apikey", + authHeader: "x-api-key", + format: "cartesia", + models: [ + { + id: "sonic-2", + name: "Sonic 2" + }, + { + id: "sonic-3", + name: "Sonic 3" + } + ] + }, + hidden: true +}; diff --git a/open-sse/providers/registry/cline.js b/open-sse/providers/registry/cline.js index ffc0ffc0..cfa788c4 100644 --- a/open-sse/providers/registry/cline.js +++ b/open-sse/providers/registry/cline.js @@ -1,6 +1,6 @@ export default { id: "cline", - priority: 70, + priority: 80, alias: "cl", uiAlias: "cl", display: { diff --git a/open-sse/providers/registry/cloudflare-ai.js b/open-sse/providers/registry/cloudflare-ai.js index 0c08e05e..2b9a5bd0 100644 --- a/open-sse/providers/registry/cloudflare-ai.js +++ b/open-sse/providers/registry/cloudflare-ai.js @@ -1,6 +1,6 @@ export default { id: "cloudflare-ai", - priority: 20, + priority: 60, hasFree: true, alias: "cloudflare-ai", aliases: [ diff --git a/open-sse/providers/registry/codebuddy.js b/open-sse/providers/registry/codebuddy.js index f317b7f2..4bf2c811 100644 --- a/open-sse/providers/registry/codebuddy.js +++ b/open-sse/providers/registry/codebuddy.js @@ -1,6 +1,7 @@ export default { id: "codebuddy", - priority: 80, + hidden: true, + priority: 90, display: { name: "CodeBuddy", icon: "smart_toy", diff --git a/open-sse/providers/registry/coqui.js b/open-sse/providers/registry/coqui.js new file mode 100644 index 00000000..8f108e21 --- /dev/null +++ b/open-sse/providers/registry/coqui.js @@ -0,0 +1,30 @@ +export default { + id: "coqui", + alias: "coqui", + display: { + name: "Coqui TTS", + icon: "record_voice_over", + color: "#10B981", + textIcon: "CQ", + website: "https://github.com/coqui-ai/TTS" + }, + category: "freeTier", + authType: "none", + serviceKinds: [ + "tts" + ], + noAuth: true, + ttsConfig: { + baseUrl: "http://localhost:5002/api/tts", + authType: "none", + authHeader: "none", + format: "coqui", + models: [ + { + id: "tts_models/en/ljspeech/tacotron2-DDC", + name: "Tacotron2 DDC (LJSpeech)" + } + ] + }, + hidden: true +}; diff --git a/open-sse/providers/registry/cursor.js b/open-sse/providers/registry/cursor.js index 154bfbc7..ca0ecdb1 100644 --- a/open-sse/providers/registry/cursor.js +++ b/open-sse/providers/registry/cursor.js @@ -1,6 +1,6 @@ export default { id: "cursor", - priority: 40, + priority: 50, alias: "cu", uiAlias: "cu", display: { diff --git a/open-sse/providers/registry/edge-tts.js b/open-sse/providers/registry/edge-tts.js new file mode 100644 index 00000000..74781e8a --- /dev/null +++ b/open-sse/providers/registry/edge-tts.js @@ -0,0 +1,24 @@ +export default { + id: "edge-tts", + alias: "edge-tts", + display: { + name: "Edge TTS", + icon: "record_voice_over", + color: "#0078D4", + textIcon: "ET" + }, + category: "freeTier", + authType: "none", + serviceKinds: [ + "tts" + ], + mediaPriority: 5, + noAuth: true, + ttsConfig: { + baseUrl: "edge-tts", + authType: "none", + authHeader: "none", + format: "edge-tts", + models: [] + } +}; diff --git a/open-sse/providers/registry/elevenlabs.js b/open-sse/providers/registry/elevenlabs.js new file mode 100644 index 00000000..fad3227d --- /dev/null +++ b/open-sse/providers/registry/elevenlabs.js @@ -0,0 +1,35 @@ +export default { + id: "elevenlabs", + alias: "el", + display: { + name: "ElevenLabs", + icon: "record_voice_over", + color: "#6C47FF", + textIcon: "EL", + website: "https://elevenlabs.io", + notice: { + apiKeyUrl: "https://elevenlabs.io/app/settings/api-keys" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "tts" + ], + ttsConfig: { + baseUrl: "https://api.elevenlabs.io/v1/text-to-speech", + authType: "apikey", + authHeader: "xi-api-key", + format: "elevenlabs", + models: [ + { + id: "eleven_multilingual_v2", + name: "Eleven Multilingual v2" + }, + { + id: "eleven_turbo_v2_5", + name: "Eleven Turbo v2.5" + } + ] + } +}; diff --git a/open-sse/providers/registry/exa.js b/open-sse/providers/registry/exa.js new file mode 100644 index 00000000..75b8bf0a --- /dev/null +++ b/open-sse/providers/registry/exa.js @@ -0,0 +1,50 @@ +export default { + id: "exa", + alias: "exa", + display: { + name: "Exa", + icon: "manage_search", + color: "#2563EB", + textIcon: "EX", + website: "https://exa.ai", + notice: { + apiKeyUrl: "https://dashboard.exa.ai/api-keys" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webSearch", + "webFetch" + ], + searchConfig: { + baseUrl: "https://api.exa.ai/search", + method: "POST", + authType: "apikey", + authHeader: "x-api-key", + costPerQuery: 0.007, + freeMonthlyQuota: 1000, + searchTypes: [ + "web", + "news" + ], + defaultMaxResults: 5, + maxMaxResults: 100, + timeoutMs: 10000, + cacheTTLMs: 300000 + }, + fetchConfig: { + baseUrl: "https://api.exa.ai/contents", + method: "POST", + authType: "apikey", + authHeader: "x-api-key", + costPerQuery: 0.001, + freeMonthlyQuota: 1000, + formats: [ + "text", + "markdown" + ], + maxCharacters: 100000, + timeoutMs: 15000 + } +}; diff --git a/open-sse/providers/registry/firecrawl.js b/open-sse/providers/registry/firecrawl.js new file mode 100644 index 00000000..fab98fd7 --- /dev/null +++ b/open-sse/providers/registry/firecrawl.js @@ -0,0 +1,34 @@ +export default { + id: "firecrawl", + alias: "firecrawl", + display: { + name: "Firecrawl", + icon: "local_fire_department", + color: "#F59E0B", + textIcon: "FC", + website: "https://firecrawl.dev", + notice: { + apiKeyUrl: "https://www.firecrawl.dev/app/api-keys" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webFetch" + ], + fetchConfig: { + baseUrl: "https://api.firecrawl.dev/v1/scrape", + method: "POST", + authType: "apikey", + authHeader: "bearer", + costPerQuery: 0.002, + freeMonthlyQuota: 500, + formats: [ + "markdown", + "html", + "text" + ], + maxCharacters: 200000, + timeoutMs: 30000 + } +}; diff --git a/open-sse/providers/registry/gemini-cli.js b/open-sse/providers/registry/gemini-cli.js index d9d1e86b..8af51690 100644 --- a/open-sse/providers/registry/gemini-cli.js +++ b/open-sse/providers/registry/gemini-cli.js @@ -2,7 +2,7 @@ import { GOOGLE_OAUTH_CLIENT } from "../shared.js"; export default { id: "gemini-cli", - priority: 130, + priority: 20, hasFree: true, alias: "gc", uiAlias: "gc", diff --git a/open-sse/providers/registry/gemini.js b/open-sse/providers/registry/gemini.js index a96f2fd5..65ebb316 100644 --- a/open-sse/providers/registry/gemini.js +++ b/open-sse/providers/registry/gemini.js @@ -2,7 +2,7 @@ import { GOOGLE_OAUTH_CLIENT } from "../shared.js"; export default { id: "gemini", - priority: 10, + priority: 50, hasFree: true, alias: "gemini", display: { diff --git a/open-sse/providers/registry/github.js b/open-sse/providers/registry/github.js index 1f2916ca..9251c5c2 100644 --- a/open-sse/providers/registry/github.js +++ b/open-sse/providers/registry/github.js @@ -1,6 +1,6 @@ export default { id: "github", - priority: 50, + priority: 40, alias: "gh", uiAlias: "gh", display: { diff --git a/open-sse/providers/registry/gitlab.js b/open-sse/providers/registry/gitlab.js index 18b17061..319379f6 100644 --- a/open-sse/providers/registry/gitlab.js +++ b/open-sse/providers/registry/gitlab.js @@ -1,6 +1,7 @@ export default { id: "gitlab", - priority: 120, + hidden: true, + priority: 100, display: { name: "GitLab Duo", icon: "code", diff --git a/open-sse/providers/registry/google-pse.js b/open-sse/providers/registry/google-pse.js new file mode 100644 index 00000000..f2a1c0b4 --- /dev/null +++ b/open-sse/providers/registry/google-pse.js @@ -0,0 +1,35 @@ +export default { + id: "google-pse", + alias: "gpse", + display: { + name: "Google PSE", + icon: "search", + color: "#4285F4", + textIcon: "GP", + website: "https://programmablesearchengine.google.com", + notice: { + apiKeyUrl: "https://programmablesearchengine.google.com/controlpanel/create" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webSearch" + ], + searchConfig: { + baseUrl: "https://www.googleapis.com/customsearch/v1", + method: "GET", + authType: "apikey", + authHeader: "key", + costPerQuery: 0.005, + freeMonthlyQuota: 3000, + searchTypes: [ + "web", + "news" + ], + defaultMaxResults: 5, + maxMaxResults: 10, + timeoutMs: 10000, + cacheTTLMs: 300000 + } +}; diff --git a/open-sse/providers/registry/google-tts.js b/open-sse/providers/registry/google-tts.js new file mode 100644 index 00000000..0b4d748e --- /dev/null +++ b/open-sse/providers/registry/google-tts.js @@ -0,0 +1,24 @@ +export default { + id: "google-tts", + alias: "google-tts", + display: { + name: "Google TTS", + icon: "record_voice_over", + color: "#4285F4", + textIcon: "GT" + }, + category: "freeTier", + authType: "none", + serviceKinds: [ + "tts" + ], + mediaPriority: 5, + noAuth: true, + ttsConfig: { + baseUrl: "google-tts", + authType: "none", + authHeader: "none", + format: "google-tts", + models: [] + } +}; diff --git a/open-sse/providers/registry/iflow.js b/open-sse/providers/registry/iflow.js index 71781501..a48a103c 100644 --- a/open-sse/providers/registry/iflow.js +++ b/open-sse/providers/registry/iflow.js @@ -1,6 +1,7 @@ export default { id: "iflow", - priority: 170, + hidden: true, + priority: 110, alias: "if", display: { name: "iFlow AI", diff --git a/open-sse/providers/registry/index.js b/open-sse/providers/registry/index.js index 3ef1d05a..a45bb1e3 100644 --- a/open-sse/providers/registry/index.js +++ b/open-sse/providers/registry/index.js @@ -1,75 +1,98 @@ // Auto-generated: static imports of all registry entries -import p0 from './alicode-intl.js'; -import p1 from './alicode.js'; -import p2 from './anthropic.js'; -import p3 from './antigravity.js'; -import p4 from './assemblyai.js'; -import p5 from './azure.js'; -import p6 from './black-forest-labs.js'; -import p7 from './blackbox.js'; -import p8 from './byteplus.js'; -import p9 from './cerebras.js'; -import p10 from './chutes.js'; -import p11 from './claude.js'; -import p12 from './cline.js'; -import p13 from './cloudflare-ai.js'; -import p14 from './codebuddy.js'; -import p15 from './codex.js'; -import p16 from './cohere.js'; -import p17 from './comfyui.js'; -import p18 from './commandcode.js'; -import p19 from './cursor.js'; -import p20 from './deepgram.js'; -import p21 from './deepseek.js'; -import p22 from './fal-ai.js'; -import p23 from './fireworks.js'; -import p24 from './gemini-cli.js'; -import p25 from './gemini.js'; -import p26 from './github.js'; -import p27 from './gitlab.js'; -import p28 from './glm-cn.js'; -import p29 from './glm.js'; -import p30 from './grok-web.js'; -import p31 from './groq.js'; -import p32 from './huggingface.js'; -import p33 from './hyperbolic.js'; -import p34 from './iflow.js'; -import p35 from './kilocode.js'; -import p36 from './kimi-coding.js'; -import p37 from './kimi.js'; -import p38 from './kiro.js'; -import p39 from './mimo-free.js'; -import p40 from './minimax-cn.js'; -import p41 from './minimax.js'; -import p42 from './mistral.js'; -import p43 from './mmf.js'; -import p44 from './nanobanana.js'; -import p45 from './nebius.js'; -import p46 from './nvidia.js'; -import p47 from './ollama-local.js'; -import p48 from './ollama.js'; -import p49 from './openai.js'; -import p50 from './opencode-go.js'; -import p51 from './opencode.js'; -import p52 from './openrouter.js'; -import p53 from './perplexity-web.js'; -import p54 from './perplexity.js'; -import p55 from './qoder.js'; -import p56 from './qwen.js'; -import p57 from './recraft.js'; -import p58 from './runwayml.js'; -import p59 from './sdwebui.js'; -import p60 from './siliconflow.js'; -import p61 from './stability-ai.js'; -import p62 from './together.js'; -import p63 from './vercel-ai-gateway.js'; -import p64 from './vertex-partner.js'; -import p65 from './vertex.js'; -import p66 from './volcengine-ark.js'; -import p67 from './voyage-ai.js'; -import p68 from './xai.js'; -import p69 from './xiaomi-mimo.js'; -import p70 from './xiaomi-tokenplan.js'; +import p0 from "./alicode.js"; +import p1 from "./alicode-intl.js"; +import p2 from "./anthropic.js"; +import p3 from "./antigravity.js"; +import p4 from "./assemblyai.js"; +import p5 from "./aws-polly.js"; +import p6 from "./azure.js"; +import p7 from "./black-forest-labs.js"; +import p8 from "./blackbox.js"; +import p9 from "./brave-search.js"; +import p10 from "./byteplus.js"; +import p11 from "./cartesia.js"; +import p12 from "./cerebras.js"; +import p13 from "./chutes.js"; +import p14 from "./claude.js"; +import p15 from "./cline.js"; +import p16 from "./cloudflare-ai.js"; +import p17 from "./codebuddy.js"; +import p18 from "./codex.js"; +import p19 from "./cohere.js"; +import p20 from "./comfyui.js"; +import p21 from "./commandcode.js"; +import p22 from "./coqui.js"; +import p23 from "./cursor.js"; +import p24 from "./deepgram.js"; +import p25 from "./deepseek.js"; +import p26 from "./edge-tts.js"; +import p27 from "./elevenlabs.js"; +import p28 from "./exa.js"; +import p29 from "./fal-ai.js"; +import p30 from "./firecrawl.js"; +import p31 from "./fireworks.js"; +import p32 from "./gemini.js"; +import p33 from "./gemini-cli.js"; +import p34 from "./github.js"; +import p35 from "./gitlab.js"; +import p36 from "./glm.js"; +import p37 from "./glm-cn.js"; +import p38 from "./google-pse.js"; +import p39 from "./google-tts.js"; +import p40 from "./grok-web.js"; +import p41 from "./groq.js"; +import p42 from "./huggingface.js"; +import p43 from "./hyperbolic.js"; +import p44 from "./iflow.js"; +import p45 from "./inworld.js"; +import p46 from "./jina-ai.js"; +import p47 from "./jina-reader.js"; +import p48 from "./kilocode.js"; +import p49 from "./kimi.js"; +import p50 from "./kimi-coding.js"; +import p51 from "./kiro.js"; +import p52 from "./linkup.js"; +import p53 from "./local-device.js"; +import p54 from "./mimo-free.js"; +import p55 from "./minimax.js"; +import p56 from "./minimax-cn.js"; +import p57 from "./mistral.js"; +import p58 from "./mmf.js"; +import p59 from "./nanobanana.js"; +import p60 from "./nebius.js"; +import p61 from "./nvidia.js"; +import p62 from "./ollama.js"; +import p63 from "./ollama-local.js"; +import p64 from "./openai.js"; +import p65 from "./opencode.js"; +import p66 from "./opencode-go.js"; +import p67 from "./openrouter.js"; +import p68 from "./perplexity.js"; +import p69 from "./perplexity-web.js"; +import p70 from "./playht.js"; +import p71 from "./qoder.js"; +import p72 from "./qwen.js"; +import p73 from "./recraft.js"; +import p74 from "./runwayml.js"; +import p75 from "./sdwebui.js"; +import p76 from "./searchapi.js"; +import p77 from "./searxng.js"; +import p78 from "./serper.js"; +import p79 from "./siliconflow.js"; +import p80 from "./stability-ai.js"; +import p81 from "./tavily.js"; +import p82 from "./together.js"; +import p83 from "./topaz.js"; +import p84 from "./tortoise.js"; +import p85 from "./vercel-ai-gateway.js"; +import p86 from "./vertex.js"; +import p87 from "./vertex-partner.js"; +import p88 from "./volcengine-ark.js"; +import p89 from "./voyage-ai.js"; +import p90 from "./xai.js"; +import p91 from "./xiaomi-mimo.js"; +import p92 from "./xiaomi-tokenplan.js"; +import p93 from "./youcom.js"; export default [ p0, @@ -142,5 +165,28 @@ export default [ p67, p68, p69, - p70 + p70, + p71, + p72, + p73, + p74, + p75, + p76, + p77, + p78, + p79, + p80, + p81, + p82, + p83, + p84, + p85, + p86, + p87, + p88, + p89, + p90, + p91, + p92, + p93 ]; diff --git a/open-sse/providers/registry/inworld.js b/open-sse/providers/registry/inworld.js new file mode 100644 index 00000000..bc9bad57 --- /dev/null +++ b/open-sse/providers/registry/inworld.js @@ -0,0 +1,36 @@ +export default { + id: "inworld", + alias: "inworld", + display: { + name: "Inworld TTS", + icon: "record_voice_over", + color: "#FF6B6B", + textIcon: "IW", + website: "https://inworld.ai", + notice: { + text: "Free tier: 40 minutes/month TTS. Paid: TTS-1.5 Mini $0.01/min ($15/1M chars), TTS-1.5 Max $0.025/min ($30/1M chars). 270+ voices, 15 languages.", + apiKeyUrl: "https://platform.inworld.ai/api-keys" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "tts" + ], + ttsConfig: { + baseUrl: "https://api.inworld.ai/tts/v1/voice", + authType: "apikey", + authHeader: "basic", + format: "inworld", + models: [ + { + id: "inworld-tts-1.5-mini", + name: "Inworld TTS 1.5 Mini ($0.01/min)" + }, + { + id: "inworld-tts-1.5-max", + name: "Inworld TTS 1.5 Max ($0.025/min)" + } + ] + } +}; diff --git a/open-sse/providers/registry/jina-ai.js b/open-sse/providers/registry/jina-ai.js new file mode 100644 index 00000000..90c7da2b --- /dev/null +++ b/open-sse/providers/registry/jina-ai.js @@ -0,0 +1,42 @@ +export default { + id: "jina-ai", + alias: "jina", + display: { + name: "Jina AI", + icon: "blur_on", + color: "#2563EB", + textIcon: "JA", + website: "https://jina.ai", + notice: { + text: "10M free tokens on signup (non-commercial), no credit card required.", + apiKeyUrl: "https://jina.ai/?sui=apikey" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "embedding" + ], + embeddingConfig: { + baseUrl: "https://api.jina.ai/v1/embeddings", + authType: "apikey", + authHeader: "bearer", + models: [ + { + id: "jina-embeddings-v3", + name: "Jina Embeddings v3", + dimensions: 1024 + }, + { + id: "jina-embeddings-v2-base-en", + name: "Jina Embeddings v2 Base EN", + dimensions: 768 + }, + { + id: "jina-embeddings-v2-base-code", + name: "Jina Embeddings v2 Base Code", + dimensions: 768 + } + ] + } +}; diff --git a/open-sse/providers/registry/jina-reader.js b/open-sse/providers/registry/jina-reader.js new file mode 100644 index 00000000..35ffae94 --- /dev/null +++ b/open-sse/providers/registry/jina-reader.js @@ -0,0 +1,34 @@ +export default { + id: "jina-reader", + alias: "jina-reader", + display: { + name: "Jina Reader", + icon: "menu_book", + color: "#000000", + textIcon: "JR", + website: "https://jina.ai/reader", + notice: { + apiKeyUrl: "https://jina.ai/?sui=apikey" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webFetch" + ], + fetchConfig: { + baseUrl: "https://r.jina.ai", + method: "GET", + authType: "apikey", + authHeader: "bearer", + costPerQuery: 0, + freeMonthlyQuota: 1000000, + formats: [ + "markdown", + "text", + "html" + ], + maxCharacters: 200000, + timeoutMs: 30000 + } +}; diff --git a/open-sse/providers/registry/kilocode.js b/open-sse/providers/registry/kilocode.js index 93338bc0..c259ac79 100644 --- a/open-sse/providers/registry/kilocode.js +++ b/open-sse/providers/registry/kilocode.js @@ -1,6 +1,6 @@ export default { id: "kilocode", - priority: 60, + priority: 70, alias: "kc", uiAlias: "kc", display: { diff --git a/open-sse/providers/registry/kimi-coding.js b/open-sse/providers/registry/kimi-coding.js index a222b2a5..77ec4564 100644 --- a/open-sse/providers/registry/kimi-coding.js +++ b/open-sse/providers/registry/kimi-coding.js @@ -2,7 +2,8 @@ import { CLAUDE_API_HEADERS, KIMI_CODING_BASE_URL } from "../shared.js"; export default { id: "kimi-coding", - priority: 180, + hidden: true, + priority: 120, alias: "kmc", display: { name: "Kimi Coding", diff --git a/open-sse/providers/registry/kiro.js b/open-sse/providers/registry/kiro.js index e9eda6db..5c092a02 100644 --- a/open-sse/providers/registry/kiro.js +++ b/open-sse/providers/registry/kiro.js @@ -1,6 +1,6 @@ export default { id: "kiro", - priority: 80, + priority: 10, alias: "kr", uiAlias: "kr", display: { diff --git a/open-sse/providers/registry/linkup.js b/open-sse/providers/registry/linkup.js new file mode 100644 index 00000000..19be6bb2 --- /dev/null +++ b/open-sse/providers/registry/linkup.js @@ -0,0 +1,34 @@ +export default { + id: "linkup", + alias: "linkup", + display: { + name: "Linkup", + icon: "link", + color: "#0EA5E9", + textIcon: "LK", + website: "https://linkup.so", + notice: { + apiKeyUrl: "https://app.linkup.so/api-keys" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webSearch" + ], + searchConfig: { + baseUrl: "https://api.linkup.so/v1/search", + method: "POST", + authType: "apikey", + authHeader: "bearer", + costPerQuery: 0.005, + freeMonthlyQuota: 1000, + searchTypes: [ + "web" + ], + defaultMaxResults: 5, + maxMaxResults: 50, + timeoutMs: 10000, + cacheTTLMs: 300000 + } +}; diff --git a/open-sse/providers/registry/local-device.js b/open-sse/providers/registry/local-device.js new file mode 100644 index 00000000..b24fc2de --- /dev/null +++ b/open-sse/providers/registry/local-device.js @@ -0,0 +1,24 @@ +export default { + id: "local-device", + alias: "local-device", + display: { + name: "Local Device", + icon: "speaker", + color: "#64748B", + textIcon: "LD" + }, + category: "freeTier", + authType: "none", + serviceKinds: [ + "tts" + ], + mediaPriority: 5, + noAuth: true, + ttsConfig: { + baseUrl: "local-device", + authType: "none", + authHeader: "none", + format: "local-device", + models: [] + } +}; diff --git a/open-sse/providers/registry/mimo-free.js b/open-sse/providers/registry/mimo-free.js index 44bddd60..4b9074d1 100644 --- a/open-sse/providers/registry/mimo-free.js +++ b/open-sse/providers/registry/mimo-free.js @@ -1,6 +1,6 @@ export default { id: "mimo-free", - priority: 120, + priority: 50, hasFree: true, alias: "mmf", uiAlias: "mmf", diff --git a/open-sse/providers/registry/mmf.js b/open-sse/providers/registry/mmf.js index 9f5e0e4e..63c776f4 100644 --- a/open-sse/providers/registry/mmf.js +++ b/open-sse/providers/registry/mmf.js @@ -1,5 +1,6 @@ export default { id: "mmf", + hidden: true, priority: 200, display: { name: "MMF", diff --git a/open-sse/providers/registry/nanobanana.js b/open-sse/providers/registry/nanobanana.js index 277f4c76..f57b49af 100644 --- a/open-sse/providers/registry/nanobanana.js +++ b/open-sse/providers/registry/nanobanana.js @@ -27,7 +27,7 @@ export default { { id: "nanobanana-flash", name: "NanoBanana Flash", params: ["n","size"], kind: "image" }, { id: "nanobanana-pro", name: "NanoBanana Pro", params: ["n","size"], kind: "image" }, ], - serviceKinds: ["llm","image"], + serviceKinds: ["image"], imageConfig: { baseUrl: "https://api.nanobananaapi.ai/api/v1/nanobanana/generate", pollUrl: "https://api.nanobananaapi.ai/api/v1/nanobanana/record-info", diff --git a/open-sse/providers/registry/nvidia.js b/open-sse/providers/registry/nvidia.js index 58adaa40..35a1f768 100644 --- a/open-sse/providers/registry/nvidia.js +++ b/open-sse/providers/registry/nvidia.js @@ -1,6 +1,6 @@ export default { id: "nvidia", - priority: 100, + priority: 20, hasFree: true, alias: "nvidia", display: { diff --git a/open-sse/providers/registry/ollama.js b/open-sse/providers/registry/ollama.js index cccb31d9..0c3978bf 100644 --- a/open-sse/providers/registry/ollama.js +++ b/open-sse/providers/registry/ollama.js @@ -1,6 +1,6 @@ export default { id: "ollama", - priority: 40, + priority: 30, hasFree: true, alias: "ollama", display: { diff --git a/open-sse/providers/registry/opencode.js b/open-sse/providers/registry/opencode.js index e5bd914c..e83ad3a7 100644 --- a/open-sse/providers/registry/opencode.js +++ b/open-sse/providers/registry/opencode.js @@ -1,6 +1,6 @@ export default { id: "opencode", - priority: 110, + priority: 40, hasFree: true, alias: "oc", uiAlias: "oc", diff --git a/open-sse/providers/registry/openrouter.js b/open-sse/providers/registry/openrouter.js index 0a1c94ee..7ea00d30 100644 --- a/open-sse/providers/registry/openrouter.js +++ b/open-sse/providers/registry/openrouter.js @@ -1,6 +1,6 @@ export default { id: "openrouter", - priority: 30, + priority: 10, hasFree: true, alias: "openrouter", display: { diff --git a/open-sse/providers/registry/playht.js b/open-sse/providers/registry/playht.js new file mode 100644 index 00000000..1373e563 --- /dev/null +++ b/open-sse/providers/registry/playht.js @@ -0,0 +1,36 @@ +export default { + id: "playht", + alias: "playht", + display: { + name: "PlayHT", + icon: "play_circle", + color: "#00B4D8", + textIcon: "PH", + website: "https://play.ht", + notice: { + apiKeyUrl: "https://play.ht/studio/api-access" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "tts" + ], + ttsConfig: { + baseUrl: "https://api.play.ht/api/v2/tts/stream", + authType: "apikey", + authHeader: "playht", + format: "playht", + models: [ + { + id: "PlayDialog", + name: "PlayDialog" + }, + { + id: "Play3.0-mini", + name: "Play 3.0 Mini" + } + ] + }, + hidden: true +}; diff --git a/open-sse/providers/registry/qoder.js b/open-sse/providers/registry/qoder.js index 750b77a9..c150e3a6 100644 --- a/open-sse/providers/registry/qoder.js +++ b/open-sse/providers/registry/qoder.js @@ -1,6 +1,6 @@ export default { id: "qoder", - priority: 230, + priority: 30, alias: "qd", uiAlias: "qd", display: { diff --git a/open-sse/providers/registry/qwen.js b/open-sse/providers/registry/qwen.js index 599ca46f..0df381ab 100644 --- a/open-sse/providers/registry/qwen.js +++ b/open-sse/providers/registry/qwen.js @@ -1,6 +1,7 @@ export default { id: "qwen", - priority: 240, + hidden: true, + priority: 130, alias: "qw", display: { name: "Qwen Code", diff --git a/open-sse/providers/registry/searchapi.js b/open-sse/providers/registry/searchapi.js new file mode 100644 index 00000000..c558ba9b --- /dev/null +++ b/open-sse/providers/registry/searchapi.js @@ -0,0 +1,35 @@ +export default { + id: "searchapi", + alias: "searchapi", + display: { + name: "SearchAPI", + icon: "search", + color: "#0EA5A4", + textIcon: "SA", + website: "https://www.searchapi.io", + notice: { + apiKeyUrl: "https://www.searchapi.io/dashboard" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webSearch" + ], + searchConfig: { + baseUrl: "https://www.searchapi.io/api/v1/search", + method: "GET", + authType: "apikey", + authHeader: "api_key", + costPerQuery: 0.004, + freeMonthlyQuota: 100, + searchTypes: [ + "web", + "news" + ], + defaultMaxResults: 5, + maxMaxResults: 100, + timeoutMs: 10000, + cacheTTLMs: 300000 + } +}; diff --git a/open-sse/providers/registry/searxng.js b/open-sse/providers/registry/searxng.js new file mode 100644 index 00000000..308eabbc --- /dev/null +++ b/open-sse/providers/registry/searxng.js @@ -0,0 +1,33 @@ +export default { + id: "searxng", + alias: "searxng", + display: { + name: "SearXNG", + icon: "saved_search", + color: "#3B82F6", + textIcon: "SX", + website: "https://docs.searxng.org" + }, + category: "freeTier", + authType: "none", + serviceKinds: [ + "webSearch" + ], + noAuth: true, + searchConfig: { + baseUrl: "http://localhost:8888/search", + method: "GET", + authType: "none", + authHeader: "none", + costPerQuery: 0, + freeMonthlyQuota: 999999, + searchTypes: [ + "web", + "news" + ], + defaultMaxResults: 5, + maxMaxResults: 50, + timeoutMs: 10000, + cacheTTLMs: 180000 + } +}; diff --git a/open-sse/providers/registry/serper.js b/open-sse/providers/registry/serper.js new file mode 100644 index 00000000..b98d5af0 --- /dev/null +++ b/open-sse/providers/registry/serper.js @@ -0,0 +1,35 @@ +export default { + id: "serper", + alias: "serper", + display: { + name: "Serper", + icon: "search", + color: "#4F46E5", + textIcon: "SP", + website: "https://serper.dev", + notice: { + apiKeyUrl: "https://serper.dev/api-key" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webSearch" + ], + searchConfig: { + baseUrl: "https://google.serper.dev", + method: "POST", + authType: "apikey", + authHeader: "x-api-key", + costPerQuery: 0.001, + freeMonthlyQuota: 2500, + searchTypes: [ + "web", + "news" + ], + defaultMaxResults: 5, + maxMaxResults: 100, + timeoutMs: 10000, + cacheTTLMs: 300000 + } +}; diff --git a/open-sse/providers/registry/tavily.js b/open-sse/providers/registry/tavily.js new file mode 100644 index 00000000..4386c973 --- /dev/null +++ b/open-sse/providers/registry/tavily.js @@ -0,0 +1,50 @@ +export default { + id: "tavily", + alias: "tavily", + display: { + name: "Tavily", + icon: "search", + color: "#5B21B6", + textIcon: "TV", + website: "https://tavily.com", + notice: { + apiKeyUrl: "https://app.tavily.com/home" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webSearch", + "webFetch" + ], + searchConfig: { + baseUrl: "https://api.tavily.com/search", + method: "POST", + authType: "apikey", + authHeader: "bearer", + costPerQuery: 0.008, + freeMonthlyQuota: 1000, + searchTypes: [ + "web", + "news" + ], + defaultMaxResults: 5, + maxMaxResults: 20, + timeoutMs: 10000, + cacheTTLMs: 300000 + }, + fetchConfig: { + baseUrl: "https://api.tavily.com/extract", + method: "POST", + authType: "apikey", + authHeader: "bearer", + costPerQuery: 0.008, + freeMonthlyQuota: 1000, + formats: [ + "markdown", + "text" + ], + maxCharacters: 100000, + timeoutMs: 15000 + } +}; diff --git a/open-sse/providers/registry/topaz.js b/open-sse/providers/registry/topaz.js new file mode 100644 index 00000000..1a4bb7a5 --- /dev/null +++ b/open-sse/providers/registry/topaz.js @@ -0,0 +1,19 @@ +export default { + id: "topaz", + alias: "topaz", + display: { + name: "Topaz", + icon: "image", + color: "#059669", + textIcon: "TP", + website: "https://topazlabs.com", + notice: { + apiKeyUrl: "https://topazlabs.com/account" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "image" + ] +}; diff --git a/open-sse/providers/registry/tortoise.js b/open-sse/providers/registry/tortoise.js new file mode 100644 index 00000000..4d3b87c1 --- /dev/null +++ b/open-sse/providers/registry/tortoise.js @@ -0,0 +1,30 @@ +export default { + id: "tortoise", + alias: "tortoise", + display: { + name: "Tortoise TTS", + icon: "record_voice_over", + color: "#7C3AED", + textIcon: "TT", + website: "https://github.com/neonbjb/tortoise-tts" + }, + category: "freeTier", + authType: "none", + serviceKinds: [ + "tts" + ], + noAuth: true, + ttsConfig: { + baseUrl: "http://localhost:5000/api/tts", + authType: "none", + authHeader: "none", + format: "tortoise", + models: [ + { + id: "tortoise-v2", + name: "Tortoise v2" + } + ] + }, + hidden: true +}; diff --git a/open-sse/providers/registry/vertex.js b/open-sse/providers/registry/vertex.js index 5e101595..b8765de3 100644 --- a/open-sse/providers/registry/vertex.js +++ b/open-sse/providers/registry/vertex.js @@ -1,6 +1,6 @@ export default { id: "vertex", - priority: 140, + priority: 40, alias: "vertex", aliases: [ "vx", diff --git a/open-sse/providers/registry/xai.js b/open-sse/providers/registry/xai.js index 0b55006b..efe13bdd 100644 --- a/open-sse/providers/registry/xai.js +++ b/open-sse/providers/registry/xai.js @@ -12,7 +12,7 @@ export default { apiKeyUrl: "https://console.x.ai", }, }, - category: "apikey", + category: "oauth", authModes: [ "oauth", "apikey", diff --git a/open-sse/providers/registry/youcom.js b/open-sse/providers/registry/youcom.js new file mode 100644 index 00000000..d090c638 --- /dev/null +++ b/open-sse/providers/registry/youcom.js @@ -0,0 +1,35 @@ +export default { + id: "youcom", + alias: "youcom", + display: { + name: "You.com Search", + icon: "search", + color: "#7C3AED", + textIcon: "YC", + website: "https://you.com", + notice: { + apiKeyUrl: "https://api.you.com" + } + }, + category: "apikey", + authType: "apikey", + serviceKinds: [ + "webSearch" + ], + searchConfig: { + baseUrl: "https://ydc-index.io/v1/search", + method: "GET", + authType: "apikey", + authHeader: "x-api-key", + costPerQuery: 0.005, + freeMonthlyQuota: 0, + searchTypes: [ + "web", + "news" + ], + defaultMaxResults: 5, + maxMaxResults: 100, + timeoutMs: 10000, + cacheTTLMs: 300000 + } +}; diff --git a/open-sse/translator/formats/claude.js b/open-sse/translator/formats/claude.js index b18dfd75..24c6a5fe 100644 --- a/open-sse/translator/formats/claude.js +++ b/open-sse/translator/formats/claude.js @@ -3,7 +3,7 @@ import { DEFAULT_THINKING_CLAUDE_SIGNATURE } from "../../config/defaultThinkingS import { ROLE, CLAUDE_BLOCK } from "../schema/index.js"; import { adjustMaxTokens } from "./maxTokens.js"; import { applyCloaking } from "../../utils/claudeCloaking.js"; -import { deriveSessionId } from "../../utils/sessionManager.js"; +import { resolveSessionId } from "../../utils/sessionManager.js"; import { PROVIDERS } from "../../providers/index.js"; // Check if message has valid non-empty content @@ -252,7 +252,7 @@ export function prepareClaudeRequest(body, provider = null, apiKey = null, conne // Apply cloaking for OAuth tokens (billing header + fake user ID) // session_id in user_id must match X-Claude-Code-Session-Id for fingerprint consistency if ((provider === "claude" || provider?.startsWith("anthropic-compatible")) && apiKey) { - const sessionId = connectionId ? deriveSessionId(connectionId) : null; + const sessionId = resolveSessionId({ body, connectionId, scope: "claude" }); body = applyCloaking(body, apiKey, sessionId); } diff --git a/open-sse/translator/index.js b/open-sse/translator/index.js index 863583be..0053c5a4 100644 --- a/open-sse/translator/index.js +++ b/open-sse/translator/index.js @@ -7,15 +7,16 @@ import { normalizeThinkingConfig } from "../services/provider.js"; import { AntigravityExecutor } from "../executors/antigravity.js"; import { PROVIDERS } from "../providers/index.js"; -// Registry for translators -const requestRegistry = new Map(); -const responseRegistry = new Map(); - -// Track initialization state -let initialized = false; +// Registry for translators. Lazy-init guards against circular-import order: +// translator modules call register() (side-effect) before this module's body runs. +// var (not let): hoisted as undefined so register() can run during circular import (no TDZ). +var requestRegistry; +var responseRegistry; // Register translator export function register(from, to, requestFn, responseFn) { + requestRegistry ??= new Map(); + responseRegistry ??= new Map(); const key = `${from}:${to}`; if (requestFn) { requestRegistry.set(key, requestFn); @@ -25,35 +26,8 @@ export function register(from, to, requestFn, responseFn) { } } -// Lazy load translators (called once on first use) -function ensureInitialized() { - if (initialized) return; - initialized = true; - - // Request translators - sync require pattern for bundler - require("./request/claude-to-openai.js"); - require("./request/openai-to-claude.js"); - require("./request/gemini-to-openai.js"); - require("./request/openai-to-gemini.js"); - require("./request/openai-to-vertex.js"); - require("./request/antigravity-to-openai.js"); - require("./request/openai-responses.js"); - require("./request/openai-to-kiro.js"); - require("./request/openai-to-cursor.js"); - require("./request/openai-to-ollama.js"); - require("./request/openai-to-commandcode.js"); - - // Response translators - require("./response/claude-to-openai.js"); - require("./response/openai-to-claude.js"); - require("./response/gemini-to-openai.js"); - require("./response/openai-to-antigravity.js"); - require("./response/openai-responses.js"); - require("./response/kiro-to-openai.js"); - require("./response/cursor-to-openai.js"); - require("./response/ollama-to-openai.js"); - require("./response/commandcode-to-openai.js"); -} +// No-op: translators self-register via the static imports at the bottom of this file. +function ensureInitialized() {} // Strip specific content types from messages (explicit opt-in via strip[] in PROVIDER_MODELS) function stripContentTypes(body, stripList = []) { @@ -246,7 +220,29 @@ export function initState(sourceFormat) { return base; } -// Initialize all translators (kept for backward compatibility) +// Kept for backward compatibility; translators are already registered at import time. export function initTranslators() { ensureInitialized(); } + +// Static side-effect imports: each module calls register() at load (works in ESM + bundler). +import "./request/claude-to-openai.js"; +import "./request/openai-to-claude.js"; +import "./request/gemini-to-openai.js"; +import "./request/openai-to-gemini.js"; +import "./request/openai-to-vertex.js"; +import "./request/antigravity-to-openai.js"; +import "./request/openai-responses.js"; +import "./request/openai-to-kiro.js"; +import "./request/openai-to-cursor.js"; +import "./request/openai-to-ollama.js"; +import "./request/openai-to-commandcode.js"; +import "./response/claude-to-openai.js"; +import "./response/openai-to-claude.js"; +import "./response/gemini-to-openai.js"; +import "./response/openai-to-antigravity.js"; +import "./response/openai-responses.js"; +import "./response/kiro-to-openai.js"; +import "./response/cursor-to-openai.js"; +import "./response/ollama-to-openai.js"; +import "./response/commandcode-to-openai.js"; diff --git a/open-sse/translator/request/antigravity-to-openai.js b/open-sse/translator/request/antigravity-to-openai.js index cc1a0f7f..b1dbd7bf 100644 --- a/open-sse/translator/request/antigravity-to-openai.js +++ b/open-sse/translator/request/antigravity-to-openai.js @@ -160,7 +160,8 @@ function convertContent(content) { // Function call if (part.functionCall) { toolCalls.push({ - id: part.functionCall.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, + // Deterministic id from name so the matching functionResponse pairs correctly. + id: part.functionCall.id || `call_${part.functionCall.name}`, type: OPENAI_BLOCK.FUNCTION, function: { name: part.functionCall.name, @@ -173,7 +174,7 @@ function convertContent(content) { if (part.functionResponse) { toolResults.push({ role: ROLE.TOOL, - tool_call_id: part.functionResponse.id || part.functionResponse.name, + tool_call_id: part.functionResponse.id || `call_${part.functionResponse.name}`, content: JSON.stringify(part.functionResponse.response?.result || part.functionResponse.response || {}) }); } diff --git a/open-sse/translator/request/gemini-to-openai.js b/open-sse/translator/request/gemini-to-openai.js index 4ac2f221..161ea9be 100644 --- a/open-sse/translator/request/gemini-to-openai.js +++ b/open-sse/translator/request/gemini-to-openai.js @@ -97,8 +97,10 @@ function convertGeminiContent(content) { } if (part.functionCall) { + // Gemini lacks a native call id; derive a deterministic one from the name so the + // matching functionResponse maps to the same tool_call_id (providers require pairing). toolCalls.push({ - id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, + id: part.functionCall.id || `call_${part.functionCall.name}`, type: OPENAI_BLOCK.FUNCTION, function: { name: part.functionCall.name, @@ -110,7 +112,7 @@ function convertGeminiContent(content) { if (part.functionResponse) { return { role: ROLE.TOOL, - tool_call_id: part.functionResponse.id || part.functionResponse.name, + tool_call_id: part.functionResponse.id || `call_${part.functionResponse.name}`, content: JSON.stringify(part.functionResponse.response?.result || part.functionResponse.response || {}) }; } diff --git a/open-sse/translator/request/openai-responses.js b/open-sse/translator/request/openai-responses.js index d25b3d38..0f49c059 100644 --- a/open-sse/translator/request/openai-responses.js +++ b/open-sse/translator/request/openai-responses.js @@ -177,6 +177,12 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials) } // Cleanup Responses API specific fields + // Map Responses-only max_output_tokens to Chat max_tokens (avoid leaking unknown field upstream) + if (result.max_output_tokens !== undefined) { + if (result.max_tokens === undefined) result.max_tokens = result.max_output_tokens; + delete result.max_output_tokens; + } + delete result.input; delete result.instructions; delete result.include; diff --git a/open-sse/translator/request/openai-to-kiro.js b/open-sse/translator/request/openai-to-kiro.js index a8255360..4399f703 100644 --- a/open-sse/translator/request/openai-to-kiro.js +++ b/open-sse/translator/request/openai-to-kiro.js @@ -5,6 +5,7 @@ import { register } from "../index.js"; import { FORMATS } from "../formats.js"; import { v4 as uuidv4 } from "uuid"; +import { resolveSessionId } from "../../utils/sessionManager.js"; import { resolveKiroModel, isThinkingEnabled, @@ -546,7 +547,7 @@ export function openaiToKiroRequest(model, body, stream, credentials) { const payload = { conversationState: { chatTriggerType: "MANUAL", - conversationId: uuidv4(), + conversationId: resolveSessionId({ headers: credentials?.rawHeaders, body, connectionId: credentials?.connectionId, scope: "kiro" }), currentMessage: { userInputMessage: { content: finalContent, diff --git a/open-sse/utils/sessionManager.js b/open-sse/utils/sessionManager.js index 7c08013e..d8663bf5 100644 --- a/open-sse/utils/sessionManager.js +++ b/open-sse/utils/sessionManager.js @@ -79,4 +79,126 @@ export function generateBinaryStyleId() { */ export function clearSessionStore() { runtimeSessionStore.clear(); + assistantSessionStore.clear(); } + +// Conversation-stable session store: Key = hash(scope+assistant text), Value = { sessionId, lastUsed } +const assistantSessionStore = new Map(); +const ASSISTANT_MIN_LEN = 50; +const ASSISTANT_CAP_LEN = 200; +const MAX_ASSISTANT_SESSIONS = 5000; + +// Client headers/body fields that carry an upstream session id (priority order) +const SESSION_HEADER_KEYS = ["x-session-id", "session_id", "x-amp-thread-id", "x-client-request-id"]; +const CLAUDE_CODE_SESSION_RE = /_session_([a-f0-9-]+)$/; + +function sha16(text) { + return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16); +} + +// Normalize a session id candidate (trim, length cap) +function normalizeSessionId(value) { + if (typeof value !== "string") return null; + const v = value.trim(); + if (!v || v.length > 256) return null; + return v; +} + +// Extract Claude Code session id from metadata.user_id (_session_{uuid} | JSON {session_id}) +function extractClaudeCodeSession(userId) { + if (typeof userId !== "string" || !userId) return null; + const m = userId.match(CLAUDE_CODE_SESSION_RE); + if (m) return m[1]; + if (userId[0] === "{") { + try { return normalizeSessionId(JSON.parse(userId)?.session_id); } catch { /* noop */ } + } + return null; +} + +// Lowercase-key lookup for raw client headers +function headerValue(headers, key) { + if (!headers || typeof headers !== "object") return null; + return normalizeSessionId(headers[key] ?? headers[key.toLowerCase()]); +} + +// Read client-provided session id from headers/body (no generation) +function extractClientSessionId(headers, body) { + const claude = extractClaudeCodeSession(body?.metadata?.user_id); + if (claude) return `claude:${claude}`; + for (const key of SESSION_HEADER_KEYS) { + const v = headerValue(headers, key); + if (v) return v; + } + const fromBody = + normalizeSessionId(body?.prompt_cache_key) || + normalizeSessionId(body?.session_id) || + normalizeSessionId(body?.conversation_id) || + normalizeSessionId(body?.metadata?.user_id); + return fromBody || null; +} + +// Accumulate assistant text from OpenAI/Responses-style input/messages (cap-limited) +function accumulateAssistantText(body) { + const items = Array.isArray(body?.input) ? body.input + : Array.isArray(body?.messages) ? body.messages : null; + if (!items) return ""; + let text = ""; + for (const item of items) { + if (item?.role !== "assistant") continue; + if (typeof item.content === "string") text += item.content; + else if (Array.isArray(item.content)) { + for (const c of item.content) text += c?.text || c?.output || ""; + } + if (text.length >= ASSISTANT_CAP_LEN) break; + } + return text; +} + +// Stable session id keyed on accumulated assistant text (avoids collision on identical first user prompt) +function assistantTextSessionId(scope, body) { + const text = accumulateAssistantText(body); + if (text.length < ASSISTANT_MIN_LEN) return null; + const hash = sha16(`${scope}:${text.slice(0, ASSISTANT_CAP_LEN)}`); + const existing = assistantSessionStore.get(hash); + if (existing) { + existing.lastUsed = Date.now(); + return existing.sessionId; + } + if (assistantSessionStore.size >= MAX_ASSISTANT_SESSIONS) { + assistantSessionStore.delete(assistantSessionStore.keys().next().value); + } + const sessionId = generateBinaryStyleId(); + assistantSessionStore.set(hash, { sessionId, lastUsed: Date.now() }); + return sessionId; +} + +/** + * Resolve a conversation-stable session id (generalizes Codex resolveCacheSessionId). + * Priority: client session → accumulated-assistant-text hash → workspaceId → per-connection. + * + * @param {object} opts + * @param {object} [opts.headers] - Raw client request headers (lowercase keys) + * @param {object} [opts.body] - Parsed request body + * @param {string} [opts.connectionId] - Connection identifier (fallback scope) + * @param {string} [opts.workspaceId] - Provider workspace id (account-wide fallback) + * @param {string} [opts.scope] - Provider scope to isolate cache keys across providers + * @returns {string} A stable session id + */ +export function resolveSessionId({ headers, body, connectionId, workspaceId, scope = "" } = {}) { + const client = extractClientSessionId(headers, body); + if (client) return client; + const fromAssistant = assistantTextSessionId(`${scope}:${connectionId || ""}`, body); + if (fromAssistant) return fromAssistant; + const ws = normalizeSessionId(workspaceId); + if (ws) return ws; + return deriveSessionId(connectionId); +} + +// Cleanup expired assistant-session entries +const assistantCleanup = setInterval(() => { + const now = Date.now(); + for (const [key, entry] of assistantSessionStore) { + if (now - entry.lastUsed > MEMORY_CONFIG.sessionTtlMs) assistantSessionStore.delete(key); + } +}, MEMORY_CONFIG.sessionCleanupIntervalMs); +if (assistantCleanup.unref) assistantCleanup.unref(); diff --git a/src/app/(dashboard)/dashboard/providers/page.js b/src/app/(dashboard)/dashboard/providers/page.js index 3218f252..53581ccc 100644 --- a/src/app/(dashboard)/dashboard/providers/page.js +++ b/src/app/(dashboard)/dashboard/providers/page.js @@ -284,24 +284,26 @@ export default function ProvidersPage() { const freeEntries = Object.entries(FREE_PROVIDERS).filter( ([, info]) => !info.hidden && matchSearch(info.name), ); - const freeTierEntries = Object.entries(FREE_TIER_PROVIDERS) - .filter(([, info]) => !info.hidden && matchSearch(info.name)) - .sort(([, a], [, b]) => { - // hasFree providers first, then by priority - const fa = a.hasFree ? 0 : 1; - const fb = b.hasFree ? 0 : 1; - if (fa !== fb) return fa - fb; - return (a.priority ?? 999) - (b.priority ?? 999); - }); - const apikeyEntries = sortByPriority( - Object.entries(APIKEY_PROVIDERS).filter( + const freeTierEntries = sortByPriority( + Object.entries(FREE_TIER_PROVIDERS).filter( + ([, info]) => !info.hidden && matchSearch(info.name), + ), + "freeTier", + ); + // API Key: connected providers first, then alphabetical by name + const apikeyEntries = Object.entries(APIKEY_PROVIDERS) + .filter( ([, info]) => !info.hidden && (info.serviceKinds ?? ["llm"]).includes("llm") && matchSearch(info.name), - ), - "apikey", - ); + ) + .sort(([ka, a], [kb, b]) => { + const ca = getProviderStats(ka, "apikey").total > 0 ? 0 : 1; + const cb = getProviderStats(kb, "apikey").total > 0 ? 0 : 1; + if (ca !== cb) return ca - cb; + return (a.name || "").localeCompare(b.name || ""); + }); const isApikeySearching = !!searchQuery.trim(); const visibleApikeyEntries = isApikeySearching || showAllApikey diff --git a/src/app/api/pricing/route.js b/src/app/api/pricing/route.js index 7d553a4c..18a8584c 100644 --- a/src/app/api/pricing/route.js +++ b/src/app/api/pricing/route.js @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { getPricing, updatePricing, resetPricing, resetAllPricing } from "@/lib/localDb.js"; -import { getDefaultPricing } from "@/shared/constants/pricing.js"; +import { getDefaultPricing } from "open-sse/providers/pricing.js"; /** * GET /api/pricing diff --git a/src/lib/db/repos/pricingRepo.js b/src/lib/db/repos/pricingRepo.js index 8b39295c..6467274b 100644 --- a/src/lib/db/repos/pricingRepo.js +++ b/src/lib/db/repos/pricingRepo.js @@ -20,7 +20,7 @@ export async function getPricing() { if (cache.value && cache.expiresAt > now) return cache.value; const userPricing = await getUserPricing(); - const { PROVIDER_PRICING } = await import("@/shared/constants/pricing.js"); + const { PROVIDER_PRICING } = await import("open-sse/providers/pricing.js"); const merged = {}; for (const [provider, models] of Object.entries(PROVIDER_PRICING)) { @@ -52,7 +52,7 @@ export async function getPricingForModel(provider, model) { if (!model) return null; const userPricing = await getUserPricing(); if (provider && userPricing[provider]?.[model]) return userPricing[provider][model]; - const { getPricingForModel: resolveConst } = await import("@/shared/constants/pricing.js"); + const { getPricingForModel: resolveConst } = await import("open-sse/providers/pricing.js"); return resolveConst(provider, model); } diff --git a/src/shared/components/PricingModal.js b/src/shared/components/PricingModal.js index ad21725d..1e89231e 100644 --- a/src/shared/components/PricingModal.js +++ b/src/shared/components/PricingModal.js @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect } from "react"; -import { getDefaultPricing, formatCost } from "@/shared/constants/pricing.js"; +import { getDefaultPricing, formatCost } from "open-sse/providers/pricing.js"; export default function PricingModal({ isOpen, onClose, onSave }) { const [pricingData, setPricingData] = useState({}); diff --git a/src/shared/constants/providers.js b/src/shared/constants/providers.js index eafaa8e0..dc729454 100644 --- a/src/shared/constants/providers.js +++ b/src/shared/constants/providers.js @@ -19,6 +19,7 @@ function buildProviderEntry(r) { ...(r.display || {}), id: r.id, alias: r.uiAlias || r.alias, + ...(r.hidden ? { hidden: true } : {}), ...mediaFields, ...(r.priority !== undefined ? { priority: r.priority } : {}), ...(r.hasFree ? { hasFree: true } : {}), diff --git a/src/shared/constants/providersDisplay.js b/src/shared/constants/providersDisplay.js index 9f848daf..cd1d54a3 100644 --- a/src/shared/constants/providersDisplay.js +++ b/src/shared/constants/providersDisplay.js @@ -1,238 +1,12 @@ -// UI display config — registry providers derive from registry.display. -// Non-registry providers (media-only: tts, stt, search, fetch) kept hardcoded here. +// UI display config — all providers derive from registry.display. import REGISTRY from "open-sse/providers/registry/index.js"; export const RISK_NOTICE = "⚠️ Risk Notice: This provider uses a subscription/OAuth session not officially licensed for proxy/router use. Account may be restricted or banned. Use at your own risk."; -// Non-registry media-only providers display config -const MEDIA_ONLY_DISPLAY = { - "elevenlabs": { - "name": "ElevenLabs", - "icon": "record_voice_over", - "color": "#6C47FF", - "textIcon": "EL", - "website": "https://elevenlabs.io", - "notice": { - "apiKeyUrl": "https://elevenlabs.io/app/settings/api-keys" - } - }, - "cartesia": { - "name": "Cartesia", - "icon": "spatial_audio", - "color": "#FF4F8B", - "textIcon": "CA", - "website": "https://cartesia.ai", - "notice": { - "apiKeyUrl": "https://play.cartesia.ai/keys" - }, - "hidden": true - }, - "playht": { - "name": "PlayHT", - "icon": "play_circle", - "color": "#00B4D8", - "textIcon": "PH", - "website": "https://play.ht", - "notice": { - "apiKeyUrl": "https://play.ht/studio/api-access" - }, - "hidden": true - }, - "local-device": { - "name": "Local Device", - "icon": "speaker", - "color": "#64748B", - "textIcon": "LD", - "mediaPriority": 5 - }, - "google-tts": { - "name": "Google TTS", - "icon": "record_voice_over", - "color": "#4285F4", - "textIcon": "GT", - "mediaPriority": 5 - }, - "edge-tts": { - "name": "Edge TTS", - "icon": "record_voice_over", - "color": "#0078D4", - "textIcon": "ET", - "mediaPriority": 5 - }, - "coqui": { - "name": "Coqui TTS", - "icon": "record_voice_over", - "color": "#10B981", - "textIcon": "CQ", - "website": "https://github.com/coqui-ai/TTS", - "hidden": true - }, - "tortoise": { - "name": "Tortoise TTS", - "icon": "record_voice_over", - "color": "#7C3AED", - "textIcon": "TT", - "website": "https://github.com/neonbjb/tortoise-tts", - "hidden": true - }, - "inworld": { - "name": "Inworld TTS", - "icon": "record_voice_over", - "color": "#FF6B6B", - "textIcon": "IW", - "website": "https://inworld.ai", - "notice": { - "text": "Free tier: 40 minutes/month TTS. Paid: TTS-1.5 Mini $0.01/min ($15/1M chars), TTS-1.5 Max $0.025/min ($30/1M chars). 270+ voices, 15 languages.", - "apiKeyUrl": "https://platform.inworld.ai/api-keys" - } - }, - "aws-polly": { - "name": "AWS Polly", - "icon": "record_voice_over", - "color": "#FF9900", - "textIcon": "PL", - "website": "https://aws.amazon.com/polly/", - "notice": { - "text": "Use AWS Secret Access Key as API key; set providerSpecificData.accessKeyId and optional region.", - "apiKeyUrl": "https://console.aws.amazon.com/iam/home#/security_credentials" - } - }, - "jina-ai": { - "name": "Jina AI", - "icon": "blur_on", - "color": "#2563EB", - "textIcon": "JA", - "website": "https://jina.ai", - "notice": { - "text": "10M free tokens on signup (non-commercial), no credit card required.", - "apiKeyUrl": "https://jina.ai/?sui=apikey" - } - }, - "jina-reader": { - "name": "Jina Reader", - "icon": "menu_book", - "color": "#000000", - "textIcon": "JR", - "website": "https://jina.ai/reader", - "notice": { - "apiKeyUrl": "https://jina.ai/?sui=apikey" - } - }, - "tavily": { - "name": "Tavily", - "icon": "search", - "color": "#5B21B6", - "textIcon": "TV", - "website": "https://tavily.com", - "notice": { - "apiKeyUrl": "https://app.tavily.com/home" - } - }, - "brave-search": { - "name": "Brave Search", - "icon": "travel_explore", - "color": "#FB542B", - "textIcon": "BR", - "website": "https://brave.com/search/api", - "notice": { - "apiKeyUrl": "https://api-dashboard.search.brave.com/app/keys" - } - }, - "serper": { - "name": "Serper", - "icon": "search", - "color": "#4F46E5", - "textIcon": "SP", - "website": "https://serper.dev", - "notice": { - "apiKeyUrl": "https://serper.dev/api-key" - } - }, - "exa": { - "name": "Exa", - "icon": "manage_search", - "color": "#2563EB", - "textIcon": "EX", - "website": "https://exa.ai", - "notice": { - "apiKeyUrl": "https://dashboard.exa.ai/api-keys" - } - }, - "searxng": { - "name": "SearXNG", - "icon": "saved_search", - "color": "#3B82F6", - "textIcon": "SX", - "website": "https://docs.searxng.org" - }, - "google-pse": { - "name": "Google PSE", - "icon": "search", - "color": "#4285F4", - "textIcon": "GP", - "website": "https://programmablesearchengine.google.com", - "notice": { - "apiKeyUrl": "https://programmablesearchengine.google.com/controlpanel/create" - } - }, - "linkup": { - "name": "Linkup", - "icon": "link", - "color": "#0EA5E9", - "textIcon": "LK", - "website": "https://linkup.so", - "notice": { - "apiKeyUrl": "https://app.linkup.so/api-keys" - } - }, - "searchapi": { - "name": "SearchAPI", - "icon": "search", - "color": "#0EA5A4", - "textIcon": "SA", - "website": "https://www.searchapi.io", - "notice": { - "apiKeyUrl": "https://www.searchapi.io/dashboard" - } - }, - "youcom": { - "name": "You.com Search", - "icon": "search", - "color": "#7C3AED", - "textIcon": "YC", - "website": "https://you.com", - "notice": { - "apiKeyUrl": "https://api.you.com" - } - }, - "firecrawl": { - "name": "Firecrawl", - "icon": "local_fire_department", - "color": "#F59E0B", - "textIcon": "FC", - "website": "https://firecrawl.dev", - "notice": { - "apiKeyUrl": "https://www.firecrawl.dev/app/api-keys" - } - }, - "topaz": { - "name": "Topaz", - "icon": "image", - "color": "#059669", - "textIcon": "TP", - "website": "https://topazlabs.com", - "notice": { - "apiKeyUrl": "https://topazlabs.com/account" - } - }, -}; - // Resolve "RISK_NOTICE" token → real notice text (registry stores token to avoid import cycle) const resolveDisplay = (d) => d.deprecationNotice === "RISK_NOTICE" ? { ...d, deprecationNotice: RISK_NOTICE } : d; -// Merge: registry providers take precedence -export const PROVIDER_DISPLAY = { - ...MEDIA_ONLY_DISPLAY, - ...Object.fromEntries(REGISTRY.filter(r => r.display).map(r => [r.id, resolveDisplay(r.display)])), -}; +export const PROVIDER_DISPLAY = Object.fromEntries( + REGISTRY.filter((r) => r.display).map((r) => [r.id, resolveDisplay(r.display)]), +); diff --git a/tests/__baseline__/current.json b/tests/__baseline__/current.json index 52cab674..d8a2e228 100644 --- a/tests/__baseline__/current.json +++ b/tests/__baseline__/current.json @@ -1 +1 @@ -{"numTotalTestSuites":253,"numPassedTestSuites":236,"numFailedTestSuites":17,"numPendingTestSuites":0,"numTotalTests":787,"numPassedTests":741,"numFailedTests":26,"numPendingTests":20,"numTodoTests":0,"snapshot":{"added":0,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":99,"total":99,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0,"didUpdate":false},"startTime":1781448826369,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["Antigravity → OpenAI"],"fullName":"Antigravity → OpenAI functionResponse + functionCall in same content keeps both","status":"passed","title":"functionResponse + functionCall in same content keeps both","duration":46.134209,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity → OpenAI"],"fullName":"Antigravity → OpenAI functionCall without id keeps a stable matchable id","status":"passed","title":"functionCall without id keeps a stable matchable id","duration":0.7979580000000226,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity → OpenAI"],"fullName":"Antigravity → OpenAI signature-only part does not produce empty text","status":"passed","title":"signature-only part does not produce empty text","duration":0.15758299999998826,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829961,"endTime":1781448830008.1575,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-antigravity.test.js"},{"assertionResults":[{"ancestorTitles":["Claude Code CLI context → OpenAI"],"fullName":"Claude Code CLI context → OpenAI system array keeps all text parts","status":"passed","title":"system array keeps all text parts","duration":38.78233300000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Claude Code CLI context → OpenAI"],"fullName":"Claude Code CLI context → OpenAI assistant thinking block survives Claude→Claude passthrough","status":"passed","title":"assistant thinking block survives Claude→Claude passthrough","duration":0.3607499999999959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Claude Code CLI context → OpenAI"],"fullName":"Claude Code CLI context → OpenAI redacted_thinking block is not silently dropped","status":"passed","title":"redacted_thinking block is not silently dropped","duration":2.3989160000000425,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Claude Code CLI context → OpenAI"],"fullName":"Claude Code CLI context → OpenAI tool_result image block is preserved","status":"passed","title":"tool_result image block is preserved","duration":0.7824999999999704,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829655,"endTime":1781448829697.7825,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-claudeCode-context.test.js"},{"assertionResults":[{"ancestorTitles":["Codex CLI Responses → OpenAI"],"fullName":"Codex CLI Responses → OpenAI assistant has no empty tool_calls array when all names are empty","status":"passed","title":"assistant has no empty tool_calls array when all names are empty","duration":39.25316600000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex CLI Responses → OpenAI"],"fullName":"Codex CLI Responses → OpenAI function_call arguments end up as a string","status":"passed","title":"function_call arguments end up as a string","duration":1.7877090000000067,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex CLI Responses → OpenAI"],"fullName":"Codex CLI Responses → OpenAI input_image with file_id is not used as a raw url","status":"passed","title":"input_image with file_id is not used as a raw url","duration":2.1281659999999647,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Codex Responses (reverse)"],"fullName":"OpenAI → Codex Responses (reverse) call_id longer than 64 chars is clamped","status":"passed","title":"call_id longer than 64 chars is clamped","duration":0.3876659999999674,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829436,"endTime":1781448829480.3877,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-codexCli-responses.test.js"},{"assertionResults":[{"ancestorTitles":["OpenAI → Gemini"],"fullName":"OpenAI → Gemini multiple system messages are all kept","status":"passed","title":"multiple system messages are all kept","duration":34.220665999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Cursor"],"fullName":"OpenAI → Cursor image content is preserved","status":"passed","title":"image content is preserved","duration":0.5820420000000013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Cursor"],"fullName":"OpenAI → Cursor respects client max_tokens","status":"passed","title":"respects client max_tokens","duration":1.018542000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → CommandCode"],"fullName":"OpenAI → CommandCode malformed tool arguments are not silently emptied","status":"passed","title":"malformed tool arguments are not silently emptied","duration":2.457790999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → CommandCode"],"fullName":"OpenAI → CommandCode image content is preserved","status":"passed","title":"image content is preserved","duration":1.5786669999999958,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829314,"endTime":1781448829354.5786,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-gemini-cursor-commandcode.test.js"},{"assertionResults":[{"ancestorTitles":["OpenAI → Kiro"],"fullName":"OpenAI → Kiro malformed tool arguments do not throw the whole request","status":"passed","title":"malformed tool arguments do not throw the whole request","duration":47.7655,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Kiro"],"fullName":"OpenAI → Kiro respects client max_tokens","status":"passed","title":"respects client max_tokens","duration":4.433667000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Kiro"],"fullName":"OpenAI → Kiro remote image url is preserved as an image, not text","status":"passed","title":"remote image url is preserved as an image, not text","duration":1.420500000000004,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829615,"endTime":1781448829669.4204,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-kiro.test.js"},{"assertionResults":[{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss image with source.type=url is preserved (NOT dropped)","status":"passed","title":"image with source.type=url is preserved (NOT dropped)","duration":48.50266600000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss thinking block survives round-trip Claude→OpenAI→Claude","status":"passed","title":"thinking block survives round-trip Claude→OpenAI→Claude","duration":0.4353339999999548,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss tool_result with image block is not turned into raw JSON / dropped","status":"passed","title":"tool_result with image block is not turned into raw JSON / dropped","duration":0.7052079999999705,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss tool_result is_error flag is preserved","status":"passed","title":"tool_result is_error flag is preserved","duration":0.7304999999999495,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss system array non-text parts are not silently dropped","status":"passed","title":"system array non-text parts are not silently dropped","duration":0.3050000000000068,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: tool_call id stability across bridge"],"fullName":"bug: tool_call id stability across bridge sanitized tool id stays matched between call and result","status":"passed","title":"sanitized tool id stays matched between call and result","duration":0.17379099999999426,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: empty content message handling"],"fullName":"bug: empty content message handling assistant message with only tool_calls is not dropped","status":"passed","title":"assistant message with only tool_calls is not dropped","duration":0.09483399999999165,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829919,"endTime":1781448829970.0947,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-openai-bridge.test.js"},{"assertionResults":[{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping does not inject Claude Code system prompt for compatible providers","status":"passed","title":"does not inject Claude Code system prompt for compatible providers","duration":39.40537499999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping assistant reasoning_content becomes a thinking block","status":"passed","title":"assistant reasoning_content becomes a thinking block","duration":1.416749999999979,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping tool_choice=none is not turned into auto","status":"passed","title":"tool_choice=none is not turned into auto","duration":0.5657909999999902,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping input_audio content is preserved","status":"passed","title":"input_audio content is preserved","duration":0.6144589999999539,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping remote http image_url is preserved","status":"passed","title":"remote http image_url is preserved","duration":1.1070000000000277,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829542,"endTime":1781448829586.107,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-toClaude-context.test.js"},{"assertionResults":[{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'alicode-intl': all models OpenAI→target","status":"passed","title":"'alicode-intl': all models OpenAI→target","duration":53.20150000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'alicode': all models OpenAI→target","status":"passed","title":"'alicode': all models OpenAI→target","duration":0.928875000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'anthropic': all models OpenAI→target","status":"passed","title":"'anthropic': all models OpenAI→target","duration":1.6289999999999623,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'ag': all models OpenAI→target","status":"passed","title":"'ag': all models OpenAI→target","duration":3.467708000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'assemblyai': all models OpenAI→target","status":"passed","title":"'assemblyai': all models OpenAI→target","duration":0.18849999999997635,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'black-forest-labs': all models OpenAI→target","status":"passed","title":"'black-forest-labs': all models OpenAI→target","duration":0.13054100000005064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'blackbox': all models OpenAI→target","status":"passed","title":"'blackbox': all models OpenAI→target","duration":0.27987499999994725,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'byteplus': all models OpenAI→target","status":"passed","title":"'byteplus': all models OpenAI→target","duration":0.1199579999999969,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cerebras': all models OpenAI→target","status":"passed","title":"'cerebras': all models OpenAI→target","duration":0.4177090000000021,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cc': all models OpenAI→target","status":"passed","title":"'cc': all models OpenAI→target","duration":1.1243329999999787,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cl': all models OpenAI→target","status":"passed","title":"'cl': all models OpenAI→target","duration":4.137667000000022,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cloudflare-ai': all models OpenAI→target","status":"passed","title":"'cloudflare-ai': all models OpenAI→target","duration":0.9026250000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cx': all models OpenAI→target","status":"passed","title":"'cx': all models OpenAI→target","duration":1.5753750000000082,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cohere': all models OpenAI→target","status":"passed","title":"'cohere': all models OpenAI→target","duration":0.21325000000001637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'comfyui': all models OpenAI→target","status":"passed","title":"'comfyui': all models OpenAI→target","duration":0.06816600000001927,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'commandcode': all models OpenAI→target","status":"passed","title":"'commandcode': all models OpenAI→target","duration":1.7166669999999726,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cu': all models OpenAI→target","status":"passed","title":"'cu': all models OpenAI→target","duration":0.5188750000000368,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'deepgram': all models OpenAI→target","status":"passed","title":"'deepgram': all models OpenAI→target","duration":0.0750829999999496,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'deepseek': all models OpenAI→target","status":"passed","title":"'deepseek': all models OpenAI→target","duration":0.08566700000000083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'fal-ai': all models OpenAI→target","status":"passed","title":"'fal-ai': all models OpenAI→target","duration":0.09504199999997809,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'fireworks': all models OpenAI→target","status":"passed","title":"'fireworks': all models OpenAI→target","duration":0.059041999999976724,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gc': all models OpenAI→target","status":"passed","title":"'gc': all models OpenAI→target","duration":0.23491699999999582,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gemini': all models OpenAI→target","status":"passed","title":"'gemini': all models OpenAI→target","duration":0.400874999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gh': all models OpenAI→target","status":"passed","title":"'gh': all models OpenAI→target","duration":1.6774169999999913,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'glm-cn': all models OpenAI→target","status":"passed","title":"'glm-cn': all models OpenAI→target","duration":0.08279199999998355,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'glm': all models OpenAI→target","status":"passed","title":"'glm': all models OpenAI→target","duration":0.10541599999999107,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'grok-web': all models OpenAI→target","status":"passed","title":"'grok-web': all models OpenAI→target","duration":0.11112499999995862,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'groq': all models OpenAI→target","status":"passed","title":"'groq': all models OpenAI→target","duration":0.08174999999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'huggingface': all models OpenAI→target","status":"passed","title":"'huggingface': all models OpenAI→target","duration":0.05583400000000438,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'hyperbolic': all models OpenAI→target","status":"passed","title":"'hyperbolic': all models OpenAI→target","duration":0.09266700000000583,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'if': all models OpenAI→target","status":"passed","title":"'if': all models OpenAI→target","duration":0.15629200000000765,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'kc': all models OpenAI→target","status":"passed","title":"'kc': all models OpenAI→target","duration":0.09500000000002728,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'kmc': all models OpenAI→target","status":"passed","title":"'kmc': all models OpenAI→target","duration":0.0772909999999456,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'kimi': all models OpenAI→target","status":"passed","title":"'kimi': all models OpenAI→target","duration":0.07141599999999926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'kr': all models OpenAI→target","status":"passed","title":"'kr': all models OpenAI→target","duration":1.2562500000000227,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'mmf': all models OpenAI→target","status":"passed","title":"'mmf': all models OpenAI→target","duration":0.0362920000000031,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'minimax-cn': all models OpenAI→target","status":"passed","title":"'minimax-cn': all models OpenAI→target","duration":0.16954199999997854,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'minimax': all models OpenAI→target","status":"passed","title":"'minimax': all models OpenAI→target","duration":0.16416700000002038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'mistral': all models OpenAI→target","status":"passed","title":"'mistral': all models OpenAI→target","duration":0.0542920000000322,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'nanobanana': all models OpenAI→target","status":"passed","title":"'nanobanana': all models OpenAI→target","duration":0.03479200000003857,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'nebius': all models OpenAI→target","status":"passed","title":"'nebius': all models OpenAI→target","duration":0.034958000000017364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'nvidia': all models OpenAI→target","status":"passed","title":"'nvidia': all models OpenAI→target","duration":0.06925000000001091,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'ollama': all models OpenAI→target","status":"passed","title":"'ollama': all models OpenAI→target","duration":0.2847500000000309,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openai': all models OpenAI→target","status":"passed","title":"'openai': all models OpenAI→target","duration":0.34679199999999355,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'opencode-go': all models OpenAI→target","status":"passed","title":"'opencode-go': all models OpenAI→target","duration":0.11679199999997536,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openrouter': all models OpenAI→target","status":"passed","title":"'openrouter': all models OpenAI→target","duration":0.13858300000003965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'perplexity-web': all models OpenAI→target","status":"passed","title":"'perplexity-web': all models OpenAI→target","duration":0.06900000000001683,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'perplexity': all models OpenAI→target","status":"passed","title":"'perplexity': all models OpenAI→target","duration":0.11487499999998363,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'qd': all models OpenAI→target","status":"passed","title":"'qd': all models OpenAI→target","duration":0.1257500000000391,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'qw': all models OpenAI→target","status":"passed","title":"'qw': all models OpenAI→target","duration":0.050583000000017364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'recraft': all models OpenAI→target","status":"passed","title":"'recraft': all models OpenAI→target","duration":0.034500000000036835,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'runwayml': all models OpenAI→target","status":"passed","title":"'runwayml': all models OpenAI→target","duration":0.06920800000000327,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'sdwebui': all models OpenAI→target","status":"passed","title":"'sdwebui': all models OpenAI→target","duration":0.03270799999995688,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'siliconflow': all models OpenAI→target","status":"passed","title":"'siliconflow': all models OpenAI→target","duration":0.15399999999999636,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'stability-ai': all models OpenAI→target","status":"passed","title":"'stability-ai': all models OpenAI→target","duration":0.05879099999998516,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'together': all models OpenAI→target","status":"passed","title":"'together': all models OpenAI→target","duration":0.06758300000001327,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'vertex-partner': all models OpenAI→target","status":"passed","title":"'vertex-partner': all models OpenAI→target","duration":0.05012499999997999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'vertex': all models OpenAI→target","status":"passed","title":"'vertex': all models OpenAI→target","duration":0.8507920000000126,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'volcengine-ark': all models OpenAI→target","status":"passed","title":"'volcengine-ark': all models OpenAI→target","duration":0.9757089999999948,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'voyage-ai': all models OpenAI→target","status":"passed","title":"'voyage-ai': all models OpenAI→target","duration":0.2681660000000079,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'xai': all models OpenAI→target","status":"passed","title":"'xai': all models OpenAI→target","duration":0.0761669999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'xiaomi-mimo': all models OpenAI→target","status":"passed","title":"'xiaomi-mimo': all models OpenAI→target","duration":0.0671249999999759,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'xiaomi-tokenplan': all models OpenAI→target","status":"passed","title":"'xiaomi-tokenplan': all models OpenAI→target","duration":0.11366700000002083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openai-tts-models': all models OpenAI→target","status":"passed","title":"'openai-tts-models': all models OpenAI→target","duration":0.052042000000028565,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openai-tts-voices': all models OpenAI→target","status":"passed","title":"'openai-tts-voices': all models OpenAI→target","duration":0.1297089999999912,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openrouter-tts-models': all models OpenAI→target","status":"passed","title":"'openrouter-tts-models': all models OpenAI→target","duration":0.042792000000019925,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openrouter-tts-voices': all models OpenAI→target","status":"passed","title":"'openrouter-tts-voices': all models OpenAI→target","duration":0.14125000000001364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'elevenlabs-tts-models': all models OpenAI→target","status":"passed","title":"'elevenlabs-tts-models': all models OpenAI→target","duration":0.050292000000013104,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'edge-tts': all models OpenAI→target","status":"passed","title":"'edge-tts': all models OpenAI→target","duration":0.11687499999999318,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'local-device': all models OpenAI→target","status":"passed","title":"'local-device': all models OpenAI→target","duration":0.6371669999999767,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'google-tts': all models OpenAI→target","status":"passed","title":"'google-tts': all models OpenAI→target","duration":8.20350000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gemini-tts-models': all models OpenAI→target","status":"passed","title":"'gemini-tts-models': all models OpenAI→target","duration":0.1797499999999559,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gemini-tts-voices': all models OpenAI→target","status":"passed","title":"'gemini-tts-voices': all models OpenAI→target","duration":1.7296249999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: image-strip models drop image content"],"fullName":"coverage: image-strip models drop image content 'kr'/'deepseek-3.2' strips image when strip=[image]","status":"passed","title":"'kr'/'deepseek-3.2' strips image when strip=[image]","duration":0.27224999999998545,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: image-strip models drop image content"],"fullName":"coverage: image-strip models drop image content 'kr'/'qwen3-coder-next' strips image when strip=[image]","status":"passed","title":"'kr'/'qwen3-coder-next' strips image when strip=[image]","duration":0.05612500000000864,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829356,"endTime":1781448829448.2722,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/coverage-all-models.test.js"},{"assertionResults":[{"ancestorTitles":["roundtrip: Claude source preserves core fields → OpenAI"],"fullName":"roundtrip: Claude source preserves core fields → OpenAI system → system role","status":"passed","title":"system → system role","duration":1.0347079999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: Claude source preserves core fields → OpenAI"],"fullName":"roundtrip: Claude source preserves core fields → OpenAI tool_use → assistant.tool_calls with matching id","status":"passed","title":"tool_use → assistant.tool_calls with matching id","duration":0.27962500000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: Claude source preserves core fields → OpenAI"],"fullName":"roundtrip: Claude source preserves core fields → OpenAI tool_result → tool message with matching id","status":"passed","title":"tool_result → tool message with matching id","duration":0.16370900000001143,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: Claude source preserves core fields → OpenAI"],"fullName":"roundtrip: Claude source preserves core fields → OpenAI tool arguments are valid JSON string","status":"passed","title":"tool arguments are valid JSON string","duration":0.45658299999999485,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: OpenAI tools → Claude → keeps tool name"],"fullName":"roundtrip: OpenAI tools → Claude → keeps tool name tool name survives openai→claude","status":"passed","title":"tool name survives openai→claude","duration":0.13500000000001933,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: parallel tool calls keep distinct ids"],"fullName":"roundtrip: parallel tool calls keep distinct ids two tool_calls, two distinct ids","status":"passed","title":"two tool_calls, two distinct ids","duration":0.08233299999997712,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: parallel tool calls keep distinct ids"],"fullName":"roundtrip: parallel tool calls keep distinct ids each tool_call has a matching tool result","status":"passed","title":"each tool_call has a matching tool result","duration":0.10720800000001418,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830910,"endTime":1781448830913.1072,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/format-roundtrip.test.js"},{"assertionResults":[{"ancestorTitles":["GOLDEN request: OpenAI → Claude"],"fullName":"GOLDEN request: OpenAI → Claude full body (system/image/tool/tool_result)","status":"passed","title":"full body (system/image/tool/tool_result)","duration":43.583541,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN request: OpenAI → Claude"],"fullName":"GOLDEN request: OpenAI → Claude reasoning_effort → thinking budget","status":"passed","title":"reasoning_effort → thinking budget","duration":0.7155839999999785,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN request: OpenAI → Gemini"],"fullName":"GOLDEN request: OpenAI → Gemini full body (system/image/tool/tool_result)","status":"passed","title":"full body (system/image/tool/tool_result)","duration":2.36666699999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN request: OpenAI → Kiro"],"fullName":"GOLDEN request: OpenAI → Kiro full body (image base64 + tool_result)","status":"passed","title":"full body (image base64 + tool_result)","duration":2.4541249999999764,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829839,"endTime":1781448829888.454,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/golden-request.test.js"},{"assertionResults":[{"ancestorTitles":["GOLDEN response stream: Claude → OpenAI"],"fullName":"GOLDEN response stream: Claude → OpenAI text + thinking + tool_use + usage + finish","status":"passed","title":"text + thinking + tool_use + usage + finish","duration":44.21562499999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Gemini → OpenAI"],"fullName":"GOLDEN response stream: Gemini → OpenAI text + thought(no-sig) + functionCall + usage + finish","status":"passed","title":"text + thought(no-sig) + functionCall + usage + finish","duration":2.174915999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Gemini → OpenAI"],"fullName":"GOLDEN response stream: Gemini → OpenAI image output (inlineData → delta.images)","status":"passed","title":"image output (inlineData → delta.images)","duration":0.9732079999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Kiro → OpenAI"],"fullName":"GOLDEN response stream: Kiro → OpenAI text + reasoning + toolUse + usage + stop","status":"passed","title":"text + reasoning + toolUse + usage + stop","duration":1.118750000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Ollama → OpenAI"],"fullName":"GOLDEN response stream: Ollama → OpenAI content + thinking + tool_calls + done usage","status":"passed","title":"content + thinking + tool_calls + done usage","duration":0.6188329999999951,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: OpenAI-Responses (codex) → OpenAI"],"fullName":"GOLDEN response stream: OpenAI-Responses (codex) → OpenAI text + reasoning + tool_call + completed usage","status":"passed","title":"text + reasoning + tool_call + completed usage","duration":0.5160410000000297,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: OpenAI-Responses (codex) → OpenAI"],"fullName":"GOLDEN response stream: OpenAI-Responses (codex) → OpenAI error event → error chunk (fallback id/created)","status":"passed","title":"error event → error chunk (fallback id/created)","duration":0.14674999999999727,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829348,"endTime":1781448829398.516,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/golden-response-stream.test.js"},{"assertionResults":[{"ancestorTitles":["GOLDEN response stream: CommandCode → OpenAI"],"fullName":"GOLDEN response stream: CommandCode → OpenAI text + reasoning + tool + finish-step usage","status":"passed","title":"text + reasoning + tool + finish-step usage","duration":39.42795799999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Kiro → OpenAI (finish after tool)"],"fullName":"GOLDEN response stream: Kiro → OpenAI (finish after tool) toolUse then stop — lock current finish_reason behavior","status":"passed","title":"toolUse then stop — lock current finish_reason behavior","duration":0.5800000000000409,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Ollama → OpenAI (finish after tool)"],"fullName":"GOLDEN response stream: Ollama → OpenAI (finish after tool) tool_calls then done_reason=stop — lock current finish_reason","status":"passed","title":"tool_calls then done_reason=stop — lock current finish_reason","duration":0.39100000000001955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN passthrough: same format = no translation"],"fullName":"GOLDEN passthrough: same format = no translation response openai→openai returns chunk unchanged","status":"passed","title":"response openai→openai returns chunk unchanged","duration":0.3667909999999779,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN passthrough: same format = no translation"],"fullName":"GOLDEN passthrough: same format = no translation request openai→openai keeps messages (filterToOpenAIFormat normalize)","status":"passed","title":"request openai→openai keeps messages (filterToOpenAIFormat normalize)","duration":0.5475829999999746,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN usage math: Claude prompt = input + cache (lock)"],"fullName":"GOLDEN usage math: Claude prompt = input + cache (lock) prompt_tokens sums input + cache_read + cache_creation","status":"passed","title":"prompt_tokens sums input + cache_read + cache_creation","duration":2.714917000000014,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829686,"endTime":1781448829729.7148,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/golden-translator-concerns.test.js"},{"assertionResults":[{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) alicode → url (stream + non-stream)","status":"passed","title":"alicode → url (stream + non-stream)","duration":2.7092910000000074,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) alicode-intl → url (stream + non-stream)","status":"passed","title":"alicode-intl → url (stream + non-stream)","duration":0.3195829999999944,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) anthropic → url (stream + non-stream)","status":"passed","title":"anthropic → url (stream + non-stream)","duration":0.1812919999999849,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) assemblyai → url (stream + non-stream)","status":"passed","title":"assemblyai → url (stream + non-stream)","duration":0.1732499999999959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) blackbox → url (stream + non-stream)","status":"passed","title":"blackbox → url (stream + non-stream)","duration":0.15874999999999773,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) byteplus → url (stream + non-stream)","status":"passed","title":"byteplus → url (stream + non-stream)","duration":0.09708299999999781,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) cerebras → url (stream + non-stream)","status":"passed","title":"cerebras → url (stream + non-stream)","duration":0.13254200000000083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) chutes → url (stream + non-stream)","status":"passed","title":"chutes → url (stream + non-stream)","duration":0.0855410000000063,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) claude → url (stream + non-stream)","status":"passed","title":"claude → url (stream + non-stream)","duration":0.6682920000000081,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) cline → url (stream + non-stream)","status":"passed","title":"cline → url (stream + non-stream)","duration":0.1505409999999756,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) cloudflare-ai → url (stream + non-stream)","status":"passed","title":"cloudflare-ai → url (stream + non-stream)","duration":0.13158300000000622,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) codebuddy → url (stream + non-stream)","status":"passed","title":"codebuddy → url (stream + non-stream)","duration":0.05462500000001569,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) cohere → url (stream + non-stream)","status":"passed","title":"cohere → url (stream + non-stream)","duration":0.04604100000000244,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) deepgram → url (stream + non-stream)","status":"passed","title":"deepgram → url (stream + non-stream)","duration":0.03991600000000517,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) deepseek → url (stream + non-stream)","status":"passed","title":"deepseek → url (stream + non-stream)","duration":0.042999999999977945,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) fireworks → url (stream + non-stream)","status":"passed","title":"fireworks → url (stream + non-stream)","duration":0.07608399999998028,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) gemini → url (stream + non-stream)","status":"passed","title":"gemini → url (stream + non-stream)","duration":0.043250000000000455,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) gitlab → url (stream + non-stream)","status":"passed","title":"gitlab → url (stream + non-stream)","duration":0.04070799999999508,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) glm → url (stream + non-stream)","status":"passed","title":"glm → url (stream + non-stream)","duration":0.041416999999995596,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) glm-cn → url (stream + non-stream)","status":"passed","title":"glm-cn → url (stream + non-stream)","duration":0.04049999999998022,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) groq → url (stream + non-stream)","status":"passed","title":"groq → url (stream + non-stream)","duration":0.03829199999998423,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) hyperbolic → url (stream + non-stream)","status":"passed","title":"hyperbolic → url (stream + non-stream)","duration":0.038375000000002046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) kilocode → url (stream + non-stream)","status":"passed","title":"kilocode → url (stream + non-stream)","duration":0.038250000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) kimi → url (stream + non-stream)","status":"passed","title":"kimi → url (stream + non-stream)","duration":0.04200000000000159,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) kimi-coding → url (stream + non-stream)","status":"passed","title":"kimi-coding → url (stream + non-stream)","duration":0.039749999999997954,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) minimax → url (stream + non-stream)","status":"passed","title":"minimax → url (stream + non-stream)","duration":0.03970800000001873,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) minimax-cn → url (stream + non-stream)","status":"passed","title":"minimax-cn → url (stream + non-stream)","duration":0.26145800000000463,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) mistral → url (stream + non-stream)","status":"passed","title":"mistral → url (stream + non-stream)","duration":0.16249999999999432,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) mmf → url (stream + non-stream)","status":"passed","title":"mmf → url (stream + non-stream)","duration":0.13616700000000037,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) nanobanana → url (stream + non-stream)","status":"passed","title":"nanobanana → url (stream + non-stream)","duration":0.11208300000001259,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) nebius → url (stream + non-stream)","status":"passed","title":"nebius → url (stream + non-stream)","duration":0.10695800000002009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) nvidia → url (stream + non-stream)","status":"passed","title":"nvidia → url (stream + non-stream)","duration":0.07708399999998505,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) ollama → url (stream + non-stream)","status":"passed","title":"ollama → url (stream + non-stream)","duration":0.045416999999986274,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) openai → url (stream + non-stream)","status":"passed","title":"openai → url (stream + non-stream)","duration":0.04099999999999682,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) openrouter → url (stream + non-stream)","status":"passed","title":"openrouter → url (stream + non-stream)","duration":0.03891699999999787,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) perplexity → url (stream + non-stream)","status":"passed","title":"perplexity → url (stream + non-stream)","duration":0.037916999999993095,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) siliconflow → url (stream + non-stream)","status":"passed","title":"siliconflow → url (stream + non-stream)","duration":0.03895800000000804,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) together → url (stream + non-stream)","status":"passed","title":"together → url (stream + non-stream)","duration":0.039332999999999174,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) vercel-ai-gateway → url (stream + non-stream)","status":"passed","title":"vercel-ai-gateway → url (stream + non-stream)","duration":0.040208000000006905,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) volcengine-ark → url (stream + non-stream)","status":"passed","title":"volcengine-ark → url (stream + non-stream)","duration":0.03920800000000213,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) xai → url (stream + non-stream)","status":"passed","title":"xai → url (stream + non-stream)","duration":0.04104100000000699,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) xiaomi-mimo → url (stream + non-stream)","status":"passed","title":"xiaomi-mimo → url (stream + non-stream)","duration":0.0362909999999772,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) alicode → headers (apiKey / oauth)","status":"passed","title":"alicode → headers (apiKey / oauth)","duration":0.41508400000000734,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) alicode-intl → headers (apiKey / oauth)","status":"passed","title":"alicode-intl → headers (apiKey / oauth)","duration":0.0833329999999819,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) anthropic → headers (apiKey / oauth)","status":"passed","title":"anthropic → headers (apiKey / oauth)","duration":0.15408300000001418,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) assemblyai → headers (apiKey / oauth)","status":"passed","title":"assemblyai → headers (apiKey / oauth)","duration":0.07195799999999508,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) blackbox → headers (apiKey / oauth)","status":"passed","title":"blackbox → headers (apiKey / oauth)","duration":0.06312500000001364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) byteplus → headers (apiKey / oauth)","status":"passed","title":"byteplus → headers (apiKey / oauth)","duration":0.1278329999999812,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) cerebras → headers (apiKey / oauth)","status":"passed","title":"cerebras → headers (apiKey / oauth)","duration":0.06779199999999719,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) chutes → headers (apiKey / oauth)","status":"passed","title":"chutes → headers (apiKey / oauth)","duration":0.06212500000000887,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) claude → headers (apiKey / oauth)","status":"passed","title":"claude → headers (apiKey / oauth)","duration":0.22145799999998417,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) cline → headers (apiKey / oauth)","status":"passed","title":"cline → headers (apiKey / oauth)","duration":0.19966600000000767,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) cloudflare-ai → headers (apiKey / oauth)","status":"passed","title":"cloudflare-ai → headers (apiKey / oauth)","duration":0.07858300000000895,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) codebuddy → headers (apiKey / oauth)","status":"passed","title":"codebuddy → headers (apiKey / oauth)","duration":0.06312499999998522,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) cohere → headers (apiKey / oauth)","status":"passed","title":"cohere → headers (apiKey / oauth)","duration":0.0609579999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) deepgram → headers (apiKey / oauth)","status":"passed","title":"deepgram → headers (apiKey / oauth)","duration":0.059708999999998014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) deepseek → headers (apiKey / oauth)","status":"passed","title":"deepseek → headers (apiKey / oauth)","duration":0.0654999999999859,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) fireworks → headers (apiKey / oauth)","status":"passed","title":"fireworks → headers (apiKey / oauth)","duration":0.057833000000016455,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) gemini → headers (apiKey / oauth)","status":"passed","title":"gemini → headers (apiKey / oauth)","duration":0.06929199999999014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) gitlab → headers (apiKey / oauth)","status":"passed","title":"gitlab → headers (apiKey / oauth)","duration":0.1425830000000019,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) glm → headers (apiKey / oauth)","status":"passed","title":"glm → headers (apiKey / oauth)","duration":0.1452910000000145,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) glm-cn → headers (apiKey / oauth)","status":"passed","title":"glm-cn → headers (apiKey / oauth)","duration":0.07579200000000696,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) groq → headers (apiKey / oauth)","status":"passed","title":"groq → headers (apiKey / oauth)","duration":0.07991699999999469,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) hyperbolic → headers (apiKey / oauth)","status":"passed","title":"hyperbolic → headers (apiKey / oauth)","duration":0.06704099999998903,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) kilocode → headers (apiKey / oauth)","status":"passed","title":"kilocode → headers (apiKey / oauth)","duration":0.0988749999999925,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) kimi → headers (apiKey / oauth)","status":"passed","title":"kimi → headers (apiKey / oauth)","duration":0.0726669999999956,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) kimi-coding → headers (apiKey / oauth)","status":"passed","title":"kimi-coding → headers (apiKey / oauth)","duration":0.1758750000000191,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) minimax → headers (apiKey / oauth)","status":"passed","title":"minimax → headers (apiKey / oauth)","duration":0.06545799999997826,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) minimax-cn → headers (apiKey / oauth)","status":"passed","title":"minimax-cn → headers (apiKey / oauth)","duration":0.06404100000000312,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) mistral → headers (apiKey / oauth)","status":"passed","title":"mistral → headers (apiKey / oauth)","duration":0.05816599999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) mmf → headers (apiKey / oauth)","status":"passed","title":"mmf → headers (apiKey / oauth)","duration":0.057332999999999856,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) nanobanana → headers (apiKey / oauth)","status":"passed","title":"nanobanana → headers (apiKey / oauth)","duration":0.07337499999999864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) nebius → headers (apiKey / oauth)","status":"passed","title":"nebius → headers (apiKey / oauth)","duration":0.05329199999999901,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) nvidia → headers (apiKey / oauth)","status":"passed","title":"nvidia → headers (apiKey / oauth)","duration":0.078125,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) ollama → headers (apiKey / oauth)","status":"passed","title":"ollama → headers (apiKey / oauth)","duration":0.05895899999998733,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) openai → headers (apiKey / oauth)","status":"passed","title":"openai → headers (apiKey / oauth)","duration":0.05533299999999031,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) openrouter → headers (apiKey / oauth)","status":"passed","title":"openrouter → headers (apiKey / oauth)","duration":0.1534159999999929,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) perplexity → headers (apiKey / oauth)","status":"passed","title":"perplexity → headers (apiKey / oauth)","duration":0.08208300000001145,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) siliconflow → headers (apiKey / oauth)","status":"passed","title":"siliconflow → headers (apiKey / oauth)","duration":0.0664160000000038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) together → headers (apiKey / oauth)","status":"passed","title":"together → headers (apiKey / oauth)","duration":0.0604579999999828,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) vercel-ai-gateway → headers (apiKey / oauth)","status":"passed","title":"vercel-ai-gateway → headers (apiKey / oauth)","duration":0.06204199999999105,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) volcengine-ark → headers (apiKey / oauth)","status":"passed","title":"volcengine-ark → headers (apiKey / oauth)","duration":0.05633299999999508,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) xai → headers (apiKey / oauth)","status":"passed","title":"xai → headers (apiKey / oauth)","duration":0.06920799999997485,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) xiaomi-mimo → headers (apiKey / oauth)","status":"passed","title":"xiaomi-mimo → headers (apiKey / oauth)","duration":0.06441699999999173,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830518,"endTime":1781448830530.069,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/golden-url-header.test.js"},{"assertionResults":[{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) has at least one active AG connection with refreshToken","status":"skipped","title":"has at least one active AG connection with refreshToken","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) same sessionId → cache hit on repeated call","status":"skipped","title":"same sessionId → cache hit on repeated call","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) different sessionId (same account) → cache still hits (session-independent)","status":"skipped","title":"different sessionId (same account) → cache still hits (session-independent)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) cross-account → cache SHARED (content-based global cache)","status":"skipped","title":"cross-account → cache SHARED (content-based global cache)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) codex-style sessionId vs random sessionId on unique prompt","status":"skipped","title":"codex-style sessionId vs random sessionId on unique prompt","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) unique prompt (never seen) → explore when cache starts hitting","status":"skipped","title":"unique prompt (never seen) → explore when cache starts hitting","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448826369,"endTime":1781448826369,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/antigravity-cache.test.js"},{"assertionResults":[{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling flags the out-of-box agent/Default model mandatory","status":"failed","title":"flags the out-of-box agent/Default model mandatory","duration":8.034291999999994,"failureMessages":["AssertionError: expected undefined to be true // Object.is equality\n at /Users/Working/router4/app/tests/unit/antigravity-mitm.test.js:17:86\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling leaves models not proven auto-sent optional","status":"passed","title":"leaves models not proven auto-sent optional","duration":0.6699169999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling excludes tab-autocomplete model 'tab_jump_flash_lite_preview' from re-routing","status":"passed","title":"excludes tab-autocomplete model 'tab_jump_flash_lite_preview' from re-routing","duration":0.22750000000002046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling excludes tab-autocomplete model 'tab_flash_lite_preview' from re-routing","status":"passed","title":"excludes tab-autocomplete model 'tab_flash_lite_preview' from re-routing","duration":1.139959000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling does not exclude real agent models from re-routing","status":"passed","title":"does not exclude real agent models from re-routing","duration":0.5558330000000069,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448826895,"endTime":1781448826906.556,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/antigravity-mitm.test.js"},{"assertionResults":[{"ancestorTitles":["antigravity oauth client (deduped)"],"fullName":"antigravity oauth client (deduped) shared source holds the canonical credentials","status":"passed","title":"shared source holds the canonical credentials","duration":2.1541660000000036,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity oauth client (deduped)"],"fullName":"antigravity oauth client (deduped) registry transport keeps clientId/clientSecret","status":"passed","title":"registry transport keeps clientId/clientSecret","duration":0.9000419999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity oauth client (deduped)"],"fullName":"antigravity oauth client (deduped) google client shared by gemini + gemini-cli","status":"passed","title":"google client shared by gemini + gemini-cli","duration":2.0759999999999934,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity oauth client (deduped)"],"fullName":"antigravity oauth client (deduped) src oauth.js imports shared client + keeps full shape","status":"passed","title":"src oauth.js imports shared client + keeps full shape","duration":0.9947080000000028,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830618,"endTime":1781448830623.9946,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/antigravity-oauth-client.test.js"},{"assertionResults":[{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) uses Retry-After header (seconds → ms) when within cap","status":"passed","title":"uses Retry-After header (seconds → ms) when within cap","duration":0.9599580000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) vetoes (false) when Retry-After exceeds cap","status":"passed","title":"vetoes (false) when Retry-After exceeds cap","duration":0.16929200000001288,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) parses retry time from error body when no header","status":"passed","title":"parses retry time from error body when no header","duration":0.27008299999999963,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) exponential backoff for 429 when no retry info","status":"passed","title":"exponential backoff for 429 when no retry info","duration":0.5591249999999945,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) 503 without retry info → veto (no auto backoff)","status":"passed","title":"503 without retry info → veto (no auto backoff)","duration":0.20812499999999545,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) buildHeaders includes cached session id after transformRequest","status":"passed","title":"buildHeaders includes cached session id after transformRequest","duration":0.3364999999999725,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448831308,"endTime":1781448831311.3364,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/antigravity-retry-hook.test.js"},{"assertionResults":[{"ancestorTitles":["BaseExecutor.execute — retry by status (config-driven)"],"fullName":"BaseExecutor.execute — retry by status (config-driven) retries 502 `attempts` times then succeeds","status":"passed","title":"retries 502 `attempts` times then succeeds","duration":18.023083000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — retry by status (config-driven)"],"fullName":"BaseExecutor.execute — retry by status (config-driven) stops after exhausting 502 attempts on a single url and throws","status":"passed","title":"stops after exhausting 502 attempts on a single url and throws","duration":3.1823750000000075,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — baseUrls fallback"],"fullName":"BaseExecutor.execute — baseUrls fallback falls over to the next url on 429 (shouldRetry)","status":"passed","title":"falls over to the next url on 429 (shouldRetry)","duration":1.5171660000000031,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — network error retry/fallback"],"fullName":"BaseExecutor.execute — network error retry/fallback maps network exception to 502 retry config","status":"passed","title":"maps network exception to 502 retry config","duration":2.6724590000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — network error retry/fallback"],"fullName":"BaseExecutor.execute — network error retry/fallback throws when the only url fails with network error and no retries left","status":"passed","title":"throws when the only url fails with network error and no retries left","duration":0.8342499999999973,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — computeRetryDelay hook veto"],"fullName":"BaseExecutor.execute — computeRetryDelay hook veto hook returning false skips retry (uses fallback path)","status":"passed","title":"hook returning false skips retry (uses fallback path)","duration":0.7385830000000055,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830164,"endTime":1781448830191.7385,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/base-executor-retry.test.js"},{"assertionResults":[{"ancestorTitles":["PR #1175 - buildOutput filter detection"],"fullName":"PR #1175 - buildOutput filter detection detects npm install output","status":"passed","title":"detects npm install output","duration":2.2902500000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput filter detection"],"fullName":"PR #1175 - buildOutput filter detection detects cargo build output (no longer misdetected as git-status)","status":"passed","title":"detects cargo build output (no longer misdetected as git-status)","duration":1.4048749999999899,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput compression behavior"],"fullName":"PR #1175 - buildOutput compression behavior compresses npm install with deprecations","status":"passed","title":"compresses npm install with deprecations","duration":2.001458999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput compression behavior"],"fullName":"PR #1175 - buildOutput compression behavior compresses cargo build output","status":"passed","title":"compresses cargo build output","duration":0.7572499999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput compression behavior"],"fullName":"PR #1175 - buildOutput compression behavior keeps cargo errors verbatim","status":"passed","title":"keeps cargo errors verbatim","duration":0.16412499999999852,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput compression behavior"],"fullName":"PR #1175 - buildOutput compression behavior keeps maven BUILD FAILED as error","status":"passed","title":"keeps maven BUILD FAILED as error","duration":0.170416000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regex fix edge cases"],"fullName":"PR #1175 - porcelain regex fix edge cases git status --porcelain workdir-only (space first char) STILL detects as gitStatus (minimal fix preserved old regex)","status":"passed","title":"git status --porcelain workdir-only (space first char) STILL detects as gitStatus (minimal fix preserved old regex)","duration":0.1774170000000055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regex fix edge cases"],"fullName":"PR #1175 - porcelain regex fix edge cases git status --porcelain with staged (status code first char) still detects","status":"passed","title":"git status --porcelain with staged (status code first char) still detects","duration":0.06725000000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regex fix edge cases"],"fullName":"PR #1175 - porcelain regex fix edge cases cargo Compiling lines NOT detected as git-status (porcelain false positive fix)","status":"passed","title":"cargo Compiling lines NOT detected as git-status (porcelain false positive fix)","duration":0.3643339999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regex fix edge cases"],"fullName":"PR #1175 - porcelain regex fix edge cases long-form git status with 'On branch' always detects","status":"passed","title":"long-form git status with 'On branch' always detects","duration":0.13474999999999682,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - false positive risks"],"fullName":"PR #1175 - false positive risks generic app log with 'ERROR:' triggers buildOutput (potential false positive)","status":"passed","title":"generic app log with 'ERROR:' triggers buildOutput (potential false positive)","duration":0.5736660000000029,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - false positive risks"],"fullName":"PR #1175 - false positive risks generic 'Compiling templates' (non-build context) triggers buildOutput","status":"passed","title":"generic 'Compiling templates' (non-build context) triggers buildOutput","duration":0.2843330000000037,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - false positive risks"],"fullName":"PR #1175 - false positive risks plain text with no patterns falls through (no false positive)","status":"passed","title":"plain text with no patterns falls through (no false positive)","duration":0.12304100000000062,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - safety: no data corruption"],"fullName":"PR #1175 - safety: no data corruption empty input returns input","status":"passed","title":"empty input returns input","duration":0.09537499999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - safety: no data corruption"],"fullName":"PR #1175 - safety: no data corruption input with only errors preserves all errors","status":"passed","title":"input with only errors preserves all errors","duration":0.06141599999999414,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - safety: no data corruption"],"fullName":"PR #1175 - safety: no data corruption input with no recognized patterns returns input (fallback)","status":"passed","title":"input with no recognized patterns returns input (fallback)","duration":0.04408300000000054,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - safety: no data corruption"],"fullName":"PR #1175 - safety: no data corruption limits warnings to 5 + summary line","status":"passed","title":"limits warnings to 5 + summary line","duration":0.08050000000000068,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830706,"endTime":1781448830716.0806,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/buildOutputFilter.test.js"},{"assertionResults":[{"ancestorTitles":["PR #1175 - priority with overlapping patterns"],"fullName":"PR #1175 - priority with overlapping patterns git-diff wins over buildOutput when both present","status":"passed","title":"git-diff wins over buildOutput when both present","duration":2.138582999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - priority with overlapping patterns"],"fullName":"PR #1175 - priority with overlapping patterns git-status (long form) wins over buildOutput","status":"passed","title":"git-status (long form) wins over buildOutput","duration":0.2729580000000027,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - DETECT_WINDOW boundary"],"fullName":"PR #1175 - DETECT_WINDOW boundary build pattern beyond DETECT_WINDOW chars: NOT detected","status":"passed","title":"build pattern beyond DETECT_WINDOW chars: NOT detected","duration":0.6493750000000063,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - DETECT_WINDOW boundary"],"fullName":"PR #1175 - DETECT_WINDOW boundary build pattern at very start: detected","status":"passed","title":"build pattern at very start: detected","duration":0.13750000000000284,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - line endings & whitespace"],"fullName":"PR #1175 - line endings & whitespace CRLF line endings still detect","status":"passed","title":"CRLF line endings still detect","duration":0.07858299999999474,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - line endings & whitespace"],"fullName":"PR #1175 - line endings & whitespace Tab-prefixed Compiling (real cargo output uses leading spaces, not tab)","status":"passed","title":"Tab-prefixed Compiling (real cargo output uses leading spaces, not tab)","duration":0.06083399999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - line endings & whitespace"],"fullName":"PR #1175 - line endings & whitespace Compiling without leading spaces","status":"passed","title":"Compiling without leading spaces","duration":0.10649999999999693,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - adversarial: user code containing build strings"],"fullName":"PR #1175 - adversarial: user code containing build strings user JS code with console.log('npm warn ...') triggers buildOutput","status":"passed","title":"user JS code with console.log('npm warn ...') triggers buildOutput","duration":0.5720830000000063,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - adversarial: user code containing build strings"],"fullName":"PR #1175 - adversarial: user code containing build strings file content with 'BUILD SUCCESS' on its own line triggers buildOutput","status":"passed","title":"file content with 'BUILD SUCCESS' on its own line triggers buildOutput","duration":1.0887910000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - adversarial: user code containing build strings"],"fullName":"PR #1175 - adversarial: user code containing build strings real cargo error spanning multiple lines preserves context","status":"passed","title":"real cargo error spanning multiple lines preserves context","duration":0.29512499999999875,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety input with only progress lines (no errors/warnings/summary) returns input fallback","status":"passed","title":"input with only progress lines (no errors/warnings/summary) returns input fallback","duration":0.174082999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety input with only Downloading lines","status":"passed","title":"input with only Downloading lines","duration":0.05108299999999133,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety input with ONLY a single ERROR: line","status":"passed","title":"input with ONLY a single ERROR: line","duration":0.041499999999999204,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety unicode/emoji in deprecation warning preserved (minimal fix keeps first 3 verbatim)","status":"passed","title":"unicode/emoji in deprecation warning preserved (minimal fix keeps first 3 verbatim)","duration":0.4263339999999971,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety more than 3 deprecations: keep first 3 verbatim + count rest","status":"passed","title":"more than 3 deprecations: keep first 3 verbatim + count rest","duration":0.09695899999999824,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety safeApply wraps buildOutput against panics","status":"passed","title":"safeApply wraps buildOutput against panics","duration":0.05825000000000102,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - integration with compressMessages"],"fullName":"PR #1175 - integration with compressMessages npm install output above MIN_COMPRESS_SIZE → compressed","status":"passed","title":"npm install output above MIN_COMPRESS_SIZE → compressed","duration":0.3119170000000082,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - integration with compressMessages"],"fullName":"PR #1175 - integration with compressMessages input below MIN_COMPRESS_SIZE → NOT compressed","status":"passed","title":"input below MIN_COMPRESS_SIZE → NOT compressed","duration":0.06258300000000361,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - integration with compressMessages"],"fullName":"PR #1175 - integration with compressMessages compressed output never grows input (safety guard)","status":"passed","title":"compressed output never grows input (safety guard)","duration":0.07054200000000321,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - integration with compressMessages"],"fullName":"PR #1175 - integration with compressMessages tool_result with is_error:true is NOT compressed (preserve error traces)","status":"passed","title":"tool_result with is_error:true is NOT compressed (preserve error traces)","duration":0.11929200000000151,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regression deeper"],"fullName":"PR #1175 - porcelain regression deeper mixed staged + workdir + untracked porcelain → detected (has status code first char)","status":"passed","title":"mixed staged + workdir + untracked porcelain → detected (has status code first char)","duration":0.04204099999999755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regression deeper"],"fullName":"PR #1175 - porcelain regression deeper 100% workdir-only porcelain → STILL detects gitStatus (minimal fix preserved old regex)","status":"passed","title":"100% workdir-only porcelain → STILL detects gitStatus (minimal fix preserved old regex)","duration":0.03654199999999719,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regression deeper"],"fullName":"PR #1175 - porcelain regression deeper manual gitStatus() call on workdir-only porcelain still parses correctly","status":"passed","title":"manual gitStatus() call on workdir-only porcelain still parses correctly","duration":0.16345800000000565,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - pathological"],"fullName":"PR #1175 - pathological very long single line (no newlines) with build pattern","status":"passed","title":"very long single line (no newlines) with build pattern","duration":0.07408300000000168,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - pathological"],"fullName":"PR #1175 - pathological 10000 Compiling lines don't crash","status":"passed","title":"10000 Compiling lines don't crash","duration":7.819040999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - pathological"],"fullName":"PR #1175 - pathological input with only newlines","status":"passed","title":"input with only newlines","duration":0.09229200000000048,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - pathological"],"fullName":"PR #1175 - pathological null/undefined safety via safeApply","status":"passed","title":"null/undefined safety via safeApply","duration":0.29137500000000216,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830257,"endTime":1781448830273.2913,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/buildOutputFilterAdversarial.test.js"},{"assertionResults":[{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools suffixes client tool names and maps them back","status":"passed","title":"suffixes client tool names and maps them back","duration":1.0524170000000197,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools suffixes a forced tool_choice to match the renamed tool","status":"passed","title":"suffixes a forced tool_choice to match the renamed tool","duration":0.6629159999999956,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools suffixes only the chosen tool when several are present","status":"passed","title":"suffixes only the chosen tool when several are present","duration":0.1766669999999806,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools leaves non-forced tool_choice untouched","status":"passed","title":"leaves non-forced tool_choice untouched","duration":0.20025000000001114,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools does not suffix a forced choice that targets a non-client (decoy/built-in) tool","status":"passed","title":"does not suffix a forced choice that targets a non-client (decoy/built-in) tool","duration":0.10450000000000159,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools renames tool_use names in message history","status":"passed","title":"renames tool_use names in message history","duration":0.09920900000000188,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools returns the body unchanged when there are no tools","status":"passed","title":"returns the body unchanged when there are no tools","duration":0.20545799999999304,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448831062,"endTime":1781448831064.2056,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/claude-cloaking.test.js"},{"assertionResults":[{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache returns null before any headers are cached (cold start)","status":"passed","title":"returns null before any headers are cached (cold start)","duration":52.71554100000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache caches headers when user-agent contains 'claude-code'","status":"passed","title":"caches headers when user-agent contains 'claude-code'","duration":2.803541999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache caches headers when user-agent contains 'claude-cli'","status":"passed","title":"caches headers when user-agent contains 'claude-cli'","duration":0.5054999999999836,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache caches headers when x-app is 'cli' (regardless of user-agent)","status":"passed","title":"caches headers when x-app is 'cli' (regardless of user-agent)","duration":0.6334999999999695,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache does NOT cache headers for non-Claude clients","status":"passed","title":"does NOT cache headers for non-Claude clients","duration":0.34704199999998764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache refreshes cache on each matching request","status":"passed","title":"refreshes cache on each matching request","duration":0.8358329999999796,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache ignores calls with null or non-object headers","status":"passed","title":"ignores calls with null or non-object headers","duration":0.7400419999999599,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache only stores keys that are actually present in the headers object","status":"passed","title":"only stores keys that are actually present in the headers object","duration":1.0257090000000062,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider overlays live cached headers over static provider defaults","status":"passed","title":"overlays live cached headers over static provider defaults","duration":952.2814169999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider removes conflicting Title-Case static keys when cached lowercase keys exist","status":"passed","title":"removes conflicting Title-Case static keys when cached lowercase keys exist","duration":8.474292000000105,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider sets x-api-key auth when apiKey is provided","status":"passed","title":"sets x-api-key auth when apiKey is provided","duration":9.460792000000083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider sets Bearer Authorization when only accessToken is provided","status":"passed","title":"sets Bearer Authorization when only accessToken is provided","duration":17.311249999999973,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider includes Accept: text/event-stream when stream=true","status":"passed","title":"includes Accept: text/event-stream when stream=true","duration":7.4880829999999605,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider omits Accept: text/event-stream when stream=false","status":"passed","title":"omits Accept: text/event-stream when stream=false","duration":38.23316599999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider cold start (no cache)"],"fullName":"DefaultExecutor.buildHeaders() — claude provider cold start (no cache) falls back to static provider headers when cache is empty","status":"passed","title":"falls back to static provider headers when cache is empty","duration":41.568250000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider cold start (no cache)"],"fullName":"DefaultExecutor.buildHeaders() — claude provider cold start (no cache) does not throw when cache returns null","status":"passed","title":"does not throw when cache returns null","duration":48.76774999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping strips x-app and anthropic-dangerous-direct-browser-access for non-Anthropic host","status":"passed","title":"strips x-app and anthropic-dangerous-direct-browser-access for non-Anthropic host","duration":21.565582999999833,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping removes claude-code-20250219 from anthropic-beta for non-Anthropic host","status":"passed","title":"removes claude-code-20250219 from anthropic-beta for non-Anthropic host","duration":31.927208000000064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping keeps other beta flags intact after stripping","status":"passed","title":"keeps other beta flags intact after stripping","duration":22.385041999999885,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping does NOT strip headers when baseUrl is api.anthropic.com","status":"passed","title":"does NOT strip headers when baseUrl is api.anthropic.com","duration":8.006125000000111,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping does NOT strip headers when baseUrl is empty (defaults to Anthropic)","status":"passed","title":"does NOT strip headers when baseUrl is empty (defaults to Anthropic)","duration":11.795165999999881,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["proxyAwareFetch — api.anthropic.com routing"],"fullName":"proxyAwareFetch — api.anthropic.com routing routes api.anthropic.com to gotScraping (non-streaming) and returns ok response","status":"failed","title":"routes api.anthropic.com to gotScraping (non-streaming) and returns ok response","duration":466.29037500000004,"failureMessages":["AssertionError: expected \"vi.fn()\" to be called once, but got 0 times\n at /Users/Working/router4/app/tests/unit/claude-header-forwarding.test.js:354:25\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["proxyAwareFetch — api.anthropic.com routing"],"fullName":"proxyAwareFetch — api.anthropic.com routing falls back gracefully when got-scraping throws on non-streaming path","status":"passed","title":"falls back gracefully when got-scraping throws on non-streaming path","duration":2.6225829999998496,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["proxyAwareFetch — api.anthropic.com routing"],"fullName":"proxyAwareFetch — api.anthropic.com routing does NOT route non-Anthropic hosts through gotScraping","status":"passed","title":"does NOT route non-Anthropic hosts through gotScraping","duration":3.0617919999999685,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448826842,"endTime":1781448828594.0618,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/claude-header-forwarding.test.js"},{"assertionResults":[{"ancestorTitles":["CodexExecutor image handling"],"fullName":"CodexExecutor image handling fetches 1MB remote image and inlines it as base64 data URI","status":"passed","title":"fetches 1MB remote image and inlines it as base64 data URI","duration":3.6371249999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CodexExecutor image handling"],"fullName":"CodexExecutor image handling passes through existing data URIs without calling fetch","status":"passed","title":"passes through existing data URIs without calling fetch","duration":0.39537500000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CodexExecutor image handling"],"fullName":"CodexExecutor image handling falls back to original URL when remote fetch fails","status":"passed","title":"falls back to original URL when remote fetch fails","duration":0.31450000000000955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CodexExecutor image handling"],"fullName":"CodexExecutor image handling execute() prefetches images before sending to upstream","status":"passed","title":"execute() prefetches images before sending to upstream","duration":23.04566700000001,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829918,"endTime":1781448829945.0457,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/codex-image-fetch.test.js"},{"assertionResults":[{"ancestorTitles":["Codex Refresh Token","refreshCodexToken"],"fullName":"Codex Refresh Token refreshCodexToken should return new refresh_token when server provides one (token rotation)","status":"passed","title":"should return new refresh_token when server provides one (token rotation)","duration":101.40699999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","refreshCodexToken"],"fullName":"Codex Refresh Token refreshCodexToken should keep old refresh_token when server does not return new one","status":"passed","title":"should keep old refresh_token when server does not return new one","duration":12.124750000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","CodexExecutor credential lifecycle"],"fullName":"Codex Refresh Token CodexExecutor credential lifecycle should refresh Codex credentials and preserve omitted id_token","status":"passed","title":"should refresh Codex credentials and preserve omitted id_token","duration":103.41533300000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","CodexExecutor credential lifecycle"],"fullName":"Codex Refresh Token CodexExecutor credential lifecycle should refresh Codex when lastRefreshAt is older than the upstream stale window","status":"passed","title":"should refresh Codex when lastRefreshAt is older than the upstream stale window","duration":33.55208399999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","CodexExecutor credential lifecycle"],"fullName":"Codex Refresh Token CodexExecutor credential lifecycle should de-duplicate concurrent refreshes for the same Codex connection","status":"passed","title":"should de-duplicate concurrent refreshes for the same Codex connection","duration":20.89166700000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","getRefreshLeadMs (early refresh config)"],"fullName":"Codex Refresh Token getRefreshLeadMs (early refresh config) should return provider-specific lead time for OAuth providers","status":"passed","title":"should return provider-specific lead time for OAuth providers","duration":4.57104099999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","getRefreshLeadMs (early refresh config)"],"fullName":"Codex Refresh Token getRefreshLeadMs (early refresh config) should fallback to default buffer for unknown providers","status":"passed","title":"should fallback to default buffer for unknown providers","duration":7.271374999999978,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","getRefreshLeadMs (early refresh config)"],"fullName":"Codex Refresh Token getRefreshLeadMs (early refresh config) codex lead should be greater than default buffer","status":"passed","title":"codex lead should be greater than default buffer","duration":5.727790999999968,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828281,"endTime":1781448828570.7278,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/codex-refresh-token.test.js"},{"assertionResults":[{"ancestorTitles":["combo round-robin routing"],"fullName":"combo round-robin routing keeps existing one-request round-robin behavior by default","status":"passed","title":"keeps existing one-request round-robin behavior by default","duration":2.872124999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["combo round-robin routing"],"fullName":"combo round-robin routing sticks to each combo model for the configured number of requests","status":"passed","title":"sticks to each combo model for the configured number of requests","duration":0.9707919999999888,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["combo round-robin routing"],"fullName":"combo round-robin routing tracks sticky rotation independently per combo","status":"passed","title":"tracks sticky rotation independently per combo","duration":0.5725000000000051,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["combo round-robin routing"],"fullName":"combo round-robin routing does not rotate fallback combos","status":"passed","title":"does not rotate fallback combos","duration":0.713957999999991,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448831082,"endTime":1781448831086.7139,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/combo-routing.test.js"},{"assertionResults":[{"ancestorTitles":["commandcode-to-openai — text-delta"],"fullName":"commandcode-to-openai — text-delta emits assistant role on first delta then content-only","status":"passed","title":"emits assistant role on first delta then content-only","duration":1.1284159999999872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — reasoning-delta"],"fullName":"commandcode-to-openai — reasoning-delta maps reasoning-delta to reasoning_content delta","status":"passed","title":"maps reasoning-delta to reasoning_content delta","duration":0.18291600000000585,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — tool-input-* with id field (live schema)"],"fullName":"commandcode-to-openai — tool-input-* with id field (live schema) registers tool index using event.id (NOT toolCallId)","status":"passed","title":"registers tool index using event.id (NOT toolCallId)","duration":0.20674999999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — tool-input-* with id field (live schema)"],"fullName":"commandcode-to-openai — tool-input-* with id field (live schema) ignores tool-input-delta when id is unknown (no prior start)","status":"passed","title":"ignores tool-input-delta when id is unknown (no prior start)","duration":0.07287500000001046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — final tool-call event"],"fullName":"commandcode-to-openai — final tool-call event does NOT re-emit tool_calls when tool-input-* deltas already fired","status":"passed","title":"does NOT re-emit tool_calls when tool-input-* deltas already fired","duration":0.21008299999999736,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — final tool-call event"],"fullName":"commandcode-to-openai — final tool-call event emits a consolidated tool_calls when only the final tool-call event arrives","status":"passed","title":"emits a consolidated tool_calls when only the final tool-call event arrives","duration":0.15583300000000122,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — finish"],"fullName":"commandcode-to-openai — finish emits a final chunk with finish_reason=tool_calls when finishReason is tool-calls","status":"passed","title":"emits a final chunk with finish_reason=tool_calls when finishReason is tool-calls","duration":0.19729200000000446,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — finish"],"fullName":"commandcode-to-openai — finish includes usage on the final chunk when totalUsage provided","status":"passed","title":"includes usage on the final chunk when totalUsage provided","duration":0.3539159999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — error event"],"fullName":"commandcode-to-openai — error event stringifies object errors so client sees readable message","status":"passed","title":"stringifies object errors so client sees readable message","duration":0.46341699999999264,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830798,"endTime":1781448830801.4634,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/commandcode-to-openai.test.js"},{"assertionResults":[{"ancestorTitles":["compatible provider connections API"],"fullName":"compatible provider connections API creates one API-key connection for an OpenAI-compatible node","status":"passed","title":"creates one API-key connection for an OpenAI-compatible node","duration":245.04566699999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["compatible provider connections API"],"fullName":"compatible provider connections API creates one API-key connection for an Anthropic-compatible node","status":"passed","title":"creates one API-key connection for an Anthropic-compatible node","duration":18.675040999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["compatible provider connections API"],"fullName":"compatible provider connections API returns 400 for a duplicate connection on the same compatible node","status":"passed","title":"returns 400 for a duplicate connection on the same compatible node","duration":21.979458000000022,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828337,"endTime":1781448828622.9795,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/compatible-provider-connections.test.js"},{"assertionResults":[{"ancestorTitles":["CursorExecutor Composer thinking-field responses"],"fullName":"CursorExecutor Composer thinking-field responses uses visible content after for non-streaming Composer responses","status":"passed","title":"uses visible content after for non-streaming Composer responses","duration":10.081124999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CursorExecutor Composer thinking-field responses"],"fullName":"CursorExecutor Composer thinking-field responses streams only visible content after for Composer responses","status":"passed","title":"streams only visible content after for Composer responses","duration":1.0071669999999813,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CursorExecutor Composer thinking-field responses"],"fullName":"CursorExecutor Composer thinking-field responses does not treat thinking as visible output for non-Composer models","status":"passed","title":"does not treat thinking as visible output for non-Composer models","duration":0.2962090000000046,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830570,"endTime":1781448830581.2961,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/cursor-composer-thinking.test.js"},{"assertionResults":[{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows loopback public LLM API without API key","status":"passed","title":"allows loopback public LLM API without API key","duration":13.970500000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access rejects remote rewritten public LLM API without API key","status":"passed","title":"rejects remote rewritten public LLM API without API key","duration":0.44845799999998803,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows loopback rewritten public LLM API without API key","status":"passed","title":"allows loopback rewritten public LLM API without API key","duration":0.22212500000000546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access rejects remote beta public LLM API without API key","status":"passed","title":"rejects remote beta public LLM API without API key","duration":0.23170899999999506,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access rejects remote rewritten beta public LLM API without API key","status":"passed","title":"rejects remote rewritten beta public LLM API without API key","duration":0.4197500000000076,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows remote public LLM API with valid bearer API key","status":"passed","title":"allows remote public LLM API with valid bearer API key","duration":1.186041000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows remote public LLM API with valid x-api-key","status":"passed","title":"allows remote public LLM API with valid x-api-key","duration":0.3912079999999918,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows remote rewritten beta public LLM API with valid API key","status":"passed","title":"allows remote rewritten beta public LLM API with valid API key","duration":0.16516699999999673,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access rejects local-only route from non-loopback host without CLI token","status":"passed","title":"rejects local-only route from non-loopback host without CLI token","duration":0.41391699999999787,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access rejects local-only route on loopback when requireLogin=true and no JWT","status":"passed","title":"rejects local-only route on loopback when requireLogin=true and no JWT","duration":0.23750000000001137,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access allows local-only route on loopback when requireLogin=false","status":"passed","title":"allows local-only route on loopback when requireLogin=false","duration":0.24445800000000872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access rejects local-only route from tunnel host even when requireLogin=false","status":"passed","title":"rejects local-only route from tunnel host even when requireLogin=false","duration":0.07483400000000984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access rejects local-only route when Origin is non-loopback (CSRF block)","status":"passed","title":"rejects local-only route when Origin is non-loopback (CSRF block)","duration":0.07533300000000054,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access allows local-only route with valid CLI token","status":"passed","title":"allows local-only route with valid CLI token","duration":0.08179199999999298,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard helpers"],"fullName":"dashboard guard helpers extracts bearer API keys before x-api-key","status":"passed","title":"extracts bearer API keys before x-api-key","duration":0.06270800000000065,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830303,"endTime":1781448830322.2444,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/dashboard-guard.test.js"},{"assertionResults":[{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb INSERT 500 provider connections","status":"passed","title":"INSERT 500 provider connections","duration":2316.035916,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb READ 200 filtered queries","status":"passed","title":"READ 200 filtered queries","duration":587.3802920000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb READ 200 by id (point lookup)","status":"passed","title":"READ 200 by id (point lookup)","duration":515.9946249999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb saveRequestUsage 500 entries","status":"passed","title":"saveRequestUsage 500 entries","duration":1281.41475,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb getUsageStats(24h) repeat 50x","status":"passed","title":"getUsageStats(24h) repeat 50x","duration":512.6632079999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448827573,"endTime":1781448832786.663,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-benchmark.test.js"},{"assertionResults":[{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety 100 parallel saveRequestUsage → no count loss","status":"passed","title":"100 parallel saveRequestUsage → no count loss","duration":98.01462500000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety 200 parallel saveRequestDetail → all flushed","status":"passed","title":"200 parallel saveRequestDetail → all flushed","duration":6008.915875,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety mixed concurrent: usage + details + connections + aliases","status":"passed","title":"mixed concurrent: usage + details + connections + aliases","duration":58.9637500000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety updateSettings parallel → no merge loss","status":"passed","title":"updateSettings parallel → no merge loss","duration":7.27704200000062,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety OAuth refresh race: parallel updateProviderConnection on same id","status":"passed","title":"OAuth refresh race: parallel updateProviderConnection on same id","duration":9.822417000000314,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety addCustomModel race: parallel duplicate adds → only 1 inserted","status":"passed","title":"addCustomModel race: parallel duplicate adds → only 1 inserted","duration":0.8167919999996229,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety updatePricing race: parallel adds different models → all merged","status":"passed","title":"updatePricing race: parallel adds different models → all merged","duration":4.401708999999755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety daily summary aggregates correctly under parallel writes","status":"passed","title":"daily summary aggregates correctly under parallel writes","duration":29.839584000000286,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448827557,"endTime":1781448833774.8396,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-concurrent.test.js"},{"assertionResults":[{"ancestorTitles":["Driver fallback chain"],"fullName":"Driver fallback chain default → picks better-sqlite3 when available","status":"passed","title":"default → picks better-sqlite3 when available","duration":33.246542000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Driver fallback chain"],"fullName":"Driver fallback chain falls back to node:sqlite when better-sqlite3 unavailable","status":"passed","title":"falls back to node:sqlite when better-sqlite3 unavailable","duration":23.883916,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Driver fallback chain"],"fullName":"Driver fallback chain falls back to sql.js when both native drivers unavailable","status":"passed","title":"falls back to sql.js when both native drivers unavailable","duration":69.648708,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828737,"endTime":1781448828863.6487,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-driver-chain.test.js"},{"assertionResults":[{"ancestorTitles":["Schema migrations"],"fullName":"Schema migrations fresh DB → applies migrations & stamps schemaVersion","status":"passed","title":"fresh DB → applies migrations & stamps schemaVersion","duration":38.470624999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Schema migrations"],"fullName":"Schema migrations existing DB at older schemaVersion → re-applies pending migrations on restart","status":"passed","title":"existing DB at older schemaVersion → re-applies pending migrations on restart","duration":18.870082999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Schema migrations"],"fullName":"Schema migrations fresh DB + legacy db.json → imports data automatically","status":"passed","title":"fresh DB + legacy db.json → imports data automatically","duration":18.342083000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Schema migrations"],"fullName":"Schema migrations auto-sync re-creates missing index when DB lacks it","status":"passed","title":"auto-sync re-creates missing index when DB lacks it","duration":14.697249999999997,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828256,"endTime":1781448828346.6973,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-migration-chain.test.js"},{"assertionResults":[{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity settings: get → defaults; update → merge","status":"passed","title":"settings: get → defaults; update → merge","duration":2.023750000000007,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity isCloudEnabled reflects settings","status":"passed","title":"isCloudEnabled reflects settings","duration":0.5072499999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity apiKeys: create/get/validate/delete","status":"passed","title":"apiKeys: create/get/validate/delete","duration":11.761500000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity providerConnections: CRUD + reorder by priority","status":"passed","title":"providerConnections: CRUD + reorder by priority","duration":4.07020799999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity providerConnections: optional fields persisted via JSON column","status":"passed","title":"providerConnections: optional fields persisted via JSON column","duration":0.9960409999999911,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity providerNodes: CRUD","status":"passed","title":"providerNodes: CRUD","duration":1.0446249999999964,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity proxyPools: CRUD with sort by updatedAt desc","status":"passed","title":"proxyPools: CRUD with sort by updatedAt desc","duration":12.965125,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity combos: CRUD","status":"passed","title":"combos: CRUD","duration":1.0181249999999977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity modelAliases: KV ops","status":"passed","title":"modelAliases: KV ops","duration":1.348208999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity customModels: add/list/delete with dedupe","status":"passed","title":"customModels: add/list/delete with dedupe","duration":0.7443339999999807,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity mitmAlias: get/set per tool","status":"passed","title":"mitmAlias: get/set per tool","duration":1.0085829999999874,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity disabledModels: add/remove per provider","status":"passed","title":"disabledModels: add/remove per provider","duration":0.8902500000000089,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity usage: saveRequestUsage + getUsageHistory + getUsageStats","status":"passed","title":"usage: saveRequestUsage + getUsageHistory + getUsageStats","duration":4.730540999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity usage: pending tracking in-memory","status":"passed","title":"usage: pending tracking in-memory","duration":18.194750000000028,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity requestDetails: save → query with paging","status":"passed","title":"requestDetails: save → query with paging","duration":224.21970899999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity exportDb / importDb roundtrip","status":"passed","title":"exportDb / importDb roundtrip","duration":23.091834000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity pricing: user pricing merged with constants","status":"passed","title":"pricing: user pricing merged with constants","duration":13.752708999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity getChartData: 24h buckets","status":"passed","title":"getChartData: 24h buckets","duration":4.409416999999962,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity getChartData: 7d buckets","status":"passed","title":"getChartData: 7d buckets","duration":0.7220839999999953,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448827648,"endTime":1781448827976.7222,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-sqlite-vs-lowdb.test.js"},{"assertionResults":[],"startTime":1781448826369,"endTime":1781448826369,"status":"failed","message":"Cannot find module '/cloud/src/handlers/embeddings.js' imported from /Users/Working/router4/app/tests/unit/embeddings.cloud.test.js","name":"/Users/Working/router4/app/tests/unit/embeddings.cloud.test.js"},{"assertionResults":[{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody single string input — includes model and input, default encoding_format=float","status":"passed","title":"single string input — includes model and input, default encoding_format=float","duration":19.951875,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody array input — passes array as-is","status":"passed","title":"array input — passes array as-is","duration":1.6299589999999853,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody custom encoding_format is forwarded","status":"passed","title":"custom encoding_format is forwarded","duration":0.6812090000000239,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody no encoding_format in body → defaults to float","status":"passed","title":"no encoding_format in body → defaults to float","duration":0.4567079999999919,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody gemini single input forwards dimensions as outputDimensionality","status":"passed","title":"gemini single input forwards dimensions as outputDimensionality","duration":1.9345830000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody gemini batch input forwards dimensions on each request","status":"passed","title":"gemini batch input forwards dimensions on each request","duration":1.6552079999999876,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openai → https://api.openai.com/v1/embeddings","status":"passed","title":"openai → https://api.openai.com/v1/embeddings","duration":0.40058400000000915,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openrouter → https://openrouter.ai/api/v1/embeddings","status":"passed","title":"openrouter → https://openrouter.ai/api/v1/embeddings","duration":0.21479200000001697,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl vercel-ai-gateway → https://ai-gateway.vercel.sh/v1/embeddings","status":"passed","title":"vercel-ai-gateway → https://ai-gateway.vercel.sh/v1/embeddings","duration":0.9066669999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openai-compatible-* → uses baseUrl from providerSpecificData","status":"passed","title":"openai-compatible-* → uses baseUrl from providerSpecificData","duration":0.32491699999999923,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openai-compatible-* strips trailing slash from baseUrl","status":"passed","title":"openai-compatible-* strips trailing slash from baseUrl","duration":0.3528750000000116,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openai-compatible-* without baseUrl → falls back to api.openai.com","status":"passed","title":"openai-compatible-* without baseUrl → falls back to api.openai.com","duration":0.25395799999998303,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl unsupported provider (e.g. gemini-cli) → 400 error, no fetch called","status":"passed","title":"unsupported provider (e.g. gemini-cli) → 400 error, no fetch called","duration":0.3526249999999891,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl antigravity (non-openai-compatible, no URL mapping) → 400","status":"passed","title":"antigravity (non-openai-compatible, no URL mapping) → 400","duration":0.13283300000000509,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsHeaders"],"fullName":"buildEmbeddingsHeaders openai → Authorization: Bearer, Content-Type: application/json","status":"passed","title":"openai → Authorization: Bearer, Content-Type: application/json","duration":0.18616700000001174,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsHeaders"],"fullName":"buildEmbeddingsHeaders openai — uses accessToken when apiKey is absent","status":"passed","title":"openai — uses accessToken when apiKey is absent","duration":0.23804100000000972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsHeaders"],"fullName":"buildEmbeddingsHeaders openrouter → adds HTTP-Referer and X-Title headers","status":"passed","title":"openrouter → adds HTTP-Referer and X-Title headers","duration":0.18233299999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsHeaders"],"fullName":"buildEmbeddingsHeaders openai-compatible-* → Authorization: Bearer only (no extra headers)","status":"passed","title":"openai-compatible-* → Authorization: Bearer only (no extra headers)","duration":0.17533300000002328,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation missing input → 400 Bad Request","status":"passed","title":"missing input → 400 Bad Request","duration":0.1529999999999916,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation input is a number → 400 Bad Request","status":"passed","title":"input is a number → 400 Bad Request","duration":0.12554199999999582,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation input is an object → 400 Bad Request","status":"passed","title":"input is an object → 400 Bad Request","duration":0.13308299999999917,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation input is null → 400 Bad Request","status":"passed","title":"input is null → 400 Bad Request","duration":0.0902499999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation empty string input passes validation","status":"passed","title":"empty string input passes validation","duration":0.13462499999999977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation empty array input passes validation and reaches provider","status":"passed","title":"empty array input passes validation and reaches provider","duration":0.16841700000000515,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path returns success=true with Response on 200","status":"passed","title":"returns success=true with Response on 200","duration":0.21612500000000523,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path response body is valid OpenAI-format JSON","status":"passed","title":"response body is valid OpenAI-format JSON","duration":0.23887500000000728,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path response includes CORS header Access-Control-Allow-Origin: *","status":"passed","title":"response includes CORS header Access-Control-Allow-Origin: *","duration":0.18629199999998036,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path response Content-Type is application/json","status":"passed","title":"response Content-Type is application/json","duration":0.41704200000000924,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path calls onRequestSuccess callback on success","status":"passed","title":"calls onRequestSuccess callback on success","duration":0.14008300000000418,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path does not call onRequestSuccess on provider error","status":"passed","title":"does not call onRequestSuccess on provider error","duration":0.24412499999999682,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path provider response with non-standard format is passed through as-is","status":"passed","title":"provider response with non-standard format is passed through as-is","duration":0.1906669999999906,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling provider 400 → returns success=false with status 400","status":"passed","title":"provider 400 → returns success=false with status 400","duration":0.16512499999998909,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling provider 429 → returns success=false with status 429","status":"passed","title":"provider 429 → returns success=false with status 429","duration":0.23754200000001902,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling provider 500 → returns success=false with status 500","status":"passed","title":"provider 500 → returns success=false with status 500","duration":0.14316699999997695,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling network error (fetch throws) → returns 502 Bad Gateway","status":"passed","title":"network error (fetch throws) → returns 502 Bad Gateway","duration":0.17129099999999653,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling invalid JSON from provider → returns 502","status":"passed","title":"invalid JSON from provider → returns 502","duration":0.16012499999999363,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling error result response has OpenAI-format error body","status":"passed","title":"error result response has OpenAI-format error body","duration":0.15766700000000355,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — token refresh on 401/403"],"fullName":"handleEmbeddingsCore — token refresh on 401/403 on 401, attempts retry after refresh; succeeds if refresh gives new token","status":"passed","title":"on 401, attempts retry after refresh; succeeds if refresh gives new token","duration":0.20479199999999764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — token refresh on 401/403"],"fullName":"handleEmbeddingsCore — token refresh on 401/403 on 401 with no refresh token, falls back gracefully (no crash)","status":"passed","title":"on 401 with no refresh token, falls back gracefully (no crash)","duration":0.14612499999998363,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830099,"endTime":1781448830134.2048,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/embeddingsCore.test.js"},{"assertionResults":[{"ancestorTitles":["forceStream provider config"],"fullName":"forceStream provider config only openai/codex/commandcode force streaming","status":"passed","title":"only openai/codex/commandcode force streaming","duration":115.37783300000001,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828489,"endTime":1781448828604.378,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/force-stream-config.test.js"},{"assertionResults":[{"ancestorTitles":["Gemini CLI usage project id resolution"],"fullName":"Gemini CLI usage project id resolution uses the projectId stored on the provider connection","status":"passed","title":"uses the projectId stored on the provider connection","duration":16.365458000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Gemini CLI usage project id resolution"],"fullName":"Gemini CLI usage project id resolution normalizes project objects returned by loadCodeAssist","status":"passed","title":"normalizes project objects returned by loadCodeAssist","duration":1.166624999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Gemini CLI usage project id resolution"],"fullName":"Gemini CLI usage project id resolution returns actionable guidance when no project id is available","status":"passed","title":"returns actionable guidance when no project id is available","duration":0.741375000000005,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830360,"endTime":1781448830378.7415,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/gemini-usage-projectid.test.js"},{"assertionResults":[{"ancestorTitles":["GithubExecutor.supportsResponsesEndpoint"],"fullName":"GithubExecutor.supportsResponsesEndpoint excludes Gemini models from the /responses endpoint","status":"passed","title":"excludes Gemini models from the /responses endpoint","duration":0.8419580000000053,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GithubExecutor.supportsResponsesEndpoint"],"fullName":"GithubExecutor.supportsResponsesEndpoint excludes Claude models from the /responses endpoint","status":"passed","title":"excludes Claude models from the /responses endpoint","duration":0.19241700000000606,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GithubExecutor.supportsResponsesEndpoint"],"fullName":"GithubExecutor.supportsResponsesEndpoint allows OpenAI/codex models on the /responses endpoint","status":"passed","title":"allows OpenAI/codex models on the /responses endpoint","duration":0.1009170000000097,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GithubExecutor.supportsResponsesEndpoint"],"fullName":"GithubExecutor.supportsResponsesEndpoint is null-safe","status":"passed","title":"is null-safe","duration":0.14720800000000622,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GithubExecutor.execute cached-route guard (#1062)"],"fullName":"GithubExecutor.execute cached-route guard (#1062) does NOT use /responses for a Gemini model even if it was wrongly cached as codex","status":"passed","title":"does NOT use /responses for a Gemini model even if it was wrongly cached as codex","duration":0.7049999999999841,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830894,"endTime":1781448830896.705,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/github-responses-routing.test.js"},{"assertionResults":[{"ancestorTitles":["HuggingFace model alias parsing"],"fullName":"HuggingFace model alias parsing resolves hf alias to huggingface provider","status":"passed","title":"resolves hf alias to huggingface provider","duration":1.258375000000001,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830982,"endTime":1781448830983.2583,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/hf-model-routing.test.js"},{"assertionResults":[{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore validates required prompt field","status":"passed","title":"validates required prompt field","duration":8.625875000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore rejects unsupported provider","status":"passed","title":"rejects unsupported provider","duration":0.4166249999999536,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with OpenAI format","status":"passed","title":"generates image with OpenAI format","duration":2.4425840000000107,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with Gemini format","status":"passed","title":"generates image with Gemini format","duration":0.65137500000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with Minimax format","status":"passed","title":"generates image with Minimax format","duration":0.42262499999998226,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with NanoBanana format","status":"passed","title":"generates image with NanoBanana format","duration":2.00895799999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with SD WebUI format","status":"passed","title":"generates image with SD WebUI format","duration":0.7524579999999901,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles OpenRouter with HTTP-Referer header","status":"passed","title":"handles OpenRouter with HTTP-Referer header","duration":0.2996249999999918,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles Vercel AI Gateway image generation as OpenAI-compatible","status":"passed","title":"handles Vercel AI Gateway image generation as OpenAI-compatible","duration":1.080791999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles HuggingFace binary response","status":"passed","title":"handles HuggingFace binary response","duration":1.7431250000000205,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with Codex gpt-5.5-image using current Codex version header","status":"passed","title":"generates image with Codex gpt-5.5-image using current Codex version header","duration":1.870999999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with Cloudflare Workers AI JSON response","status":"passed","title":"generates image with Cloudflare Workers AI JSON response","duration":0.6506669999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore uses multipart form data for Cloudflare FLUX.2 models","status":"passed","title":"uses multipart form data for Cloudflare FLUX.2 models","duration":0.6340000000000146,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore resolves Cloudflare img2img and inpainting URL inputs before sending","status":"passed","title":"resolves Cloudflare img2img and inpainting URL inputs before sending","duration":0.4860830000000078,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles provider error responses","status":"passed","title":"handles provider error responses","duration":0.24625000000003183,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles network errors","status":"passed","title":"handles network errors","duration":0.40866700000003675,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore calls onRequestSuccess callback on success","status":"passed","title":"calls onRequestSuccess callback on success","duration":0.26575000000002547,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830379,"endTime":1781448830402.2659,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/image-generation.test.js"},{"assertionResults":[{"ancestorTitles":["Kiro MITM model slots"],"fullName":"Kiro MITM model slots exposes the kiro mitm tool","status":"passed","title":"exposes the kiro mitm tool","duration":2.9826659999999947,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro MITM model slots"],"fullName":"Kiro MITM model slots offers a mappable slot for the agent default model id 'auto'","status":"failed","title":"offers a mappable slot for the agent default model id 'auto'","duration":4.312666000000036,"failureMessages":["AssertionError: expected undefined to be truthy\n at /Users/Working/router4/app/tests/unit/kiro-model-slots.test.js:21:18\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["Kiro MITM model slots"],"fullName":"Kiro MITM model slots offers a mappable slot for the background sub-task model id 'simple-task'","status":"passed","title":"offers a mappable slot for the background sub-task model id 'simple-task'","duration":0.4522919999999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448826895,"endTime":1781448826902.4524,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/kiro-model-slots.test.js"},{"assertionResults":[{"ancestorTitles":["generateSessionId"],"fullName":"generateSessionId uses the ses_ prefix and a 24-char random suffix","status":"passed","title":"uses the ses_ prefix and a 24-char random suffix","duration":1.0752499999999827,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateSessionId"],"fullName":"generateSessionId only emits lowercase alphanumeric characters in the suffix","status":"passed","title":"only emits lowercase alphanumeric characters in the suffix","duration":0.20495799999997644,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateSessionId"],"fullName":"generateSessionId produces a fresh id on each call","status":"passed","title":"produces a fresh id on each call","duration":0.38887499999998454,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateFingerprint"],"fullName":"generateFingerprint returns a 64-char hex sha256 digest","status":"passed","title":"returns a 64-char hex sha256 digest","duration":2.5813329999999723,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateFingerprint"],"fullName":"generateFingerprint is stable per machine (deterministic across calls)","status":"passed","title":"is stable per machine (deterministic across calls)","duration":0.29870900000003076,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseJwtExp"],"fullName":"parseJwtExp derives the expiry from the JWT exp claim (ms)","status":"passed","title":"derives the expiry from the JWT exp claim (ms)","duration":0.1552920000000313,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseJwtExp"],"fullName":"parseJwtExp falls back to a future timestamp when the JWT is unparseable","status":"passed","title":"falls back to a future timestamp when the JWT is unparseable","duration":0.19200000000000728,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker prepends a system message with the marker when none is present","status":"passed","title":"prepends a system message with the marker when none is present","duration":0.20116600000000062,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker preserves the original user message after injection","status":"passed","title":"preserves the original user message after injection","duration":0.355624999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker keeps a caller-provided system prompt alongside the marker","status":"passed","title":"keeps a caller-provided system prompt alongside the marker","duration":0.12049999999999272,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker does not duplicate the marker when already present","status":"passed","title":"does not duplicate the marker when already present","duration":0.13570799999996552,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker leaves a body without a messages array untouched","status":"passed","title":"leaves a body without a messages array untouched","duration":0.05279200000001083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt returns the jwt from the bootstrap response","status":"passed","title":"returns the jwt from the bootstrap response","duration":0.3385830000000283,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt sends the machine fingerprint as the bootstrap client","status":"passed","title":"sends the machine fingerprint as the bootstrap client","duration":0.18595799999997098,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt caches the jwt and does not re-fetch while still valid","status":"passed","title":"caches the jwt and does not re-fetch while still valid","duration":0.15916700000002493,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt re-fetches once the cached jwt is within the expiry buffer","status":"passed","title":"re-fetches once the cached jwt is within the expiry buffer","duration":0.14670800000004647,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt throws when the bootstrap response is not ok","status":"passed","title":"throws when the bootstrap response is not ok","duration":0.6292500000000132,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt throws when the bootstrap response has no jwt","status":"passed","title":"throws when the bootstrap response has no jwt","duration":0.13850000000002183,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor buildUrl returns the free-ai chat endpoint","status":"passed","title":"buildUrl returns the free-ai chat endpoint","duration":0.049541999999973996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor buildHeaders includes the MiMo source and session affinity","status":"passed","title":"buildHeaders includes the MiMo source and session affinity","duration":0.0843749999999659,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor transformRequest injects the system marker","status":"passed","title":"transformRequest injects the system marker","duration":0.05033299999996643,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor execute injects the marker and sends a Bearer JWT to the chat endpoint","status":"passed","title":"execute injects the marker and sends a Bearer JWT to the chat endpoint","duration":0.3338330000000269,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor re-bootstraps and retries once on a 403 from the chat endpoint","status":"passed","title":"re-bootstraps and retries once on a 403 from the chat endpoint","duration":0.21375000000000455,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration registers a specialized executor for mimo-free and the mmf alias","status":"passed","title":"registers a specialized executor for mimo-free and the mmf alias","duration":0.17279200000001538,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration registers mimo-free as a no-auth provider in open-sse config","status":"passed","title":"registers mimo-free as a no-auth provider in open-sse config","duration":0.05662499999999682,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration exposes only mimo-auto (the sole free-channel model)","status":"passed","title":"exposes only mimo-auto (the sole free-channel model)","duration":0.11700000000001864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration maps the mimo-free alias to mmf","status":"passed","title":"maps the mimo-free alias to mmf","duration":0.040792000000010376,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration lists mimo-free in the dashboard FREE_PROVIDERS catalog","status":"passed","title":"lists mimo-free in the dashboard FREE_PROVIDERS catalog","duration":0.04995800000000372,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830600,"endTime":1781448830609.117,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/mimo-free.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax TTS"],"fullName":"MiniMax TTS sends MiniMax T2A payload and converts hex audio to base64 JSON","status":"passed","title":"sends MiniMax T2A payload and converts hex audio to base64 JSON","duration":19.469167,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax TTS"],"fullName":"MiniMax TTS uses the default MiniMax voice when no voice is provided","status":"passed","title":"uses the default MiniMax voice when no voice is provided","duration":0.603541999999976,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax TTS"],"fullName":"MiniMax TTS surfaces MiniMax base_resp errors","status":"passed","title":"surfaces MiniMax base_resp errors","duration":0.6619169999999883,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830161,"endTime":1781448830181.6619,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/minimax-tts.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage parses token-plan TTS quota counts as used counts","status":"passed","title":"parses token-plan TTS quota counts as used counts","duration":13.015708000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage parses coding-plan TTS quota counts as remaining counts","status":"passed","title":"parses coding-plan TTS quota counts as remaining counts","duration":0.5898750000000064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage keeps non-TTS MiniMax quota rows instead of filtering to text only","status":"passed","title":"keeps non-TTS MiniMax quota rows instead of filtering to text only","duration":0.3959999999999866,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage includes M-series percent-only buckets that have no count totals","status":"passed","title":"includes M-series percent-only buckets that have no count totals","duration":0.5042919999999924,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage normalizes M-series percent-only buckets on the coding_plan (countMeansRemaining) endpoint too","status":"passed","title":"normalizes M-series percent-only buckets on the coding_plan (countMeansRemaining) endpoint too","duration":0.4350000000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage renders the M3-era MiniMax-M* wildcard as a friendly series label","status":"passed","title":"renders the M3-era MiniMax-M* wildcard as a friendly series label","duration":0.4444169999999872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage prefers the upstream-provided remaining percent when counts are also present","status":"passed","title":"prefers the upstream-provided remaining percent when counts are also present","duration":0.2526670000000024,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830462,"endTime":1781448830477.4443,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/minimax-usage.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax voices API"],"fullName":"MiniMax voices API fetches global MiniMax voices with stored API key","status":"passed","title":"fetches global MiniMax voices with stored API key","duration":15.609374999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax voices API"],"fullName":"MiniMax voices API fetches China MiniMax voices when provider=minimax-cn","status":"passed","title":"fetches China MiniMax voices when provider=minimax-cn","duration":1.0512079999999742,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830331,"endTime":1781448830347.0513,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/minimax-voices.test.js"},{"assertionResults":[{"ancestorTitles":["model name regex fallback (C2)"],"fullName":"model name regex fallback (C2) derives display name from id per family","status":"passed","title":"derives display name from id per family","duration":1.2154159999999905,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model name regex fallback (C2)"],"fullName":"model name regex fallback (C2) falls back to id verbatim when no pattern matches","status":"passed","title":"falls back to id verbatim when no pattern matches","duration":0.18862500000000182,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model name regex fallback (C2)"],"fullName":"model name regex fallback (C2) normalizeModel: explicit name always wins over regex","status":"passed","title":"normalizeModel: explicit name always wins over regex","duration":0.08054200000000833,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model name regex fallback (C2)"],"fullName":"model name regex fallback (C2) normalizeModel: terse string id becomes object with derived name","status":"passed","title":"normalizeModel: terse string id becomes object with derived name","duration":0.14237500000000125,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830814,"endTime":1781448830815.2153,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/model-name-regex.test.js"},{"assertionResults":[{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing routes image model tests to /api/v1/images/generations","status":"passed","title":"routes image model tests to /api/v1/images/generations","duration":130.29983399999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing routes embedding model tests to /api/v1/embeddings","status":"passed","title":"routes embedding model tests to /api/v1/embeddings","duration":1.8100000000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing fails embedding model tests when provider returns no embedding data","status":"passed","title":"fails embedding model tests when provider returns no embedding data","duration":1.7659170000000302,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing routes stt model tests to /api/v1/audio/transcriptions","status":"passed","title":"routes stt model tests to /api/v1/audio/transcriptions","duration":3.7134170000000495,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing returns formatted HTTP errors for non-2xx embedding responses","status":"passed","title":"returns formatted HTTP errors for non-2xx embedding responses","duration":0.6535420000000158,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828719,"endTime":1781448828857.6536,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/model-test-routing.test.js"},{"assertionResults":[{"ancestorTitles":["openai→claude: image_url.detail is dropped (docs 11 §4)"],"fullName":"openai→claude: image_url.detail is dropped (docs 11 §4) converts image to base64 source WITHOUT a detail field","status":"passed","title":"converts image to base64 source WITHOUT a detail field","duration":3.7802079999999876,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openai→claude: image_url.detail is dropped (docs 11 §4)"],"fullName":"openai→claude: image_url.detail is dropped (docs 11 §4) drops input_audio entirely (claude has no audio block)","status":"passed","title":"drops input_audio entirely (claude has no audio block)","duration":0.48070800000002123,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openai→gemini: input_audio is mapped to inlineData (docs 11 §4)"],"fullName":"openai→gemini: input_audio is mapped to inlineData (docs 11 §4) maps wav → audio/wav inlineData","status":"passed","title":"maps wav → audio/wav inlineData","duration":0.26400000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openai→gemini: input_audio is mapped to inlineData (docs 11 §4)"],"fullName":"openai→gemini: input_audio is mapped to inlineData (docs 11 §4) maps mp3 → audio/mpeg inlineData","status":"passed","title":"maps mp3 → audio/mpeg inlineData","duration":0.07975000000001842,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openai→gemini: input_audio is mapped to inlineData (docs 11 §4)"],"fullName":"openai→gemini: input_audio is mapped to inlineData (docs 11 §4) drops image_url.detail (not carried into inlineData)","status":"passed","title":"drops image_url.detail (not carried into inlineData)","duration":0.16579200000001038,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448831111,"endTime":1781448831116.1658,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/multimodal-drop-lock.test.js"},{"assertionResults":[{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import returns not-found when no macOS cursor db paths are accessible","status":"failed","title":"returns not-found when no macOS cursor db paths are accessible","duration":61.59179200000003,"failureMessages":["AssertionError: expected 'Cursor database not found. Checked lo…' to contain 'Cursor database not found in known ma…'\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:74:33\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import returns descriptive error if macOS db file exists but cannot be opened","status":"failed","title":"returns descriptive error if macOS db file exists but cannot be opened","duration":163.90858400000002,"failureMessages":["AssertionError: the given combination of arguments (undefined and string) is invalid for this assertion. You can use an array, a map, an object, a set, a string, or a weakset instead of a string\n at Proxy. (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/expect/dist/index.js:1319:15)\n at Proxy. (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/expect/dist/index.js:1156:15)\n at Proxy.methodWrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/chai/index.js:1700:25)\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:84:33\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import extracts tokens using exact keys","status":"failed","title":"extracts tokens using exact keys","duration":62.18458400000003,"failureMessages":["AssertionError: expected false to be true // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:101:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import unwraps JSON-encoded string values","status":"failed","title":"unwraps JSON-encoded string values","duration":158.72770799999995,"failureMessages":["AssertionError: expected false to be true // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:118:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import falls back to fuzzy key matching on macOS when exact keys are missing","status":"failed","title":"falls back to fuzzy key matching on macOS when exact keys are missing","duration":102.66083400000002,"failureMessages":["AssertionError: expected false to be true // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:142:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import returns login-prompt error when tokens are missing even after fallback","status":"failed","title":"returns login-prompt error when tokens are missing even after fallback","duration":43.062082999999916,"failureMessages":["AssertionError: the given combination of arguments (undefined and string) is invalid for this assertion. You can use an array, a map, an object, a set, a string, or a weakset instead of a string\n at Proxy. (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/expect/dist/index.js:1319:15)\n at Proxy. (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/expect/dist/index.js:1156:15)\n at Proxy.methodWrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/chai/index.js:1700:25)\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:156:33\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import linux uses single hardcoded path and original error message","status":"failed","title":"linux uses single hardcoded path and original error message","duration":3.568832999999927,"failureMessages":["AssertionError: expected 'Cursor database not found. Checked lo…' to be 'Cursor database not found. Make sure …' // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:169:33\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import unsupported platform returns 400","status":"failed","title":"unsupported platform returns 400","duration":0.5238749999999754,"failureMessages":["AssertionError: expected 200 to be 400 // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:181:29\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]}],"startTime":1781448826822,"endTime":1781448827418.524,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js"},{"assertionResults":[{"ancestorTitles":["OpenAI Responses streaming termination"],"fullName":"OpenAI Responses streaming termination emits a response.failed event when a Responses stream closes before a terminal event","status":"passed","title":"emits a response.failed event when a Responses stream closes before a terminal event","duration":64.62937499999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI Responses streaming termination"],"fullName":"OpenAI Responses streaming termination does not add response.failed when a Responses stream already completed","status":"passed","title":"does not add response.failed when a Responses stream already completed","duration":1.0588750000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI Responses streaming termination"],"fullName":"OpenAI Responses streaming termination emits response.failed before DONE when a Responses stream sends DONE without a terminal event","status":"passed","title":"emits response.failed before DONE when a Responses stream sends DONE without a terminal event","duration":1.6548329999999964,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829172,"endTime":1781448829238.6548,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-responses-terminal-event.test.js"},{"assertionResults":[{"ancestorTitles":["openaiToClaudeResponse tool argument sanitization"],"fullName":"openaiToClaudeResponse tool argument sanitization drops invalid Read pages and clamps numeric bounds","status":"passed","title":"drops invalid Read pages and clamps numeric bounds","duration":2.4383339999999976,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeResponse tool argument sanitization"],"fullName":"openaiToClaudeResponse tool argument sanitization keeps valid PDF pages","status":"passed","title":"keeps valid PDF pages","duration":0.37983400000001666,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448831243,"endTime":1781448831246.38,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-to-claude-response-tools.test.js"},{"assertionResults":[{"ancestorTitles":["openaiToClaudeRequest","response_format handling"],"fullName":"openaiToClaudeRequest response_format handling should inject JSON schema instructions for json_schema type","status":"passed","title":"should inject JSON schema instructions for json_schema type","duration":11.04704199999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","response_format handling"],"fullName":"openaiToClaudeRequest response_format handling should inject basic JSON instructions for json_object type","status":"passed","title":"should inject basic JSON instructions for json_object type","duration":0.8050829999999678,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","response_format handling"],"fullName":"openaiToClaudeRequest response_format handling should not modify system prompt when response_format is missing","status":"passed","title":"should not modify system prompt when response_format is missing","duration":0.21783300000015515,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","response_format handling"],"fullName":"openaiToClaudeRequest response_format handling should preserve existing system messages when adding response_format","status":"passed","title":"should preserve existing system messages when adding response_format","duration":0.4149999999999636,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling converts OpenAI forced tool ({type:'function'}) to Claude {type:'tool'}","status":"passed","title":"converts OpenAI forced tool ({type:'function'}) to Claude {type:'tool'}","duration":0.7562500000001364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling maps string tool_choice values","status":"passed","title":"maps string tool_choice values","duration":1.0359579999999369,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling passes through Claude-native tool_choice objects unchanged","status":"passed","title":"passes through Claude-native tool_choice objects unchanged","duration":1.3487499999998818,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling never leaks an invalid type (falls back to auto)","status":"passed","title":"never leaks an invalid type (falls back to auto)","duration":7.088334000000032,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling omits tool_choice entirely when the request has none","status":"passed","title":"omits tool_choice entirely when the request has none","duration":2.1676250000000437,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeResponse"],"fullName":"openaiToClaudeResponse omits empty Read pages tool argument before emitting Claude input deltas","status":"failed","title":"omits empty Read pages tool argument before emitting Claude input deltas","duration":30.790541000000076,"failureMessages":["AssertionError: expected undefined to be defined\n at /Users/Working/router4/app/tests/unit/openai-to-claude.test.js:199:24\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]}],"startTime":1781448827906,"endTime":1781448827966.7905,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-to-claude.test.js"},{"assertionResults":[{"ancestorTitles":["openaiToCommandCodeRequest — basic envelope"],"fullName":"openaiToCommandCodeRequest — basic envelope returns the expected top-level envelope shape","status":"passed","title":"returns the expected top-level envelope shape","duration":1.9422910000000115,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — system handling"],"fullName":"openaiToCommandCodeRequest — system handling hoists system messages to params.system (string), not messages[]","status":"passed","title":"hoists system messages to params.system (string), not messages[]","duration":0.5932499999999834,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — system handling"],"fullName":"openaiToCommandCodeRequest — system handling joins multiple system messages with blank line","status":"passed","title":"joins multiple system messages with blank line","duration":0.17016699999999219,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — system handling"],"fullName":"openaiToCommandCodeRequest — system handling omits params.system when no system messages","status":"passed","title":"omits params.system when no system messages","duration":0.09966700000001083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — content shape"],"fullName":"openaiToCommandCodeRequest — content shape MUST always emit content as Array (never string) for user","status":"passed","title":"MUST always emit content as Array (never string) for user","duration":0.3215829999999755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — content shape"],"fullName":"openaiToCommandCodeRequest — content shape MUST always emit content as Array for assistant","status":"passed","title":"MUST always emit content as Array for assistant","duration":0.14791599999998084,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — tool role / tool-result (AI SDK)"],"fullName":"openaiToCommandCodeRequest — tool role / tool-result (AI SDK) converts role:\"tool\" to role:\"tool\" with tool-result block; output is {type:\"text\",value}","status":"passed","title":"converts role:\"tool\" to role:\"tool\" with tool-result block; output is {type:\"text\",value}","duration":0.1653750000000116,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — assistant tool_calls / tool-call"],"fullName":"openaiToCommandCodeRequest — assistant tool_calls / tool-call converts assistant.tool_calls[] into content blocks of type tool-call","status":"passed","title":"converts assistant.tool_calls[] into content blocks of type tool-call","duration":0.1334590000000162,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — tools schema conversion"],"fullName":"openaiToCommandCodeRequest — tools schema conversion converts OpenAI {type:\"function\", function:{...}} to Anthropic plain {name, input_schema}","status":"passed","title":"converts OpenAI {type:\"function\", function:{...}} to Anthropic plain {name, input_schema}","duration":0.4405840000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — tools schema conversion"],"fullName":"openaiToCommandCodeRequest — tools schema conversion preserves description on converted tool","status":"passed","title":"preserves description on converted tool","duration":0.10604100000000471,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — tools schema conversion"],"fullName":"openaiToCommandCodeRequest — tools schema conversion does not include tools field when input has none","status":"passed","title":"does not include tools field when input has none","duration":0.10658399999999801,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830820,"endTime":1781448830825.1067,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-to-commandcode.test.js"},{"assertionResults":[{"ancestorTitles":["openaiToKiroRequest","basic message conversion"],"fullName":"openaiToKiroRequest basic message conversion should convert a simple text message","status":"passed","title":"should convert a simple text message","duration":2.212874999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","basic message conversion"],"fullName":"openaiToKiroRequest basic message conversion should not include images field when no images are present","status":"passed","title":"should not include images field when no images are present","duration":0.13391699999999673,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should forward base64 image from image_url content part","status":"passed","title":"should forward base64 image from image_url content part","duration":0.6194580000000087,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should forward multiple base64 images","status":"passed","title":"should forward multiple base64 images","duration":0.336749999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should not include images field when images array is empty","status":"passed","title":"should not include images field when images array is empty","duration":0.22679200000001742,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should include both images and text content together","status":"passed","title":"should include both images and text content together","duration":0.1943339999999978,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should treat http image URLs as text fallback (Kiro only supports base64)","status":"passed","title":"should treat http image URLs as text fallback (Kiro only supports base64)","duration":0.09691599999999312,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","tool interaction without client-provided tools"],"fullName":"openaiToKiroRequest tool interaction without client-provided tools should flatten OpenAI tool_calls + tool result into history text with no tools array","status":"passed","title":"should flatten OpenAI tool_calls + tool result into history text with no tools array","duration":0.24958300000000122,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","tool interaction without client-provided tools"],"fullName":"openaiToKiroRequest tool interaction without client-provided tools should flatten Claude tool_use / tool_result blocks with no tools array","status":"passed","title":"should flatten Claude tool_use / tool_result blocks with no tools array","duration":0.5567500000000223,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","tool interaction without client-provided tools"],"fullName":"openaiToKiroRequest tool interaction without client-provided tools should keep structured tools when the client DOES provide a tools array","status":"passed","title":"should keep structured tools when the client DOES provide a tools array","duration":0.2936249999999916,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","tool interaction without client-provided tools"],"fullName":"openaiToKiroRequest tool interaction without client-provided tools should salvage orphaned tool_result content as text instead of discarding it","status":"passed","title":"should salvage orphaned tool_result content as text instead of discarding it","duration":0.18508299999999167,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830665,"endTime":1781448830670.2937,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-to-kiro.test.js"},{"assertionResults":[{"ancestorTitles":["parseOpenAIMessages"],"fullName":"parseOpenAIMessages extracts system + history + current msg","status":"passed","title":"extracts system + history + current msg","duration":2.9319159999999727,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseOpenAIMessages"],"fullName":"parseOpenAIMessages treats developer role as system","status":"passed","title":"treats developer role as system","duration":0.4455829999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseOpenAIMessages"],"fullName":"parseOpenAIMessages handles multi-part content (array of text blocks)","status":"passed","title":"handles multi-part content (array of text blocks)","duration":0.22958400000001689,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseOpenAIMessages"],"fullName":"parseOpenAIMessages skips empty content messages","status":"passed","title":"skips empty content messages","duration":0.2723749999999825,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery first turn: returns JSON with instructions + query","status":"passed","title":"first turn: returns JSON with instructions + query","duration":0.8488330000000133,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery follow-up (with backendUuid): returns plain currentMsg, no JSON","status":"passed","title":"follow-up (with backendUuid): returns plain currentMsg, no JSON","duration":0.1402089999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery includes history when present on first turn","status":"passed","title":"includes history when present on first turn","duration":0.16941599999998402,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery injects tools into instructions on first turn","status":"passed","title":"injects tools into instructions on first turn","duration":0.13075000000000614,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery ignores tools on follow-up turn (uses session)","status":"passed","title":"ignores tools on follow-up turn (uses session)","duration":0.9647080000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery truncates query if JSON exceeds 96000 chars","status":"passed","title":"truncates query if JSON exceeds 96000 chars","duration":0.6628330000000062,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatToolsHint"],"fullName":"formatToolsHint returns empty string for no tools","status":"passed","title":"returns empty string for no tools","duration":0.2652089999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatToolsHint"],"fullName":"formatToolsHint handles OpenAI tool schema (function wrapper)","status":"passed","title":"handles OpenAI tool schema (function wrapper)","duration":0.12562499999998522,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatToolsHint"],"fullName":"formatToolsHint handles flat tool schema","status":"passed","title":"handles flat tool schema","duration":0.09466699999998696,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatToolsHint"],"fullName":"formatToolsHint truncates long descriptions to first line, max 200 chars","status":"passed","title":"truncates long descriptions to first line, max 200 chars","duration":0.5120420000000081,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildPplxRequestBody"],"fullName":"buildPplxRequestBody sets query_str at both top-level AND params (required by upstream API)","status":"passed","title":"sets query_str at both top-level AND params (required by upstream API)","duration":22.991000000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildPplxRequestBody"],"fullName":"buildPplxRequestBody includes required params","status":"passed","title":"includes required params","duration":0.7584580000000187,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute maps pplx-auto → mode=concise, pref=pplx_pro","status":"passed","title":"maps pplx-auto → mode=concise, pref=pplx_pro","duration":30.380041000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute applies THINKING_MAP when reasoning_effort is set","status":"passed","title":"applies THINKING_MAP when reasoning_effort is set","duration":1.8470419999999876,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute sends Cookie header when credentials.apiKey provided","status":"passed","title":"sends Cookie header when credentials.apiKey provided","duration":1.160790999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute sends Bearer header when credentials.accessToken provided","status":"passed","title":"sends Bearer header when credentials.accessToken provided","duration":0.5256660000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute injects body.tools into query_str instructions","status":"passed","title":"injects body.tools into query_str instructions","duration":0.9201659999999947,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute returns 400 on missing messages","status":"passed","title":"returns 400 on missing messages","duration":0.18850000000000477,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute surfaces upstream 401 with friendly auth message","status":"passed","title":"surfaces upstream 401 with friendly auth message","duration":1.3179580000000044,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute surfaces 429 with rate-limit message","status":"passed","title":"surfaces 429 with rate-limit message","duration":1.7379170000000101,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829668,"endTime":1781448829738.738,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/perplexity-web.test.js"},{"assertionResults":[{"ancestorTitles":["provider display split (E1)"],"fullName":"provider display split (E1) AI_PROVIDERS entries still carry merged display + transport","status":"passed","title":"AI_PROVIDERS entries still carry merged display + transport","duration":51.73449999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["provider display split (E1)"],"fullName":"provider display split (E1) display fields source from providersDisplay.js","status":"passed","title":"display fields source from providersDisplay.js","duration":16.248084000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["provider display split (E1)"],"fullName":"provider display split (E1) helpers still work after split","status":"passed","title":"helpers still work after split","duration":0.2819159999999954,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828974,"endTime":1781448829042.282,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-display-split.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax-M3 model registration"],"fullName":"MiniMax-M3 model registration includes MiniMax-M3 in PROVIDER_MODELS.minimax","status":"passed","title":"includes MiniMax-M3 in PROVIDER_MODELS.minimax","duration":3.2913330000000087,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 model registration"],"fullName":"MiniMax-M3 model registration includes MiniMax-M3 in PROVIDER_MODELS['minimax-cn']","status":"passed","title":"includes MiniMax-M3 in PROVIDER_MODELS['minimax-cn']","duration":0.2988330000000019,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 model registration"],"fullName":"MiniMax-M3 model registration exposes MiniMax-M3 through getModelsByProviderId for both provider IDs","status":"passed","title":"exposes MiniMax-M3 through getModelsByProviderId for both provider IDs","duration":0.1611249999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 model registration"],"fullName":"MiniMax-M3 model registration does not regress the existing M2.7 / M2.5 / M2.1 entries","status":"passed","title":"does not regress the existing M2.7 / M2.5 / M2.1 entries","duration":0.650416999999976,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830954,"endTime":1781448830957.6504,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-models-minimax-m3.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing includes MiniMax-M3 in MODEL_PRICING","status":"passed","title":"includes MiniMax-M3 in MODEL_PRICING","duration":1.8166250000000161,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing MiniMax-M3 pricing has numeric shape (input, output, cached)","status":"passed","title":"MiniMax-M3 pricing has numeric shape (input, output, cached)","duration":0.6368750000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing MiniMax-M3 input price matches the design spec (0.30)","status":"passed","title":"MiniMax-M3 input price matches the design spec (0.30)","duration":0.11537500000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing MiniMax-M3 output price matches the design spec (1.20)","status":"passed","title":"MiniMax-M3 output price matches the design spec (1.20)","duration":0.11566600000000449,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing MiniMax-M3 cached price matches the design spec (0.06)","status":"passed","title":"MiniMax-M3 cached price matches the design spec (0.06)","duration":0.2629579999999976,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448831225,"endTime":1781448831228.263,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-pricing-minimax-m3.test.js"},{"assertionResults":[{"ancestorTitles":["provider test-models route kind routing"],"fullName":"provider test-models route kind routing routes huggingface image models to /api/v1/images/generations","status":"passed","title":"routes huggingface image models to /api/v1/images/generations","duration":152.113791,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828746,"endTime":1781448828898.1138,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-test-models-routing.test.js"},{"assertionResults":[{"ancestorTitles":["Provider Validation API","OpenAI Compatible"],"fullName":"Provider Validation API OpenAI Compatible should return valid:true when /models succeeds","status":"passed","title":"should return valid:true when /models succeeds","duration":2.3407080000000065,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","OpenAI Compatible"],"fullName":"Provider Validation API OpenAI Compatible should fallback to chat/completions when /models fails and modelId provided","status":"passed","title":"should fallback to chat/completions when /models fails and modelId provided","duration":0.23466699999998752,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","OpenAI Compatible"],"fullName":"Provider Validation API OpenAI Compatible should return error when /models fails and no modelId","status":"passed","title":"should return error when /models fails and no modelId","duration":0.25875000000000625,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Anthropic Compatible"],"fullName":"Provider Validation API Anthropic Compatible should normalize URL by removing /messages suffix","status":"passed","title":"should normalize URL by removing /messages suffix","duration":0.13416700000000503,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Anthropic Compatible"],"fullName":"Provider Validation API Anthropic Compatible should send correct headers for Anthropic API","status":"passed","title":"should send correct headers for Anthropic API","duration":0.40629200000000765,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - Network"],"fullName":"Provider Validation API Error Messages - Network should map ECONNREFUSED to user-friendly message","status":"passed","title":"should map ECONNREFUSED to user-friendly message","duration":0.08112500000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - Network"],"fullName":"Provider Validation API Error Messages - Network should map ENOTFOUND to user-friendly message","status":"passed","title":"should map ENOTFOUND to user-friendly message","duration":0.10541700000000276,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - Network"],"fullName":"Provider Validation API Error Messages - Network should map timeout to user-friendly message","status":"passed","title":"should map timeout to user-friendly message","duration":0.06612499999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - Network"],"fullName":"Provider Validation API Error Messages - Network should map CERT_HAS_EXPIRED to user-friendly message","status":"passed","title":"should map CERT_HAS_EXPIRED to user-friendly message","duration":0.2922920000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","URL Validation"],"fullName":"Provider Validation API URL Validation should validate correct URL format","status":"passed","title":"should validate correct URL format","duration":0.14879100000000278,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return auth error for 401","status":"passed","title":"should return auth error for 401","duration":0.11925000000000807,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return auth error for 403","status":"passed","title":"should return auth error for 403","duration":0.04504200000000935,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return not found for 404","status":"passed","title":"should return not found for 404","duration":0.03900000000000148,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return server error for 500","status":"passed","title":"should return server error for 500","duration":0.03525000000000489,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return server error for 502","status":"passed","title":"should return server error for 502","duration":0.03645900000000779,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return unexpected for other codes","status":"passed","title":"should return unexpected for other codes","duration":0.10833399999999926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return auth error for 401","status":"passed","title":"should return auth error for 401","duration":0.04724999999999113,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return invalid model for 400","status":"passed","title":"should return invalid model for 400","duration":0.03458299999999781,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return not found for 404","status":"passed","title":"should return not found for 404","duration":0.03741599999999323,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return server error for 503","status":"passed","title":"should return server error for 503","duration":0.03737499999999727,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return failed for other codes","status":"passed","title":"should return failed for other codes","duration":0.036708000000004404,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Response Format"],"fullName":"Provider Validation API Response Format should return correct format for success via /models","status":"passed","title":"should return correct format for success via /models","duration":0.06616699999999298,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Response Format"],"fullName":"Provider Validation API Response Format should return correct format for success via chat","status":"passed","title":"should return correct format for success via chat","duration":0.07070799999999622,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Response Format"],"fullName":"Provider Validation API Response Format should return correct format for failure with error","status":"passed","title":"should return correct format for failure with error","duration":0.057291999999989685,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830713,"endTime":1781448830718.1084,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-validation.test.js"},{"assertionResults":[{"ancestorTitles":["QODER_MODEL_MAP"],"fullName":"QODER_MODEL_MAP allows Qoder's latest model key","status":"passed","title":"allows Qoder's latest model key","duration":1.6849999999999739,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["QODER_MODEL_MAP"],"fullName":"QODER_MODEL_MAP exposes Qoder's latest model in the static provider catalog","status":"passed","title":"exposes Qoder's latest model in the static provider catalog","duration":0.18670899999997914,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody preserves base64 length (input length divisible by 3)","status":"passed","title":"preserves base64 length (input length divisible by 3)","duration":0.14300000000000068,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody preserves base64 length (input length not divisible by 3)","status":"passed","title":"preserves base64 length (input length not divisible by 3)","duration":0.13008299999998485,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody handles empty input without throwing","status":"passed","title":"handles empty input without throwing","duration":0.06920800000000327,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody accepts string and Buffer inputs equivalently","status":"passed","title":"accepts string and Buffer inputs equivalently","duration":0.12166700000000219,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody only emits characters from the custom alphabet","status":"passed","title":"only emits characters from the custom alphabet","duration":1.7204169999999976,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody is deterministic for identical input","status":"passed","title":"is deterministic for identical input","duration":0.15854200000001129,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody produces different output for different input","status":"passed","title":"produces different output for different input","duration":0.6565840000000094,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generatePkcePair"],"fullName":"generatePkcePair produces base64url-safe verifier and challenge of the right length","status":"passed","title":"produces base64url-safe verifier and challenge of the right length","duration":0.5022500000000036,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generatePkcePair"],"fullName":"generatePkcePair verifier and challenge are different (challenge is sha256 of verifier)","status":"passed","title":"verifier and challenge are different (challenge is sha256 of verifier)","duration":0.15541699999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generatePkcePair"],"fullName":"generatePkcePair returns codeVerifier (not verifier) on the higher-level helper","status":"passed","title":"returns codeVerifier (not verifier) on the higher-level helper","duration":0.2626669999999933,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["initiateDeviceFlow"],"fullName":"initiateDeviceFlow produces a verification URL pointing at qoder.com/device/selectAccounts","status":"passed","title":"produces a verification URL pointing at qoder.com/device/selectAccounts","duration":0.15541699999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["initiateDeviceFlow"],"fullName":"initiateDeviceFlow returns nonce and machineId as UUIDs","status":"passed","title":"returns nonce and machineId as UUIDs","duration":0.1292500000000132,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders produces all required Cosy-* headers","status":"passed","title":"produces all required Cosy-* headers","duration":1.1134999999999877,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Authorization is a Bearer COSY token with payload+sig","status":"passed","title":"Authorization is a Bearer COSY token with payload+sig","duration":0.23270799999997394,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-Sigpath strips the leading /algo prefix","status":"passed","title":"Cosy-Sigpath strips the leading /algo prefix","duration":0.1302079999999819,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-Sigpath also handles the encoded chat URL","status":"passed","title":"Cosy-Sigpath also handles the encoded chat URL","duration":0.18174999999999386,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-Bodyhash is the MD5 of the request body, Cosy-Bodylength is the length","status":"passed","title":"Cosy-Bodyhash is the MD5 of the request body, Cosy-Bodylength is the length","duration":0.12954099999998903,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders empty body produces the canonical empty-MD5 hash","status":"passed","title":"empty body produces the canonical empty-MD5 hash","duration":0.12633299999998826,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-Machineid + Cosy-Machinetoken match the supplied machineId","status":"passed","title":"Cosy-Machineid + Cosy-Machinetoken match the supplied machineId","duration":0.11070799999998826,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders auto-generates a machineId when none is supplied","status":"passed","title":"auto-generates a machineId when none is supplied","duration":0.11299999999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders throws when userId is missing","status":"passed","title":"throws when userId is missing","duration":0.34279200000000287,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders throws when authToken is missing","status":"passed","title":"throws when authToken is missing","duration":0.0842089999999871,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-User reflects the supplied userId verbatim","status":"passed","title":"Cosy-User reflects the supplied userId verbatim","duration":0.429916999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders two calls with identical inputs differ only in fields that include fresh randomness","status":"passed","title":"two calls with identical inputs differ only in fields that include fresh randomness","duration":0.2632499999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry accepts ms-epoch as a JSON number","status":"passed","title":"accepts ms-epoch as a JSON number","duration":0.06929199999999014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry accepts ms-epoch as a numeric string","status":"passed","title":"accepts ms-epoch as a numeric string","duration":0.05333299999998076,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry accepts RFC3339 strings","status":"passed","title":"accepts RFC3339 strings","duration":0.05120900000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry does not interpret short numeric strings as a year","status":"passed","title":"does not interpret short numeric strings as a year","duration":0.03516700000000128,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry falls back to expiresInSeconds when expiresAt is missing","status":"passed","title":"falls back to expiresInSeconds when expiresAt is missing","duration":0.08329200000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry treats expires_in: 0 as already expired (now), not 30-day fallback","status":"passed","title":"treats expires_in: 0 as already expired (now), not 30-day fallback","duration":0.041957999999993945,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry falls back to ~30 days when both inputs are missing","status":"passed","title":"falls back to ~30 days when both inputs are missing","duration":0.044749999999993406,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry falls back to ~30 days when both inputs are unparseable","status":"passed","title":"falls back to ~30 days when both inputs are unparseable","duration":0.046165999999999485,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeMessages"],"fullName":"normalizeMessages hoists role:system out of messages into systemText","status":"passed","title":"hoists role:system out of messages into systemText","duration":0.3987500000000068,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeMessages"],"fullName":"normalizeMessages flattens multipart text content into a string","status":"passed","title":"flattens multipart text content into a string","duration":0.045417000000014696,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeMessages"],"fullName":"normalizeMessages joins multiple system messages with a blank line","status":"passed","title":"joins multiple system messages with a blank line","duration":0.03766700000002743,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeMessages"],"fullName":"normalizeMessages returns empty results for empty input","status":"passed","title":"returns empty results for empty input","duration":0.09320900000000165,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE forwards an OpenAI envelope chunk and emits [DONE] in flush","status":"passed","title":"forwards an OpenAI envelope chunk and emits [DONE] in flush","duration":19.861582999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE drains a trailing partial line without a newline in flush()","status":"passed","title":"drains a trailing partial line without a newline in flush()","duration":0.3746669999999881,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE does not forward chunks after [DONE] has been emitted","status":"passed","title":"does not forward chunks after [DONE] has been emitted","duration":0.4540839999999946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE strips embedded newlines from inner body before forwarding","status":"passed","title":"strips embedded newlines from inner body before forwarding","duration":0.33229199999999537,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE upstream error envelope produces an error chunk + [DONE]","status":"passed","title":"upstream error envelope produces an error chunk + [DONE]","duration":0.655208999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE non-ok responses are returned unchanged (no transform)","status":"passed","title":"non-ok responses are returned unchanged (no transform)","duration":2.7979589999999916,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829951,"endTime":1781448829985.7979,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/qoder.test.js"},{"assertionResults":[{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip injects reasoning_content on a deepseek- assistant message that lacks it","status":"passed","title":"injects reasoning_content on a deepseek- assistant message that lacks it","duration":1.0151249999999834,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip preserves an existing reasoning_content instead of overwriting it","status":"passed","title":"preserves an existing reasoning_content instead of overwriting it","duration":0.18574999999998454,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip applies provider-level rule for provider 'deepseek' (scope all)","status":"passed","title":"applies provider-level rule for provider 'deepseek' (scope all)","duration":0.09191699999999514,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip matches deepseek model id case-insensitively for custom providers (#1543)","status":"passed","title":"matches deepseek model id case-insensitively for custom providers (#1543)","duration":0.11424999999999841,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip does not touch non-deepseek providers/models","status":"passed","title":"does not touch non-deepseek providers/models","duration":0.07758299999997575,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip maps deepseek-v4-pro-none alias to disabled thinking and strips reasoning_effort","status":"passed","title":"maps deepseek-v4-pro-none alias to disabled thinking and strips reasoning_effort","duration":0.14158299999999713,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip injects reasoning_content on a minimax assistant message that lacks it","status":"passed","title":"injects reasoning_content on a minimax assistant message that lacks it","duration":0.13154099999999858,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip injects reasoning_content on minimax assistant message with tool_calls but no reasoning_content","status":"passed","title":"injects reasoning_content on minimax assistant message with tool_calls but no reasoning_content","duration":0.12887500000002206,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip applies provider-level rule for provider 'minimax-cn' (scope all)","status":"passed","title":"applies provider-level rule for provider 'minimax-cn' (scope all)","duration":0.36283399999999233,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip preserves an existing reasoning_content on minimax instead of overwriting","status":"passed","title":"preserves an existing reasoning_content on minimax instead of overwriting","duration":0.11899999999999977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip DefaultExecutor transformRequest runs the injector for minimax","status":"passed","title":"DefaultExecutor transformRequest runs the injector for minimax","duration":21.891750000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenCodeExecutor — issue #1543 regression"],"fullName":"OpenCodeExecutor — issue #1543 regression runs the injector so deepseek-v4-flash-free round-trips reasoning_content","status":"passed","title":"runs the injector so deepseek-v4-flash-free round-trips reasoning_content","duration":0.14916699999997718,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830154,"endTime":1781448830178.8918,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/reasoningContentInjector.test.js"},{"assertionResults":[{"ancestorTitles":["Responses abort terminal synthesis"],"fullName":"Responses abort terminal synthesis emits response.failed + [DONE] when upstream errors (abort/stall)","status":"passed","title":"emits response.failed + [DONE] when upstream errors (abort/stall)","duration":2.596041999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Responses abort terminal synthesis"],"fullName":"Responses abort terminal synthesis does not synthesize terminal for non-Responses streams (callback null)","status":"passed","title":"does not synthesize terminal for non-Responses streams (callback null)","duration":0.33508399999999483,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830918,"endTime":1781448830921.3352,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/responses-abort-terminal.test.js"},{"assertionResults":[{"ancestorTitles":["RTK end-to-end"],"fullName":"RTK end-to-end server is reachable","status":"skipped","title":"server is reachable","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK end-to-end"],"fullName":"RTK end-to-end rtkEnabled flag is true (user must enable via dashboard)","status":"skipped","title":"rtkEnabled flag is true (user must enable via dashboard)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK end-to-end"],"fullName":"RTK end-to-end compresses git diff tool_result and writes [RTK] savings to log","status":"skipped","title":"compresses git diff tool_result and writes [RTK] savings to log","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK end-to-end"],"fullName":"RTK end-to-end compresses grep-style tool_result","status":"skipped","title":"compresses grep-style tool_result","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448826369,"endTime":1781448826369,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/rtk.e2e.test.js"},{"assertionResults":[{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E server reachable and rtkEnabled=true","status":"skipped","title":"server reachable and rtkEnabled=true","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for claude (cc/* → openai→claude)","status":"skipped","title":"compresses git diff for claude (cc/* → openai→claude)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for codex (cx/* → openai→openai-responses)","status":"skipped","title":"compresses git diff for codex (cx/* → openai→openai-responses)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for antigravity (ag/* → openai→antigravity)","status":"skipped","title":"compresses git diff for antigravity (ag/* → openai→antigravity)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for cursor (cu/* → openai→cursor)","status":"skipped","title":"compresses git diff for cursor (cu/* → openai→cursor)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for kiro (kr/* → openai→kiro)","status":"skipped","title":"compresses git diff for kiro (kr/* → openai→kiro)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for gemini (gemini/* → openai→gemini)","status":"skipped","title":"compresses git diff for gemini (gemini/* → openai→gemini)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for deepseek (deepseek/* → openai, passthrough)","status":"skipped","title":"compresses git diff for deepseek (deepseek/* → openai, passthrough)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for ollama (ollama/* → openai→ollama)","status":"skipped","title":"compresses git diff for ollama (ollama/* → openai→ollama)","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448826369,"endTime":1781448826369,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/rtk.multi-provider.e2e.test.js"},{"assertionResults":[{"ancestorTitles":["RTK flag"],"fullName":"RTK flag default off, toggle works","status":"failed","title":"default off, toggle works","duration":11.526624999999967,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:58:18\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters gitDiff truncates hunks beyond 100 lines and preserves file header","status":"passed","title":"gitDiff truncates hunks beyond 100 lines and preserves file header","duration":2.8191249999999854,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters gitStatus groups by kind and produces compact output (Rust format)","status":"passed","title":"gitStatus groups by kind and produces compact output (Rust format)","duration":0.9860830000000078,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters grep groups matches by file and caps per-file lines (Rust format)","status":"passed","title":"grep groups matches by file and caps per-file lines (Rust format)","duration":0.5720420000000104,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters find groups paths by parent dir, shows basenames (Rust format)","status":"passed","title":"find groups paths by parent dir, shows basenames (Rust format)","duration":0.3197499999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters dedupLog collapses consecutive duplicates","status":"passed","title":"dedupLog collapses consecutive duplicates","duration":0.22562500000003638,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter detects git diff","status":"passed","title":"detects git diff","duration":0.20862499999998363,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter detects git status","status":"passed","title":"detects git status","duration":0.12941699999998946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter detects grep","status":"passed","title":"detects grep","duration":0.7276249999999891,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter detects find","status":"passed","title":"detects find","duration":0.25433300000008785,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter falls back to dedupLog for generic text","status":"passed","title":"falls back to dedupLog for generic text","duration":0.5111670000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) ls: compact_ls strips perms/owner, keeps name + size","status":"passed","title":"ls: compact_ls strips perms/owner, keeps name + size","duration":0.9263329999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) ls: filters noise dirs","status":"passed","title":"ls: filters noise dirs","duration":0.7515840000000935,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) tree: removes summary, keeps structure","status":"passed","title":"tree: removes summary, keeps structure","duration":2.492416999999932,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) smartTruncate: keeps head+tail, drops middle","status":"passed","title":"smartTruncate: keeps head+tail, drops middle","duration":0.19612499999993815,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) smartTruncate: passes through small input","status":"passed","title":"smartTruncate: passes through small input","duration":0.05299999999999727,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) readNumbered: compacts very long line-numbered dump","status":"passed","title":"readNumbered: compacts very long line-numbered dump","duration":1.1271659999999883,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) searchList: groups Cursor Glob output by parent dir","status":"passed","title":"searchList: groups Cursor Glob output by parent dir","duration":1.0964999999999918,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter (extras)"],"fullName":"autoDetectFilter (extras) detects tree via box-drawing glyphs","status":"passed","title":"detects tree via box-drawing glyphs","duration":0.6144580000000133,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter (extras)"],"fullName":"autoDetectFilter (extras) detects ls via total + perms rows","status":"passed","title":"detects ls via total + perms rows","duration":0.11400000000003274,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter (extras)"],"fullName":"autoDetectFilter (extras) detects Cursor search list","status":"passed","title":"detects Cursor search list","duration":0.12279200000000401,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["safeApply"],"fullName":"safeApply returns input if filter throws","status":"passed","title":"returns input if filter throws","duration":1.9920000000000755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["safeApply"],"fullName":"safeApply returns input if filter returns non-string","status":"passed","title":"returns input if filter returns non-string","duration":0.4993329999999787,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (disabled)"],"fullName":"compressMessages (disabled) returns null when disabled","status":"failed","title":"returns null when disabled","duration":1.1737499999999272,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:248:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) compresses OpenAI tool message (string content)","status":"failed","title":"compresses OpenAI tool message (string content)","duration":0.18762499999991178,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) compresses Claude string-form tool_result","status":"failed","title":"compresses Claude string-form tool_result","duration":0.19366700000000492,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) compresses Claude array-form tool_result text parts","status":"failed","title":"compresses Claude array-form tool_result text parts","duration":0.1823339999999689,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) skips is_error tool_result","status":"failed","title":"skips is_error tool_result","duration":0.09362500000008822,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) skips below MIN_COMPRESS_SIZE (<500 bytes)","status":"failed","title":"skips below MIN_COMPRESS_SIZE (<500 bytes)","duration":0.08920799999998508,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) never produces empty content (R14 guard)","status":"failed","title":"never produces empty content (R14 guard)","duration":0.08004099999993741,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) skips when body has no messages","status":"failed","title":"skips when body has no messages","duration":0.39620800000000145,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) handles mix of messages without crashing","status":"failed","title":"handles mix of messages without crashing","duration":0.20229100000005928,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["formatRtkLog"],"fullName":"formatRtkLog returns null when no hits","status":"passed","title":"returns null when no hits","duration":0.1179580000000442,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatRtkLog"],"fullName":"formatRtkLog formats savings line with percentage","status":"passed","title":"formats savings line with percentage","duration":0.14929099999994833,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448827203,"endTime":1781448827235.3962,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/rtk.test.js"},{"assertionResults":[{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support compresses tool results in Kiro conversationState.currentMessage","status":"passed","title":"compresses tool results in Kiro conversationState.currentMessage","duration":2.2714169999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support compresses tool results in Kiro conversationState.history","status":"passed","title":"compresses tool results in Kiro conversationState.history","duration":0.47995800000001054,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support handles multiple tool results across history and currentMessage","status":"passed","title":"handles multiple tool results across history and currentMessage","duration":0.23729200000001072,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support preserves error tool results without compression","status":"passed","title":"preserves error tool results without compression","duration":0.13787500000000819,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support returns null when RTK is disabled","status":"passed","title":"returns null when RTK is disabled","duration":0.06516599999999073,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support handles Kiro body with no tool results gracefully","status":"passed","title":"handles Kiro body with no tool results gracefully","duration":0.09054199999999923,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support handles malformed Kiro body without crashing","status":"passed","title":"handles malformed Kiro body without crashing","duration":0.13270799999999383,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830644,"endTime":1781448830648.1328,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/rtkKiro.test.js"},{"assertionResults":[{"ancestorTitles":["tokenRefresh dispatch"],"fullName":"tokenRefresh dispatch getAccessToken returns null for missing/invalid refreshToken","status":"passed","title":"getAccessToken returns null for missing/invalid refreshToken","duration":95.6345,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["tokenRefresh dispatch"],"fullName":"tokenRefresh dispatch getAccessToken default: unsupported provider → null","status":"passed","title":"getAccessToken default: unsupported provider → null","duration":0.4908749999999884,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["tokenRefresh dispatch"],"fullName":"tokenRefresh dispatch refreshTokenByProvider returns null without refreshToken","status":"passed","title":"refreshTokenByProvider returns null without refreshToken","duration":0.2383750000000191,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448829064,"endTime":1781448829160.491,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/token-refresh-dispatch.test.js"},{"assertionResults":[{"ancestorTitles":["normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1)"],"fullName":"normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1) downgrades adaptive thinking to enabled+budget for haiku models","status":"passed","title":"downgrades adaptive thinking to enabled+budget for haiku models","duration":2.535584,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1)"],"fullName":"normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1) keeps adaptive thinking for sonnet/opus","status":"passed","title":"keeps adaptive thinking for sonnet/opus","duration":0.49166700000000674,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1)"],"fullName":"normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1) hoists mid-conversation system messages into top-level system","status":"passed","title":"hoists mid-conversation system messages into top-level system","duration":0.41749999999998977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDataUri / encodeDataUri (docs 11 §4)"],"fullName":"parseDataUri / encodeDataUri (docs 11 §4) parses a base64 data uri","status":"passed","title":"parses a base64 data uri","duration":0.2642500000000041,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDataUri / encodeDataUri (docs 11 §4)"],"fullName":"parseDataUri / encodeDataUri (docs 11 §4) tolerates newlines inside base64 payload","status":"passed","title":"tolerates newlines inside base64 payload","duration":0.19916699999998855,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDataUri / encodeDataUri (docs 11 §4)"],"fullName":"parseDataUri / encodeDataUri (docs 11 §4) returns null for http urls and non-strings","status":"passed","title":"returns null for http urls and non-strings","duration":0.2878329999999778,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDataUri / encodeDataUri (docs 11 §4)"],"fullName":"parseDataUri / encodeDataUri (docs 11 §4) encode/parse roundtrip","status":"passed","title":"encode/parse roundtrip","duration":0.4401670000000024,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448831154,"endTime":1781448831159.4402,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/translator-helpers-edge.test.js"},{"assertionResults":[{"ancestorTitles":["request normalization"],"fullName":"request normalization claudeToOpenAIRequest flattens text-only content arrays into string","status":"failed","title":"claudeToOpenAIRequest flattens text-only content arrays into string","duration":15.007790999999997,"failureMessages":["AssertionError: expected [ { type: 'text', text: 'hi' }, …(1) ] to be 'hi\\nthere' // Object.is equality\n at /Users/Working/router4/app/tests/unit/translator-request-normalization.test.js:24:40\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization claudeToOpenAIRequest preserves multimodal arrays","status":"passed","title":"claudeToOpenAIRequest preserves multimodal arrays","duration":1.1699169999999413,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization filterToOpenAIFormat flattens text-only arrays to string","status":"failed","title":"filterToOpenAIFormat flattens text-only arrays to string","duration":1.6009169999999813,"failureMessages":["AssertionError: expected [ { type: 'text', text: 'a' }, …(1) ] to be 'a\\nb' // Object.is equality\n at /Users/Working/router4/app/tests/unit/translator-request-normalization.test.js:65:40\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization translateRequest keeps /v1/messages Claude->OpenAI text payloads string-safe","status":"failed","title":"translateRequest keeps /v1/messages Claude->OpenAI text payloads string-safe","duration":263.516167,"failureMessages":["AssertionError: expected 'object' to be 'string' // Object.is equality\n at /Users/Working/router4/app/tests/unit/translator-request-normalization.test.js:95:40\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization translateRequest strips unsupported Anthropic output_config for MiniMax Claude-compatible endpoints","status":"passed","title":"translateRequest strips unsupported Anthropic output_config for MiniMax Claude-compatible endpoints","duration":1.077291999999943,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization translateRequest preserves output_config for Anthropic Claude","status":"passed","title":"translateRequest preserves output_config for Anthropic Claude","duration":0.27595900000005713,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization parseSSELine supports provider raw NDJSON stream lines","status":"failed","title":"parseSSELine supports provider raw NDJSON stream lines","duration":1.2840000000001055,"failureMessages":["AssertionError: expected null to deeply equal { model: 'gpt-oss:120b', …(2) }\n at /Users/Working/router4/app/tests/unit/translator-request-normalization.test.js:175:20\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/hoanganh/.npm/_npx/69c381f8ad94b576/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization parseSSELine still supports SSE data lines","status":"passed","title":"parseSSELine still supports SSE data lines","duration":0.3563329999999496,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448827888,"endTime":1781448828172.3564,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/translator-request-normalization.test.js"},{"assertionResults":[{"ancestorTitles":["usage dispatch"],"fullName":"usage dispatch unsupported provider → not-implemented message","status":"passed","title":"unsupported provider → not-implemented message","duration":207.98575,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["usage dispatch"],"fullName":"usage dispatch every supported provider routes to its handler (no fallback message)","status":"passed","title":"every supported provider routes to its handler (no fallback message)","duration":4.360208,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828729,"endTime":1781448828941.36,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/usage-dispatch.test.js"},{"assertionResults":[{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:true when response is 200","status":"passed","title":"should return valid:true when response is 200","duration":2.4441670000000073,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:true when response is 400 (auth accepted but bad body)","status":"passed","title":"should return valid:true when response is 400 (auth accepted but bad body)","duration":0.7552919999999972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:true when response is 429 (rate limited but auth ok)","status":"passed","title":"should return valid:true when response is 429 (rate limited but auth ok)","duration":0.7054590000000047,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:false with error when response is 401","status":"passed","title":"should return valid:false with error when response is 401","duration":0.26941700000000424,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:false with error when response is 403","status":"passed","title":"should return valid:false with error when response is 403","duration":0.20945799999999792,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should strip sso= prefix from apiKey","status":"passed","title":"should strip sso= prefix from apiKey","duration":0.16320799999999736,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should accept raw token without sso= prefix","status":"passed","title":"should accept raw token without sso= prefix","duration":0.15487499999998988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should POST to /rest/app-chat/conversations/new","status":"passed","title":"should POST to /rest/app-chat/conversations/new","duration":1.1317909999999927,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should send Cloudflare-bypass headers","status":"passed","title":"should send Cloudflare-bypass headers","duration":0.7269580000000104,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should return valid:true when response is 200","status":"passed","title":"should return valid:true when response is 200","duration":0.17483400000000415,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should return valid:false when response is 401","status":"passed","title":"should return valid:false when response is 401","duration":0.1570000000000107,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should return valid:false when response is 403","status":"passed","title":"should return valid:false when response is 403","duration":0.061666999999999916,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should strip __Secure-next-auth.session-token= prefix","status":"passed","title":"should strip __Secure-next-auth.session-token= prefix","duration":0.05962499999999693,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should accept raw token without prefix","status":"passed","title":"should accept raw token without prefix","duration":0.05608300000000099,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should POST to /rest/sse/perplexity_ask","status":"passed","title":"should POST to /rest/sse/perplexity_ask","duration":0.19299999999999784,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448830501,"endTime":1781448830508.193,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/web-cookie-validation.test.js"},{"assertionResults":[{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service validates discovered endpoints are https x.ai URLs","status":"passed","title":"validates discovered endpoints are https x.ai URLs","duration":422.15141700000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service discovers endpoints without custom user-agent headers","status":"passed","title":"discovers endpoints without custom user-agent headers","duration":23.46616700000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service builds authorize URLs with CLIProxyAPI query extras","status":"passed","title":"builds authorize URLs with CLIProxyAPI query extras","duration":33.17491699999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service generates dashboard auth data with CLIProxyAPI PKCE size and discovered endpoints","status":"passed","title":"generates dashboard auth data with CLIProxyAPI PKCE size and discovered endpoints","duration":926.627708,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service exchanges dashboard codes against the discovered xAI token endpoint","status":"passed","title":"exchanges dashboard codes against the discovered xAI token endpoint","duration":43.309541999999965,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448827453,"endTime":1781448828902.3096,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/xai-oauth-service.test.js"},{"assertionResults":[{"ancestorTitles":["xai/token-refresh wrapper"],"fullName":"xai/token-refresh wrapper refreshXaiToken module loads without throwing","status":"passed","title":"refreshXaiToken module loads without throwing","duration":59.87154199999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/token-refresh wrapper"],"fullName":"xai/token-refresh wrapper formatProviderCredentials returns Bearer-shape for xai","status":"passed","title":"formatProviderCredentials returns Bearer-shape for xai","duration":0.45750000000001023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/token-refresh wrapper"],"fullName":"xai/token-refresh wrapper refreshTokenByProvider returns null when refreshToken missing","status":"passed","title":"refreshTokenByProvider returns null when refreshToken missing","duration":0.11100000000001842,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/token-refresh wrapper"],"fullName":"xai/token-refresh wrapper refreshTokenByProvider returns expiresIn for refreshed xai tokens","status":"passed","title":"refreshTokenByProvider returns expiresIn for refreshed xai tokens","duration":11.818874999999991,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448828974,"endTime":1781448829046.8188,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/xai-tokenRefresh.test.js"},{"assertionResults":[{"ancestorTitles":["REAL provider smoke"],"fullName":"REAL provider smoke has active providers in DB","status":"skipped","title":"has active providers in DB","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781448826369,"endTime":1781448826369,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/real/smoke-providers.real.test.js"}]} \ No newline at end of file +{"numTotalTestSuites":276,"numPassedTestSuites":259,"numFailedTestSuites":17,"numPendingTestSuites":0,"numTotalTests":840,"numPassedTests":788,"numFailedTests":26,"numPendingTests":26,"numTodoTests":0,"snapshot":{"added":0,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":99,"total":99,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0,"didUpdate":false},"startTime":1781497742906,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["Antigravity → OpenAI"],"fullName":"Antigravity → OpenAI functionResponse + functionCall in same content keeps both","status":"passed","title":"functionResponse + functionCall in same content keeps both","duration":4.76124999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity → OpenAI"],"fullName":"Antigravity → OpenAI functionCall without id keeps a stable matchable id","status":"passed","title":"functionCall without id keeps a stable matchable id","duration":0.2752909999999815,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity → OpenAI"],"fullName":"Antigravity → OpenAI signature-only part does not produce empty text","status":"passed","title":"signature-only part does not produce empty text","duration":0.12412499999999227,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745628,"endTime":1781497745633.2754,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-antigravity.test.js"},{"assertionResults":[{"ancestorTitles":["Claude Code CLI context → OpenAI"],"fullName":"Claude Code CLI context → OpenAI system array keeps all text parts","status":"passed","title":"system array keeps all text parts","duration":1.5511250000000132,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Claude Code CLI context → OpenAI"],"fullName":"Claude Code CLI context → OpenAI assistant thinking block survives Claude→Claude passthrough","status":"passed","title":"assistant thinking block survives Claude→Claude passthrough","duration":0.3392080000000135,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Claude Code CLI context → OpenAI"],"fullName":"Claude Code CLI context → OpenAI redacted_thinking block is not silently dropped","status":"passed","title":"redacted_thinking block is not silently dropped","duration":3.540666999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Claude Code CLI context → OpenAI"],"fullName":"Claude Code CLI context → OpenAI tool_result image block is preserved","status":"passed","title":"tool_result image block is preserved","duration":1.1281659999999931,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745439,"endTime":1781497745446.1282,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-claudeCode-context.test.js"},{"assertionResults":[{"ancestorTitles":["Codex CLI Responses → OpenAI"],"fullName":"Codex CLI Responses → OpenAI assistant has no empty tool_calls array when all names are empty","status":"passed","title":"assistant has no empty tool_calls array when all names are empty","duration":3.1669160000000147,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex CLI Responses → OpenAI"],"fullName":"Codex CLI Responses → OpenAI function_call arguments end up as a string","status":"passed","title":"function_call arguments end up as a string","duration":1.9879589999999894,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex CLI Responses → OpenAI"],"fullName":"Codex CLI Responses → OpenAI input_image with file_id is not used as a raw url","status":"passed","title":"input_image with file_id is not used as a raw url","duration":0.8092090000000098,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Codex Responses (reverse)"],"fullName":"OpenAI → Codex Responses (reverse) call_id longer than 64 chars is clamped","status":"passed","title":"call_id longer than 64 chars is clamped","duration":0.31595799999999485,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745239,"endTime":1781497745245.316,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-codexCli-responses.test.js"},{"assertionResults":[{"ancestorTitles":["OpenAI → Gemini"],"fullName":"OpenAI → Gemini multiple system messages are all kept","status":"passed","title":"multiple system messages are all kept","duration":4.6682500000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Cursor"],"fullName":"OpenAI → Cursor image content is preserved","status":"passed","title":"image content is preserved","duration":0.7739169999999831,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Cursor"],"fullName":"OpenAI → Cursor respects client max_tokens","status":"passed","title":"respects client max_tokens","duration":1.0494999999999948,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → CommandCode"],"fullName":"OpenAI → CommandCode malformed tool arguments are not silently emptied","status":"passed","title":"malformed tool arguments are not silently emptied","duration":1.5220410000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → CommandCode"],"fullName":"OpenAI → CommandCode image content is preserved","status":"passed","title":"image content is preserved","duration":0.641541999999987,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744975,"endTime":1781497744984.6416,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-gemini-cursor-commandcode.test.js"},{"assertionResults":[{"ancestorTitles":["OpenAI → Kiro"],"fullName":"OpenAI → Kiro malformed tool arguments do not throw the whole request","status":"passed","title":"malformed tool arguments do not throw the whole request","duration":5.476499999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Kiro"],"fullName":"OpenAI → Kiro respects client max_tokens","status":"passed","title":"respects client max_tokens","duration":5.955707999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Kiro"],"fullName":"OpenAI → Kiro remote image url is preserved as an image, not text","status":"passed","title":"remote image url is preserved as an image, not text","duration":1.6610830000000192,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745126,"endTime":1781497745139.6611,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-kiro.test.js"},{"assertionResults":[{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss image with source.type=url is preserved (NOT dropped)","status":"passed","title":"image with source.type=url is preserved (NOT dropped)","duration":10.171915999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss thinking block survives round-trip Claude→OpenAI→Claude","status":"passed","title":"thinking block survives round-trip Claude→OpenAI→Claude","duration":0.5456669999999804,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss tool_result with image block is not turned into raw JSON / dropped","status":"passed","title":"tool_result with image block is not turned into raw JSON / dropped","duration":0.9221660000000043,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss tool_result is_error flag is preserved","status":"passed","title":"tool_result is_error flag is preserved","duration":0.8278750000000059,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: Claude → OpenAI bridge data loss"],"fullName":"bug: Claude → OpenAI bridge data loss system array non-text parts are not silently dropped","status":"passed","title":"system array non-text parts are not silently dropped","duration":0.3147920000000113,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: tool_call id stability across bridge"],"fullName":"bug: tool_call id stability across bridge sanitized tool id stays matched between call and result","status":"passed","title":"sanitized tool id stays matched between call and result","duration":0.18141699999998195,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bug: empty content message handling"],"fullName":"bug: empty content message handling assistant message with only tool_calls is not dropped","status":"passed","title":"assistant message with only tool_calls is not dropped","duration":0.09662499999998886,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745173,"endTime":1781497745186.3147,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-openai-bridge.test.js"},{"assertionResults":[{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping does not inject Claude Code system prompt for compatible providers","status":"passed","title":"does not inject Claude Code system prompt for compatible providers","duration":4.808250000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping assistant reasoning_content becomes a thinking block","status":"passed","title":"assistant reasoning_content becomes a thinking block","duration":1.5292910000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping tool_choice=none is not turned into auto","status":"passed","title":"tool_choice=none is not turned into auto","duration":0.5632920000000183,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping input_audio content is preserved","status":"passed","title":"input_audio content is preserved","duration":0.6614579999999819,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI → Claude context mapping"],"fullName":"OpenAI → Claude context mapping remote http image_url is preserved","status":"passed","title":"remote http image_url is preserved","duration":0.4367080000000101,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745088,"endTime":1781497745096.4368,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/bugs-toClaude-context.test.js"},{"assertionResults":[{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'alicode': all models OpenAI→target","status":"passed","title":"'alicode': all models OpenAI→target","duration":1.5401669999999967,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'alicode-intl': all models OpenAI→target","status":"passed","title":"'alicode-intl': all models OpenAI→target","duration":0.3819159999999897,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'anthropic': all models OpenAI→target","status":"passed","title":"'anthropic': all models OpenAI→target","duration":0.6794999999999902,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'ag': all models OpenAI→target","status":"passed","title":"'ag': all models OpenAI→target","duration":2.007499999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'assemblyai': all models OpenAI→target","status":"passed","title":"'assemblyai': all models OpenAI→target","duration":0.1619579999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'black-forest-labs': all models OpenAI→target","status":"passed","title":"'black-forest-labs': all models OpenAI→target","duration":0.12641600000000608,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'blackbox': all models OpenAI→target","status":"passed","title":"'blackbox': all models OpenAI→target","duration":0.27883399999998915,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'byteplus': all models OpenAI→target","status":"passed","title":"'byteplus': all models OpenAI→target","duration":0.1635830000000169,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cerebras': all models OpenAI→target","status":"passed","title":"'cerebras': all models OpenAI→target","duration":0.40587499999998045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cc': all models OpenAI→target","status":"passed","title":"'cc': all models OpenAI→target","duration":0.5622909999999877,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cl': all models OpenAI→target","status":"passed","title":"'cl': all models OpenAI→target","duration":0.17329100000000608,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cloudflare-ai': all models OpenAI→target","status":"passed","title":"'cloudflare-ai': all models OpenAI→target","duration":0.30462499999998727,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cx': all models OpenAI→target","status":"passed","title":"'cx': all models OpenAI→target","duration":0.5545419999999979,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cohere': all models OpenAI→target","status":"passed","title":"'cohere': all models OpenAI→target","duration":1.1602499999999907,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'comfyui': all models OpenAI→target","status":"passed","title":"'comfyui': all models OpenAI→target","duration":0.05275000000000318,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'commandcode': all models OpenAI→target","status":"passed","title":"'commandcode': all models OpenAI→target","duration":2.2671670000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'cu': all models OpenAI→target","status":"passed","title":"'cu': all models OpenAI→target","duration":0.5327080000000137,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'deepgram': all models OpenAI→target","status":"passed","title":"'deepgram': all models OpenAI→target","duration":0.07883300000000304,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'deepseek': all models OpenAI→target","status":"passed","title":"'deepseek': all models OpenAI→target","duration":0.08516600000001517,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'fal-ai': all models OpenAI→target","status":"passed","title":"'fal-ai': all models OpenAI→target","duration":0.09375,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'fireworks': all models OpenAI→target","status":"passed","title":"'fireworks': all models OpenAI→target","duration":0.059750000000008185,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gemini': all models OpenAI→target","status":"passed","title":"'gemini': all models OpenAI→target","duration":0.4443330000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gc': all models OpenAI→target","status":"passed","title":"'gc': all models OpenAI→target","duration":0.20691700000000424,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gh': all models OpenAI→target","status":"passed","title":"'gh': all models OpenAI→target","duration":0.26637499999998226,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'glm': all models OpenAI→target","status":"passed","title":"'glm': all models OpenAI→target","duration":0.09554099999999721,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'glm-cn': all models OpenAI→target","status":"passed","title":"'glm-cn': all models OpenAI→target","duration":0.06779099999999971,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'grok-web': all models OpenAI→target","status":"passed","title":"'grok-web': all models OpenAI→target","duration":0.1133339999999805,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'groq': all models OpenAI→target","status":"passed","title":"'groq': all models OpenAI→target","duration":0.08833300000000577,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'huggingface': all models OpenAI→target","status":"passed","title":"'huggingface': all models OpenAI→target","duration":0.056417000000010376,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'hyperbolic': all models OpenAI→target","status":"passed","title":"'hyperbolic': all models OpenAI→target","duration":0.0877089999999896,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'if': all models OpenAI→target","status":"passed","title":"'if': all models OpenAI→target","duration":0.15245800000002419,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'kc': all models OpenAI→target","status":"passed","title":"'kc': all models OpenAI→target","duration":0.09424999999998818,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'kimi': all models OpenAI→target","status":"passed","title":"'kimi': all models OpenAI→target","duration":0.07729100000000244,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'kmc': all models OpenAI→target","status":"passed","title":"'kmc': all models OpenAI→target","duration":0.06441699999999173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'kr': all models OpenAI→target","status":"passed","title":"'kr': all models OpenAI→target","duration":1.3660830000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'mmf': all models OpenAI→target","status":"passed","title":"'mmf': all models OpenAI→target","duration":0.036000000000001364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'minimax': all models OpenAI→target","status":"passed","title":"'minimax': all models OpenAI→target","duration":0.18295899999998255,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'minimax-cn': all models OpenAI→target","status":"passed","title":"'minimax-cn': all models OpenAI→target","duration":0.15725000000000477,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'mistral': all models OpenAI→target","status":"passed","title":"'mistral': all models OpenAI→target","duration":0.05583400000000438,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'nanobanana': all models OpenAI→target","status":"passed","title":"'nanobanana': all models OpenAI→target","duration":0.03562500000001023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'nebius': all models OpenAI→target","status":"passed","title":"'nebius': all models OpenAI→target","duration":0.033959000000010064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'nvidia': all models OpenAI→target","status":"passed","title":"'nvidia': all models OpenAI→target","duration":0.0689999999999884,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'ollama': all models OpenAI→target","status":"passed","title":"'ollama': all models OpenAI→target","duration":0.48220799999998576,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openai': all models OpenAI→target","status":"passed","title":"'openai': all models OpenAI→target","duration":0.335583999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'opencode-go': all models OpenAI→target","status":"passed","title":"'opencode-go': all models OpenAI→target","duration":0.13745900000000688,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openrouter': all models OpenAI→target","status":"passed","title":"'openrouter': all models OpenAI→target","duration":0.16116700000000606,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'perplexity': all models OpenAI→target","status":"passed","title":"'perplexity': all models OpenAI→target","duration":0.038624999999996135,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'perplexity-web': all models OpenAI→target","status":"passed","title":"'perplexity-web': all models OpenAI→target","duration":0.16929200000001288,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'qd': all models OpenAI→target","status":"passed","title":"'qd': all models OpenAI→target","duration":0.14520900000002257,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'qw': all models OpenAI→target","status":"passed","title":"'qw': all models OpenAI→target","duration":0.05795799999998508,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'recraft': all models OpenAI→target","status":"passed","title":"'recraft': all models OpenAI→target","duration":0.03795800000000327,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'runwayml': all models OpenAI→target","status":"passed","title":"'runwayml': all models OpenAI→target","duration":0.06562500000001137,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'sdwebui': all models OpenAI→target","status":"passed","title":"'sdwebui': all models OpenAI→target","duration":0.03612499999999841,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'siliconflow': all models OpenAI→target","status":"passed","title":"'siliconflow': all models OpenAI→target","duration":0.1613749999999925,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'stability-ai': all models OpenAI→target","status":"passed","title":"'stability-ai': all models OpenAI→target","duration":0.062207999999998265,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'together': all models OpenAI→target","status":"passed","title":"'together': all models OpenAI→target","duration":0.07362499999999272,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'vertex': all models OpenAI→target","status":"passed","title":"'vertex': all models OpenAI→target","duration":0.1759580000000085,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'vertex-partner': all models OpenAI→target","status":"passed","title":"'vertex-partner': all models OpenAI→target","duration":0.05554100000000517,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'volcengine-ark': all models OpenAI→target","status":"passed","title":"'volcengine-ark': all models OpenAI→target","duration":0.09904099999999971,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'voyage-ai': all models OpenAI→target","status":"passed","title":"'voyage-ai': all models OpenAI→target","duration":0.0801670000000172,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'xai': all models OpenAI→target","status":"passed","title":"'xai': all models OpenAI→target","duration":0.06125000000000114,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'xiaomi-mimo': all models OpenAI→target","status":"passed","title":"'xiaomi-mimo': all models OpenAI→target","duration":0.05900000000002592,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'xiaomi-tokenplan': all models OpenAI→target","status":"passed","title":"'xiaomi-tokenplan': all models OpenAI→target","duration":0.11525000000000318,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openai-tts-models': all models OpenAI→target","status":"passed","title":"'openai-tts-models': all models OpenAI→target","duration":0.054250000000024556,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openai-tts-voices': all models OpenAI→target","status":"passed","title":"'openai-tts-voices': all models OpenAI→target","duration":0.12975000000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openrouter-tts-models': all models OpenAI→target","status":"passed","title":"'openrouter-tts-models': all models OpenAI→target","duration":0.04179200000001515,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'openrouter-tts-voices': all models OpenAI→target","status":"passed","title":"'openrouter-tts-voices': all models OpenAI→target","duration":0.1319159999999897,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'elevenlabs-tts-models': all models OpenAI→target","status":"passed","title":"'elevenlabs-tts-models': all models OpenAI→target","duration":0.050333000000023276,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'edge-tts': all models OpenAI→target","status":"passed","title":"'edge-tts': all models OpenAI→target","duration":0.11104199999999764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'local-device': all models OpenAI→target","status":"passed","title":"'local-device': all models OpenAI→target","duration":0.024541999999996733,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'google-tts': all models OpenAI→target","status":"passed","title":"'google-tts': all models OpenAI→target","duration":0.601083999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gemini-tts-models': all models OpenAI→target","status":"passed","title":"'gemini-tts-models': all models OpenAI→target","duration":0.034416999999990594,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: every model translates without throwing"],"fullName":"coverage: every model translates without throwing 'gemini-tts-voices': all models OpenAI→target","status":"passed","title":"'gemini-tts-voices': all models OpenAI→target","duration":0.2928750000000093,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: image-strip models drop image content"],"fullName":"coverage: image-strip models drop image content 'kr'/'deepseek-3.2' strips image when strip=[image]","status":"passed","title":"'kr'/'deepseek-3.2' strips image when strip=[image]","duration":0.229582999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["coverage: image-strip models drop image content"],"fullName":"coverage: image-strip models drop image content 'kr'/'qwen3-coder-next' strips image when strip=[image]","status":"passed","title":"'kr'/'qwen3-coder-next' strips image when strip=[image]","duration":0.05229199999999423,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744737,"endTime":1781497744757.2295,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/coverage-all-models.test.js"},{"assertionResults":[{"ancestorTitles":["roundtrip: Claude source preserves core fields → OpenAI"],"fullName":"roundtrip: Claude source preserves core fields → OpenAI system → system role","status":"passed","title":"system → system role","duration":0.8013749999999789,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: Claude source preserves core fields → OpenAI"],"fullName":"roundtrip: Claude source preserves core fields → OpenAI tool_use → assistant.tool_calls with matching id","status":"passed","title":"tool_use → assistant.tool_calls with matching id","duration":0.21670800000001122,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: Claude source preserves core fields → OpenAI"],"fullName":"roundtrip: Claude source preserves core fields → OpenAI tool_result → tool message with matching id","status":"passed","title":"tool_result → tool message with matching id","duration":0.1357500000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: Claude source preserves core fields → OpenAI"],"fullName":"roundtrip: Claude source preserves core fields → OpenAI tool arguments are valid JSON string","status":"passed","title":"tool arguments are valid JSON string","duration":0.4172499999999957,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: OpenAI tools → Claude → keeps tool name"],"fullName":"roundtrip: OpenAI tools → Claude → keeps tool name tool name survives openai→claude","status":"passed","title":"tool name survives openai→claude","duration":0.12758400000001302,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: parallel tool calls keep distinct ids"],"fullName":"roundtrip: parallel tool calls keep distinct ids two tool_calls, two distinct ids","status":"passed","title":"two tool_calls, two distinct ids","duration":0.09441699999999287,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["roundtrip: parallel tool calls keep distinct ids"],"fullName":"roundtrip: parallel tool calls keep distinct ids each tool_call has a matching tool result","status":"passed","title":"each tool_call has a matching tool result","duration":0.10670799999999758,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745892,"endTime":1781497745894.1277,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/format-roundtrip.test.js"},{"assertionResults":[{"ancestorTitles":["GOLDEN request: OpenAI → Claude"],"fullName":"GOLDEN request: OpenAI → Claude full body (system/image/tool/tool_result)","status":"passed","title":"full body (system/image/tool/tool_result)","duration":3.2300419999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN request: OpenAI → Claude"],"fullName":"GOLDEN request: OpenAI → Claude reasoning_effort → thinking budget","status":"passed","title":"reasoning_effort → thinking budget","duration":0.3754590000000064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN request: OpenAI → Gemini"],"fullName":"GOLDEN request: OpenAI → Gemini full body (system/image/tool/tool_result)","status":"passed","title":"full body (system/image/tool/tool_result)","duration":1.0155839999999898,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN request: OpenAI → Kiro"],"fullName":"GOLDEN request: OpenAI → Kiro full body (image base64 + tool_result)","status":"passed","title":"full body (image base64 + tool_result)","duration":1.6666250000000105,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745201,"endTime":1781497745207.6665,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/golden-request.test.js"},{"assertionResults":[{"ancestorTitles":["GOLDEN response stream: Claude → OpenAI"],"fullName":"GOLDEN response stream: Claude → OpenAI text + thinking + tool_use + usage + finish","status":"passed","title":"text + thinking + tool_use + usage + finish","duration":2.77445800000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Gemini → OpenAI"],"fullName":"GOLDEN response stream: Gemini → OpenAI text + thought(no-sig) + functionCall + usage + finish","status":"passed","title":"text + thought(no-sig) + functionCall + usage + finish","duration":0.6568750000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Gemini → OpenAI"],"fullName":"GOLDEN response stream: Gemini → OpenAI image output (inlineData → delta.images)","status":"passed","title":"image output (inlineData → delta.images)","duration":0.3387910000000147,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Kiro → OpenAI"],"fullName":"GOLDEN response stream: Kiro → OpenAI text + reasoning + toolUse + usage + stop","status":"passed","title":"text + reasoning + toolUse + usage + stop","duration":0.38375000000002046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Ollama → OpenAI"],"fullName":"GOLDEN response stream: Ollama → OpenAI content + thinking + tool_calls + done usage","status":"passed","title":"content + thinking + tool_calls + done usage","duration":1.0436669999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: OpenAI-Responses (codex) → OpenAI"],"fullName":"GOLDEN response stream: OpenAI-Responses (codex) → OpenAI text + reasoning + tool_call + completed usage","status":"passed","title":"text + reasoning + tool_call + completed usage","duration":1.1626249999999914,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: OpenAI-Responses (codex) → OpenAI"],"fullName":"GOLDEN response stream: OpenAI-Responses (codex) → OpenAI error event → error chunk (fallback id/created)","status":"passed","title":"error event → error chunk (fallback id/created)","duration":0.19799999999997908,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745325,"endTime":1781497745332.198,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/golden-response-stream.test.js"},{"assertionResults":[{"ancestorTitles":["GOLDEN response stream: CommandCode → OpenAI"],"fullName":"GOLDEN response stream: CommandCode → OpenAI text + reasoning + tool + finish-step usage","status":"passed","title":"text + reasoning + tool + finish-step usage","duration":2.500249999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Kiro → OpenAI (finish after tool)"],"fullName":"GOLDEN response stream: Kiro → OpenAI (finish after tool) toolUse then stop — lock current finish_reason behavior","status":"passed","title":"toolUse then stop — lock current finish_reason behavior","duration":0.523083000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN response stream: Ollama → OpenAI (finish after tool)"],"fullName":"GOLDEN response stream: Ollama → OpenAI (finish after tool) tool_calls then done_reason=stop — lock current finish_reason","status":"passed","title":"tool_calls then done_reason=stop — lock current finish_reason","duration":0.3717499999999916,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN passthrough: same format = no translation"],"fullName":"GOLDEN passthrough: same format = no translation response openai→openai returns chunk unchanged","status":"passed","title":"response openai→openai returns chunk unchanged","duration":0.36454100000000267,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN passthrough: same format = no translation"],"fullName":"GOLDEN passthrough: same format = no translation request openai→openai keeps messages (filterToOpenAIFormat normalize)","status":"passed","title":"request openai→openai keeps messages (filterToOpenAIFormat normalize)","duration":0.4329580000000135,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN usage math: Claude prompt = input + cache (lock)"],"fullName":"GOLDEN usage math: Claude prompt = input + cache (lock) prompt_tokens sums input + cache_read + cache_creation","status":"passed","title":"prompt_tokens sums input + cache_read + cache_creation","duration":0.46229200000001924,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745672,"endTime":1781497745677.4624,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/golden-translator-concerns.test.js"},{"assertionResults":[{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) alicode → url (stream + non-stream)","status":"passed","title":"alicode → url (stream + non-stream)","duration":1.5889160000000118,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) alicode-intl → url (stream + non-stream)","status":"passed","title":"alicode-intl → url (stream + non-stream)","duration":0.2955000000000041,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) anthropic → url (stream + non-stream)","status":"passed","title":"anthropic → url (stream + non-stream)","duration":0.18279200000000628,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) assemblyai → url (stream + non-stream)","status":"passed","title":"assemblyai → url (stream + non-stream)","duration":0.16170900000000188,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) blackbox → url (stream + non-stream)","status":"passed","title":"blackbox → url (stream + non-stream)","duration":0.16091599999998607,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) byteplus → url (stream + non-stream)","status":"passed","title":"byteplus → url (stream + non-stream)","duration":0.09704099999999016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) cerebras → url (stream + non-stream)","status":"passed","title":"cerebras → url (stream + non-stream)","duration":0.13083299999999554,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) chutes → url (stream + non-stream)","status":"passed","title":"chutes → url (stream + non-stream)","duration":0.09000000000000341,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) claude → url (stream + non-stream)","status":"passed","title":"claude → url (stream + non-stream)","duration":0.7322090000000117,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) cline → url (stream + non-stream)","status":"passed","title":"cline → url (stream + non-stream)","duration":0.1520840000000021,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) cloudflare-ai → url (stream + non-stream)","status":"passed","title":"cloudflare-ai → url (stream + non-stream)","duration":0.13470799999998917,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) codebuddy → url (stream + non-stream)","status":"passed","title":"codebuddy → url (stream + non-stream)","duration":0.05612500000000864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) cohere → url (stream + non-stream)","status":"passed","title":"cohere → url (stream + non-stream)","duration":0.04800000000000182,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) deepgram → url (stream + non-stream)","status":"passed","title":"deepgram → url (stream + non-stream)","duration":0.04629099999999653,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) deepseek → url (stream + non-stream)","status":"passed","title":"deepseek → url (stream + non-stream)","duration":0.044749999999993406,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) fireworks → url (stream + non-stream)","status":"passed","title":"fireworks → url (stream + non-stream)","duration":0.08270799999999667,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) gemini → url (stream + non-stream)","status":"passed","title":"gemini → url (stream + non-stream)","duration":0.1741660000000138,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) gitlab → url (stream + non-stream)","status":"passed","title":"gitlab → url (stream + non-stream)","duration":0.2367089999999905,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) glm → url (stream + non-stream)","status":"passed","title":"glm → url (stream + non-stream)","duration":0.16345800000001987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) glm-cn → url (stream + non-stream)","status":"passed","title":"glm-cn → url (stream + non-stream)","duration":0.132000000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) groq → url (stream + non-stream)","status":"passed","title":"groq → url (stream + non-stream)","duration":0.11379199999998946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) hyperbolic → url (stream + non-stream)","status":"passed","title":"hyperbolic → url (stream + non-stream)","duration":0.10858300000001009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) kilocode → url (stream + non-stream)","status":"passed","title":"kilocode → url (stream + non-stream)","duration":0.10866699999999696,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) kimi → url (stream + non-stream)","status":"passed","title":"kimi → url (stream + non-stream)","duration":0.1123749999999859,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) kimi-coding → url (stream + non-stream)","status":"passed","title":"kimi-coding → url (stream + non-stream)","duration":0.10633300000000645,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) minimax → url (stream + non-stream)","status":"passed","title":"minimax → url (stream + non-stream)","duration":0.1094579999999894,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) minimax-cn → url (stream + non-stream)","status":"passed","title":"minimax-cn → url (stream + non-stream)","duration":0.10074999999997658,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) mistral → url (stream + non-stream)","status":"passed","title":"mistral → url (stream + non-stream)","duration":0.10183299999999917,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) mmf → url (stream + non-stream)","status":"passed","title":"mmf → url (stream + non-stream)","duration":0.10354100000000699,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) nanobanana → url (stream + non-stream)","status":"passed","title":"nanobanana → url (stream + non-stream)","duration":0.10554199999998559,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) nebius → url (stream + non-stream)","status":"passed","title":"nebius → url (stream + non-stream)","duration":0.10154199999999491,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) nvidia → url (stream + non-stream)","status":"passed","title":"nvidia → url (stream + non-stream)","duration":0.11525000000000318,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) ollama → url (stream + non-stream)","status":"passed","title":"ollama → url (stream + non-stream)","duration":0.11195900000001302,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) openai → url (stream + non-stream)","status":"passed","title":"openai → url (stream + non-stream)","duration":0.10616699999999923,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) openrouter → url (stream + non-stream)","status":"passed","title":"openrouter → url (stream + non-stream)","duration":0.10312499999997726,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) perplexity → url (stream + non-stream)","status":"passed","title":"perplexity → url (stream + non-stream)","duration":0.10266599999999926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) siliconflow → url (stream + non-stream)","status":"passed","title":"siliconflow → url (stream + non-stream)","duration":0.10366700000000151,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) together → url (stream + non-stream)","status":"passed","title":"together → url (stream + non-stream)","duration":0.10412500000001046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) vercel-ai-gateway → url (stream + non-stream)","status":"passed","title":"vercel-ai-gateway → url (stream + non-stream)","duration":0.10758300000000531,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) volcengine-ark → url (stream + non-stream)","status":"passed","title":"volcengine-ark → url (stream + non-stream)","duration":0.1039169999999956,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) xai → url (stream + non-stream)","status":"passed","title":"xai → url (stream + non-stream)","duration":0.1042500000000075,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildUrl (default executor providers)"],"fullName":"GOLDEN buildUrl (default executor providers) xiaomi-mimo → url (stream + non-stream)","status":"passed","title":"xiaomi-mimo → url (stream + non-stream)","duration":0.09854100000001154,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) alicode → headers (apiKey / oauth)","status":"passed","title":"alicode → headers (apiKey / oauth)","duration":0.9032919999999933,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) alicode-intl → headers (apiKey / oauth)","status":"passed","title":"alicode-intl → headers (apiKey / oauth)","duration":0.22862499999999386,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) anthropic → headers (apiKey / oauth)","status":"passed","title":"anthropic → headers (apiKey / oauth)","duration":0.4243749999999977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) assemblyai → headers (apiKey / oauth)","status":"passed","title":"assemblyai → headers (apiKey / oauth)","duration":0.19870799999998212,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) blackbox → headers (apiKey / oauth)","status":"passed","title":"blackbox → headers (apiKey / oauth)","duration":0.16954200000000696,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) byteplus → headers (apiKey / oauth)","status":"passed","title":"byteplus → headers (apiKey / oauth)","duration":0.30637500000000273,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) cerebras → headers (apiKey / oauth)","status":"passed","title":"cerebras → headers (apiKey / oauth)","duration":0.2697909999999979,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) chutes → headers (apiKey / oauth)","status":"passed","title":"chutes → headers (apiKey / oauth)","duration":0.17108299999998167,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) claude → headers (apiKey / oauth)","status":"passed","title":"claude → headers (apiKey / oauth)","duration":0.5617090000000076,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) cline → headers (apiKey / oauth)","status":"passed","title":"cline → headers (apiKey / oauth)","duration":0.5082500000000039,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) cloudflare-ai → headers (apiKey / oauth)","status":"passed","title":"cloudflare-ai → headers (apiKey / oauth)","duration":0.20462499999999295,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) codebuddy → headers (apiKey / oauth)","status":"passed","title":"codebuddy → headers (apiKey / oauth)","duration":0.17416700000001129,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) cohere → headers (apiKey / oauth)","status":"passed","title":"cohere → headers (apiKey / oauth)","duration":0.1681250000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) deepgram → headers (apiKey / oauth)","status":"passed","title":"deepgram → headers (apiKey / oauth)","duration":0.1633329999999944,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) deepseek → headers (apiKey / oauth)","status":"passed","title":"deepseek → headers (apiKey / oauth)","duration":0.16312499999997954,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) fireworks → headers (apiKey / oauth)","status":"passed","title":"fireworks → headers (apiKey / oauth)","duration":0.18991700000000833,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) gemini → headers (apiKey / oauth)","status":"passed","title":"gemini → headers (apiKey / oauth)","duration":0.1736669999999947,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) gitlab → headers (apiKey / oauth)","status":"passed","title":"gitlab → headers (apiKey / oauth)","duration":0.14516600000001745,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) glm → headers (apiKey / oauth)","status":"passed","title":"glm → headers (apiKey / oauth)","duration":1.4152919999999938,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) glm-cn → headers (apiKey / oauth)","status":"passed","title":"glm-cn → headers (apiKey / oauth)","duration":0.20041700000001583,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) groq → headers (apiKey / oauth)","status":"passed","title":"groq → headers (apiKey / oauth)","duration":0.1705829999999935,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) hyperbolic → headers (apiKey / oauth)","status":"passed","title":"hyperbolic → headers (apiKey / oauth)","duration":0.15741599999998357,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) kilocode → headers (apiKey / oauth)","status":"passed","title":"kilocode → headers (apiKey / oauth)","duration":0.21187500000002046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) kimi → headers (apiKey / oauth)","status":"passed","title":"kimi → headers (apiKey / oauth)","duration":0.15887499999999477,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) kimi-coding → headers (apiKey / oauth)","status":"passed","title":"kimi-coding → headers (apiKey / oauth)","duration":0.18070800000000986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) minimax → headers (apiKey / oauth)","status":"passed","title":"minimax → headers (apiKey / oauth)","duration":0.08241599999999494,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) minimax-cn → headers (apiKey / oauth)","status":"passed","title":"minimax-cn → headers (apiKey / oauth)","duration":0.07200000000000273,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) mistral → headers (apiKey / oauth)","status":"passed","title":"mistral → headers (apiKey / oauth)","duration":0.06116700000001174,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) mmf → headers (apiKey / oauth)","status":"passed","title":"mmf → headers (apiKey / oauth)","duration":0.0619999999999834,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) nanobanana → headers (apiKey / oauth)","status":"passed","title":"nanobanana → headers (apiKey / oauth)","duration":0.055499999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) nebius → headers (apiKey / oauth)","status":"passed","title":"nebius → headers (apiKey / oauth)","duration":0.054291000000006306,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) nvidia → headers (apiKey / oauth)","status":"passed","title":"nvidia → headers (apiKey / oauth)","duration":0.05579199999999673,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) ollama → headers (apiKey / oauth)","status":"passed","title":"ollama → headers (apiKey / oauth)","duration":0.05500000000000682,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) openai → headers (apiKey / oauth)","status":"passed","title":"openai → headers (apiKey / oauth)","duration":0.055833000000006905,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) openrouter → headers (apiKey / oauth)","status":"passed","title":"openrouter → headers (apiKey / oauth)","duration":0.07183299999999804,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) perplexity → headers (apiKey / oauth)","status":"passed","title":"perplexity → headers (apiKey / oauth)","duration":0.05533300000001873,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) siliconflow → headers (apiKey / oauth)","status":"passed","title":"siliconflow → headers (apiKey / oauth)","duration":0.05587499999998613,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) together → headers (apiKey / oauth)","status":"passed","title":"together → headers (apiKey / oauth)","duration":0.05433300000001395,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) vercel-ai-gateway → headers (apiKey / oauth)","status":"passed","title":"vercel-ai-gateway → headers (apiKey / oauth)","duration":0.055999999999983174,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) volcengine-ark → headers (apiKey / oauth)","status":"passed","title":"volcengine-ark → headers (apiKey / oauth)","duration":0.05324999999999136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) xai → headers (apiKey / oauth)","status":"passed","title":"xai → headers (apiKey / oauth)","duration":0.05379100000001813,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GOLDEN buildHeaders (default executor providers)"],"fullName":"GOLDEN buildHeaders (default executor providers) xiaomi-mimo → headers (apiKey / oauth)","status":"passed","title":"xiaomi-mimo → headers (apiKey / oauth)","duration":0.05316700000000196,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745034,"endTime":1781497745051.0537,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/golden-url-header.test.js"},{"assertionResults":[{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) has at least one active AG connection with refreshToken","status":"skipped","title":"has at least one active AG connection with refreshToken","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) same sessionId → cache hit on repeated call","status":"skipped","title":"same sessionId → cache hit on repeated call","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) different sessionId (same account) → cache still hits (session-independent)","status":"skipped","title":"different sessionId (same account) → cache still hits (session-independent)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) cross-account → cache SHARED (content-based global cache)","status":"skipped","title":"cross-account → cache SHARED (content-based global cache)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) codex-style sessionId vs random sessionId on unique prompt","status":"skipped","title":"codex-style sessionId vs random sessionId on unique prompt","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity cache behavior (real API)"],"fullName":"Antigravity cache behavior (real API) unique prompt (never seen) → explore when cache starts hitting","status":"skipped","title":"unique prompt (never seen) → explore when cache starts hitting","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497742906,"endTime":1781497742906,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/antigravity-cache.test.js"},{"assertionResults":[{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling flags the out-of-box agent/Default model mandatory","status":"failed","title":"flags the out-of-box agent/Default model mandatory","duration":3.108542,"failureMessages":["AssertionError: expected undefined to be true // Object.is equality\n at /Users/Working/router4/app/tests/unit/antigravity-mitm.test.js:17:86\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling leaves models not proven auto-sent optional","status":"passed","title":"leaves models not proven auto-sent optional","duration":0.2819590000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling excludes tab-autocomplete model 'tab_jump_flash_lite_preview' from re-routing","status":"passed","title":"excludes tab-autocomplete model 'tab_jump_flash_lite_preview' from re-routing","duration":0.09350000000000591,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling excludes tab-autocomplete model 'tab_flash_lite_preview' from re-routing","status":"passed","title":"excludes tab-autocomplete model 'tab_flash_lite_preview' from re-routing","duration":0.16445799999999622,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Antigravity MITM model handling"],"fullName":"Antigravity MITM model handling does not exclude real agent models from re-routing","status":"passed","title":"does not exclude real agent models from re-routing","duration":0.1379169999999874,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743076,"endTime":1781497743080.1646,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/antigravity-mitm.test.js"},{"assertionResults":[{"ancestorTitles":["antigravity oauth client (deduped)"],"fullName":"antigravity oauth client (deduped) shared source holds the canonical credentials","status":"passed","title":"shared source holds the canonical credentials","duration":2.948875000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity oauth client (deduped)"],"fullName":"antigravity oauth client (deduped) registry transport keeps clientId/clientSecret","status":"passed","title":"registry transport keeps clientId/clientSecret","duration":0.8634999999999877,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity oauth client (deduped)"],"fullName":"antigravity oauth client (deduped) google client shared by gemini + gemini-cli","status":"passed","title":"google client shared by gemini + gemini-cli","duration":2.8681669999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity oauth client (deduped)"],"fullName":"antigravity oauth client (deduped) src oauth.js imports shared client + keeps full shape","status":"passed","title":"src oauth.js imports shared client + keeps full shape","duration":1.082250000000002,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745461,"endTime":1781497745469.0823,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/antigravity-oauth-client.test.js"},{"assertionResults":[{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) uses Retry-After header (seconds → ms) when within cap","status":"passed","title":"uses Retry-After header (seconds → ms) when within cap","duration":0.9103330000000085,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) vetoes (false) when Retry-After exceeds cap","status":"passed","title":"vetoes (false) when Retry-After exceeds cap","duration":0.17483300000000668,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) parses retry time from error body when no header","status":"passed","title":"parses retry time from error body when no header","duration":0.1621669999999824,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) exponential backoff for 429 when no retry info","status":"passed","title":"exponential backoff for 429 when no retry info","duration":0.20912500000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) 503 without retry info → veto (no auto backoff)","status":"passed","title":"503 without retry info → veto (no auto backoff)","duration":0.11945800000000872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity computeRetryDelay hook (D3)"],"fullName":"antigravity computeRetryDelay hook (D3) buildHeaders includes cached session id after transformRequest","status":"passed","title":"buildHeaders includes cached session id after transformRequest","duration":0.09604100000001381,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497746174,"endTime":1781497746175.2092,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/antigravity-retry-hook.test.js"},{"assertionResults":[{"ancestorTitles":["BaseExecutor.execute — retry by status (config-driven)"],"fullName":"BaseExecutor.execute — retry by status (config-driven) retries 502 `attempts` times then succeeds","status":"passed","title":"retries 502 `attempts` times then succeeds","duration":18.579083999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — retry by status (config-driven)"],"fullName":"BaseExecutor.execute — retry by status (config-driven) stops after exhausting 502 attempts on a single url and throws","status":"passed","title":"stops after exhausting 502 attempts on a single url and throws","duration":4.99199999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — baseUrls fallback"],"fullName":"BaseExecutor.execute — baseUrls fallback falls over to the next url on 429 (shouldRetry)","status":"passed","title":"falls over to the next url on 429 (shouldRetry)","duration":1.798833000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — network error retry/fallback"],"fullName":"BaseExecutor.execute — network error retry/fallback maps network exception to 502 retry config","status":"passed","title":"maps network exception to 502 retry config","duration":2.5506249999999966,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — network error retry/fallback"],"fullName":"BaseExecutor.execute — network error retry/fallback throws when the only url fails with network error and no retries left","status":"passed","title":"throws when the only url fails with network error and no retries left","duration":0.7341250000000059,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BaseExecutor.execute — computeRetryDelay hook veto"],"fullName":"BaseExecutor.execute — computeRetryDelay hook veto hook returning false skips retry (uses fallback path)","status":"passed","title":"hook returning false skips retry (uses fallback path)","duration":0.6470000000000198,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744566,"endTime":1781497744595.647,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/base-executor-retry.test.js"},{"assertionResults":[{"ancestorTitles":["PR #1175 - buildOutput filter detection"],"fullName":"PR #1175 - buildOutput filter detection detects npm install output","status":"passed","title":"detects npm install output","duration":1.7120840000000044,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput filter detection"],"fullName":"PR #1175 - buildOutput filter detection detects cargo build output (no longer misdetected as git-status)","status":"passed","title":"detects cargo build output (no longer misdetected as git-status)","duration":0.6735829999999936,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput compression behavior"],"fullName":"PR #1175 - buildOutput compression behavior compresses npm install with deprecations","status":"passed","title":"compresses npm install with deprecations","duration":0.964500000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput compression behavior"],"fullName":"PR #1175 - buildOutput compression behavior compresses cargo build output","status":"passed","title":"compresses cargo build output","duration":0.25858300000000156,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput compression behavior"],"fullName":"PR #1175 - buildOutput compression behavior keeps cargo errors verbatim","status":"passed","title":"keeps cargo errors verbatim","duration":0.12204200000000753,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - buildOutput compression behavior"],"fullName":"PR #1175 - buildOutput compression behavior keeps maven BUILD FAILED as error","status":"passed","title":"keeps maven BUILD FAILED as error","duration":0.1530830000000094,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regex fix edge cases"],"fullName":"PR #1175 - porcelain regex fix edge cases git status --porcelain workdir-only (space first char) STILL detects as gitStatus (minimal fix preserved old regex)","status":"passed","title":"git status --porcelain workdir-only (space first char) STILL detects as gitStatus (minimal fix preserved old regex)","duration":0.1834999999999951,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regex fix edge cases"],"fullName":"PR #1175 - porcelain regex fix edge cases git status --porcelain with staged (status code first char) still detects","status":"passed","title":"git status --porcelain with staged (status code first char) still detects","duration":0.0643749999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regex fix edge cases"],"fullName":"PR #1175 - porcelain regex fix edge cases cargo Compiling lines NOT detected as git-status (porcelain false positive fix)","status":"passed","title":"cargo Compiling lines NOT detected as git-status (porcelain false positive fix)","duration":0.36316700000000424,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regex fix edge cases"],"fullName":"PR #1175 - porcelain regex fix edge cases long-form git status with 'On branch' always detects","status":"passed","title":"long-form git status with 'On branch' always detects","duration":0.08691699999999969,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - false positive risks"],"fullName":"PR #1175 - false positive risks generic app log with 'ERROR:' triggers buildOutput (potential false positive)","status":"passed","title":"generic app log with 'ERROR:' triggers buildOutput (potential false positive)","duration":0.5575410000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - false positive risks"],"fullName":"PR #1175 - false positive risks generic 'Compiling templates' (non-build context) triggers buildOutput","status":"passed","title":"generic 'Compiling templates' (non-build context) triggers buildOutput","duration":0.20425000000000182,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - false positive risks"],"fullName":"PR #1175 - false positive risks plain text with no patterns falls through (no false positive)","status":"passed","title":"plain text with no patterns falls through (no false positive)","duration":0.11916700000000446,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - safety: no data corruption"],"fullName":"PR #1175 - safety: no data corruption empty input returns input","status":"passed","title":"empty input returns input","duration":0.09545899999999108,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - safety: no data corruption"],"fullName":"PR #1175 - safety: no data corruption input with only errors preserves all errors","status":"passed","title":"input with only errors preserves all errors","duration":0.05933400000000688,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - safety: no data corruption"],"fullName":"PR #1175 - safety: no data corruption input with no recognized patterns returns input (fallback)","status":"passed","title":"input with no recognized patterns returns input (fallback)","duration":0.04375000000000284,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - safety: no data corruption"],"fullName":"PR #1175 - safety: no data corruption limits warnings to 5 + summary line","status":"passed","title":"limits warnings to 5 + summary line","duration":0.08004100000000847,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745561,"endTime":1781497745568.0955,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/buildOutputFilter.test.js"},{"assertionResults":[{"ancestorTitles":["PR #1175 - priority with overlapping patterns"],"fullName":"PR #1175 - priority with overlapping patterns git-diff wins over buildOutput when both present","status":"passed","title":"git-diff wins over buildOutput when both present","duration":0.9644159999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - priority with overlapping patterns"],"fullName":"PR #1175 - priority with overlapping patterns git-status (long form) wins over buildOutput","status":"passed","title":"git-status (long form) wins over buildOutput","duration":0.22029100000000312,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - DETECT_WINDOW boundary"],"fullName":"PR #1175 - DETECT_WINDOW boundary build pattern beyond DETECT_WINDOW chars: NOT detected","status":"passed","title":"build pattern beyond DETECT_WINDOW chars: NOT detected","duration":0.635666999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - DETECT_WINDOW boundary"],"fullName":"PR #1175 - DETECT_WINDOW boundary build pattern at very start: detected","status":"passed","title":"build pattern at very start: detected","duration":0.13295799999998792,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - line endings & whitespace"],"fullName":"PR #1175 - line endings & whitespace CRLF line endings still detect","status":"passed","title":"CRLF line endings still detect","duration":0.07600000000000762,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - line endings & whitespace"],"fullName":"PR #1175 - line endings & whitespace Tab-prefixed Compiling (real cargo output uses leading spaces, not tab)","status":"passed","title":"Tab-prefixed Compiling (real cargo output uses leading spaces, not tab)","duration":0.05995800000000884,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - line endings & whitespace"],"fullName":"PR #1175 - line endings & whitespace Compiling without leading spaces","status":"passed","title":"Compiling without leading spaces","duration":0.1128750000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - adversarial: user code containing build strings"],"fullName":"PR #1175 - adversarial: user code containing build strings user JS code with console.log('npm warn ...') triggers buildOutput","status":"passed","title":"user JS code with console.log('npm warn ...') triggers buildOutput","duration":0.5640000000000072,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - adversarial: user code containing build strings"],"fullName":"PR #1175 - adversarial: user code containing build strings file content with 'BUILD SUCCESS' on its own line triggers buildOutput","status":"passed","title":"file content with 'BUILD SUCCESS' on its own line triggers buildOutput","duration":1.0916249999999934,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - adversarial: user code containing build strings"],"fullName":"PR #1175 - adversarial: user code containing build strings real cargo error spanning multiple lines preserves context","status":"passed","title":"real cargo error spanning multiple lines preserves context","duration":0.27687499999998977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety input with only progress lines (no errors/warnings/summary) returns input fallback","status":"passed","title":"input with only progress lines (no errors/warnings/summary) returns input fallback","duration":0.16362499999999613,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety input with only Downloading lines","status":"passed","title":"input with only Downloading lines","duration":0.05254200000000253,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety input with ONLY a single ERROR: line","status":"passed","title":"input with ONLY a single ERROR: line","duration":0.04375000000000284,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety unicode/emoji in deprecation warning preserved (minimal fix keeps first 3 verbatim)","status":"passed","title":"unicode/emoji in deprecation warning preserved (minimal fix keeps first 3 verbatim)","duration":0.42991600000000574,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety more than 3 deprecations: keep first 3 verbatim + count rest","status":"passed","title":"more than 3 deprecations: keep first 3 verbatim + count rest","duration":0.09487500000000182,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - corruption safety"],"fullName":"PR #1175 - corruption safety safeApply wraps buildOutput against panics","status":"passed","title":"safeApply wraps buildOutput against panics","duration":0.05741699999998673,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - integration with compressMessages"],"fullName":"PR #1175 - integration with compressMessages npm install output above MIN_COMPRESS_SIZE → compressed","status":"passed","title":"npm install output above MIN_COMPRESS_SIZE → compressed","duration":0.31862499999999727,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - integration with compressMessages"],"fullName":"PR #1175 - integration with compressMessages input below MIN_COMPRESS_SIZE → NOT compressed","status":"passed","title":"input below MIN_COMPRESS_SIZE → NOT compressed","duration":0.07425000000000637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - integration with compressMessages"],"fullName":"PR #1175 - integration with compressMessages compressed output never grows input (safety guard)","status":"passed","title":"compressed output never grows input (safety guard)","duration":0.0800000000000125,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - integration with compressMessages"],"fullName":"PR #1175 - integration with compressMessages tool_result with is_error:true is NOT compressed (preserve error traces)","status":"passed","title":"tool_result with is_error:true is NOT compressed (preserve error traces)","duration":0.12983400000000245,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regression deeper"],"fullName":"PR #1175 - porcelain regression deeper mixed staged + workdir + untracked porcelain → detected (has status code first char)","status":"passed","title":"mixed staged + workdir + untracked porcelain → detected (has status code first char)","duration":0.047417000000010034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regression deeper"],"fullName":"PR #1175 - porcelain regression deeper 100% workdir-only porcelain → STILL detects gitStatus (minimal fix preserved old regex)","status":"passed","title":"100% workdir-only porcelain → STILL detects gitStatus (minimal fix preserved old regex)","duration":0.038374999999987836,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - porcelain regression deeper"],"fullName":"PR #1175 - porcelain regression deeper manual gitStatus() call on workdir-only porcelain still parses correctly","status":"passed","title":"manual gitStatus() call on workdir-only porcelain still parses correctly","duration":0.17579200000000128,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - pathological"],"fullName":"PR #1175 - pathological very long single line (no newlines) with build pattern","status":"passed","title":"very long single line (no newlines) with build pattern","duration":0.06050000000000466,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - pathological"],"fullName":"PR #1175 - pathological 10000 Compiling lines don't crash","status":"passed","title":"10000 Compiling lines don't crash","duration":6.193375000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - pathological"],"fullName":"PR #1175 - pathological input with only newlines","status":"passed","title":"input with only newlines","duration":0.08358299999999019,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PR #1175 - pathological"],"fullName":"PR #1175 - pathological null/undefined safety via safeApply","status":"passed","title":"null/undefined safety via safeApply","duration":0.28562500000001023,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744730,"endTime":1781497744743.2856,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/buildOutputFilterAdversarial.test.js"},{"assertionResults":[{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools suffixes client tool names and maps them back","status":"passed","title":"suffixes client tool names and maps them back","duration":0.9414170000000013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools suffixes a forced tool_choice to match the renamed tool","status":"passed","title":"suffixes a forced tool_choice to match the renamed tool","duration":0.3693750000000193,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools suffixes only the chosen tool when several are present","status":"passed","title":"suffixes only the chosen tool when several are present","duration":0.14179099999998357,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools leaves non-forced tool_choice untouched","status":"passed","title":"leaves non-forced tool_choice untouched","duration":0.17320900000001416,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools does not suffix a forced choice that targets a non-client (decoy/built-in) tool","status":"passed","title":"does not suffix a forced choice that targets a non-client (decoy/built-in) tool","duration":0.07850000000001955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools renames tool_use names in message history","status":"passed","title":"renames tool_use names in message history","duration":0.0681250000000091,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cloakClaudeTools"],"fullName":"cloakClaudeTools returns the body unchanged when there are no tools","status":"passed","title":"returns the body unchanged when there are no tools","duration":0.18104199999999082,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745666,"endTime":1781497745668.1812,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/claude-cloaking.test.js"},{"assertionResults":[{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache returns null before any headers are cached (cold start)","status":"passed","title":"returns null before any headers are cached (cold start)","duration":13.243291,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache caches headers when user-agent contains 'claude-code'","status":"passed","title":"caches headers when user-agent contains 'claude-code'","duration":1.2785419999999874,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache caches headers when user-agent contains 'claude-cli'","status":"passed","title":"caches headers when user-agent contains 'claude-cli'","duration":0.48799999999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache caches headers when x-app is 'cli' (regardless of user-agent)","status":"passed","title":"caches headers when x-app is 'cli' (regardless of user-agent)","duration":0.392832999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache does NOT cache headers for non-Claude clients","status":"passed","title":"does NOT cache headers for non-Claude clients","duration":0.23779199999998468,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache refreshes cache on each matching request","status":"passed","title":"refreshes cache on each matching request","duration":0.29958299999998417,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache ignores calls with null or non-object headers","status":"passed","title":"ignores calls with null or non-object headers","duration":0.3752089999999839,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["claudeHeaderCache"],"fullName":"claudeHeaderCache only stores keys that are actually present in the headers object","status":"passed","title":"only stores keys that are actually present in the headers object","duration":0.44370900000001257,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider overlays live cached headers over static provider defaults","status":"passed","title":"overlays live cached headers over static provider defaults","duration":401.45075,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider removes conflicting Title-Case static keys when cached lowercase keys exist","status":"passed","title":"removes conflicting Title-Case static keys when cached lowercase keys exist","duration":9.436374999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider sets x-api-key auth when apiKey is provided","status":"passed","title":"sets x-api-key auth when apiKey is provided","duration":9.251499999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider sets Bearer Authorization when only accessToken is provided","status":"passed","title":"sets Bearer Authorization when only accessToken is provided","duration":5.920749999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider includes Accept: text/event-stream when stream=true","status":"passed","title":"includes Accept: text/event-stream when stream=true","duration":5.748541000000046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider"],"fullName":"DefaultExecutor.buildHeaders() — claude provider omits Accept: text/event-stream when stream=false","status":"passed","title":"omits Accept: text/event-stream when stream=false","duration":6.744332999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider cold start (no cache)"],"fullName":"DefaultExecutor.buildHeaders() — claude provider cold start (no cache) falls back to static provider headers when cache is empty","status":"passed","title":"falls back to static provider headers when cache is empty","duration":9.551750000000084,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — claude provider cold start (no cache)"],"fullName":"DefaultExecutor.buildHeaders() — claude provider cold start (no cache) does not throw when cache returns null","status":"passed","title":"does not throw when cache returns null","duration":4.636792000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping strips x-app and anthropic-dangerous-direct-browser-access for non-Anthropic host","status":"passed","title":"strips x-app and anthropic-dangerous-direct-browser-access for non-Anthropic host","duration":5.249957999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping removes claude-code-20250219 from anthropic-beta for non-Anthropic host","status":"passed","title":"removes claude-code-20250219 from anthropic-beta for non-Anthropic host","duration":6.692584000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping keeps other beta flags intact after stripping","status":"passed","title":"keeps other beta flags intact after stripping","duration":4.507333000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping does NOT strip headers when baseUrl is api.anthropic.com","status":"passed","title":"does NOT strip headers when baseUrl is api.anthropic.com","duration":5.089665999999966,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DefaultExecutor.buildHeaders() — anthropic-compatible stripping"],"fullName":"DefaultExecutor.buildHeaders() — anthropic-compatible stripping does NOT strip headers when baseUrl is empty (defaults to Anthropic)","status":"passed","title":"does NOT strip headers when baseUrl is empty (defaults to Anthropic)","duration":4.920790999999895,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["proxyAwareFetch — api.anthropic.com routing"],"fullName":"proxyAwareFetch — api.anthropic.com routing routes api.anthropic.com to gotScraping (non-streaming) and returns ok response","status":"failed","title":"routes api.anthropic.com to gotScraping (non-streaming) and returns ok response","duration":434.6886250000001,"failureMessages":["AssertionError: expected \"vi.fn()\" to be called once, but got 0 times\n at /Users/Working/router4/app/tests/unit/claude-header-forwarding.test.js:354:25\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["proxyAwareFetch — api.anthropic.com routing"],"fullName":"proxyAwareFetch — api.anthropic.com routing falls back gracefully when got-scraping throws on non-streaming path","status":"passed","title":"falls back gracefully when got-scraping throws on non-streaming path","duration":8.306916999999885,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["proxyAwareFetch — api.anthropic.com routing"],"fullName":"proxyAwareFetch — api.anthropic.com routing does NOT route non-Anthropic hosts through gotScraping","status":"passed","title":"does NOT route non-Anthropic hosts through gotScraping","duration":2.2377910000000156,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743088,"endTime":1781497744030.2378,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/claude-header-forwarding.test.js"},{"assertionResults":[{"ancestorTitles":["CodexExecutor image handling"],"fullName":"CodexExecutor image handling fetches 1MB remote image and inlines it as base64 data URI","status":"passed","title":"fetches 1MB remote image and inlines it as base64 data URI","duration":3.8890829999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CodexExecutor image handling"],"fullName":"CodexExecutor image handling passes through existing data URIs without calling fetch","status":"passed","title":"passes through existing data URIs without calling fetch","duration":0.40529200000000287,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CodexExecutor image handling"],"fullName":"CodexExecutor image handling falls back to original URL when remote fetch fails","status":"passed","title":"falls back to original URL when remote fetch fails","duration":0.30504199999998605,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CodexExecutor image handling"],"fullName":"CodexExecutor image handling execute() prefetches images before sending to upstream","status":"passed","title":"execute() prefetches images before sending to upstream","duration":22.720083000000017,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744486,"endTime":1781497744513.72,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/codex-image-fetch.test.js"},{"assertionResults":[{"ancestorTitles":["Codex Refresh Token","refreshCodexToken"],"fullName":"Codex Refresh Token refreshCodexToken should return new refresh_token when server provides one (token rotation)","status":"passed","title":"should return new refresh_token when server provides one (token rotation)","duration":71.50587500000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","refreshCodexToken"],"fullName":"Codex Refresh Token refreshCodexToken should keep old refresh_token when server does not return new one","status":"passed","title":"should keep old refresh_token when server does not return new one","duration":5.32650000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","CodexExecutor credential lifecycle"],"fullName":"Codex Refresh Token CodexExecutor credential lifecycle should refresh Codex credentials and preserve omitted id_token","status":"passed","title":"should refresh Codex credentials and preserve omitted id_token","duration":17.745959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","CodexExecutor credential lifecycle"],"fullName":"Codex Refresh Token CodexExecutor credential lifecycle should refresh Codex when lastRefreshAt is older than the upstream stale window","status":"passed","title":"should refresh Codex when lastRefreshAt is older than the upstream stale window","duration":5.433375000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","CodexExecutor credential lifecycle"],"fullName":"Codex Refresh Token CodexExecutor credential lifecycle should de-duplicate concurrent refreshes for the same Codex connection","status":"passed","title":"should de-duplicate concurrent refreshes for the same Codex connection","duration":7.746082999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","getRefreshLeadMs (early refresh config)"],"fullName":"Codex Refresh Token getRefreshLeadMs (early refresh config) should return provider-specific lead time for OAuth providers","status":"passed","title":"should return provider-specific lead time for OAuth providers","duration":5.812375000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","getRefreshLeadMs (early refresh config)"],"fullName":"Codex Refresh Token getRefreshLeadMs (early refresh config) should fallback to default buffer for unknown providers","status":"passed","title":"should fallback to default buffer for unknown providers","duration":4.4067079999999805,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex Refresh Token","getRefreshLeadMs (early refresh config)"],"fullName":"Codex Refresh Token getRefreshLeadMs (early refresh config) codex lead should be greater than default buffer","status":"passed","title":"codex lead should be greater than default buffer","duration":5.317875000000015,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744041,"endTime":1781497744165.3179,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/codex-refresh-token.test.js"},{"assertionResults":[{"ancestorTitles":["combo round-robin routing"],"fullName":"combo round-robin routing keeps existing one-request round-robin behavior by default","status":"passed","title":"keeps existing one-request round-robin behavior by default","duration":1.1964159999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["combo round-robin routing"],"fullName":"combo round-robin routing sticks to each combo model for the configured number of requests","status":"passed","title":"sticks to each combo model for the configured number of requests","duration":0.25079100000000665,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["combo round-robin routing"],"fullName":"combo round-robin routing tracks sticky rotation independently per combo","status":"passed","title":"tracks sticky rotation independently per combo","duration":0.18804199999999582,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["combo round-robin routing"],"fullName":"combo round-robin routing does not rotate fallback combos","status":"passed","title":"does not rotate fallback combos","duration":0.21945800000000304,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745796,"endTime":1781497745798.2195,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/combo-routing.test.js"},{"assertionResults":[{"ancestorTitles":["commandcode-to-openai — text-delta"],"fullName":"commandcode-to-openai — text-delta emits assistant role on first delta then content-only","status":"passed","title":"emits assistant role on first delta then content-only","duration":2.4240000000000066,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — reasoning-delta"],"fullName":"commandcode-to-openai — reasoning-delta maps reasoning-delta to reasoning_content delta","status":"passed","title":"maps reasoning-delta to reasoning_content delta","duration":0.22212500000000546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — tool-input-* with id field (live schema)"],"fullName":"commandcode-to-openai — tool-input-* with id field (live schema) registers tool index using event.id (NOT toolCallId)","status":"passed","title":"registers tool index using event.id (NOT toolCallId)","duration":0.204291000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — tool-input-* with id field (live schema)"],"fullName":"commandcode-to-openai — tool-input-* with id field (live schema) ignores tool-input-delta when id is unknown (no prior start)","status":"passed","title":"ignores tool-input-delta when id is unknown (no prior start)","duration":0.069500000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — final tool-call event"],"fullName":"commandcode-to-openai — final tool-call event does NOT re-emit tool_calls when tool-input-* deltas already fired","status":"passed","title":"does NOT re-emit tool_calls when tool-input-* deltas already fired","duration":0.24575000000001523,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — final tool-call event"],"fullName":"commandcode-to-openai — final tool-call event emits a consolidated tool_calls when only the final tool-call event arrives","status":"passed","title":"emits a consolidated tool_calls when only the final tool-call event arrives","duration":0.11754099999998857,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — finish"],"fullName":"commandcode-to-openai — finish emits a final chunk with finish_reason=tool_calls when finishReason is tool-calls","status":"passed","title":"emits a final chunk with finish_reason=tool_calls when finishReason is tool-calls","duration":0.14683299999998667,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — finish"],"fullName":"commandcode-to-openai — finish includes usage on the final chunk when totalUsage provided","status":"passed","title":"includes usage on the final chunk when totalUsage provided","duration":0.3611669999999947,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["commandcode-to-openai — error event"],"fullName":"commandcode-to-openai — error event stringifies object errors so client sees readable message","status":"passed","title":"stringifies object errors so client sees readable message","duration":0.4736249999999984,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745860,"endTime":1781497745865.4736,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/commandcode-to-openai.test.js"},{"assertionResults":[{"ancestorTitles":["compatible provider connections API"],"fullName":"compatible provider connections API creates one API-key connection for an OpenAI-compatible node","status":"passed","title":"creates one API-key connection for an OpenAI-compatible node","duration":153.804708,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["compatible provider connections API"],"fullName":"compatible provider connections API creates one API-key connection for an Anthropic-compatible node","status":"passed","title":"creates one API-key connection for an Anthropic-compatible node","duration":12.123792000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["compatible provider connections API"],"fullName":"compatible provider connections API returns 400 for a duplicate connection on the same compatible node","status":"passed","title":"returns 400 for a duplicate connection on the same compatible node","duration":12.209874999999982,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743624,"endTime":1781497743802.21,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/compatible-provider-connections.test.js"},{"assertionResults":[{"ancestorTitles":["CursorExecutor Composer thinking-field responses"],"fullName":"CursorExecutor Composer thinking-field responses uses visible content after for non-streaming Composer responses","status":"passed","title":"uses visible content after for non-streaming Composer responses","duration":12.16758299999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CursorExecutor Composer thinking-field responses"],"fullName":"CursorExecutor Composer thinking-field responses streams only visible content after for Composer responses","status":"passed","title":"streams only visible content after for Composer responses","duration":0.9765419999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CursorExecutor Composer thinking-field responses"],"fullName":"CursorExecutor Composer thinking-field responses does not treat thinking as visible output for non-Composer models","status":"passed","title":"does not treat thinking as visible output for non-Composer models","duration":0.2868750000000091,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744993,"endTime":1781497745006.2869,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/cursor-composer-thinking.test.js"},{"assertionResults":[{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows loopback public LLM API without API key","status":"passed","title":"allows loopback public LLM API without API key","duration":11.345667000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access rejects remote rewritten public LLM API without API key","status":"passed","title":"rejects remote rewritten public LLM API without API key","duration":0.40787499999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows loopback rewritten public LLM API without API key","status":"passed","title":"allows loopback rewritten public LLM API without API key","duration":0.20650000000000546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access rejects remote beta public LLM API without API key","status":"passed","title":"rejects remote beta public LLM API without API key","duration":0.22237500000001376,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access rejects remote rewritten beta public LLM API without API key","status":"passed","title":"rejects remote rewritten beta public LLM API without API key","duration":0.22887500000000216,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows remote public LLM API with valid bearer API key","status":"passed","title":"allows remote public LLM API with valid bearer API key","duration":0.5850839999999948,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows remote public LLM API with valid x-api-key","status":"passed","title":"allows remote public LLM API with valid x-api-key","duration":0.3187500000000085,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard public LLM API access"],"fullName":"dashboard guard public LLM API access allows remote rewritten beta public LLM API with valid API key","status":"passed","title":"allows remote rewritten beta public LLM API with valid API key","duration":0.1506249999999909,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access rejects local-only route from non-loopback host without CLI token","status":"passed","title":"rejects local-only route from non-loopback host without CLI token","duration":0.38112499999999727,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access rejects local-only route on loopback when requireLogin=true and no JWT","status":"passed","title":"rejects local-only route on loopback when requireLogin=true and no JWT","duration":0.2264160000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access allows local-only route on loopback when requireLogin=false","status":"passed","title":"allows local-only route on loopback when requireLogin=false","duration":0.19950000000000045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access rejects local-only route from tunnel host even when requireLogin=false","status":"passed","title":"rejects local-only route from tunnel host even when requireLogin=false","duration":0.07450000000000045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access rejects local-only route when Origin is non-loopback (CSRF block)","status":"passed","title":"rejects local-only route when Origin is non-loopback (CSRF block)","duration":0.07295899999999733,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard local-only access"],"fullName":"dashboard guard local-only access allows local-only route with valid CLI token","status":"passed","title":"allows local-only route with valid CLI token","duration":0.07587499999999636,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard guard helpers"],"fullName":"dashboard guard helpers extracts bearer API keys before x-api-key","status":"passed","title":"extracts bearer API keys before x-api-key","duration":0.059749999999993975,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744865,"endTime":1781497744880.076,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/dashboard-guard.test.js"},{"assertionResults":[{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb INSERT 500 provider connections","status":"passed","title":"INSERT 500 provider connections","duration":1194.752958,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb READ 200 filtered queries","status":"passed","title":"READ 200 filtered queries","duration":520.2736670000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb READ 200 by id (point lookup)","status":"passed","title":"READ 200 by id (point lookup)","duration":458.4993750000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb saveRequestUsage 500 entries","status":"passed","title":"saveRequestUsage 500 entries","duration":1408.084792,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Benchmark — SQLite vs Lowdb"],"fullName":"DB Benchmark — SQLite vs Lowdb getUsageStats(24h) repeat 50x","status":"passed","title":"getUsageStats(24h) repeat 50x","duration":507.4391249999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743387,"endTime":1781497747476.4392,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-benchmark.test.js"},{"assertionResults":[{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety 100 parallel saveRequestUsage → no count loss","status":"passed","title":"100 parallel saveRequestUsage → no count loss","duration":23.122333999999967,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety 200 parallel saveRequestDetail → all flushed","status":"passed","title":"200 parallel saveRequestDetail → all flushed","duration":6004.98225,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety mixed concurrent: usage + details + connections + aliases","status":"passed","title":"mixed concurrent: usage + details + connections + aliases","duration":21.913375000000087,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety updateSettings parallel → no merge loss","status":"passed","title":"updateSettings parallel → no merge loss","duration":2.7496659999997064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety OAuth refresh race: parallel updateProviderConnection on same id","status":"passed","title":"OAuth refresh race: parallel updateProviderConnection on same id","duration":1.968499999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety addCustomModel race: parallel duplicate adds → only 1 inserted","status":"passed","title":"addCustomModel race: parallel duplicate adds → only 1 inserted","duration":0.5107500000003711,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety updatePricing race: parallel adds different models → all merged","status":"passed","title":"updatePricing race: parallel adds different models → all merged","duration":2.3576249999996435,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB Concurrency — atomic safety"],"fullName":"DB Concurrency — atomic safety daily summary aggregates correctly under parallel writes","status":"passed","title":"daily summary aggregates correctly under parallel writes","duration":8.111417000000074,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743380,"endTime":1781497749446.1113,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-concurrent.test.js"},{"assertionResults":[{"ancestorTitles":["Driver fallback chain"],"fullName":"Driver fallback chain default → picks better-sqlite3 when available","status":"passed","title":"default → picks better-sqlite3 when available","duration":18.590333,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Driver fallback chain"],"fullName":"Driver fallback chain falls back to node:sqlite when better-sqlite3 unavailable","status":"passed","title":"falls back to node:sqlite when better-sqlite3 unavailable","duration":9.896625,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Driver fallback chain"],"fullName":"Driver fallback chain falls back to sql.js when both native drivers unavailable","status":"passed","title":"falls back to sql.js when both native drivers unavailable","duration":41.571499999999986,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743956,"endTime":1781497744026.5715,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-driver-chain.test.js"},{"assertionResults":[{"ancestorTitles":["Schema migrations"],"fullName":"Schema migrations fresh DB → applies migrations & stamps schemaVersion","status":"passed","title":"fresh DB → applies migrations & stamps schemaVersion","duration":25.217416999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Schema migrations"],"fullName":"Schema migrations existing DB at older schemaVersion → re-applies pending migrations on restart","status":"passed","title":"existing DB at older schemaVersion → re-applies pending migrations on restart","duration":10.831708000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Schema migrations"],"fullName":"Schema migrations fresh DB + legacy db.json → imports data automatically","status":"passed","title":"fresh DB + legacy db.json → imports data automatically","duration":7.295833000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Schema migrations"],"fullName":"Schema migrations auto-sync re-creates missing index when DB lacks it","status":"passed","title":"auto-sync re-creates missing index when DB lacks it","duration":7.034208000000007,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744149,"endTime":1781497744199.0342,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-migration-chain.test.js"},{"assertionResults":[{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity settings: get → defaults; update → merge","status":"passed","title":"settings: get → defaults; update → merge","duration":1.102499999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity isCloudEnabled reflects settings","status":"passed","title":"isCloudEnabled reflects settings","duration":0.3603339999999946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity apiKeys: create/get/validate/delete","status":"passed","title":"apiKeys: create/get/validate/delete","duration":5.940334000000007,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity providerConnections: CRUD + reorder by priority","status":"passed","title":"providerConnections: CRUD + reorder by priority","duration":2.221416000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity providerConnections: optional fields persisted via JSON column","status":"passed","title":"providerConnections: optional fields persisted via JSON column","duration":0.643084000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity providerNodes: CRUD","status":"passed","title":"providerNodes: CRUD","duration":0.5239159999999856,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity proxyPools: CRUD with sort by updatedAt desc","status":"passed","title":"proxyPools: CRUD with sort by updatedAt desc","duration":11.301040999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity combos: CRUD","status":"passed","title":"combos: CRUD","duration":0.5484170000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity modelAliases: KV ops","status":"passed","title":"modelAliases: KV ops","duration":0.5910000000000082,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity customModels: add/list/delete with dedupe","status":"passed","title":"customModels: add/list/delete with dedupe","duration":0.4903750000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity mitmAlias: get/set per tool","status":"passed","title":"mitmAlias: get/set per tool","duration":0.270165999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity disabledModels: add/remove per provider","status":"passed","title":"disabledModels: add/remove per provider","duration":0.8153340000000071,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity usage: saveRequestUsage + getUsageHistory + getUsageStats","status":"passed","title":"usage: saveRequestUsage + getUsageHistory + getUsageStats","duration":3.277417000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity usage: pending tracking in-memory","status":"passed","title":"usage: pending tracking in-memory","duration":12.298000000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity requestDetails: save → query with paging","status":"passed","title":"requestDetails: save → query with paging","duration":201.92520799999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity exportDb / importDb roundtrip","status":"passed","title":"exportDb / importDb roundtrip","duration":1.1673339999999826,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity pricing: user pricing merged with constants","status":"passed","title":"pricing: user pricing merged with constants","duration":1.464917000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity getChartData: 24h buckets","status":"passed","title":"getChartData: 24h buckets","duration":1.7257079999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DB SQLite layer — public API parity"],"fullName":"DB SQLite layer — public API parity getChartData: 7d buckets","status":"passed","title":"getChartData: 7d buckets","duration":1.2842919999999935,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743819,"endTime":1781497744068.2842,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/db-sqlite-vs-lowdb.test.js"},{"assertionResults":[],"startTime":1781497742906,"endTime":1781497742906,"status":"failed","message":"Cannot find module '/cloud/src/handlers/embeddings.js' imported from /Users/Working/router4/app/tests/unit/embeddings.cloud.test.js","name":"/Users/Working/router4/app/tests/unit/embeddings.cloud.test.js"},{"assertionResults":[{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody single string input — includes model and input, default encoding_format=float","status":"passed","title":"single string input — includes model and input, default encoding_format=float","duration":14.96350000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody array input — passes array as-is","status":"passed","title":"array input — passes array as-is","duration":0.8353749999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody custom encoding_format is forwarded","status":"passed","title":"custom encoding_format is forwarded","duration":0.5032500000000084,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody no encoding_format in body → defaults to float","status":"passed","title":"no encoding_format in body → defaults to float","duration":0.3033750000000168,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody gemini single input forwards dimensions as outputDimensionality","status":"passed","title":"gemini single input forwards dimensions as outputDimensionality","duration":0.7035000000000196,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsBody"],"fullName":"buildEmbeddingsBody gemini batch input forwards dimensions on each request","status":"passed","title":"gemini batch input forwards dimensions on each request","duration":0.7497499999999775,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openai → https://api.openai.com/v1/embeddings","status":"passed","title":"openai → https://api.openai.com/v1/embeddings","duration":0.4544999999999959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openrouter → https://openrouter.ai/api/v1/embeddings","status":"passed","title":"openrouter → https://openrouter.ai/api/v1/embeddings","duration":0.2414590000000203,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl vercel-ai-gateway → https://ai-gateway.vercel.sh/v1/embeddings","status":"passed","title":"vercel-ai-gateway → https://ai-gateway.vercel.sh/v1/embeddings","duration":0.8683749999999861,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openai-compatible-* → uses baseUrl from providerSpecificData","status":"passed","title":"openai-compatible-* → uses baseUrl from providerSpecificData","duration":0.3087910000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openai-compatible-* strips trailing slash from baseUrl","status":"passed","title":"openai-compatible-* strips trailing slash from baseUrl","duration":0.39629099999999085,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl openai-compatible-* without baseUrl → falls back to api.openai.com","status":"passed","title":"openai-compatible-* without baseUrl → falls back to api.openai.com","duration":0.19641699999999673,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl unsupported provider (e.g. gemini-cli) → 400 error, no fetch called","status":"passed","title":"unsupported provider (e.g. gemini-cli) → 400 error, no fetch called","duration":0.3387920000000122,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsUrl"],"fullName":"buildEmbeddingsUrl antigravity (non-openai-compatible, no URL mapping) → 400","status":"passed","title":"antigravity (non-openai-compatible, no URL mapping) → 400","duration":0.13645800000000463,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsHeaders"],"fullName":"buildEmbeddingsHeaders openai → Authorization: Bearer, Content-Type: application/json","status":"passed","title":"openai → Authorization: Bearer, Content-Type: application/json","duration":0.19033300000000963,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsHeaders"],"fullName":"buildEmbeddingsHeaders openai — uses accessToken when apiKey is absent","status":"passed","title":"openai — uses accessToken when apiKey is absent","duration":0.23954100000000267,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsHeaders"],"fullName":"buildEmbeddingsHeaders openrouter → adds HTTP-Referer and X-Title headers","status":"passed","title":"openrouter → adds HTTP-Referer and X-Title headers","duration":0.17924999999999613,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildEmbeddingsHeaders"],"fullName":"buildEmbeddingsHeaders openai-compatible-* → Authorization: Bearer only (no extra headers)","status":"passed","title":"openai-compatible-* → Authorization: Bearer only (no extra headers)","duration":0.17229199999999878,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation missing input → 400 Bad Request","status":"passed","title":"missing input → 400 Bad Request","duration":0.14400000000000546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation input is a number → 400 Bad Request","status":"passed","title":"input is a number → 400 Bad Request","duration":0.12041700000000333,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation input is an object → 400 Bad Request","status":"passed","title":"input is an object → 400 Bad Request","duration":0.1479580000000169,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation input is null → 400 Bad Request","status":"passed","title":"input is null → 400 Bad Request","duration":0.17558299999998894,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation empty string input passes validation","status":"passed","title":"empty string input passes validation","duration":0.1440409999999872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — input validation"],"fullName":"handleEmbeddingsCore — input validation empty array input passes validation and reaches provider","status":"passed","title":"empty array input passes validation and reaches provider","duration":0.1974999999999909,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path returns success=true with Response on 200","status":"passed","title":"returns success=true with Response on 200","duration":0.223542000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path response body is valid OpenAI-format JSON","status":"passed","title":"response body is valid OpenAI-format JSON","duration":0.24145899999999187,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path response includes CORS header Access-Control-Allow-Origin: *","status":"passed","title":"response includes CORS header Access-Control-Allow-Origin: *","duration":0.18454199999999332,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path response Content-Type is application/json","status":"passed","title":"response Content-Type is application/json","duration":0.17466599999997356,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path calls onRequestSuccess callback on success","status":"passed","title":"calls onRequestSuccess callback on success","duration":0.1364590000000021,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path does not call onRequestSuccess on provider error","status":"passed","title":"does not call onRequestSuccess on provider error","duration":0.23904199999998355,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — success path"],"fullName":"handleEmbeddingsCore — success path provider response with non-standard format is passed through as-is","status":"passed","title":"provider response with non-standard format is passed through as-is","duration":0.19412500000001387,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling provider 400 → returns success=false with status 400","status":"passed","title":"provider 400 → returns success=false with status 400","duration":0.1668329999999969,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling provider 429 → returns success=false with status 429","status":"passed","title":"provider 429 → returns success=false with status 429","duration":0.2126249999999743,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling provider 500 → returns success=false with status 500","status":"passed","title":"provider 500 → returns success=false with status 500","duration":0.14108300000000895,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling network error (fetch throws) → returns 502 Bad Gateway","status":"passed","title":"network error (fetch throws) → returns 502 Bad Gateway","duration":0.16591599999998152,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling invalid JSON from provider → returns 502","status":"passed","title":"invalid JSON from provider → returns 502","duration":0.16704200000000924,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — provider error handling"],"fullName":"handleEmbeddingsCore — provider error handling error result response has OpenAI-format error body","status":"passed","title":"error result response has OpenAI-format error body","duration":0.16045800000000554,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — token refresh on 401/403"],"fullName":"handleEmbeddingsCore — token refresh on 401/403 on 401, attempts retry after refresh; succeeds if refresh gives new token","status":"passed","title":"on 401, attempts retry after refresh; succeeds if refresh gives new token","duration":0.2124589999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleEmbeddingsCore — token refresh on 401/403"],"fullName":"handleEmbeddingsCore — token refresh on 401/403 on 401 with no refresh token, falls back gracefully (no crash)","status":"passed","title":"on 401 with no refresh token, falls back gracefully (no crash)","duration":0.1518339999999796,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744367,"endTime":1781497744393.2124,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/embeddingsCore.test.js"},{"assertionResults":[{"ancestorTitles":["compat base URLs / version"],"fullName":"compat base URLs / version OPENAI_COMPAT_BASE","status":"passed","title":"OPENAI_COMPAT_BASE","duration":0.9344170000000105,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["compat base URLs / version"],"fullName":"compat base URLs / version ANTHROPIC_COMPAT_BASE","status":"passed","title":"ANTHROPIC_COMPAT_BASE","duration":0.1442920000000072,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["compat base URLs / version"],"fullName":"compat base URLs / version ANTHROPIC_API_VERSION","status":"passed","title":"ANTHROPIC_API_VERSION","duration":0.07337499999999864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["default token limits"],"fullName":"default token limits max/min","status":"passed","title":"max/min","duration":0.14066599999999596,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["provider baseUrl const (full path, no trailing slash)"],"fullName":"provider baseUrl const (full path, no trailing slash) mimo-free full path","status":"passed","title":"mimo-free full path","duration":0.06575000000000841,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["provider baseUrl const (full path, no trailing slash)"],"fullName":"provider baseUrl const (full path, no trailing slash) opencode no trailing slash","status":"passed","title":"opencode no trailing slash","duration":0.055292000000008557,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity retry (intentional change: 429=6, 503=3)"],"fullName":"antigravity retry (intentional change: 429=6, 503=3) 429 attempts = 6","status":"passed","title":"429 attempts = 6","duration":0.10516700000000867,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["antigravity retry (intentional change: 429=6, 503=3)"],"fullName":"antigravity retry (intentional change: 429=6, 503=3) 503 attempts = 3","status":"passed","title":"503 attempts = 3","duration":0.13358300000000156,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497746002,"endTime":1781497746004.1406,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/executor-const-guard.test.js"},{"assertionResults":[{"ancestorTitles":["toOpenAIFinish - gemini"],"fullName":"toOpenAIFinish - gemini SAFETY -> content_filter","status":"passed","title":"SAFETY -> content_filter","duration":1.2787079999999946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - gemini"],"fullName":"toOpenAIFinish - gemini RECITATION -> content_filter","status":"passed","title":"RECITATION -> content_filter","duration":0.4854169999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - gemini"],"fullName":"toOpenAIFinish - gemini BLOCKLIST -> content_filter","status":"passed","title":"BLOCKLIST -> content_filter","duration":0.2061250000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - gemini"],"fullName":"toOpenAIFinish - gemini PROHIBITED_CONTENT -> content_filter","status":"passed","title":"PROHIBITED_CONTENT -> content_filter","duration":0.23712499999999181,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - gemini"],"fullName":"toOpenAIFinish - gemini OTHER -> stop","status":"passed","title":"OTHER -> stop","duration":0.1419999999999959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - gemini"],"fullName":"toOpenAIFinish - gemini UNKNOWN_XYZ -> stop","status":"passed","title":"UNKNOWN_XYZ -> stop","duration":0.4248750000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - gemini"],"fullName":"toOpenAIFinish - gemini STOP -> stop","status":"passed","title":"STOP -> stop","duration":0.22325000000000728,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - gemini"],"fullName":"toOpenAIFinish - gemini MAX_TOKENS -> length","status":"passed","title":"MAX_TOKENS -> length","duration":0.11591599999999858,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - ollama"],"fullName":"toOpenAIFinish - ollama length -> length","status":"passed","title":"length -> length","duration":1.2664159999999924,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - ollama"],"fullName":"toOpenAIFinish - ollama max_tokens -> length","status":"passed","title":"max_tokens -> length","duration":0.13979100000000244,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - ollama"],"fullName":"toOpenAIFinish - ollama tool_calls -> tool_calls","status":"passed","title":"tool_calls -> tool_calls","duration":0.10454099999999755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - ollama"],"fullName":"toOpenAIFinish - ollama unknown_xyz -> stop","status":"passed","title":"unknown_xyz -> stop","duration":0.03537500000000193,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - kiro"],"fullName":"toOpenAIFinish - kiro tool_use -> tool_calls","status":"passed","title":"tool_use -> tool_calls","duration":0.03912499999999852,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - claude"],"fullName":"toOpenAIFinish - claude end_turn -> stop","status":"passed","title":"end_turn -> stop","duration":0.04241700000000037,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - claude"],"fullName":"toOpenAIFinish - claude max_tokens -> length","status":"passed","title":"max_tokens -> length","duration":0.025166999999996165,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - claude"],"fullName":"toOpenAIFinish - claude tool_use -> tool_calls","status":"passed","title":"tool_use -> tool_calls","duration":0.024791000000007557,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - commandcode"],"fullName":"toOpenAIFinish - commandcode tool-calls -> tool_calls","status":"passed","title":"tool-calls -> tool_calls","duration":0.03233300000000838,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIFinish - commandcode"],"fullName":"toOpenAIFinish - commandcode unknown passthrough","status":"passed","title":"unknown passthrough","duration":0.028417000000004577,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fromOpenAIFinish round-trip - claude"],"fullName":"fromOpenAIFinish round-trip - claude tool_calls -> tool_use","status":"passed","title":"tool_calls -> tool_use","duration":0.04604100000000244,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fromOpenAIFinish round-trip - claude"],"fullName":"fromOpenAIFinish round-trip - claude length -> max_tokens","status":"passed","title":"length -> max_tokens","duration":0.03079200000000526,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enum literals (catch drift)"],"fullName":"enum literals (catch drift) OPENAI_FINISH literals","status":"passed","title":"OPENAI_FINISH literals","duration":0.056167000000002076,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enum literals (catch drift)"],"fullName":"enum literals (catch drift) CLAUDE_STOP literals","status":"passed","title":"CLAUDE_STOP literals","duration":0.04520800000000236,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enum literals (catch drift)"],"fullName":"enum literals (catch drift) GEMINI_FINISH literals","status":"passed","title":"GEMINI_FINISH literals","duration":0.04379200000001049,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745905,"endTime":1781497745911.0562,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/finish-reason-concern.test.js"},{"assertionResults":[{"ancestorTitles":["forceStream provider config"],"fullName":"forceStream provider config only openai/codex/commandcode force streaming","status":"passed","title":"only openai/codex/commandcode force streaming","duration":42.50274999999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743892,"endTime":1781497743934.5027,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/force-stream-config.test.js"},{"assertionResults":[{"ancestorTitles":["Gemini CLI usage project id resolution"],"fullName":"Gemini CLI usage project id resolution uses the projectId stored on the provider connection","status":"passed","title":"uses the projectId stored on the provider connection","duration":13.00954200000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Gemini CLI usage project id resolution"],"fullName":"Gemini CLI usage project id resolution normalizes project objects returned by loadCodeAssist","status":"passed","title":"normalizes project objects returned by loadCodeAssist","duration":0.6141660000000115,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Gemini CLI usage project id resolution"],"fullName":"Gemini CLI usage project id resolution returns actionable guidance when no project id is available","status":"passed","title":"returns actionable guidance when no project id is available","duration":0.3138749999999959,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744920,"endTime":1781497744934.314,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/gemini-usage-projectid.test.js"},{"assertionResults":[{"ancestorTitles":["GithubExecutor.supportsResponsesEndpoint"],"fullName":"GithubExecutor.supportsResponsesEndpoint excludes Gemini models from the /responses endpoint","status":"passed","title":"excludes Gemini models from the /responses endpoint","duration":0.8396669999999915,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GithubExecutor.supportsResponsesEndpoint"],"fullName":"GithubExecutor.supportsResponsesEndpoint excludes Claude models from the /responses endpoint","status":"passed","title":"excludes Claude models from the /responses endpoint","duration":0.17354199999999764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GithubExecutor.supportsResponsesEndpoint"],"fullName":"GithubExecutor.supportsResponsesEndpoint allows OpenAI/codex models on the /responses endpoint","status":"passed","title":"allows OpenAI/codex models on the /responses endpoint","duration":0.09620799999999008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GithubExecutor.supportsResponsesEndpoint"],"fullName":"GithubExecutor.supportsResponsesEndpoint is null-safe","status":"passed","title":"is null-safe","duration":0.15174999999999272,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["GithubExecutor.execute cached-route guard (#1062)"],"fullName":"GithubExecutor.execute cached-route guard (#1062) does NOT use /responses for a Gemini model even if it was wrongly cached as codex","status":"passed","title":"does NOT use /responses for a Gemini model even if it was wrongly cached as codex","duration":1.5804589999999905,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745886,"endTime":1781497745888.5806,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/github-responses-routing.test.js"},{"assertionResults":[{"ancestorTitles":["HuggingFace model alias parsing"],"fullName":"HuggingFace model alias parsing resolves hf alias to huggingface provider","status":"passed","title":"resolves hf alias to huggingface provider","duration":1.1622500000000002,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497746176,"endTime":1781497746177.1624,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/hf-model-routing.test.js"},{"assertionResults":[{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore validates required prompt field","status":"passed","title":"validates required prompt field","duration":8.38349999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore rejects unsupported provider","status":"passed","title":"rejects unsupported provider","duration":0.46091599999999744,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with OpenAI format","status":"passed","title":"generates image with OpenAI format","duration":2.4228749999999764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with Gemini format","status":"passed","title":"generates image with Gemini format","duration":0.6623339999999871,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with Minimax format","status":"passed","title":"generates image with Minimax format","duration":0.41925000000003365,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with NanoBanana format","status":"passed","title":"generates image with NanoBanana format","duration":1.9841670000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with SD WebUI format","status":"passed","title":"generates image with SD WebUI format","duration":0.7345000000000255,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles OpenRouter with HTTP-Referer header","status":"passed","title":"handles OpenRouter with HTTP-Referer header","duration":0.29062499999997726,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles Vercel AI Gateway image generation as OpenAI-compatible","status":"passed","title":"handles Vercel AI Gateway image generation as OpenAI-compatible","duration":0.6404589999999644,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles HuggingFace binary response","status":"passed","title":"handles HuggingFace binary response","duration":0.5862079999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with Codex gpt-5.5-image using current Codex version header","status":"passed","title":"generates image with Codex gpt-5.5-image using current Codex version header","duration":1.032083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore generates image with Cloudflare Workers AI JSON response","status":"passed","title":"generates image with Cloudflare Workers AI JSON response","duration":0.6118330000000469,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore uses multipart form data for Cloudflare FLUX.2 models","status":"passed","title":"uses multipart form data for Cloudflare FLUX.2 models","duration":0.6202920000000063,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore resolves Cloudflare img2img and inpainting URL inputs before sending","status":"passed","title":"resolves Cloudflare img2img and inpainting URL inputs before sending","duration":0.481207999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles provider error responses","status":"passed","title":"handles provider error responses","duration":0.23895799999996825,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore handles network errors","status":"passed","title":"handles network errors","duration":0.2672089999999798,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["handleImageGenerationCore"],"fullName":"handleImageGenerationCore calls onRequestSuccess callback on success","status":"passed","title":"calls onRequestSuccess callback on success","duration":0.16416700000002038,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744710,"endTime":1781497744730.164,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/image-generation.test.js"},{"assertionResults":[{"ancestorTitles":["Kiro MITM model slots"],"fullName":"Kiro MITM model slots exposes the kiro mitm tool","status":"passed","title":"exposes the kiro mitm tool","duration":0.864042000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro MITM model slots"],"fullName":"Kiro MITM model slots offers a mappable slot for the agent default model id 'auto'","status":"failed","title":"offers a mappable slot for the agent default model id 'auto'","duration":2.297208000000012,"failureMessages":["AssertionError: expected undefined to be truthy\n at /Users/Working/router4/app/tests/unit/kiro-model-slots.test.js:21:18\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["Kiro MITM model slots"],"fullName":"Kiro MITM model slots offers a mappable slot for the background sub-task model id 'simple-task'","status":"passed","title":"offers a mappable slot for the background sub-task model id 'simple-task'","duration":0.12016699999998082,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743076,"endTime":1781497743079.297,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/kiro-model-slots.test.js"},{"assertionResults":[{"ancestorTitles":["generateSessionId"],"fullName":"generateSessionId uses the ses_ prefix and a 24-char random suffix","status":"passed","title":"uses the ses_ prefix and a 24-char random suffix","duration":0.9622920000000192,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateSessionId"],"fullName":"generateSessionId only emits lowercase alphanumeric characters in the suffix","status":"passed","title":"only emits lowercase alphanumeric characters in the suffix","duration":0.19820799999999394,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateSessionId"],"fullName":"generateSessionId produces a fresh id on each call","status":"passed","title":"produces a fresh id on each call","duration":0.3919159999999806,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateFingerprint"],"fullName":"generateFingerprint returns a 64-char hex sha256 digest","status":"passed","title":"returns a 64-char hex sha256 digest","duration":2.2920830000000194,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateFingerprint"],"fullName":"generateFingerprint is stable per machine (deterministic across calls)","status":"passed","title":"is stable per machine (deterministic across calls)","duration":0.24879199999998036,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseJwtExp"],"fullName":"parseJwtExp derives the expiry from the JWT exp claim (ms)","status":"passed","title":"derives the expiry from the JWT exp claim (ms)","duration":0.14549999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseJwtExp"],"fullName":"parseJwtExp falls back to a future timestamp when the JWT is unparseable","status":"passed","title":"falls back to a future timestamp when the JWT is unparseable","duration":0.17650000000003274,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker prepends a system message with the marker when none is present","status":"passed","title":"prepends a system message with the marker when none is present","duration":0.18308300000001054,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker preserves the original user message after injection","status":"passed","title":"preserves the original user message after injection","duration":0.3290420000000154,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker keeps a caller-provided system prompt alongside the marker","status":"passed","title":"keeps a caller-provided system prompt alongside the marker","duration":0.10591700000003357,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker does not duplicate the marker when already present","status":"passed","title":"does not duplicate the marker when already present","duration":0.1289160000000038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectSystemMarker"],"fullName":"injectSystemMarker leaves a body without a messages array untouched","status":"passed","title":"leaves a body without a messages array untouched","duration":0.04612499999996089,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt returns the jwt from the bootstrap response","status":"passed","title":"returns the jwt from the bootstrap response","duration":0.3078750000000241,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt sends the machine fingerprint as the bootstrap client","status":"passed","title":"sends the machine fingerprint as the bootstrap client","duration":0.18720799999999826,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt caches the jwt and does not re-fetch while still valid","status":"passed","title":"caches the jwt and does not re-fetch while still valid","duration":0.09991700000000492,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt re-fetches once the cached jwt is within the expiry buffer","status":"passed","title":"re-fetches once the cached jwt is within the expiry buffer","duration":0.22233300000004874,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt throws when the bootstrap response is not ok","status":"passed","title":"throws when the bootstrap response is not ok","duration":0.6356250000000045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["bootstrapJwt"],"fullName":"bootstrapJwt throws when the bootstrap response has no jwt","status":"passed","title":"throws when the bootstrap response has no jwt","duration":0.13337499999994407,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor buildUrl returns the free-ai chat endpoint","status":"passed","title":"buildUrl returns the free-ai chat endpoint","duration":0.04995800000000372,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor buildHeaders includes the MiMo source and session affinity","status":"passed","title":"buildHeaders includes the MiMo source and session affinity","duration":0.08025000000003502,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor transformRequest injects the system marker","status":"passed","title":"transformRequest injects the system marker","duration":0.04804100000001199,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor execute injects the marker and sends a Bearer JWT to the chat endpoint","status":"passed","title":"execute injects the marker and sends a Bearer JWT to the chat endpoint","duration":0.325999999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MimoFreeExecutor"],"fullName":"MimoFreeExecutor re-bootstraps and retries once on a 403 from the chat endpoint","status":"passed","title":"re-bootstraps and retries once on a 403 from the chat endpoint","duration":0.21041600000000926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration registers a specialized executor for mimo-free and the mmf alias","status":"passed","title":"registers a specialized executor for mimo-free and the mmf alias","duration":0.3289169999999899,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration registers mimo-free as a no-auth provider in open-sse config","status":"passed","title":"registers mimo-free as a no-auth provider in open-sse config","duration":0.08704099999999926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration exposes only mimo-auto (the sole free-channel model)","status":"passed","title":"exposes only mimo-auto (the sole free-channel model)","duration":0.19325000000003456,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration maps the mimo-free alias to mmf","status":"passed","title":"maps the mimo-free alias to mmf","duration":0.04566599999998289,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiMo Free provider registration"],"fullName":"MiMo Free provider registration lists mimo-free in the dashboard FREE_PROVIDERS catalog","status":"passed","title":"lists mimo-free in the dashboard FREE_PROVIDERS catalog","duration":0.052666999999985364,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745368,"endTime":1781497745376.3289,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/mimo-free.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax TTS"],"fullName":"MiniMax TTS sends MiniMax T2A payload and converts hex audio to base64 JSON","status":"passed","title":"sends MiniMax T2A payload and converts hex audio to base64 JSON","duration":17.394040999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax TTS"],"fullName":"MiniMax TTS uses the default MiniMax voice when no voice is provided","status":"passed","title":"uses the default MiniMax voice when no voice is provided","duration":0.5503329999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax TTS"],"fullName":"MiniMax TTS surfaces MiniMax base_resp errors","status":"passed","title":"surfaces MiniMax base_resp errors","duration":0.6269580000000019,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744790,"endTime":1781497744808.627,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/minimax-tts.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage parses token-plan TTS quota counts as used counts","status":"passed","title":"parses token-plan TTS quota counts as used counts","duration":14.350333000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage parses coding-plan TTS quota counts as remaining counts","status":"passed","title":"parses coding-plan TTS quota counts as remaining counts","duration":0.6160000000000139,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage keeps non-TTS MiniMax quota rows instead of filtering to text only","status":"passed","title":"keeps non-TTS MiniMax quota rows instead of filtering to text only","duration":0.4076249999999959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage includes M-series percent-only buckets that have no count totals","status":"passed","title":"includes M-series percent-only buckets that have no count totals","duration":0.40441599999999767,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage normalizes M-series percent-only buckets on the coding_plan (countMeansRemaining) endpoint too","status":"passed","title":"normalizes M-series percent-only buckets on the coding_plan (countMeansRemaining) endpoint too","duration":0.9839999999999804,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage renders the M3-era MiniMax-M* wildcard as a friendly series label","status":"passed","title":"renders the M3-era MiniMax-M* wildcard as a friendly series label","duration":0.9829169999999863,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax usage"],"fullName":"MiniMax usage prefers the upstream-provided remaining percent when counts are also present","status":"passed","title":"prefers the upstream-provided remaining percent when counts are also present","duration":0.5825419999999895,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744762,"endTime":1781497744780.5825,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/minimax-usage.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax voices API"],"fullName":"MiniMax voices API fetches global MiniMax voices with stored API key","status":"passed","title":"fetches global MiniMax voices with stored API key","duration":12.843082999999979,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax voices API"],"fullName":"MiniMax voices API fetches China MiniMax voices when provider=minimax-cn","status":"passed","title":"fetches China MiniMax voices when provider=minimax-cn","duration":1.9652910000000077,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744876,"endTime":1781497744891.9653,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/minimax-voices.test.js"},{"assertionResults":[{"ancestorTitles":["model name regex fallback (C2)"],"fullName":"model name regex fallback (C2) derives display name from id per family","status":"passed","title":"derives display name from id per family","duration":1.8797079999999937,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model name regex fallback (C2)"],"fullName":"model name regex fallback (C2) falls back to id verbatim when no pattern matches","status":"passed","title":"falls back to id verbatim when no pattern matches","duration":0.4510840000000087,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model name regex fallback (C2)"],"fullName":"model name regex fallback (C2) normalizeModel: explicit name always wins over regex","status":"passed","title":"normalizeModel: explicit name always wins over regex","duration":0.20204199999999162,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model name regex fallback (C2)"],"fullName":"model name regex fallback (C2) normalizeModel: terse string id becomes object with derived name","status":"passed","title":"normalizeModel: terse string id becomes object with derived name","duration":0.31629200000000424,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497746003,"endTime":1781497746005.4512,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/model-name-regex.test.js"},{"assertionResults":[{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing routes image model tests to /api/v1/images/generations","status":"passed","title":"routes image model tests to /api/v1/images/generations","duration":86.22166600000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing routes embedding model tests to /api/v1/embeddings","status":"passed","title":"routes embedding model tests to /api/v1/embeddings","duration":1.0232910000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing fails embedding model tests when provider returns no embedding data","status":"passed","title":"fails embedding model tests when provider returns no embedding data","duration":1.2170830000000024,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing routes stt model tests to /api/v1/audio/transcriptions","status":"passed","title":"routes stt model tests to /api/v1/audio/transcriptions","duration":1.2928339999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["model test route kind routing"],"fullName":"model test route kind routing returns formatted HTTP errors for non-2xx embedding responses","status":"passed","title":"returns formatted HTTP errors for non-2xx embedding responses","duration":0.4940830000000176,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744052,"endTime":1781497744142.4941,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/model-test-routing.test.js"},{"assertionResults":[{"ancestorTitles":["openai→claude: image_url.detail is dropped (docs 11 §4)"],"fullName":"openai→claude: image_url.detail is dropped (docs 11 §4) converts image to base64 source WITHOUT a detail field","status":"passed","title":"converts image to base64 source WITHOUT a detail field","duration":2.639125000000007,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openai→claude: image_url.detail is dropped (docs 11 §4)"],"fullName":"openai→claude: image_url.detail is dropped (docs 11 §4) drops input_audio entirely (claude has no audio block)","status":"passed","title":"drops input_audio entirely (claude has no audio block)","duration":0.1993750000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openai→gemini: input_audio is mapped to inlineData (docs 11 §4)"],"fullName":"openai→gemini: input_audio is mapped to inlineData (docs 11 §4) maps wav → audio/wav inlineData","status":"passed","title":"maps wav → audio/wav inlineData","duration":0.17320899999998574,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openai→gemini: input_audio is mapped to inlineData (docs 11 §4)"],"fullName":"openai→gemini: input_audio is mapped to inlineData (docs 11 §4) maps mp3 → audio/mpeg inlineData","status":"passed","title":"maps mp3 → audio/mpeg inlineData","duration":0.06791699999999423,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openai→gemini: input_audio is mapped to inlineData (docs 11 §4)"],"fullName":"openai→gemini: input_audio is mapped to inlineData (docs 11 §4) drops image_url.detail (not carried into inlineData)","status":"passed","title":"drops image_url.detail (not carried into inlineData)","duration":0.19095799999999485,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497746080,"endTime":1781497746083.1995,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/multimodal-drop-lock.test.js"},{"assertionResults":[{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import returns not-found when no macOS cursor db paths are accessible","status":"failed","title":"returns not-found when no macOS cursor db paths are accessible","duration":33.883540999999994,"failureMessages":["AssertionError: expected 'Cursor database not found. Checked lo…' to contain 'Cursor database not found in known ma…'\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:74:33\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import returns descriptive error if macOS db file exists but cannot be opened","status":"failed","title":"returns descriptive error if macOS db file exists but cannot be opened","duration":50.96504200000001,"failureMessages":["AssertionError: the given combination of arguments (undefined and string) is invalid for this assertion. You can use an array, a map, an object, a set, a string, or a weakset instead of a string\n at Proxy. (file:///Users/Working/router4/app/tests/node_modules/@vitest/expect/dist/index.js:1319:15)\n at Proxy. (file:///Users/Working/router4/app/tests/node_modules/@vitest/expect/dist/index.js:1156:15)\n at Proxy.methodWrapper (file:///Users/Working/router4/app/tests/node_modules/chai/index.js:1700:25)\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:84:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import extracts tokens using exact keys","status":"failed","title":"extracts tokens using exact keys","duration":28.586375000000004,"failureMessages":["AssertionError: expected false to be true // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:101:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import unwraps JSON-encoded string values","status":"failed","title":"unwraps JSON-encoded string values","duration":27.546208999999976,"failureMessages":["AssertionError: expected false to be true // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:118:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import falls back to fuzzy key matching on macOS when exact keys are missing","status":"failed","title":"falls back to fuzzy key matching on macOS when exact keys are missing","duration":27.33104099999997,"failureMessages":["AssertionError: expected false to be true // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:142:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import returns login-prompt error when tokens are missing even after fallback","status":"failed","title":"returns login-prompt error when tokens are missing even after fallback","duration":27.147790999999984,"failureMessages":["AssertionError: the given combination of arguments (undefined and string) is invalid for this assertion. You can use an array, a map, an object, a set, a string, or a weakset instead of a string\n at Proxy. (file:///Users/Working/router4/app/tests/node_modules/@vitest/expect/dist/index.js:1319:15)\n at Proxy. (file:///Users/Working/router4/app/tests/node_modules/@vitest/expect/dist/index.js:1156:15)\n at Proxy.methodWrapper (file:///Users/Working/router4/app/tests/node_modules/chai/index.js:1700:25)\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:156:33\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import linux uses single hardcoded path and original error message","status":"failed","title":"linux uses single hardcoded path and original error message","duration":1.2962089999999762,"failureMessages":["AssertionError: expected 'Cursor database not found. Checked lo…' to be 'Cursor database not found. Make sure …' // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:169:33\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["GET /api/oauth/cursor/auto-import"],"fullName":"GET /api/oauth/cursor/auto-import unsupported platform returns 400","status":"failed","title":"unsupported platform returns 400","duration":0.377125000000035,"failureMessages":["AssertionError: expected 200 to be 400 // Object.is equality\n at /Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js:181:29\n at processTicksAndRejections (node:internal/process/task_queues:105:5)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]}],"startTime":1781497743057,"endTime":1781497743254.3772,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/oauth-cursor-auto-import.test.js"},{"assertionResults":[{"ancestorTitles":["OpenAI Responses streaming termination"],"fullName":"OpenAI Responses streaming termination emits a response.failed event when a Responses stream closes before a terminal event","status":"passed","title":"emits a response.failed event when a Responses stream closes before a terminal event","duration":19.862791999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI Responses streaming termination"],"fullName":"OpenAI Responses streaming termination does not add response.failed when a Responses stream already completed","status":"passed","title":"does not add response.failed when a Responses stream already completed","duration":0.934584000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI Responses streaming termination"],"fullName":"OpenAI Responses streaming termination emits response.failed before DONE when a Responses stream sends DONE without a terminal event","status":"passed","title":"emits response.failed before DONE when a Responses stream sends DONE without a terminal event","duration":1.092250000000007,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744563,"endTime":1781497744585.0923,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-responses-terminal-event.test.js"},{"assertionResults":[{"ancestorTitles":["openaiToClaudeResponse tool argument sanitization"],"fullName":"openaiToClaudeResponse tool argument sanitization drops invalid Read pages and clamps numeric bounds","status":"passed","title":"drops invalid Read pages and clamps numeric bounds","duration":1.4875830000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeResponse tool argument sanitization"],"fullName":"openaiToClaudeResponse tool argument sanitization keeps valid PDF pages","status":"passed","title":"keeps valid PDF pages","duration":0.23954200000000014,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497746278,"endTime":1781497746280.2395,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-to-claude-response-tools.test.js"},{"assertionResults":[{"ancestorTitles":["openaiToClaudeRequest","response_format handling"],"fullName":"openaiToClaudeRequest response_format handling should inject JSON schema instructions for json_schema type","status":"passed","title":"should inject JSON schema instructions for json_schema type","duration":1.4599580000000287,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","response_format handling"],"fullName":"openaiToClaudeRequest response_format handling should inject basic JSON instructions for json_object type","status":"passed","title":"should inject basic JSON instructions for json_object type","duration":0.2166659999999183,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","response_format handling"],"fullName":"openaiToClaudeRequest response_format handling should not modify system prompt when response_format is missing","status":"passed","title":"should not modify system prompt when response_format is missing","duration":0.1279580000000351,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","response_format handling"],"fullName":"openaiToClaudeRequest response_format handling should preserve existing system messages when adding response_format","status":"passed","title":"should preserve existing system messages when adding response_format","duration":0.10179199999993216,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling converts OpenAI forced tool ({type:'function'}) to Claude {type:'tool'}","status":"passed","title":"converts OpenAI forced tool ({type:'function'}) to Claude {type:'tool'}","duration":0.42679199999997763,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling maps string tool_choice values","status":"passed","title":"maps string tool_choice values","duration":0.30891699999995126,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling passes through Claude-native tool_choice objects unchanged","status":"passed","title":"passes through Claude-native tool_choice objects unchanged","duration":0.3030420000000049,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling never leaks an invalid type (falls back to auto)","status":"passed","title":"never leaks an invalid type (falls back to auto)","duration":0.09233300000005329,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeRequest","tool_choice handling"],"fullName":"openaiToClaudeRequest tool_choice handling omits tool_choice entirely when the request has none","status":"passed","title":"omits tool_choice entirely when the request has none","duration":0.5153749999999491,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToClaudeResponse"],"fullName":"openaiToClaudeResponse omits empty Read pages tool argument before emitting Claude input deltas","status":"failed","title":"omits empty Read pages tool argument before emitting Claude input deltas","duration":3.391124999999988,"failureMessages":["AssertionError: expected undefined to be defined\n at /Users/Working/router4/app/tests/unit/openai-to-claude.test.js:199:24\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]}],"startTime":1781497743659,"endTime":1781497743666.391,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-to-claude.test.js"},{"assertionResults":[{"ancestorTitles":["openaiToCommandCodeRequest — basic envelope"],"fullName":"openaiToCommandCodeRequest — basic envelope returns the expected top-level envelope shape","status":"passed","title":"returns the expected top-level envelope shape","duration":2.0773749999999893,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — system handling"],"fullName":"openaiToCommandCodeRequest — system handling hoists system messages to params.system (string), not messages[]","status":"passed","title":"hoists system messages to params.system (string), not messages[]","duration":1.32216600000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — system handling"],"fullName":"openaiToCommandCodeRequest — system handling joins multiple system messages with blank line","status":"passed","title":"joins multiple system messages with blank line","duration":0.19858399999998255,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — system handling"],"fullName":"openaiToCommandCodeRequest — system handling omits params.system when no system messages","status":"passed","title":"omits params.system when no system messages","duration":0.0870830000000069,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — content shape"],"fullName":"openaiToCommandCodeRequest — content shape MUST always emit content as Array (never string) for user","status":"passed","title":"MUST always emit content as Array (never string) for user","duration":0.3846249999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — content shape"],"fullName":"openaiToCommandCodeRequest — content shape MUST always emit content as Array for assistant","status":"passed","title":"MUST always emit content as Array for assistant","duration":0.11170799999999304,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — tool role / tool-result (AI SDK)"],"fullName":"openaiToCommandCodeRequest — tool role / tool-result (AI SDK) converts role:\"tool\" to role:\"tool\" with tool-result block; output is {type:\"text\",value}","status":"passed","title":"converts role:\"tool\" to role:\"tool\" with tool-result block; output is {type:\"text\",value}","duration":0.20983300000000327,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — assistant tool_calls / tool-call"],"fullName":"openaiToCommandCodeRequest — assistant tool_calls / tool-call converts assistant.tool_calls[] into content blocks of type tool-call","status":"passed","title":"converts assistant.tool_calls[] into content blocks of type tool-call","duration":0.13212500000000205,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — tools schema conversion"],"fullName":"openaiToCommandCodeRequest — tools schema conversion converts OpenAI {type:\"function\", function:{...}} to Anthropic plain {name, input_schema}","status":"passed","title":"converts OpenAI {type:\"function\", function:{...}} to Anthropic plain {name, input_schema}","duration":1.4018329999999821,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — tools schema conversion"],"fullName":"openaiToCommandCodeRequest — tools schema conversion preserves description on converted tool","status":"passed","title":"preserves description on converted tool","duration":0.14637500000000614,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToCommandCodeRequest — tools schema conversion"],"fullName":"openaiToCommandCodeRequest — tools schema conversion does not include tools field when input has none","status":"passed","title":"does not include tools field when input has none","duration":0.13049999999998363,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745495,"endTime":1781497745502.1306,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-to-commandcode.test.js"},{"assertionResults":[{"ancestorTitles":["openaiToKiroRequest","basic message conversion"],"fullName":"openaiToKiroRequest basic message conversion should convert a simple text message","status":"passed","title":"should convert a simple text message","duration":2.3461250000000007,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","basic message conversion"],"fullName":"openaiToKiroRequest basic message conversion should not include images field when no images are present","status":"passed","title":"should not include images field when no images are present","duration":0.13599999999999568,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should forward base64 image from image_url content part","status":"passed","title":"should forward base64 image from image_url content part","duration":0.5616250000000207,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should forward multiple base64 images","status":"passed","title":"should forward multiple base64 images","duration":0.38591699999997786,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should not include images field when images array is empty","status":"passed","title":"should not include images field when images array is empty","duration":0.26304199999998445,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should include both images and text content together","status":"passed","title":"should include both images and text content together","duration":0.1582080000000019,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","image forwarding"],"fullName":"openaiToKiroRequest image forwarding should treat http image URLs as text fallback (Kiro only supports base64)","status":"passed","title":"should treat http image URLs as text fallback (Kiro only supports base64)","duration":0.09537499999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","tool interaction without client-provided tools"],"fullName":"openaiToKiroRequest tool interaction without client-provided tools should flatten OpenAI tool_calls + tool result into history text with no tools array","status":"passed","title":"should flatten OpenAI tool_calls + tool result into history text with no tools array","duration":0.28016700000000583,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","tool interaction without client-provided tools"],"fullName":"openaiToKiroRequest tool interaction without client-provided tools should flatten Claude tool_use / tool_result blocks with no tools array","status":"passed","title":"should flatten Claude tool_use / tool_result blocks with no tools array","duration":0.5252499999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","tool interaction without client-provided tools"],"fullName":"openaiToKiroRequest tool interaction without client-provided tools should keep structured tools when the client DOES provide a tools array","status":"passed","title":"should keep structured tools when the client DOES provide a tools array","duration":0.2982910000000061,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToKiroRequest","tool interaction without client-provided tools"],"fullName":"openaiToKiroRequest tool interaction without client-provided tools should salvage orphaned tool_result content as text instead of discarding it","status":"passed","title":"should salvage orphaned tool_result content as text instead of discarding it","duration":0.18912499999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745406,"endTime":1781497745412.2983,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-to-kiro.test.js"},{"assertionResults":[{"ancestorTitles":["openaiToOllamaRequest - tool_calls arguments parsing"],"fullName":"openaiToOllamaRequest - tool_calls arguments parsing malformed JSON args -> {} (no throw)","status":"passed","title":"malformed JSON args -> {} (no throw)","duration":1.4967499999999916,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["openaiToOllamaRequest - tool_calls arguments parsing"],"fullName":"openaiToOllamaRequest - tool_calls arguments parsing valid JSON args -> parsed object","status":"passed","title":"valid JSON args -> parsed object","duration":0.1962919999999997,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497746200,"endTime":1781497746201.4968,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/openai-to-ollama-malformed.test.js"},{"assertionResults":[{"ancestorTitles":["parseOpenAIMessages"],"fullName":"parseOpenAIMessages extracts system + history + current msg","status":"passed","title":"extracts system + history + current msg","duration":1.162333000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseOpenAIMessages"],"fullName":"parseOpenAIMessages treats developer role as system","status":"passed","title":"treats developer role as system","duration":0.1822909999999922,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseOpenAIMessages"],"fullName":"parseOpenAIMessages handles multi-part content (array of text blocks)","status":"passed","title":"handles multi-part content (array of text blocks)","duration":0.0841660000000104,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseOpenAIMessages"],"fullName":"parseOpenAIMessages skips empty content messages","status":"passed","title":"skips empty content messages","duration":0.11504199999998832,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery first turn: returns JSON with instructions + query","status":"passed","title":"first turn: returns JSON with instructions + query","duration":0.8047909999999945,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery follow-up (with backendUuid): returns plain currentMsg, no JSON","status":"passed","title":"follow-up (with backendUuid): returns plain currentMsg, no JSON","duration":0.12966699999998355,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery includes history when present on first turn","status":"passed","title":"includes history when present on first turn","duration":0.15929099999999607,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery injects tools into instructions on first turn","status":"passed","title":"injects tools into instructions on first turn","duration":0.13095899999999006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery ignores tools on follow-up turn (uses session)","status":"passed","title":"ignores tools on follow-up turn (uses session)","duration":0.41712499999999864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildQuery"],"fullName":"buildQuery truncates query if JSON exceeds 96000 chars","status":"passed","title":"truncates query if JSON exceeds 96000 chars","duration":0.3595409999999788,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatToolsHint"],"fullName":"formatToolsHint returns empty string for no tools","status":"passed","title":"returns empty string for no tools","duration":0.11683400000001143,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatToolsHint"],"fullName":"formatToolsHint handles OpenAI tool schema (function wrapper)","status":"passed","title":"handles OpenAI tool schema (function wrapper)","duration":0.05275000000000318,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatToolsHint"],"fullName":"formatToolsHint handles flat tool schema","status":"passed","title":"handles flat tool schema","duration":0.039083000000005086,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatToolsHint"],"fullName":"formatToolsHint truncates long descriptions to first line, max 200 chars","status":"passed","title":"truncates long descriptions to first line, max 200 chars","duration":0.08199999999999363,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildPplxRequestBody"],"fullName":"buildPplxRequestBody sets query_str at both top-level AND params (required by upstream API)","status":"passed","title":"sets query_str at both top-level AND params (required by upstream API)","duration":13.767791999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildPplxRequestBody"],"fullName":"buildPplxRequestBody includes required params","status":"passed","title":"includes required params","duration":0.32579200000000696,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute maps pplx-auto → mode=concise, pref=pplx_pro","status":"passed","title":"maps pplx-auto → mode=concise, pref=pplx_pro","duration":19.535083000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute applies THINKING_MAP when reasoning_effort is set","status":"passed","title":"applies THINKING_MAP when reasoning_effort is set","duration":0.7529169999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute sends Cookie header when credentials.apiKey provided","status":"passed","title":"sends Cookie header when credentials.apiKey provided","duration":0.38212500000000205,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute sends Bearer header when credentials.accessToken provided","status":"passed","title":"sends Bearer header when credentials.accessToken provided","duration":0.3620420000000024,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute injects body.tools into query_str instructions","status":"passed","title":"injects body.tools into query_str instructions","duration":0.9731660000000204,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute returns 400 on missing messages","status":"passed","title":"returns 400 on missing messages","duration":0.21041699999997832,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute surfaces upstream 401 with friendly auth message","status":"passed","title":"surfaces upstream 401 with friendly auth message","duration":0.6443749999999966,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PerplexityWebExecutor.execute"],"fullName":"PerplexityWebExecutor.execute surfaces 429 with rate-limit message","status":"passed","title":"surfaces 429 with rate-limit message","duration":0.4858330000000137,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744370,"endTime":1781497744411.6443,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/perplexity-web.test.js"},{"assertionResults":[{"ancestorTitles":["provider display split (E1)"],"fullName":"provider display split (E1) AI_PROVIDERS entries still carry merged display + transport","status":"passed","title":"AI_PROVIDERS entries still carry merged display + transport","duration":71.32433400000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["provider display split (E1)"],"fullName":"provider display split (E1) display fields source from providersDisplay.js","status":"passed","title":"display fields source from providersDisplay.js","duration":4.863624999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["provider display split (E1)"],"fullName":"provider display split (E1) helpers still work after split","status":"passed","title":"helpers still work after split","duration":0.24837499999998158,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743790,"endTime":1781497743866.2483,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-display-split.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax-M3 model registration"],"fullName":"MiniMax-M3 model registration includes MiniMax-M3 in PROVIDER_MODELS.minimax","status":"passed","title":"includes MiniMax-M3 in PROVIDER_MODELS.minimax","duration":1.2301250000000152,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 model registration"],"fullName":"MiniMax-M3 model registration includes MiniMax-M3 in PROVIDER_MODELS['minimax-cn']","status":"passed","title":"includes MiniMax-M3 in PROVIDER_MODELS['minimax-cn']","duration":0.25508400000001075,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 model registration"],"fullName":"MiniMax-M3 model registration exposes MiniMax-M3 through getModelsByProviderId for both provider IDs","status":"passed","title":"exposes MiniMax-M3 through getModelsByProviderId for both provider IDs","duration":0.46041700000000674,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 model registration"],"fullName":"MiniMax-M3 model registration does not regress the existing M2.7 / M2.5 / M2.1 entries","status":"passed","title":"does not regress the existing M2.7 / M2.5 / M2.1 entries","duration":0.9021669999999915,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745632,"endTime":1781497745634.902,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-models-minimax-m3.test.js"},{"assertionResults":[{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing includes MiniMax-M3 in MODEL_PRICING","status":"passed","title":"includes MiniMax-M3 in MODEL_PRICING","duration":2.126125000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing MiniMax-M3 pricing has numeric shape (input, output, cached)","status":"passed","title":"MiniMax-M3 pricing has numeric shape (input, output, cached)","duration":0.6438750000000084,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing MiniMax-M3 input price matches the design spec (0.30)","status":"passed","title":"MiniMax-M3 input price matches the design spec (0.30)","duration":0.11816699999999969,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing MiniMax-M3 output price matches the design spec (1.20)","status":"passed","title":"MiniMax-M3 output price matches the design spec (1.20)","duration":0.11129200000000594,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax-M3 pricing"],"fullName":"MiniMax-M3 pricing MiniMax-M3 cached price matches the design spec (0.06)","status":"passed","title":"MiniMax-M3 cached price matches the design spec (0.06)","duration":0.06304099999999835,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745869,"endTime":1781497745872.1182,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-pricing-minimax-m3.test.js"},{"assertionResults":[{"ancestorTitles":["provider test-models route kind routing"],"fullName":"provider test-models route kind routing routes huggingface image models to /api/v1/images/generations","status":"passed","title":"routes huggingface image models to /api/v1/images/generations","duration":167.55883400000002,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743348,"endTime":1781497743515.5588,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-test-models-routing.test.js"},{"assertionResults":[{"ancestorTitles":["Provider Validation API","OpenAI Compatible"],"fullName":"Provider Validation API OpenAI Compatible should return valid:true when /models succeeds","status":"passed","title":"should return valid:true when /models succeeds","duration":3.4523750000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","OpenAI Compatible"],"fullName":"Provider Validation API OpenAI Compatible should fallback to chat/completions when /models fails and modelId provided","status":"passed","title":"should fallback to chat/completions when /models fails and modelId provided","duration":0.28020899999999926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","OpenAI Compatible"],"fullName":"Provider Validation API OpenAI Compatible should return error when /models fails and no modelId","status":"passed","title":"should return error when /models fails and no modelId","duration":0.27941599999999767,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Anthropic Compatible"],"fullName":"Provider Validation API Anthropic Compatible should normalize URL by removing /messages suffix","status":"passed","title":"should normalize URL by removing /messages suffix","duration":0.18300000000000693,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Anthropic Compatible"],"fullName":"Provider Validation API Anthropic Compatible should send correct headers for Anthropic API","status":"passed","title":"should send correct headers for Anthropic API","duration":0.396458999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - Network"],"fullName":"Provider Validation API Error Messages - Network should map ECONNREFUSED to user-friendly message","status":"passed","title":"should map ECONNREFUSED to user-friendly message","duration":0.0847920000000073,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - Network"],"fullName":"Provider Validation API Error Messages - Network should map ENOTFOUND to user-friendly message","status":"passed","title":"should map ENOTFOUND to user-friendly message","duration":0.10895800000000122,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - Network"],"fullName":"Provider Validation API Error Messages - Network should map timeout to user-friendly message","status":"passed","title":"should map timeout to user-friendly message","duration":0.06687499999999602,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - Network"],"fullName":"Provider Validation API Error Messages - Network should map CERT_HAS_EXPIRED to user-friendly message","status":"passed","title":"should map CERT_HAS_EXPIRED to user-friendly message","duration":0.2903750000000116,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","URL Validation"],"fullName":"Provider Validation API URL Validation should validate correct URL format","status":"passed","title":"should validate correct URL format","duration":0.15262500000000045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return auth error for 401","status":"passed","title":"should return auth error for 401","duration":0.12070799999999338,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return auth error for 403","status":"passed","title":"should return auth error for 403","duration":0.04345899999999858,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return not found for 404","status":"passed","title":"should return not found for 404","duration":0.0401250000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return server error for 500","status":"passed","title":"should return server error for 500","duration":0.035332999999994286,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return server error for 502","status":"passed","title":"should return server error for 502","duration":0.03491699999999298,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /models Status Codes"],"fullName":"Provider Validation API Error Messages - /models Status Codes should return unexpected for other codes","status":"passed","title":"should return unexpected for other codes","duration":0.10949999999999704,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return auth error for 401","status":"passed","title":"should return auth error for 401","duration":0.07479200000000219,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return invalid model for 400","status":"passed","title":"should return invalid model for 400","duration":0.061957999999989966,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return not found for 404","status":"passed","title":"should return not found for 404","duration":0.04449999999999932,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return server error for 503","status":"passed","title":"should return server error for 503","duration":0.04554200000001174,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Error Messages - /chat/completions Status Codes"],"fullName":"Provider Validation API Error Messages - /chat/completions Status Codes should return failed for other codes","status":"passed","title":"should return failed for other codes","duration":0.039667000000008557,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Response Format"],"fullName":"Provider Validation API Response Format should return correct format for success via /models","status":"passed","title":"should return correct format for success via /models","duration":0.07374999999998977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Response Format"],"fullName":"Provider Validation API Response Format should return correct format for success via chat","status":"passed","title":"should return correct format for success via chat","duration":0.07337499999999864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Provider Validation API","Response Format"],"fullName":"Provider Validation API Response Format should return correct format for failure with error","status":"passed","title":"should return correct format for failure with error","duration":0.060207999999988715,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745428,"endTime":1781497745435.0603,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/provider-validation.test.js"},{"assertionResults":[{"ancestorTitles":["QODER_MODEL_MAP"],"fullName":"QODER_MODEL_MAP allows Qoder's latest model key","status":"passed","title":"allows Qoder's latest model key","duration":0.8009169999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["QODER_MODEL_MAP"],"fullName":"QODER_MODEL_MAP exposes Qoder's latest model in the static provider catalog","status":"passed","title":"exposes Qoder's latest model in the static provider catalog","duration":0.14654100000001336,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody preserves base64 length (input length divisible by 3)","status":"passed","title":"preserves base64 length (input length divisible by 3)","duration":0.12937500000001023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody preserves base64 length (input length not divisible by 3)","status":"passed","title":"preserves base64 length (input length not divisible by 3)","duration":0.12062499999998977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody handles empty input without throwing","status":"passed","title":"handles empty input without throwing","duration":0.06887499999999136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody accepts string and Buffer inputs equivalently","status":"passed","title":"accepts string and Buffer inputs equivalently","duration":0.12420900000000756,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody only emits characters from the custom alphabet","status":"passed","title":"only emits characters from the custom alphabet","duration":0.8450829999999883,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody is deterministic for identical input","status":"passed","title":"is deterministic for identical input","duration":0.1368750000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["qoderEncodeBody"],"fullName":"qoderEncodeBody produces different output for different input","status":"passed","title":"produces different output for different input","duration":0.6818749999999909,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generatePkcePair"],"fullName":"generatePkcePair produces base64url-safe verifier and challenge of the right length","status":"passed","title":"produces base64url-safe verifier and challenge of the right length","duration":0.5009579999999971,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generatePkcePair"],"fullName":"generatePkcePair verifier and challenge are different (challenge is sha256 of verifier)","status":"passed","title":"verifier and challenge are different (challenge is sha256 of verifier)","duration":0.15720799999999713,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generatePkcePair"],"fullName":"generatePkcePair returns codeVerifier (not verifier) on the higher-level helper","status":"passed","title":"returns codeVerifier (not verifier) on the higher-level helper","duration":0.2604159999999922,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["initiateDeviceFlow"],"fullName":"initiateDeviceFlow produces a verification URL pointing at qoder.com/device/selectAccounts","status":"passed","title":"produces a verification URL pointing at qoder.com/device/selectAccounts","duration":0.15687500000001364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["initiateDeviceFlow"],"fullName":"initiateDeviceFlow returns nonce and machineId as UUIDs","status":"passed","title":"returns nonce and machineId as UUIDs","duration":0.12408400000001052,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders produces all required Cosy-* headers","status":"passed","title":"produces all required Cosy-* headers","duration":1.2621249999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Authorization is a Bearer COSY token with payload+sig","status":"passed","title":"Authorization is a Bearer COSY token with payload+sig","duration":0.2872910000000104,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-Sigpath strips the leading /algo prefix","status":"passed","title":"Cosy-Sigpath strips the leading /algo prefix","duration":0.1455829999999878,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-Sigpath also handles the encoded chat URL","status":"passed","title":"Cosy-Sigpath also handles the encoded chat URL","duration":0.19770900000000324,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-Bodyhash is the MD5 of the request body, Cosy-Bodylength is the length","status":"passed","title":"Cosy-Bodyhash is the MD5 of the request body, Cosy-Bodylength is the length","duration":0.13758300000000645,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders empty body produces the canonical empty-MD5 hash","status":"passed","title":"empty body produces the canonical empty-MD5 hash","duration":0.18999999999999773,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-Machineid + Cosy-Machinetoken match the supplied machineId","status":"passed","title":"Cosy-Machineid + Cosy-Machinetoken match the supplied machineId","duration":0.11337499999999068,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders auto-generates a machineId when none is supplied","status":"passed","title":"auto-generates a machineId when none is supplied","duration":0.12112500000000637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders throws when userId is missing","status":"passed","title":"throws when userId is missing","duration":0.38579200000000924,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders throws when authToken is missing","status":"passed","title":"throws when authToken is missing","duration":0.09537499999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders Cosy-User reflects the supplied userId verbatim","status":"passed","title":"Cosy-User reflects the supplied userId verbatim","duration":0.4841250000000059,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildCosyHeaders"],"fullName":"buildCosyHeaders two calls with identical inputs differ only in fields that include fresh randomness","status":"passed","title":"two calls with identical inputs differ only in fields that include fresh randomness","duration":0.2708329999999819,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry accepts ms-epoch as a JSON number","status":"passed","title":"accepts ms-epoch as a JSON number","duration":0.06858299999998962,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry accepts ms-epoch as a numeric string","status":"passed","title":"accepts ms-epoch as a numeric string","duration":0.051208000000002585,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry accepts RFC3339 strings","status":"passed","title":"accepts RFC3339 strings","duration":0.04954200000000242,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry does not interpret short numeric strings as a year","status":"passed","title":"does not interpret short numeric strings as a year","duration":0.034458000000000766,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry falls back to expiresInSeconds when expiresAt is missing","status":"passed","title":"falls back to expiresInSeconds when expiresAt is missing","duration":0.08174999999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry treats expires_in: 0 as already expired (now), not 30-day fallback","status":"passed","title":"treats expires_in: 0 as already expired (now), not 30-day fallback","duration":0.04333299999998985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry falls back to ~30 days when both inputs are missing","status":"passed","title":"falls back to ~30 days when both inputs are missing","duration":0.04449999999999932,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseExpiry"],"fullName":"parseExpiry falls back to ~30 days when both inputs are unparseable","status":"passed","title":"falls back to ~30 days when both inputs are unparseable","duration":0.04329199999997968,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeMessages"],"fullName":"normalizeMessages hoists role:system out of messages into systemText","status":"passed","title":"hoists role:system out of messages into systemText","duration":0.3937080000000037,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeMessages"],"fullName":"normalizeMessages flattens multipart text content into a string","status":"passed","title":"flattens multipart text content into a string","duration":0.04283400000002757,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeMessages"],"fullName":"normalizeMessages joins multiple system messages with a blank line","status":"passed","title":"joins multiple system messages with a blank line","duration":0.036417000000000144,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeMessages"],"fullName":"normalizeMessages returns empty results for empty input","status":"passed","title":"returns empty results for empty input","duration":0.09170800000001122,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE forwards an OpenAI envelope chunk and emits [DONE] in flush","status":"passed","title":"forwards an OpenAI envelope chunk and emits [DONE] in flush","duration":11.420041999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE drains a trailing partial line without a newline in flush()","status":"passed","title":"drains a trailing partial line without a newline in flush()","duration":0.34945899999999597,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE does not forward chunks after [DONE] has been emitted","status":"passed","title":"does not forward chunks after [DONE] has been emitted","duration":0.4539999999999793,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE strips embedded newlines from inner body before forwarding","status":"passed","title":"strips embedded newlines from inner body before forwarding","duration":0.33124999999998295,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE upstream error envelope produces an error chunk + [DONE]","status":"passed","title":"upstream error envelope produces an error chunk + [DONE]","duration":0.6365830000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wrapQoderSSE"],"fullName":"wrapQoderSSE non-ok responses are returned unchanged (no transform)","status":"passed","title":"non-ok responses are returned unchanged (no transform)","duration":0.2201249999999959,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744593,"endTime":1781497744616.2202,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/qoder.test.js"},{"assertionResults":[{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip injects reasoning_content on a deepseek- assistant message that lacks it","status":"passed","title":"injects reasoning_content on a deepseek- assistant message that lacks it","duration":0.9899999999999807,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip preserves an existing reasoning_content instead of overwriting it","status":"passed","title":"preserves an existing reasoning_content instead of overwriting it","duration":0.1647090000000162,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip applies provider-level rule for provider 'deepseek' (scope all)","status":"passed","title":"applies provider-level rule for provider 'deepseek' (scope all)","duration":0.08645800000002168,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip matches deepseek model id case-insensitively for custom providers (#1543)","status":"passed","title":"matches deepseek model id case-insensitively for custom providers (#1543)","duration":0.117416999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip does not touch non-deepseek providers/models","status":"passed","title":"does not touch non-deepseek providers/models","duration":0.07787500000000591,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — DeepSeek thinking round-trip"],"fullName":"injectReasoningContent — DeepSeek thinking round-trip maps deepseek-v4-pro-none alias to disabled thinking and strips reasoning_effort","status":"passed","title":"maps deepseek-v4-pro-none alias to disabled thinking and strips reasoning_effort","duration":0.14287500000000364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip injects reasoning_content on a minimax assistant message that lacks it","status":"passed","title":"injects reasoning_content on a minimax assistant message that lacks it","duration":0.13037499999998658,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip injects reasoning_content on minimax assistant message with tool_calls but no reasoning_content","status":"passed","title":"injects reasoning_content on minimax assistant message with tool_calls but no reasoning_content","duration":0.1220000000000141,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip applies provider-level rule for provider 'minimax-cn' (scope all)","status":"passed","title":"applies provider-level rule for provider 'minimax-cn' (scope all)","duration":0.351791999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip preserves an existing reasoning_content on minimax instead of overwriting","status":"passed","title":"preserves an existing reasoning_content on minimax instead of overwriting","duration":0.08916700000000333,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["injectReasoningContent — MiniMax thinking round-trip"],"fullName":"injectReasoningContent — MiniMax thinking round-trip DefaultExecutor transformRequest runs the injector for minimax","status":"passed","title":"DefaultExecutor transformRequest runs the injector for minimax","duration":26.337582999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenCodeExecutor — issue #1543 regression"],"fullName":"OpenCodeExecutor — issue #1543 regression runs the injector so deepseek-v4-flash-free round-trips reasoning_content","status":"passed","title":"runs the injector so deepseek-v4-flash-free round-trips reasoning_content","duration":0.26958300000001145,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744378,"endTime":1781497744407.2695,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/reasoningContentInjector.test.js"},{"assertionResults":[{"ancestorTitles":["Responses abort terminal synthesis"],"fullName":"Responses abort terminal synthesis emits response.failed + [DONE] when upstream errors (abort/stall)","status":"passed","title":"emits response.failed + [DONE] when upstream errors (abort/stall)","duration":3.972332999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Responses abort terminal synthesis"],"fullName":"Responses abort terminal synthesis does not synthesize terminal for non-Responses streams (callback null)","status":"passed","title":"does not synthesize terminal for non-Responses streams (callback null)","duration":0.7312919999999963,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497746017,"endTime":1781497746021.7312,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/responses-abort-terminal.test.js"},{"assertionResults":[{"ancestorTitles":["RTK end-to-end"],"fullName":"RTK end-to-end server is reachable","status":"skipped","title":"server is reachable","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK end-to-end"],"fullName":"RTK end-to-end rtkEnabled flag is true (user must enable via dashboard)","status":"skipped","title":"rtkEnabled flag is true (user must enable via dashboard)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK end-to-end"],"fullName":"RTK end-to-end compresses git diff tool_result and writes [RTK] savings to log","status":"skipped","title":"compresses git diff tool_result and writes [RTK] savings to log","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK end-to-end"],"fullName":"RTK end-to-end compresses grep-style tool_result","status":"skipped","title":"compresses grep-style tool_result","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497742906,"endTime":1781497742906,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/rtk.e2e.test.js"},{"assertionResults":[{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E server reachable and rtkEnabled=true","status":"skipped","title":"server reachable and rtkEnabled=true","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for claude (cc/* → openai→claude)","status":"skipped","title":"compresses git diff for claude (cc/* → openai→claude)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for codex (cx/* → openai→openai-responses)","status":"skipped","title":"compresses git diff for codex (cx/* → openai→openai-responses)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for antigravity (ag/* → openai→antigravity)","status":"skipped","title":"compresses git diff for antigravity (ag/* → openai→antigravity)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for cursor (cu/* → openai→cursor)","status":"skipped","title":"compresses git diff for cursor (cu/* → openai→cursor)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for kiro (kr/* → openai→kiro)","status":"skipped","title":"compresses git diff for kiro (kr/* → openai→kiro)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for gemini (gemini/* → openai→gemini)","status":"skipped","title":"compresses git diff for gemini (gemini/* → openai→gemini)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for deepseek (deepseek/* → openai, passthrough)","status":"skipped","title":"compresses git diff for deepseek (deepseek/* → openai, passthrough)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK multi-provider E2E"],"fullName":"RTK multi-provider E2E compresses git diff for ollama (ollama/* → openai→ollama)","status":"skipped","title":"compresses git diff for ollama (ollama/* → openai→ollama)","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497742906,"endTime":1781497742906,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/rtk.multi-provider.e2e.test.js"},{"assertionResults":[{"ancestorTitles":["RTK flag"],"fullName":"RTK flag default off, toggle works","status":"failed","title":"default off, toggle works","duration":4.233541000000002,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:58:18\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters gitDiff truncates hunks beyond 100 lines and preserves file header","status":"passed","title":"gitDiff truncates hunks beyond 100 lines and preserves file header","duration":0.76724999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters gitStatus groups by kind and produces compact output (Rust format)","status":"passed","title":"gitStatus groups by kind and produces compact output (Rust format)","duration":0.4084169999999858,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters grep groups matches by file and caps per-file lines (Rust format)","status":"passed","title":"grep groups matches by file and caps per-file lines (Rust format)","duration":0.7521249999999782,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters find groups paths by parent dir, shows basenames (Rust format)","status":"passed","title":"find groups paths by parent dir, shows basenames (Rust format)","duration":0.4069160000000238,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters"],"fullName":"RTK filters dedupLog collapses consecutive duplicates","status":"passed","title":"dedupLog collapses consecutive duplicates","duration":0.2463329999999928,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter detects git diff","status":"passed","title":"detects git diff","duration":0.2906669999999849,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter detects git status","status":"passed","title":"detects git status","duration":0.14337499999999181,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter detects grep","status":"passed","title":"detects grep","duration":0.6494999999999891,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter detects find","status":"passed","title":"detects find","duration":0.249416999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter"],"fullName":"autoDetectFilter falls back to dedupLog for generic text","status":"passed","title":"falls back to dedupLog for generic text","duration":0.23783299999996643,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) ls: compact_ls strips perms/owner, keeps name + size","status":"passed","title":"ls: compact_ls strips perms/owner, keeps name + size","duration":0.432124999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) ls: filters noise dirs","status":"passed","title":"ls: filters noise dirs","duration":0.2501249999999686,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) tree: removes summary, keeps structure","status":"passed","title":"tree: removes summary, keeps structure","duration":0.17054200000001174,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) smartTruncate: keeps head+tail, drops middle","status":"passed","title":"smartTruncate: keeps head+tail, drops middle","duration":0.1548330000000533,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) smartTruncate: passes through small input","status":"passed","title":"smartTruncate: passes through small input","duration":0.04712499999999409,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) readNumbered: compacts very long line-numbered dump","status":"passed","title":"readNumbered: compacts very long line-numbered dump","duration":0.17983400000002803,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["RTK filters (extras)"],"fullName":"RTK filters (extras) searchList: groups Cursor Glob output by parent dir","status":"passed","title":"searchList: groups Cursor Glob output by parent dir","duration":0.3078339999999571,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter (extras)"],"fullName":"autoDetectFilter (extras) detects tree via box-drawing glyphs","status":"passed","title":"detects tree via box-drawing glyphs","duration":0.29241700000000037,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter (extras)"],"fullName":"autoDetectFilter (extras) detects ls via total + perms rows","status":"passed","title":"detects ls via total + perms rows","duration":0.06970799999999144,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["autoDetectFilter (extras)"],"fullName":"autoDetectFilter (extras) detects Cursor search list","status":"passed","title":"detects Cursor search list","duration":0.07862500000004502,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["safeApply"],"fullName":"safeApply returns input if filter throws","status":"passed","title":"returns input if filter throws","duration":0.4908750000000168,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["safeApply"],"fullName":"safeApply returns input if filter returns non-string","status":"passed","title":"returns input if filter returns non-string","duration":0.04033300000003237,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (disabled)"],"fullName":"compressMessages (disabled) returns null when disabled","status":"failed","title":"returns null when disabled","duration":0.3933749999999918,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:248:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) compresses OpenAI tool message (string content)","status":"failed","title":"compresses OpenAI tool message (string content)","duration":0.149249999999995,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) compresses Claude string-form tool_result","status":"failed","title":"compresses Claude string-form tool_result","duration":0.20562499999999773,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) compresses Claude array-form tool_result text parts","status":"failed","title":"compresses Claude array-form tool_result text parts","duration":0.20274999999998045,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) skips is_error tool_result","status":"failed","title":"skips is_error tool_result","duration":0.10024999999995998,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) skips below MIN_COMPRESS_SIZE (<500 bytes)","status":"failed","title":"skips below MIN_COMPRESS_SIZE (<500 bytes)","duration":0.09279099999997698,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) never produces empty content (R14 guard)","status":"failed","title":"never produces empty content (R14 guard)","duration":0.08608400000002803,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) skips when body has no messages","status":"failed","title":"skips when body has no messages","duration":0.12954200000001492,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["compressMessages (enabled)"],"fullName":"compressMessages (enabled) handles mix of messages without crashing","status":"failed","title":"handles mix of messages without crashing","duration":0.13733300000001236,"failureMessages":["TypeError: (0 , __vite_ssr_import_1__.setRtkEnabled) is not a function\n at /Users/Working/router4/app/tests/unit/rtk.test.js:256:33\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at wrapper (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:722:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2621:52\n at run (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1150:20)\n at limiterFn (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1162:59)\n at runHook (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2620:10)"],"meta":{},"tags":[]},{"ancestorTitles":["formatRtkLog"],"fullName":"formatRtkLog returns null when no hits","status":"passed","title":"returns null when no hits","duration":0.09549999999995862,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatRtkLog"],"fullName":"formatRtkLog formats savings line with percentage","status":"passed","title":"formats savings line with percentage","duration":0.17395800000002737,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743210,"endTime":1781497743224.174,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/rtk.test.js"},{"assertionResults":[{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support compresses tool results in Kiro conversationState.currentMessage","status":"passed","title":"compresses tool results in Kiro conversationState.currentMessage","duration":2.233125000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support compresses tool results in Kiro conversationState.history","status":"passed","title":"compresses tool results in Kiro conversationState.history","duration":0.47366699999999184,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support handles multiple tool results across history and currentMessage","status":"passed","title":"handles multiple tool results across history and currentMessage","duration":0.2462500000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support preserves error tool results without compression","status":"passed","title":"preserves error tool results without compression","duration":0.1387500000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support returns null when RTK is disabled","status":"passed","title":"returns null when RTK is disabled","duration":0.06720899999999119,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support handles Kiro body with no tool results gracefully","status":"passed","title":"handles Kiro body with no tool results gracefully","duration":0.09004099999999937,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Kiro format RTK support"],"fullName":"Kiro format RTK support handles malformed Kiro body without crashing","status":"passed","title":"handles malformed Kiro body without crashing","duration":0.12937500000001023,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745751,"endTime":1781497745754.2463,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/rtkKiro.test.js"},{"assertionResults":[{"ancestorTitles":["resolveSessionId"],"fullName":"resolveSessionId stickiness: same body+connectionId+scope -> same id","status":"passed","title":"stickiness: same body+connectionId+scope -> same id","duration":1.4322499999999962,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSessionId"],"fullName":"resolveSessionId different connectionId -> different id","status":"passed","title":"different connectionId -> different id","duration":0.4677499999999952,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSessionId"],"fullName":"resolveSessionId different scope -> different id","status":"passed","title":"different scope -> different id","duration":0.26300000000000523,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSessionId"],"fullName":"resolveSessionId fallback: empty body+no header+no workspaceId -> deriveSessionId(connectionId)","status":"passed","title":"fallback: empty body+no header+no workspaceId -> deriveSessionId(connectionId)","duration":0.14791599999999505,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSessionId"],"fullName":"resolveSessionId client override: x-session-id header wins, skips later steps","status":"passed","title":"client override: x-session-id header wins, skips later steps","duration":0.13729100000000471,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSessionId"],"fullName":"resolveSessionId workspaceId path: empty body + workspaceId set -> normalized workspaceId","status":"passed","title":"workspaceId path: empty body + workspaceId set -> normalized workspaceId","duration":0.06274999999999409,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745986,"endTime":1781497745989.0627,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/session-manager.test.js"},{"assertionResults":[{"ancestorTitles":["tokenRefresh dispatch"],"fullName":"tokenRefresh dispatch getAccessToken returns null for missing/invalid refreshToken","status":"passed","title":"getAccessToken returns null for missing/invalid refreshToken","duration":77.544042,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["tokenRefresh dispatch"],"fullName":"tokenRefresh dispatch getAccessToken default: unsupported provider → null","status":"passed","title":"getAccessToken default: unsupported provider → null","duration":0.2103749999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["tokenRefresh dispatch"],"fullName":"tokenRefresh dispatch refreshTokenByProvider returns null without refreshToken","status":"passed","title":"refreshTokenByProvider returns null without refreshToken","duration":0.09800000000001319,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744253,"endTime":1781497744331.2104,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/token-refresh-dispatch.test.js"},{"assertionResults":[{"ancestorTitles":["normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1)"],"fullName":"normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1) downgrades adaptive thinking to enabled+budget for haiku models","status":"passed","title":"downgrades adaptive thinking to enabled+budget for haiku models","duration":1.0424999999999898,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1)"],"fullName":"normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1) keeps adaptive thinking for sonnet/opus","status":"passed","title":"keeps adaptive thinking for sonnet/opus","duration":0.23183299999999463,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1)"],"fullName":"normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1) hoists mid-conversation system messages into top-level system","status":"passed","title":"hoists mid-conversation system messages into top-level system","duration":0.1904169999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDataUri / encodeDataUri (docs 11 §4)"],"fullName":"parseDataUri / encodeDataUri (docs 11 §4) parses a base64 data uri","status":"passed","title":"parses a base64 data uri","duration":0.11404200000001197,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDataUri / encodeDataUri (docs 11 §4)"],"fullName":"parseDataUri / encodeDataUri (docs 11 §4) tolerates newlines inside base64 payload","status":"passed","title":"tolerates newlines inside base64 payload","duration":0.1861250000000041,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDataUri / encodeDataUri (docs 11 §4)"],"fullName":"parseDataUri / encodeDataUri (docs 11 §4) returns null for http urls and non-strings","status":"passed","title":"returns null for http urls and non-strings","duration":0.10479200000000333,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDataUri / encodeDataUri (docs 11 §4)"],"fullName":"parseDataUri / encodeDataUri (docs 11 §4) encode/parse roundtrip","status":"passed","title":"encode/parse roundtrip","duration":0.1755420000000072,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497746165,"endTime":1781497746167.186,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/translator-helpers-edge.test.js"},{"assertionResults":[{"ancestorTitles":["request normalization"],"fullName":"request normalization claudeToOpenAIRequest flattens text-only content arrays into string","status":"failed","title":"claudeToOpenAIRequest flattens text-only content arrays into string","duration":5.173790999999937,"failureMessages":["AssertionError: expected [ { type: 'text', text: 'hi' }, …(1) ] to be 'hi\\nthere' // Object.is equality\n at /Users/Working/router4/app/tests/unit/translator-request-normalization.test.js:24:40\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization claudeToOpenAIRequest preserves multimodal arrays","status":"passed","title":"claudeToOpenAIRequest preserves multimodal arrays","duration":0.2379999999999427,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization filterToOpenAIFormat flattens text-only arrays to string","status":"failed","title":"filterToOpenAIFormat flattens text-only arrays to string","duration":0.7105000000000246,"failureMessages":["AssertionError: expected [ { type: 'text', text: 'a' }, …(1) ] to be 'a\\nb' // Object.is equality\n at /Users/Working/router4/app/tests/unit/translator-request-normalization.test.js:65:40\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization translateRequest keeps /v1/messages Claude->OpenAI text payloads string-safe","status":"failed","title":"translateRequest keeps /v1/messages Claude->OpenAI text payloads string-safe","duration":1.4086670000000368,"failureMessages":["AssertionError: expected 'object' to be 'string' // Object.is equality\n at /Users/Working/router4/app/tests/unit/translator-request-normalization.test.js:95:40\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization translateRequest strips unsupported Anthropic output_config for MiniMax Claude-compatible endpoints","status":"passed","title":"translateRequest strips unsupported Anthropic output_config for MiniMax Claude-compatible endpoints","duration":0.383707999999956,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization translateRequest preserves output_config for Anthropic Claude","status":"passed","title":"translateRequest preserves output_config for Anthropic Claude","duration":0.22670800000003055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization parseSSELine supports provider raw NDJSON stream lines","status":"failed","title":"parseSSELine supports provider raw NDJSON stream lines","duration":0.47641699999996945,"failureMessages":["AssertionError: expected null to deeply equal { model: 'gpt-oss:120b', …(2) }\n at /Users/Working/router4/app/tests/unit/translator-request-normalization.test.js:175:20\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise ()\n at runWithCancel (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise ()\n at runWithTimeout (file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///Users/Working/router4/app/tests/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["request normalization"],"fullName":"request normalization parseSSELine still supports SSE data lines","status":"passed","title":"parseSSELine still supports SSE data lines","duration":0.06995799999992869,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743663,"endTime":1781497743672.07,"status":"failed","message":"","name":"/Users/Working/router4/app/tests/unit/translator-request-normalization.test.js"},{"assertionResults":[{"ancestorTitles":["toOpenAIUsage"],"fullName":"toOpenAIUsage claude: folds cache read+create into prompt, exposes details","status":"passed","title":"claude: folds cache read+create into prompt, exposes details","duration":2.097583,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIUsage"],"fullName":"toOpenAIUsage claude: no cache -> no prompt_tokens_details","status":"passed","title":"claude: no cache -> no prompt_tokens_details","duration":0.48116600000000176,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIUsage"],"fullName":"toOpenAIUsage gemini: full fields, completion = candidates + thoughts","status":"passed","title":"gemini: full fields, completion = candidates + thoughts","duration":0.49329099999999926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIUsage"],"fullName":"toOpenAIUsage gemini fallback: candidates=0 -> derive from total - prompt - thoughts","status":"passed","title":"gemini fallback: candidates=0 -> derive from total - prompt - thoughts","duration":0.23312500000000114,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIUsage"],"fullName":"toOpenAIUsage kiro: input/output straight","status":"passed","title":"kiro: input/output straight","duration":0.21391600000001176,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIUsage"],"fullName":"toOpenAIUsage ollama: prompt_eval_count/eval_count","status":"passed","title":"ollama: prompt_eval_count/eval_count","duration":0.18574999999999875,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIUsage"],"fullName":"toOpenAIUsage commandcode: keeps totalTokens fallback","status":"passed","title":"commandcode: keeps totalTokens fallback","duration":0.2881670000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["toOpenAIUsage"],"fullName":"toOpenAIUsage unknown kind / null raw -> null","status":"passed","title":"unknown kind / null raw -> null","duration":0.1807500000000033,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745670,"endTime":1781497745674.288,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/usage-concern.test.js"},{"assertionResults":[{"ancestorTitles":["usage dispatch"],"fullName":"usage dispatch unsupported provider → not-implemented message","status":"passed","title":"unsupported provider → not-implemented message","duration":130.92683300000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["usage dispatch"],"fullName":"usage dispatch every supported provider routes to its handler (no fallback message)","status":"passed","title":"every supported provider routes to its handler (no fallback message)","duration":2.7001249999999857,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744176,"endTime":1781497744309.7002,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/usage-dispatch.test.js"},{"assertionResults":[{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:true when response is 200","status":"passed","title":"should return valid:true when response is 200","duration":2.4599170000000044,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:true when response is 400 (auth accepted but bad body)","status":"passed","title":"should return valid:true when response is 400 (auth accepted but bad body)","duration":0.2662079999999918,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:true when response is 429 (rate limited but auth ok)","status":"passed","title":"should return valid:true when response is 429 (rate limited but auth ok)","duration":0.30212499999998954,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:false with error when response is 401","status":"passed","title":"should return valid:false with error when response is 401","duration":0.17425000000000068,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should return valid:false with error when response is 403","status":"passed","title":"should return valid:false with error when response is 403","duration":0.18470800000000054,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should strip sso= prefix from apiKey","status":"passed","title":"should strip sso= prefix from apiKey","duration":0.15487499999998988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should accept raw token without sso= prefix","status":"passed","title":"should accept raw token without sso= prefix","duration":0.14991700000000208,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should POST to /rest/app-chat/conversations/new","status":"passed","title":"should POST to /rest/app-chat/conversations/new","duration":1.0687499999999943,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["grok-web validation"],"fullName":"grok-web validation should send Cloudflare-bypass headers","status":"passed","title":"should send Cloudflare-bypass headers","duration":0.7276250000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should return valid:true when response is 200","status":"passed","title":"should return valid:true when response is 200","duration":0.16533400000000142,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should return valid:false when response is 401","status":"passed","title":"should return valid:false when response is 401","duration":0.1534160000000071,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should return valid:false when response is 403","status":"passed","title":"should return valid:false when response is 403","duration":0.059916999999998666,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should strip __Secure-next-auth.session-token= prefix","status":"passed","title":"should strip __Secure-next-auth.session-token= prefix","duration":0.055292000000008557,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should accept raw token without prefix","status":"passed","title":"should accept raw token without prefix","duration":0.05283300000000679,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["perplexity-web validation"],"fullName":"perplexity-web validation should POST to /rest/sse/perplexity_ask","status":"passed","title":"should POST to /rest/sse/perplexity_ask","duration":0.18912499999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497745288,"endTime":1781497745294.1892,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/web-cookie-validation.test.js"},{"assertionResults":[{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service validates discovered endpoints are https x.ai URLs","status":"passed","title":"validates discovered endpoints are https x.ai URLs","duration":190.46754199999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service discovers endpoints without custom user-agent headers","status":"passed","title":"discovers endpoints without custom user-agent headers","duration":9.25691599999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service builds authorize URLs with CLIProxyAPI query extras","status":"passed","title":"builds authorize URLs with CLIProxyAPI query extras","duration":6.939084000000037,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service generates dashboard auth data with CLIProxyAPI PKCE size and discovered endpoints","status":"passed","title":"generates dashboard auth data with CLIProxyAPI PKCE size and discovered endpoints","duration":370.40000000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/oauth service"],"fullName":"xai/oauth service exchanges dashboard codes against the discovered xAI token endpoint","status":"passed","title":"exchanges dashboard codes against the discovered xAI token endpoint","duration":18.292500000000018,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497743345,"endTime":1781497743940.2925,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/xai-oauth-service.test.js"},{"assertionResults":[{"ancestorTitles":["xai/token-refresh wrapper"],"fullName":"xai/token-refresh wrapper refreshXaiToken module loads without throwing","status":"passed","title":"refreshXaiToken module loads without throwing","duration":71.10000000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/token-refresh wrapper"],"fullName":"xai/token-refresh wrapper formatProviderCredentials returns Bearer-shape for xai","status":"passed","title":"formatProviderCredentials returns Bearer-shape for xai","duration":0.5020839999999964,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/token-refresh wrapper"],"fullName":"xai/token-refresh wrapper refreshTokenByProvider returns null when refreshToken missing","status":"passed","title":"refreshTokenByProvider returns null when refreshToken missing","duration":0.21816700000002243,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["xai/token-refresh wrapper"],"fullName":"xai/token-refresh wrapper refreshTokenByProvider returns expiresIn for refreshed xai tokens","status":"passed","title":"refreshTokenByProvider returns expiresIn for refreshed xai tokens","duration":8.605624999999975,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497744133,"endTime":1781497744213.6057,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/unit/xai-tokenRefresh.test.js"},{"assertionResults":[{"ancestorTitles":["REAL all-formats matrix"],"fullName":"REAL all-formats matrix has active providers in DB","status":"skipped","title":"has active providers in DB","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497742906,"endTime":1781497742906,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/real/all-formats.real.test.js"},{"assertionResults":[{"ancestorTitles":["REAL provider behavior cases"],"fullName":"REAL provider behavior cases gemini: finish_reason stop","status":"skipped","title":"gemini: finish_reason stop","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["REAL provider behavior cases"],"fullName":"REAL provider behavior cases kiro: tool turn -> tool_calls","status":"skipped","title":"kiro: tool turn -> tool_calls","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["REAL provider behavior cases"],"fullName":"REAL provider behavior cases ollama: max_tokens -> length","status":"skipped","title":"ollama: max_tokens -> length","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["REAL provider behavior cases"],"fullName":"REAL provider behavior cases codex: session stickiness (cached_tokens on 2nd turn)","status":"skipped","title":"codex: session stickiness (cached_tokens on 2nd turn)","failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["REAL provider behavior cases"],"fullName":"REAL provider behavior cases antigravity: responds OK","status":"skipped","title":"antigravity: responds OK","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497742906,"endTime":1781497742906,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/real/provider-cases.real.test.js"},{"assertionResults":[{"ancestorTitles":["REAL provider smoke"],"fullName":"REAL provider smoke has active providers in DB","status":"skipped","title":"has active providers in DB","failureMessages":[],"meta":{},"tags":[]}],"startTime":1781497742906,"endTime":1781497742906,"status":"passed","message":"","name":"/Users/Working/router4/app/tests/translator/real/smoke-providers.real.test.js"}]} \ No newline at end of file diff --git a/tests/translator/bugs-antigravity.test.js b/tests/translator/bugs-antigravity.test.js index 6b8052ad..62ecb6fb 100644 --- a/tests/translator/bugs-antigravity.test.js +++ b/tests/translator/bugs-antigravity.test.js @@ -27,7 +27,7 @@ describe("Antigravity → OpenAI", () => { // antigravity-to-openai.js:167 — functionCall without id gets a random Date.now() id // KNOWN BUG: unstable id breaks matching with its functionResponse - it.fails("functionCall without id keeps a stable matchable id", () => { + it("functionCall without id keeps a stable matchable id", () => { const out = AG2O({ contents: [ { role: "model", parts: [{ functionCall: { name: "search", args: { q: "x" } } }] }, diff --git a/tests/translator/real/all-formats.real.test.js b/tests/translator/real/all-formats.real.test.js new file mode 100644 index 00000000..c8455bec --- /dev/null +++ b/tests/translator/real/all-formats.real.test.js @@ -0,0 +1,293 @@ +// REAL matrix test: every active provider in DB x every inbound client format x 4 scenarios. +// Goal: maximize translation-path coverage to surface real bugs (system, multimodal image, +// tool-call/tool-result, reasoning) across all source formats. +// +// RUN_REAL=1 npx vitest run --config tests/vitest.config.js tests/translator/real/all-formats.real.test.js +// RUN_REAL=1 REAL_PROVIDERS=gemini,kiro,codex npx vitest run ... (optional filter) +// +// Skips (console.warn + pass) when: no credential/model, auth/quota status (401/402/403/429), +// or the model rejects a capability (e.g. image on a non-vision model). +import { describe, it, expect } from "vitest"; +import { getProviderCredentials } from "../../../src/sse/services/auth.js"; +import { checkAndRefreshToken } from "../../../src/sse/services/tokenRefresh.js"; +import { handleChatCore } from "../../../open-sse/handlers/chatCore.js"; +import { getModelsByProviderId } from "../../../open-sse/config/providerModels.js"; + +const RUN_REAL = process.env.RUN_REAL === "1"; +const TIMEOUT_MS = 90000; +const CRED_ISSUE = [401, 402, 403, 429]; +// Account/plan/capability rejections -> skip (not a translate bug). Kept specific to avoid masking real bugs. +const SKIP_MSG_RE = /image|multimodal|vision|modality|unsupported|not support|reasoning_effort|deprecated|temperature|subscription|valid.*plan|embedding|quota|insufficient|model not found|context length|organization policy|disallowed|allowedmodels|failed_precondition/i; + +const PROVIDER_FILTER = (process.env.REAL_PROVIDERS || "") + .split(",").map((s) => s.trim()).filter(Boolean); + +// Tiny 1x1 transparent PNG (data URI body + raw base64) for multimodal scenarios. +const PNG_B64 = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="; +const PNG_DATA_URI = `data:image/png;base64,${PNG_B64}`; + +// Pick first chat LLM, excluding non-chat kinds (embedding/image/tts/stt/...). +const NON_CHAT_KINDS = new Set(["embedding", "image", "imageToText", "tts", "stt", "video", "music", "webSearch"]); +function firstLlmModel(providerId) { + const models = getModelsByProviderId(providerId); + const llm = models.find((m) => { + const kind = m.kind || m.type || "llm"; + return kind === "llm" || (!NON_CHAT_KINDS.has(kind) && kind === "llm"); + }) || models.find((m) => !NON_CHAT_KINDS.has(m.kind || m.type || "llm")); + return llm?.id || null; +} + +async function drainSSE(response) { + if (!response?.body) return ""; + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let out = ""; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + out += decoder.decode(value, { stream: true }); + } + return out; +} + +async function prepare(providerId) { + const model = firstLlmModel(providerId); + if (!model) return null; + const credentials = await getProviderCredentials(providerId, new Set(), model); + if (!credentials || credentials.allRateLimited) return null; + const refreshed = await checkAndRefreshToken(providerId, credentials); + return { model, credentials, refreshed }; +} + +// Run one request. Returns { raw } | "skip" | throws (real translate/runtime bug). +async function runChat(providerId, prep, body, sourceFormatOverride) { + const result = await handleChatCore({ + body: { ...body, model: `${providerId}/${prep.model}` }, + modelInfo: { provider: providerId, model: prep.model }, + credentials: prep.refreshed, + connectionId: prep.credentials.connectionId, + sourceFormatOverride, + }); + if (!result.success) { + const status = Number(result.status); + if (CRED_ISSUE.includes(status)) return "skip"; + // Upstream 5xx and 406 are provider-side issues, not translate bugs. + if (status >= 500 || status === 406) return "skip"; + // Account/plan/capability rejection (e.g. non-vision model + image) is not a translate bug. + if (status === 400 && SKIP_MSG_RE.test(String(result.error || ""))) return "skip"; + throw new Error(`${providerId} [${result.status}]: ${result.error}`); + } + return { raw: await drainSSE(result.response) }; +} + +// SSE validity marker per inbound format (response is re-encoded back to source format). +const SSE_MARKER = { + openai: /chat\.completion\.chunk|"delta"|\[DONE\]/, + "openai-responses": /response\.|"type"\s*:\s*"response|\[DONE\]/, + claude: /event:\s*\w|"type"\s*:\s*"(message_start|content_block|message_delta)"/, + gemini: /"candidates"|"content"|data:/, + "gemini-cli": /"candidates"|"content"|data:/, + antigravity: /"candidates"|"content"|data:/, +}; + +// ---- Body builders: per format x scenario (full, spec-correct shapes) ---- + +const COMMON = { temperature: 0.3, top_p: 0.9, max_tokens: 256 }; +// Reasoning models often reject custom temperature (must be default/1) -> omit sampling. +const REASON_TOK = { max_tokens: 1024 }; + +// OpenAI Chat Completions +const openaiBody = { + basic: () => ({ + ...COMMON, stream: true, stream_options: { include_usage: true }, + messages: [ + { role: "system", content: "You are concise." }, + { role: "user", content: "Reply with the single word: hi" }, + ], + }), + multimodal: () => ({ + ...COMMON, stream: true, + messages: [ + { role: "system", content: "Describe images briefly." }, + { role: "user", content: [ + { type: "text", text: "What color dominates this image? One word." }, + { type: "image_url", image_url: { url: PNG_DATA_URI } }, + ] }, + ], + }), + tools: () => ({ + ...COMMON, stream: true, tool_choice: "auto", + tools: [{ type: "function", function: { name: "get_weather", description: "Get weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } } }], + messages: [ + { role: "user", content: "Weather in Paris?" }, + { role: "assistant", content: "", tool_calls: [{ id: "call_1", type: "function", function: { name: "get_weather", arguments: '{"city":"Paris"}' } }] }, + { role: "tool", tool_call_id: "call_1", content: '{"temp":"20C"}' }, + { role: "user", content: "Summarize in one short sentence." }, + ], + }), + reasoning: () => ({ + ...REASON_TOK, stream: true, reasoning_effort: "low", + messages: [{ role: "user", content: "What is 17 + 26? Reply with just the number." }], + }), +}; + +// OpenAI Responses API +const responsesBody = { + basic: () => ({ + ...COMMON, stream: true, max_output_tokens: 256, + instructions: "You are concise.", + input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "Reply with the single word: hi" }] }], + }), + multimodal: () => ({ + ...COMMON, stream: true, max_output_tokens: 256, + instructions: "Describe images briefly.", + input: [{ type: "message", role: "user", content: [ + { type: "input_text", text: "What color dominates? One word." }, + { type: "input_image", image_url: PNG_DATA_URI }, + ] }], + }), + tools: () => ({ + ...COMMON, stream: true, + tools: [{ type: "function", name: "get_weather", description: "Get weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } }], + input: [ + { type: "message", role: "user", content: [{ type: "input_text", text: "Weather in Paris?" }] }, + { type: "function_call", call_id: "call_1", name: "get_weather", arguments: '{"city":"Paris"}' }, + { type: "function_call_output", call_id: "call_1", output: '{"temp":"20C"}' }, + { type: "message", role: "user", content: [{ type: "input_text", text: "Summarize in one short sentence." }] }, + ], + }), + reasoning: () => ({ + ...REASON_TOK, stream: true, max_output_tokens: 1024, reasoning: { effort: "low" }, + input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "What is 17 + 26? Just the number." }] }], + }), +}; + +// Anthropic Messages (Claude) +const claudeBody = { + basic: () => ({ + ...COMMON, stream: true, + system: [{ type: "text", text: "You are concise." }], + messages: [{ role: "user", content: "Reply with the single word: hi" }], + }), + multimodal: () => ({ + ...COMMON, stream: true, + system: [{ type: "text", text: "Describe images briefly." }], + messages: [{ role: "user", content: [ + { type: "text", text: "What color dominates? One word." }, + { type: "image", source: { type: "base64", media_type: "image/png", data: PNG_B64 } }, + ] }], + }), + tools: () => ({ + ...COMMON, stream: true, + tools: [{ name: "get_weather", description: "Get weather", input_schema: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } }], + messages: [ + { role: "user", content: "Weather in Paris?" }, + { role: "assistant", content: [{ type: "tool_use", id: "toolu_1", name: "get_weather", input: { city: "Paris" } }] }, + { role: "user", content: [{ type: "tool_result", tool_use_id: "toolu_1", content: '{"temp":"20C"}' }] }, + { role: "user", content: "Summarize in one short sentence." }, + ], + }), + reasoning: () => ({ + ...REASON_TOK, stream: true, thinking: { type: "enabled", budget_tokens: 1024 }, + messages: [{ role: "user", content: "What is 17 + 26? Just the number." }], + }), +}; + +// Gemini generateContent +const geminiBody = { + basic: () => ({ + systemInstruction: { parts: [{ text: "You are concise." }] }, + contents: [{ role: "user", parts: [{ text: "Reply with the single word: hi" }] }], + generationConfig: { maxOutputTokens: 256, temperature: 0.3, topP: 0.9 }, + }), + multimodal: () => ({ + systemInstruction: { parts: [{ text: "Describe images briefly." }] }, + contents: [{ role: "user", parts: [ + { text: "What color dominates? One word." }, + { inlineData: { mimeType: "image/png", data: PNG_B64 } }, + ] }], + generationConfig: { maxOutputTokens: 256 }, + }), + tools: () => ({ + tools: [{ functionDeclarations: [{ name: "get_weather", description: "Get weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } }] }], + contents: [ + { role: "user", parts: [{ text: "Weather in Paris?" }] }, + { role: "model", parts: [{ functionCall: { name: "get_weather", args: { city: "Paris" } } }] }, + { role: "user", parts: [{ functionResponse: { name: "get_weather", response: { temp: "20C" } } }] }, + { role: "user", parts: [{ text: "Summarize in one short sentence." }] }, + ], + generationConfig: { maxOutputTokens: 256 }, + }), + reasoning: () => ({ + contents: [{ role: "user", parts: [{ text: "What is 17 + 26? Just the number." }] }], + generationConfig: { maxOutputTokens: 1024, thinkingConfig: { thinkingBudget: 512, includeThoughts: true } }, + }), +}; + +// Antigravity = Gemini body wrapped in { request, userAgent }. +const wrapAntigravity = (fn) => () => ({ request: fn(), userAgent: "antigravity" }); +const antigravityBody = { + basic: wrapAntigravity(geminiBody.basic), + multimodal: wrapAntigravity(geminiBody.multimodal), + tools: wrapAntigravity(geminiBody.tools), + reasoning: wrapAntigravity(geminiBody.reasoning), +}; + +const BUILDERS = { + openai: openaiBody, + "openai-responses": responsesBody, + claude: claudeBody, + gemini: geminiBody, + "gemini-cli": geminiBody, + antigravity: antigravityBody, +}; + +const FORMATS = Object.keys(BUILDERS); +const SCENARIOS = ["basic", "multimodal", "tools", "reasoning"]; + +// Read active providers from DB at module-eval time (one test per provider/format/scenario). +function targetProviders() { + try { + const Database = require("better-sqlite3"); + const os = require("os"); + const path = require("path"); + const dbPath = process.env.DATA_DIR + ? path.join(process.env.DATA_DIR, "db", "data.sqlite") + : path.join(os.homedir(), ".9router", "db", "data.sqlite"); + const db = new Database(dbPath, { readonly: true }); + const rows = db.prepare("SELECT DISTINCT provider FROM providerConnections WHERE isActive = 1").all(); + db.close(); + let list = rows.map((r) => r.provider).sort(); + if (PROVIDER_FILTER.length) list = list.filter((p) => PROVIDER_FILTER.includes(p)); + return list; + } catch { + return []; + } +} + +describe.skipIf(!RUN_REAL)("REAL all-formats matrix", () => { + const providers = RUN_REAL ? targetProviders() : []; + + it("has active providers in DB", () => { + expect(providers.length).toBeGreaterThan(0); + }); + + for (const providerId of providers) { + for (const fmt of FORMATS) { + for (const scn of SCENARIOS) { + it.concurrent(`${providerId} | ${fmt} | ${scn}`, async () => { + const prep = await prepare(providerId); + if (!prep) { console.warn(`[skip] ${providerId}: no cred/model`); return expect(true).toBe(true); } + + const body = BUILDERS[fmt][scn](); + const out = await runChat(providerId, prep, body, fmt); + if (out === "skip") { console.warn(`[skip] ${providerId} ${fmt}/${scn}: cred/quota/capability`); return expect(true).toBe(true); } + + expect(out.raw.length, `${providerId} ${fmt}/${scn}: empty SSE`).toBeGreaterThan(0); + expect(SSE_MARKER[fmt].test(out.raw), `${providerId} ${fmt}/${scn}: invalid SSE shape`).toBe(true); + }, TIMEOUT_MS); + } + } + } +}); diff --git a/tests/translator/real/provider-cases.real.test.js b/tests/translator/real/provider-cases.real.test.js new file mode 100644 index 00000000..f7e92140 --- /dev/null +++ b/tests/translator/real/provider-cases.real.test.js @@ -0,0 +1,168 @@ +// B2: REAL behavior assertions for the risky provider-specific cases. +// Unlike smoke (only "doesn't crash"), each test asserts concrete OUTPUT. +// Gated by RUN_REAL=1; any provider lacking creds/model or returning an auth/quota +// status (401/402/403/429) is skipped (console.warn + pass). +// +// RUN_REAL=1 npx vitest run --config tests/vitest.config.js tests/translator/real/provider-cases.real.test.js +import { describe, it, expect } from "vitest"; +import { getProviderCredentials } from "../../../src/sse/services/auth.js"; +import { checkAndRefreshToken } from "../../../src/sse/services/tokenRefresh.js"; +import { handleChatCore } from "../../../open-sse/handlers/chatCore.js"; +import { getModelsByProviderId } from "../../../open-sse/config/providerModels.js"; + +const RUN_REAL = process.env.RUN_REAL === "1"; +const TIMEOUT_MS = 90000; +const CRED_ISSUE = [401, 402, 403, 429]; + +// Pick the first plain llm model for a provider. +function firstLlmModel(providerId) { + const models = getModelsByProviderId(providerId); + const llm = models.find((m) => (m.type || "llm") === "llm"); + return llm?.id || null; +} + +async function drainSSE(response) { + if (!response?.body) return ""; + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let out = ""; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + out += decoder.decode(value, { stream: true }); + } + return out; +} + +// Resolve creds+model for a provider, or null when unavailable (caller skips). +async function prepare(providerId) { + const model = firstLlmModel(providerId); + if (!model) { + console.warn(`[skip] ${providerId}: no llm model`); + return null; + } + const credentials = await getProviderCredentials(providerId, new Set(), model); + if (!credentials || credentials.allRateLimited) { + console.warn(`[skip] ${providerId}: no usable credential`); + return null; + } + const refreshed = await checkAndRefreshToken(providerId, credentials); + return { model, credentials, refreshed }; +} + +// Run handleChatCore + drain; returns { raw } or null if cred/quota issue (caller skips). +async function runChat(providerId, prep, body) { + const result = await handleChatCore({ + body: { model: `${providerId}/${prep.model}`, ...body }, + modelInfo: { provider: providerId, model: prep.model }, + credentials: prep.refreshed, + connectionId: prep.credentials.connectionId, + }); + if (!result.success) { + if (CRED_ISSUE.includes(Number(result.status))) { + console.warn(`[skip] ${providerId}: ${result.status} (credential/quota)`); + return null; + } + throw new Error(`${providerId} failed: ${result.status} ${result.error}`); + } + return { raw: await drainSSE(result.response) }; +} + +describe.skipIf(!RUN_REAL)("REAL provider behavior cases", () => { + // Case #1: Gemini normal prompt -> finish_reason "stop". + it("gemini: finish_reason stop", async () => { + const prep = await prepare("gemini"); + if (!prep) return expect(true).toBe(true); + // Generous max_tokens so reasoning models (gemini-3 pro) don't hit "length" first. + const out = await runChat("gemini", prep, { + stream: true, + max_tokens: 2048, + messages: [{ role: "user", content: "Reply with the single word: hi" }], + }); + if (!out) return expect(true).toBe(true); + expect(/"finish_reason"\s*:\s*"stop"/.test(out.raw), "no stop finish_reason").toBe(true); + }, TIMEOUT_MS); + + // Case #4: Kiro tool turn -> tool_calls finish_reason + tool_calls delta. + it("kiro: tool turn -> tool_calls", async () => { + const prep = await prepare("kiro"); + if (!prep) return expect(true).toBe(true); + const out = await runChat("kiro", prep, { + stream: true, + max_tokens: 128, + tool_choice: "auto", + tools: [{ + type: "function", + function: { + name: "get_weather", + description: "Get the current weather for a city", + parameters: { + type: "object", + properties: { city: { type: "string", description: "City name" } }, + required: ["city"], + }, + }, + }], + messages: [{ role: "user", content: "What's the weather in Paris? Use the get_weather tool." }], + }); + if (!out) return expect(true).toBe(true); + expect(/"finish_reason"\s*:\s*"tool_calls"/.test(out.raw), "no tool_calls finish_reason").toBe(true); + expect(/"tool_calls"/.test(out.raw), "no tool_calls delta").toBe(true); + }, TIMEOUT_MS); + + // Case #3: Ollama tiny max_tokens + long prompt -> finish_reason "length". + it("ollama: max_tokens -> length", async () => { + const prep = await prepare("ollama"); + if (!prep) return expect(true).toBe(true); + const out = await runChat("ollama", prep, { + stream: true, + max_tokens: 4, + messages: [{ role: "user", content: "Write a long detailed essay about the history of computing." }], + }); + if (!out) return expect(true).toBe(true); + // length is model-dependent; if the model stopped on its own, skip rather than fail. + if (!/"finish_reason"\s*:\s*"length"/.test(out.raw)) { + console.warn("[skip] ollama: model did not hit length (output shorter than max_tokens)"); + return expect(true).toBe(true); + } + expect(/"finish_reason"\s*:\s*"length"/.test(out.raw)).toBe(true); + }, TIMEOUT_MS); + + // Case #4/#5: Codex multi-turn -> session stickiness (prompt-cache hit on 2nd turn). + it("codex: session stickiness (cached_tokens on 2nd turn)", async () => { + const prep = await prepare("codex"); + if (!prep) return expect(true).toBe(true); + const longContext = "The capital of France is Paris. ".repeat(40); + const messages = [ + { role: "user", content: longContext }, + { role: "assistant", content: "Understood. I have noted that context." }, + { role: "user", content: "Reply with the single word: ok" }, + ]; + const body = { stream: true, max_tokens: 32, messages }; + const first = await runChat("codex", prep, body); + if (!first) return expect(true).toBe(true); + const second = await runChat("codex", prep, body); + if (!second) return expect(true).toBe(true); + // 2nd identical-context turn should hit prompt cache when session is sticky. + const m = second.raw.match(/"cached_tokens"\s*:\s*(\d+)/); + if (!m) { + console.warn("[skip] codex: no cached_tokens in usage (provider may not report)"); + return expect(true).toBe(true); + } + expect(Number(m[1]), "cached_tokens not > 0 on 2nd turn").toBeGreaterThan(0); + }, TIMEOUT_MS); + + // Case #1/#2: Antigravity normal prompt -> valid SSE response. + it("antigravity: responds OK", async () => { + const prep = await prepare("antigravity"); + if (!prep) return expect(true).toBe(true); + const out = await runChat("antigravity", prep, { + stream: true, + max_tokens: 32, + messages: [{ role: "user", content: "Reply with the single word: hi" }], + }); + if (!out) return expect(true).toBe(true); + expect(out.raw.length, "empty response").toBeGreaterThan(0); + expect(/data:|finish_reason|"delta"|"content"|event:/.test(out.raw), "not SSE").toBe(true); + }, TIMEOUT_MS); +}); diff --git a/tests/unit/executor-const-guard.test.js b/tests/unit/executor-const-guard.test.js new file mode 100644 index 00000000..2c466ce1 --- /dev/null +++ b/tests/unit/executor-const-guard.test.js @@ -0,0 +1,48 @@ +// A5 (cases #7/#9/#10): lock hardcode->config no-op values. +import { describe, it, expect } from "vitest"; +import { + OPENAI_COMPAT_BASE, + ANTHROPIC_COMPAT_BASE, + ANTHROPIC_API_VERSION, +} from "../../open-sse/providers/shared.js"; +import { DEFAULT_MAX_TOKENS, DEFAULT_MIN_TOKENS } from "../../open-sse/config/runtimeConfig.js"; +import mimoFree from "../../open-sse/providers/registry/mimo-free.js"; +import opencode from "../../open-sse/providers/registry/opencode.js"; +import antigravity from "../../open-sse/providers/registry/antigravity.js"; + +describe("compat base URLs / version", () => { + it("OPENAI_COMPAT_BASE", () => { + expect(OPENAI_COMPAT_BASE).toBe("https://api.openai.com/v1"); + }); + it("ANTHROPIC_COMPAT_BASE", () => { + expect(ANTHROPIC_COMPAT_BASE).toBe("https://api.anthropic.com/v1"); + }); + it("ANTHROPIC_API_VERSION", () => { + expect(ANTHROPIC_API_VERSION).toBe("2023-06-01"); + }); +}); + +describe("default token limits", () => { + it("max/min", () => { + expect(DEFAULT_MAX_TOKENS).toBe(64000); + expect(DEFAULT_MIN_TOKENS).toBe(32000); + }); +}); + +describe("provider baseUrl const (full path, no trailing slash)", () => { + it("mimo-free full path", () => { + expect(mimoFree.transport.baseUrl).toBe("https://api.xiaomimimo.com/api/free-ai/openai/chat"); + }); + it("opencode no trailing slash", () => { + expect(opencode.transport.baseUrl).toBe("https://opencode.ai"); + }); +}); + +describe("antigravity retry (intentional change: 429=6, 503=3)", () => { + it("429 attempts = 6", () => { + expect(antigravity.transport.retry["429"].attempts).toBe(6); + }); + it("503 attempts = 3", () => { + expect(antigravity.transport.retry["503"].attempts).toBe(3); + }); +}); diff --git a/tests/unit/finish-reason-concern.test.js b/tests/unit/finish-reason-concern.test.js new file mode 100644 index 00000000..d4fc4713 --- /dev/null +++ b/tests/unit/finish-reason-concern.test.js @@ -0,0 +1,83 @@ +// A1: locks toOpenAIFinish/fromOpenAIFinish behavior changes vs open-sse.old. +import { describe, it, expect } from "vitest"; +import { toOpenAIFinish, fromOpenAIFinish } from "../../open-sse/translator/concerns/finishReason.js"; +import { OPENAI_FINISH, CLAUDE_STOP, GEMINI_FINISH } from "../../open-sse/translator/schema/finishReasons.js"; + +describe("toOpenAIFinish - gemini", () => { + it.each([ + ["SAFETY", "content_filter"], + ["RECITATION", "content_filter"], + ["BLOCKLIST", "content_filter"], + ["PROHIBITED_CONTENT", "content_filter"], + ["OTHER", "stop"], + ["UNKNOWN_XYZ", "stop"], + ["STOP", "stop"], + ["MAX_TOKENS", "length"], + ])("%s -> %s", (input, expected) => { + expect(toOpenAIFinish(input, "gemini")).toBe(expected); + }); +}); + +describe("toOpenAIFinish - ollama", () => { + it.each([ + ["length", "length"], + ["max_tokens", "length"], + ["tool_calls", "tool_calls"], + ["unknown_xyz", "stop"], + ])("%s -> %s", (input, expected) => { + expect(toOpenAIFinish(input, "ollama")).toBe(expected); + }); +}); + +describe("toOpenAIFinish - kiro", () => { + it("tool_use -> tool_calls", () => { + expect(toOpenAIFinish("tool_use", "kiro")).toBe("tool_calls"); + }); +}); + +describe("toOpenAIFinish - claude", () => { + it.each([ + ["end_turn", "stop"], + ["max_tokens", "length"], + ["tool_use", "tool_calls"], + ])("%s -> %s", (input, expected) => { + expect(toOpenAIFinish(input, "claude")).toBe(expected); + }); +}); + +describe("toOpenAIFinish - commandcode", () => { + it("tool-calls -> tool_calls", () => { + expect(toOpenAIFinish("tool-calls", "commandcode")).toBe("tool_calls"); + }); + it("unknown passthrough", () => { + expect(toOpenAIFinish("xyz", "commandcode")).toBe("xyz"); + }); +}); + +describe("fromOpenAIFinish round-trip - claude", () => { + it("tool_calls -> tool_use", () => { + expect(fromOpenAIFinish("tool_calls", "claude")).toBe("tool_use"); + }); + it("length -> max_tokens", () => { + expect(fromOpenAIFinish("length", "claude")).toBe("max_tokens"); + }); +}); + +describe("enum literals (catch drift)", () => { + it("OPENAI_FINISH literals", () => { + expect(OPENAI_FINISH.STOP).toBe("stop"); + expect(OPENAI_FINISH.LENGTH).toBe("length"); + expect(OPENAI_FINISH.TOOL_CALLS).toBe("tool_calls"); + expect(OPENAI_FINISH.CONTENT_FILTER).toBe("content_filter"); + }); + it("CLAUDE_STOP literals", () => { + expect(CLAUDE_STOP.END_TURN).toBe("end_turn"); + expect(CLAUDE_STOP.MAX_TOKENS).toBe("max_tokens"); + expect(CLAUDE_STOP.TOOL_USE).toBe("tool_use"); + }); + it("GEMINI_FINISH literals", () => { + expect(GEMINI_FINISH.STOP).toBe("STOP"); + expect(GEMINI_FINISH.MAX_TOKENS).toBe("MAX_TOKENS"); + expect(GEMINI_FINISH.SAFETY).toBe("SAFETY"); + }); +}); diff --git a/tests/unit/openai-to-ollama-malformed.test.js b/tests/unit/openai-to-ollama-malformed.test.js new file mode 100644 index 00000000..064731cc --- /dev/null +++ b/tests/unit/openai-to-ollama-malformed.test.js @@ -0,0 +1,30 @@ +// A4 (case #10): malformed tool_calls args must not throw -> safeParseJSON returns {}. +import { describe, it, expect } from "vitest"; +import { openaiToOllamaRequest } from "../../open-sse/translator/request/openai-to-ollama.js"; + +function reqWith(args) { + return { + messages: [ + { + role: "assistant", + content: "", + tool_calls: [{ id: "c1", type: "function", function: { name: "get_weather", arguments: args } }], + }, + ], + }; +} + +describe("openaiToOllamaRequest - tool_calls arguments parsing", () => { + it("malformed JSON args -> {} (no throw)", () => { + let out; + expect(() => { + out = openaiToOllamaRequest("m", reqWith("{invalid json"), true); + }).not.toThrow(); + expect(out.messages[0].tool_calls[0].function.arguments).toEqual({}); + }); + + it("valid JSON args -> parsed object", () => { + const out = openaiToOllamaRequest("m", reqWith('{"a":1}'), true); + expect(out.messages[0].tool_calls[0].function.arguments).toEqual({ a: 1 }); + }); +}); diff --git a/tests/unit/provider-pricing-minimax-m3.test.js b/tests/unit/provider-pricing-minimax-m3.test.js index d73bd608..0f4ee930 100644 --- a/tests/unit/provider-pricing-minimax-m3.test.js +++ b/tests/unit/provider-pricing-minimax-m3.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { MODEL_PRICING } from "../../src/shared/constants/pricing.js"; +import { MODEL_PRICING } from "../../open-sse/providers/pricing.js"; describe("MiniMax-M3 pricing", () => { it("includes MiniMax-M3 in MODEL_PRICING", () => { diff --git a/tests/unit/session-manager.test.js b/tests/unit/session-manager.test.js new file mode 100644 index 00000000..c8249657 --- /dev/null +++ b/tests/unit/session-manager.test.js @@ -0,0 +1,49 @@ +// A2: locks resolveSessionId priority/stickiness (codex/kiro/antigravity centralization). +import { describe, it, expect, beforeEach } from "vitest"; +import { resolveSessionId, deriveSessionId, clearSessionStore } from "../../open-sse/utils/sessionManager.js"; + +// Assistant text must exceed ASSISTANT_MIN_LEN (50) to trigger sticky hash path. +const longAssistant = "x".repeat(80); +const bodyWithAssistant = { messages: [{ role: "assistant", content: longAssistant }] }; + +beforeEach(() => clearSessionStore()); + +describe("resolveSessionId", () => { + it("stickiness: same body+connectionId+scope -> same id", () => { + const opts = { body: bodyWithAssistant, connectionId: "conn1", scope: "codex" }; + expect(resolveSessionId(opts)).toBe(resolveSessionId(opts)); + }); + + it("different connectionId -> different id", () => { + const a = resolveSessionId({ body: bodyWithAssistant, connectionId: "connA", scope: "codex" }); + const b = resolveSessionId({ body: bodyWithAssistant, connectionId: "connB", scope: "codex" }); + expect(a).not.toBe(b); + }); + + it("different scope -> different id", () => { + const a = resolveSessionId({ body: bodyWithAssistant, connectionId: "conn1", scope: "codex" }); + const b = resolveSessionId({ body: bodyWithAssistant, connectionId: "conn1", scope: "kiro" }); + expect(a).not.toBe(b); + }); + + it("fallback: empty body+no header+no workspaceId -> deriveSessionId(connectionId)", () => { + const got = resolveSessionId({ body: {}, connectionId: "connFallback" }); + expect(got).toBe(deriveSessionId("connFallback")); + }); + + it("client override: x-session-id header wins, skips later steps", () => { + const got = resolveSessionId({ + headers: { "x-session-id": "client-sess-123" }, + body: bodyWithAssistant, + connectionId: "conn1", + workspaceId: "ws1", + scope: "codex", + }); + expect(got).toBe("client-sess-123"); + }); + + it("workspaceId path: empty body + workspaceId set -> normalized workspaceId", () => { + const got = resolveSessionId({ body: {}, connectionId: "conn1", workspaceId: "ws-abc" }); + expect(got).toBe("ws-abc"); + }); +}); diff --git a/tests/unit/usage-concern.test.js b/tests/unit/usage-concern.test.js new file mode 100644 index 00000000..1c5fe57f --- /dev/null +++ b/tests/unit/usage-concern.test.js @@ -0,0 +1,69 @@ +// A3: locks toOpenAIUsage per-provider token math (claude/gemini/kiro/ollama/commandcode). +import { describe, it, expect } from "vitest"; +import { toOpenAIUsage } from "../../open-sse/translator/concerns/usage.js"; + +describe("toOpenAIUsage", () => { + it("claude: folds cache read+create into prompt, exposes details", () => { + const u = toOpenAIUsage( + { input_tokens: 100, output_tokens: 20, cache_read_input_tokens: 30, cache_creation_input_tokens: 10 }, + "claude" + ); + expect(u.prompt_tokens).toBe(140); + expect(u.completion_tokens).toBe(20); + expect(u.total_tokens).toBe(160); + expect(u.prompt_tokens_details.cached_tokens).toBe(30); + expect(u.prompt_tokens_details.cache_creation_tokens).toBe(10); + }); + + it("claude: no cache -> no prompt_tokens_details", () => { + const u = toOpenAIUsage({ input_tokens: 50, output_tokens: 5 }, "claude"); + expect(u.prompt_tokens).toBe(50); + expect(u.prompt_tokens_details).toBeUndefined(); + }); + + it("gemini: full fields, completion = candidates + thoughts", () => { + const u = toOpenAIUsage( + { promptTokenCount: 100, candidatesTokenCount: 40, thoughtsTokenCount: 10, totalTokenCount: 150 }, + "gemini" + ); + expect(u.prompt_tokens).toBe(100); + expect(u.completion_tokens).toBe(50); + expect(u.total_tokens).toBe(150); + expect(u.completion_tokens_details.reasoning_tokens).toBe(10); + }); + + it("gemini fallback: candidates=0 -> derive from total - prompt - thoughts", () => { + const u = toOpenAIUsage( + { promptTokenCount: 100, candidatesTokenCount: 0, thoughtsTokenCount: 10, totalTokenCount: 150 }, + "gemini" + ); + // candidates derived = 150 - 100 - 10 = 40 ; completion = 40 + 10 + expect(u.completion_tokens).toBe(50); + }); + + it("kiro: input/output straight", () => { + const u = toOpenAIUsage({ inputTokens: 12, outputTokens: 3 }, "kiro"); + expect(u.prompt_tokens).toBe(12); + expect(u.completion_tokens).toBe(3); + expect(u.total_tokens).toBe(15); + }); + + it("ollama: prompt_eval_count/eval_count", () => { + const u = toOpenAIUsage({ prompt_eval_count: 7, eval_count: 4 }, "ollama"); + expect(u.prompt_tokens).toBe(7); + expect(u.completion_tokens).toBe(4); + expect(u.total_tokens).toBe(11); + }); + + it("commandcode: keeps totalTokens fallback", () => { + const u = toOpenAIUsage({ inputTokens: 8, outputTokens: 2, totalTokens: 99 }, "commandcode"); + expect(u.prompt_tokens).toBe(8); + expect(u.completion_tokens).toBe(2); + expect(u.total_tokens).toBe(99); + }); + + it("unknown kind / null raw -> null", () => { + expect(toOpenAIUsage({}, "nope")).toBeNull(); + expect(toOpenAIUsage(null, "claude")).toBeNull(); + }); +});