mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-10 17:22:17 +03:00
Compare commits
3 Commits
feat/modal
...
maint/cher
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5ecb5e4a7 | ||
|
|
4aef0a2cb0 | ||
|
|
53ad66d2fb |
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"_rebaseline_2026_08_09_9296_adobe_media_capabilities": "PR #9296 (artickc, fix/adobe-firefly-model-capabilities) own growth: src/app/api/v1/models/catalog.ts 1590->1597 (+7). The image and video catalog serializers now expose the already-normalized Adobe Firefly discovery capability data (media_capabilities, plus the existing video modality/size fields) at their only response-emission chokepoints. The discovery parser and capability normalization remain in open-sse/services/adobeFireflyModels.ts; extracting these seven serialization fields would obscure the catalog contract. Covered by tests/unit/adobe-firefly.test.ts and tests/unit/image-upscale.test.ts.",
|
||||
"_rebaseline_2026_08_08_v3850_base_drift_batch_9757": "Base drift on release/v3.8.50, not own growth: the 08-06..08-08 merge batches grew 12 already-frozen (or newly-landed) files without carrying their rebaselines — the dedicated rebaseline PR #9616 was closed as 'superseded' but its file-size entries never actually reached the base, and later merges (#8894 combos page, #9539 EditConnectionModal, #8895 models route, #9294/#9293 catalog, #9541 db/core, #8970 tokenHealthCheck, #8925 mcp schemas+server, #8890 accountFallback, #9467 chat.ts, #8931 openai-to-kiro, ProxyRegistryManager) kept growing them. All 12 values re-measured on THIS branch's tree (= pure tip + this PR's 1-line chat.ts fix, which adds zero lines). This PR's own source changes (chat.ts identifier restore, stream.ts format carve-out) do not grow any frozen file past these values.",
|
||||
"_rebaseline_2026_08_08_migration_135_collision": "fix(db): resolve migration version 135 numbering collision — #9449's 135_connection_runtime_state.sql and #8908's 135_migrate_model_capability_max_token.sql both claimed version 135 (#9449 branched before #8908 merged and never got renumbered before landing on release/v3.8.50), which threw 'Migration version collision detected' the moment ANY code touched the database — a fresh install/deploy from this tip cannot even boot. Renumbered the later-landing file to 140 (next free slot) and added the matching isSchemaAlreadyApplied('140') retroactive guard, matching the established pattern already used for the prior 135/136 -> 137/138 renumber in the same file. Own growth: src/lib/db/migrationRunner.ts 1084->1094 (+10, the new case block) — irreducible, matches the existing per-case guard pattern exactly. Covered by tests/unit/migration-135-numbering-collision.test.ts (2/2), confirmed failing (reproducing the exact live crash) against the pre-fix colliding filenames, passing after.",
|
||||
"_rebaseline_2026_08_08_9183_reasoning_cache_index_sync": "Extracted fix(responses-api): sync reasoning-cache write index with the fixed read side (from the originally-authored #9183) — chatCore.ts's write side cached every response under a hardcoded messageIndex:0, and translator/index.ts's plain-turn (non-tool-call) cache-key lookup ALSO still hardcoded messageIndex 0 at its call site (a second, previously-undiscovered instance of the same hardcoding bug, found while re-verifying this fix against the current upstream tip — the two never agreed once a conversation went past its first assistant turn, so DeepSeek/Xiaomi-mimo plain-turn reasoning replay silently missed the cache). Own growth: open-sse/handlers/chatCore.ts 5034->5042 (+8, computing messageIndex from the incoming request's message count at both the streaming and non-streaming cache-write call sites) — irreducible call-site wiring. Covered by tests/unit/reasoning-cache.test.ts (new end-to-end write/read regression test, rebaselined below) and tests/unit/translator-helper-branches.test.ts fixture updates. Other #9183 sub-fixes (output_index collision prevention, reasoning-content-alias generalization) were originally assumed already superseded by upstream's own independent fix — a live incident 2026-08-08 disproved that for the message-vs-tool-call collision case specifically (fixed separately in #9822); not re-extracted here since this PR's own scope is the narrower messageIndex sync only.",
|
||||
"_rebaseline_2026_08_02_9259_rolling_rpm": "PR #9259 (issue #8733) own growth: open-sse/services/rateLimitManager.ts baseline 1060->1167 (+107; final source 1153). The existing withRateLimit chokepoint now composes process-local rolling RPM leases with Bottleneck admission, releases pre-dispatch leases on queue timeout/abort/connection disable, preserves caller abort reasons, and wires 429/header state into the extracted rollingRpmGate.ts. The remaining growth is irreducible lifecycle wiring at the dispatch boundary plus the real watchdog test hooks needed to verify queued-wedge recovery; moving it further would obscure lease ownership and Bottleneck cleanup. Covered by the focused rate-limit manager/sliding-window suite (33/33); distributed multi-instance coordination remains explicitly out of scope.",
|
||||
"_rebaseline_2026_07_24_8470_hyperagent_sticky_thread": "PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) own growth: open-sse/executors/hyperagent.ts 936->1025 (wc -l; check-file-size.mjs counts via split(\"\\n\").length so the gate sees 937->1026, +89, crosses the 1000 cap). Fixes a real bug where a reverse-conversion proxy (text-Intent/JSON to Claude Code native tool_calls) rewrites assistant messages between agentic tool-loop turns, breaking HyperAgent’s conversation-prefix fingerprint and cold-starting the thread mid tool-loop. Adds Anthropic tool_use/tool_result flattening to extractMessageText() plus a new rootUserFingerprint()/root-key lookup tier in resolveHyperAgentThreadBinding()/storeHyperAgentThreadAfterTurn() so the thread stays sticky across the tool loop. Cohesive additions inside the existing single-file executor; not extractable without splitting the executor mid-request-flow. Covered by tests/unit/executor-hyperagent.test.ts (19/19, +5 new cases for tool_result/tool_use flattening + root-key stickiness). Pre-merge review flagged a cross-conversation root-key collision risk (tracked in the PR’s own mandatory pre-merge checklist, not yet addressed) — unrelated to this file-size ratchet, tracked separately by /fix-prs.",
|
||||
"_rebaseline_2026_07_25_8494_capability_filter_fail_closed": "PR #8494 (fix/capability-filters-fail-closed, #8488) own growth: open-sse/services/combo.ts 3640->3693 (+53) adds a fail-closed guard after filterTargetsByRequestCompatibility() — when every eligible target is excluded by request-capability filtering (vision/tools/etc) instead of quota/health, the combo now returns an explicit `capability_mismatch` 400 (describeCapabilityFilterExhaustion, imported from combo/comboStructure.ts) rather than silently falling through to a generic no-targets error, plus a `compatFilterFailOpen` escape hatch (combo config OR settings) mirrored at both the main/auto and round-robin call sites for symmetry. combo/comboStructure.ts (previously under cap, un-frozen) grows 794->918 (+124) — new home for describeCapabilityFilterExhaustion + providerSupportsEmulatedToolCalling (#5240 emulated tool-calling exemption so fail-closed does not regress prompt-emulation-only combos like all-chatgpt-web). Irreducible orchestration wiring at the existing filter chokepoint (same precedent as #7301's universal-cooldown-retry generalization). Companion test tests/unit/combo-routing-engine.test.ts 3409->3449 (+40, fail-closed/fail-open coverage across both call sites) also rebaselined. Covered by tests/unit/8488-capability-filter-fail-closed.test.ts (new) + 95/95 passing across both files. Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_07_25_8499_ts7_result_union_predicates": "PR #8499 (backryun, chore/ts7-types-executor-scattered) own growth: muse-spark-web.ts 1396->1405 (+9, irreducible). Under this workspace's `strictNullChecks: false`, the boolean-literal discriminant on `GraphqlResult` (`{ ok: true } | { ok: false; error: string }`) narrows the positive `.ok===true` branch but leaves `!result.ok` at the full union under TS7, making `.error` unreachable to the checker at the two call sites (warmup, mode-switch). Fixed by adding a single `isGraphqlFailure()` type-predicate helper (doc comment + 3-line body) reused at both call sites instead of duplicating the predicate inline — not extractable to a shared module without splitting a single-file executor's local narrowing helper out of its own file. Covered by the existing muse-spark-web executor test suite (no behavior change, pure narrowing fix).",
|
||||
@@ -162,6 +166,7 @@
|
||||
"cap": 1000,
|
||||
"testCap": 1000,
|
||||
"testFrozen": {
|
||||
"tests/unit/reasoning-cache.test.ts": 1035,
|
||||
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
|
||||
"_rebaseline_2026_07_02_5928_base_red": "web-cookie-providers-new.test.ts 845->850: #5928 (test(security) Kimi Web URL host parse, CodeQL #689) grew the file +5 lines and merged into release/v3.8.44 WITHOUT rebaselining, leaving a fast-gates base-red that blocked every subsequent PR->release. Test growth is legitimate (a security regression test); maintainer absorbs the drift here. Frozen at 850.",
|
||||
"_rebaseline_2026_07_09_6126_clinepass_dualauth": "#6126 (ClinePass dual-auth) own test growth: oauth-providers-config.test.ts 842->845 (+3: clinepass key/config/required-fields entries reusing the Cline WorkOS flow config, needed after registering clinepass in the oauth.ts PROVIDERS enum).",
|
||||
@@ -350,7 +355,7 @@
|
||||
"open-sse/executors/deepseek-web.ts": 1148,
|
||||
"open-sse/executors/grok-web.ts": 1044,
|
||||
"open-sse/executors/muse-spark-web.ts": 1405,
|
||||
"open-sse/handlers/chatCore.ts": 5034,
|
||||
"open-sse/handlers/chatCore.ts": 5042,
|
||||
"open-sse/handlers/imageGeneration.ts": 3101,
|
||||
"open-sse/handlers/responseSanitizer.ts": 1128,
|
||||
"open-sse/handlers/search.ts": 1536,
|
||||
|
||||
@@ -207,7 +207,6 @@ import { stageTrace } from "./chatCore/stageTrace.ts";
|
||||
import { attachCompressionUsageReceiptAfterAnalytics as attachCompressionUsageReceiptAfterAnalyticsFor } from "./chatCore/compressionUsageReceipt.ts";
|
||||
import { prepareUpstreamBody } from "./chatCore/upstreamBody.ts";
|
||||
import { getQuotaScopeLabelForProvider } from "../services/antigravityQuotaFamily.ts";
|
||||
|
||||
import {
|
||||
getCallLogPipelineCaptureStreamChunks,
|
||||
getCallLogPipelineMaxSizeBytes,
|
||||
@@ -367,9 +366,7 @@ import {
|
||||
isTpmExhausted,
|
||||
isRpmExhausted,
|
||||
} from "../services/geminiRateLimitTracker.ts";
|
||||
|
||||
import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts";
|
||||
|
||||
/**
|
||||
* Core chat handler - shared between SSE and Worker
|
||||
* Returns { success, response, status, error } for caller to handle fallback
|
||||
@@ -389,10 +386,8 @@ import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts";
|
||||
* @param {boolean} options.isCombo - Whether this request is from a combo
|
||||
* @param {string} options.connectionId - Connection ID for settings lookup
|
||||
*/
|
||||
|
||||
// extractSystemRoleMessages extracted to chatCore/claudeSystemRole.ts (#3501); re-exported above so
|
||||
// existing importers (e.g. tests/unit/system-role-extraction.test.ts) keep resolving it from here.
|
||||
|
||||
export async function handleChatCore({
|
||||
body,
|
||||
modelInfo,
|
||||
@@ -428,7 +423,6 @@ export async function handleChatCore({
|
||||
/* fail open */
|
||||
}
|
||||
}
|
||||
|
||||
// Per-request model-routing metadata (first extracted slice of the request-setup phase).
|
||||
const { apiFormat, customModelTargetFormat, requestedModel } = resolveChatCoreRequestSetup(
|
||||
modelInfo,
|
||||
@@ -442,7 +436,6 @@ export async function handleChatCore({
|
||||
// (not Math.random) purely to satisfy CodeQL js/insecure-randomness — this id
|
||||
// is a log-correlation token, not a security secret.
|
||||
const traceId = globalThis.crypto.randomUUID().slice(0, 6);
|
||||
|
||||
// Emit request.started event for real-time dashboard
|
||||
setImmediate(() => {
|
||||
emit("request.started", {
|
||||
@@ -4333,9 +4326,14 @@ export async function handleChatCore({
|
||||
try {
|
||||
const firstChoice = translatedResponse?.choices?.[0];
|
||||
const msg = firstChoice?.message;
|
||||
// The response being cached now will be replayed as history on the *next*
|
||||
// turn, where the read side (translator/index.ts) keys the lookup by the
|
||||
// message's real position in that future `messages` array — i.e. right
|
||||
// after everything the client sent this turn.
|
||||
const bodyMessages = (body as { messages?: unknown[] } | null | undefined)?.messages;
|
||||
cacheReasoningFromAssistantMessage(msg, provider, model, {
|
||||
requestId: skillRequestId,
|
||||
messageIndex: 0,
|
||||
messageIndex: Array.isArray(bodyMessages) ? bodyMessages.length : 0,
|
||||
});
|
||||
} catch {
|
||||
// Cache capture is non-critical — never block the response
|
||||
@@ -4760,12 +4758,15 @@ export async function handleChatCore({
|
||||
// with tool_calls so it can be replayed on subsequent turns (DeepSeek V4, Kimi K2, etc.)
|
||||
if (normalizedStreamStatus === 200 && streamResponseBody) {
|
||||
try {
|
||||
const body = streamResponseBody as Record<string, unknown>;
|
||||
const choices = body.choices as { message?: Record<string, unknown> }[] | undefined;
|
||||
const streamBody = streamResponseBody as Record<string, unknown>;
|
||||
const choices = streamBody.choices as { message?: Record<string, unknown> }[] | undefined;
|
||||
const msg = choices?.[0]?.message;
|
||||
// See the non-streaming capture above: messageIndex must match the
|
||||
// position this message will occupy in the *next* turn's history.
|
||||
const bodyMessages = (body as { messages?: unknown[] } | null | undefined)?.messages;
|
||||
cacheReasoningFromAssistantMessage(msg, provider, model, {
|
||||
requestId: skillRequestId,
|
||||
messageIndex: 0,
|
||||
messageIndex: Array.isArray(bodyMessages) ? bodyMessages.length : 0,
|
||||
});
|
||||
} catch {
|
||||
// Cache capture is non-critical — never block the stream
|
||||
@@ -5025,7 +5026,6 @@ export async function handleChatCore({
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function isTokenExpiringSoon(expiresAt, bufferMs = 5 * 60 * 1000) {
|
||||
if (!expiresAt) return false;
|
||||
const expiresAtMs = new Date(expiresAt).getTime();
|
||||
|
||||
@@ -570,7 +570,7 @@ export function translateRequest(
|
||||
|
||||
const cacheKey = hasToolCalls
|
||||
? msg.tool_calls[0]?.id
|
||||
: getAssistantMessageCacheKey(result, 0);
|
||||
: getAssistantMessageCacheKey(result, messageIndex);
|
||||
if (cacheKey) {
|
||||
const cached = lookupReasoning(cacheKey);
|
||||
if (cached) {
|
||||
|
||||
@@ -865,11 +865,12 @@ describe("Reasoning Replay Cache — Translator Replay", () => {
|
||||
}),
|
||||
},
|
||||
});
|
||||
// NOTE: the non-tool-call cache key is built as `getAssistantMessageCacheKey(result, 0)`
|
||||
// — the message index is hardcoded to 0 in the translator, so the key is always
|
||||
// `request:<id>:message:0` regardless of the assistant message's actual position.
|
||||
// The non-tool-call cache key is built as `getAssistantMessageCacheKey(result, messageIndex)`
|
||||
// where messageIndex is the assistant message's real position in the `messages`
|
||||
// array (index 1 here: user, assistant, user) — matching what the write side
|
||||
// (chatCore.ts) now caches under once the response is generated.
|
||||
cacheReasoning(
|
||||
"request:req-plain-1:message:0",
|
||||
"request:req-plain-1:message:1",
|
||||
"deepseek",
|
||||
"deepseek-v4-pro",
|
||||
"Real cached plain-turn reasoning"
|
||||
@@ -899,6 +900,60 @@ describe("Reasoning Replay Cache — Translator Replay", () => {
|
||||
);
|
||||
assert.equal(getReasoningCacheServiceStats().replays, 1);
|
||||
});
|
||||
|
||||
it("write side (chatCore's messageIndex) and read side (translateRequest) agree on the same key end-to-end", () => {
|
||||
// Regression for a mismatch where chatCore.ts always cached under
|
||||
// `messageIndex: 0` (the position of the response within *its own* choices
|
||||
// array) while translateRequest's read side looked up the message's real
|
||||
// position in the *next* turn's full history — the two never agreed once a
|
||||
// conversation went past its first assistant turn, so replay silently
|
||||
// fell back to the placeholder in real multi-turn usage.
|
||||
clearReasoningCacheAll();
|
||||
clearModelsDevCapabilities();
|
||||
saveModelsDevCapabilities({
|
||||
deepseek: {
|
||||
"deepseek-v4-pro": buildCapability({
|
||||
interleaved_field: "reasoning_content",
|
||||
reasoning: true,
|
||||
tool_call: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
// Turn 1: the incoming request has a single user message (length 1), so
|
||||
// the assistant response chatCore is about to cache will occupy index 1
|
||||
// once it's appended to history for turn 2 — mirroring
|
||||
// `messageIndex: bodyMessages.length` in chatCore.ts.
|
||||
const turn1RequestBody = { messages: [{ role: "user", content: "hi" }] };
|
||||
cacheReasoningFromAssistantMessage(
|
||||
{ role: "assistant", content: "Hello! How can I help?", reasoning_content: "real reasoning" },
|
||||
"deepseek",
|
||||
"deepseek-v4-pro",
|
||||
{ requestId: "req-e2e-1", messageIndex: turn1RequestBody.messages.length }
|
||||
);
|
||||
|
||||
// Turn 2: client replays the full history including the cached assistant
|
||||
// turn, now genuinely at index 1.
|
||||
const translated = translateRequest(
|
||||
FORMATS.OPENAI,
|
||||
FORMATS.OPENAI,
|
||||
"deepseek-v4-pro",
|
||||
{
|
||||
request_id: "req-e2e-1",
|
||||
messages: [
|
||||
{ role: "user", content: "hi" },
|
||||
{ role: "assistant", content: "Hello! How can I help?" },
|
||||
{ role: "user", content: "tell me more" },
|
||||
],
|
||||
},
|
||||
false,
|
||||
null,
|
||||
"deepseek"
|
||||
);
|
||||
|
||||
assert.equal(translated.messages[1].reasoning_content, "real reasoning");
|
||||
assert.equal(getReasoningCacheServiceStats().replays, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Reasoning Replay Cache — API Route", () => {
|
||||
|
||||
@@ -632,7 +632,7 @@ test("translateRequest replays cached reasoning-only messages when interleaved f
|
||||
},
|
||||
});
|
||||
cacheReasoningByKey(
|
||||
"request:req_reasoning_only:message:0",
|
||||
"request:req_reasoning_only:message:1",
|
||||
"deepseek",
|
||||
"deepseek-v4-flash",
|
||||
"cached reasoning only"
|
||||
@@ -690,138 +690,131 @@ test("translateRequest does not replay reasoning-only messages for non-DeepSeek
|
||||
clearReasoningCacheAll();
|
||||
});
|
||||
|
||||
test("translateRequest uses Kimi Coding's empty thinking marker instead of cached replay", () => {
|
||||
clearReasoningCacheAll();
|
||||
cacheReasoningByKey(
|
||||
"toolu_kimi_claude",
|
||||
"kimi-coding",
|
||||
"kimi-for-coding",
|
||||
"cached thinking for Kimi tool call"
|
||||
);
|
||||
test("translateRequest uses Kimi Coding's empty thinking marker instead of cached replay", () => {
|
||||
clearReasoningCacheAll();
|
||||
cacheReasoningByKey(
|
||||
"toolu_kimi_claude",
|
||||
"kimi-coding",
|
||||
"kimi-for-coding",
|
||||
"cached thinking for Kimi tool call"
|
||||
);
|
||||
|
||||
// Claude-format request: assistant has tool_use in content[] but NO thinking block
|
||||
// This simulates the scenario that causes infinite loops
|
||||
const result = translateRequest(
|
||||
FORMATS.OPENAI,
|
||||
FORMATS.CLAUDE,
|
||||
"kimi-for-coding",
|
||||
{
|
||||
reasoning_effort: "high",
|
||||
messages: [
|
||||
{ role: "user", content: "read the file" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "toolu_kimi_claude",
|
||||
name: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ role: "tool", tool_call_id: "toolu_kimi_claude", content: "file data" },
|
||||
],
|
||||
},
|
||||
false,
|
||||
null,
|
||||
"kimi-coding"
|
||||
);
|
||||
// Claude-format request: assistant has tool_use in content[] but NO thinking block
|
||||
// This simulates the scenario that causes infinite loops
|
||||
const result = translateRequest(
|
||||
FORMATS.OPENAI,
|
||||
FORMATS.CLAUDE,
|
||||
"kimi-for-coding",
|
||||
{
|
||||
reasoning_effort: "high",
|
||||
messages: [
|
||||
{ role: "user", content: "read the file" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "toolu_kimi_claude",
|
||||
name: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ role: "tool", tool_call_id: "toolu_kimi_claude", content: "file data" },
|
||||
],
|
||||
},
|
||||
false,
|
||||
null,
|
||||
"kimi-coding"
|
||||
);
|
||||
|
||||
const assistantMsg = result.messages.find((m) => m.role === "assistant");
|
||||
assert.ok(assistantMsg, "assistant message should exist");
|
||||
assert.ok(Array.isArray(assistantMsg.content), "content should be array");
|
||||
const assistantMsg = result.messages.find((m) => m.role === "assistant");
|
||||
assert.ok(assistantMsg, "assistant message should exist");
|
||||
assert.ok(Array.isArray(assistantMsg.content), "content should be array");
|
||||
|
||||
// Kimi Code CLI 0.26 sends an explicit empty thinking marker before tool_use.
|
||||
const thinkingBlock = assistantMsg.content.find((b) => b?.type === "thinking");
|
||||
assert.ok(thinkingBlock, "thinking block should be injected");
|
||||
assert.equal(thinkingBlock.thinking, "");
|
||||
// Kimi Code CLI 0.26 sends an explicit empty thinking marker before tool_use.
|
||||
const thinkingBlock = assistantMsg.content.find((b) => b?.type === "thinking");
|
||||
assert.ok(thinkingBlock, "thinking block should be injected");
|
||||
assert.equal(thinkingBlock.thinking, "");
|
||||
|
||||
// Thinking block should appear before tool_use
|
||||
const thinkingIdx = assistantMsg.content.indexOf(thinkingBlock);
|
||||
const toolUseIdx = assistantMsg.content.findIndex((b) => b?.type === "tool_use");
|
||||
assert.ok(thinkingIdx < toolUseIdx, "thinking block should be before tool_use");
|
||||
// Thinking block should appear before tool_use
|
||||
const thinkingIdx = assistantMsg.content.indexOf(thinkingBlock);
|
||||
const toolUseIdx = assistantMsg.content.findIndex((b) => b?.type === "tool_use");
|
||||
assert.ok(thinkingIdx < toolUseIdx, "thinking block should be before tool_use");
|
||||
|
||||
assert.equal(getReasoningCacheServiceStats().replays, 0);
|
||||
clearReasoningCacheAll();
|
||||
});
|
||||
assert.equal(getReasoningCacheServiceStats().replays, 0);
|
||||
clearReasoningCacheAll();
|
||||
});
|
||||
|
||||
test("translateRequest uses an empty Kimi Coding thinking marker on cache miss", () => {
|
||||
clearReasoningCacheAll();
|
||||
test("translateRequest uses an empty Kimi Coding thinking marker on cache miss", () => {
|
||||
clearReasoningCacheAll();
|
||||
|
||||
const result = translateRequest(
|
||||
FORMATS.OPENAI,
|
||||
FORMATS.CLAUDE,
|
||||
"kimi-for-coding",
|
||||
{
|
||||
reasoning_effort: "high",
|
||||
messages: [
|
||||
{ role: "user", content: "do it" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "tool_use", id: "toolu_miss", name: "bash", input: { command: "ls" } },
|
||||
],
|
||||
},
|
||||
{ role: "tool", tool_call_id: "toolu_miss", content: "output" },
|
||||
],
|
||||
},
|
||||
false,
|
||||
null,
|
||||
"kimi-coding"
|
||||
);
|
||||
const result = translateRequest(
|
||||
FORMATS.OPENAI,
|
||||
FORMATS.CLAUDE,
|
||||
"kimi-for-coding",
|
||||
{
|
||||
reasoning_effort: "high",
|
||||
messages: [
|
||||
{ role: "user", content: "do it" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "tool_use", id: "toolu_miss", name: "bash", input: { command: "ls" } }],
|
||||
},
|
||||
{ role: "tool", tool_call_id: "toolu_miss", content: "output" },
|
||||
],
|
||||
},
|
||||
false,
|
||||
null,
|
||||
"kimi-coding"
|
||||
);
|
||||
|
||||
const assistantMsg = result.messages.find((m) => m.role === "assistant");
|
||||
assert.ok(assistantMsg, "assistant message should exist");
|
||||
const assistantMsg = result.messages.find((m) => m.role === "assistant");
|
||||
assert.ok(assistantMsg, "assistant message should exist");
|
||||
|
||||
const thinkingBlock =
|
||||
Array.isArray(assistantMsg.content) &&
|
||||
assistantMsg.content.find((b) => b?.type === "thinking");
|
||||
assert.ok(thinkingBlock, "thinking block should be injected on cache miss");
|
||||
assert.equal(thinkingBlock.thinking, "");
|
||||
const thinkingBlock =
|
||||
Array.isArray(assistantMsg.content) && assistantMsg.content.find((b) => b?.type === "thinking");
|
||||
assert.ok(thinkingBlock, "thinking block should be injected on cache miss");
|
||||
assert.equal(thinkingBlock.thinking, "");
|
||||
|
||||
clearReasoningCacheAll();
|
||||
});
|
||||
clearReasoningCacheAll();
|
||||
});
|
||||
|
||||
test("translateRequest does NOT inject duplicate thinking for Claude-format messages with existing thinking block", () => {
|
||||
clearReasoningCacheAll();
|
||||
test("translateRequest does NOT inject duplicate thinking for Claude-format messages with existing thinking block", () => {
|
||||
clearReasoningCacheAll();
|
||||
|
||||
const result = translateRequest(
|
||||
FORMATS.OPENAI,
|
||||
FORMATS.CLAUDE,
|
||||
"kimi-for-coding",
|
||||
{
|
||||
messages: [
|
||||
{ role: "user", content: "hi" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "thinking", thinking: "I already have this" },
|
||||
{ type: "tool_use", id: "toolu_existing", name: "read", input: {} },
|
||||
],
|
||||
},
|
||||
{ role: "tool", tool_call_id: "toolu_existing", content: "data" },
|
||||
],
|
||||
},
|
||||
false,
|
||||
null,
|
||||
"kimi-coding"
|
||||
);
|
||||
const result = translateRequest(
|
||||
FORMATS.OPENAI,
|
||||
FORMATS.CLAUDE,
|
||||
"kimi-for-coding",
|
||||
{
|
||||
messages: [
|
||||
{ role: "user", content: "hi" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "thinking", thinking: "I already have this" },
|
||||
{ type: "tool_use", id: "toolu_existing", name: "read", input: {} },
|
||||
],
|
||||
},
|
||||
{ role: "tool", tool_call_id: "toolu_existing", content: "data" },
|
||||
],
|
||||
},
|
||||
false,
|
||||
null,
|
||||
"kimi-coding"
|
||||
);
|
||||
|
||||
const assistantMsg = result.messages.find((m) => m.role === "assistant");
|
||||
const thinkingBlocks =
|
||||
Array.isArray(assistantMsg.content) &&
|
||||
assistantMsg.content.filter((b) => b?.type === "thinking");
|
||||
assert.equal(
|
||||
thinkingBlocks?.length,
|
||||
1,
|
||||
"should have exactly one thinking block (no duplicate)"
|
||||
);
|
||||
assert.equal(
|
||||
thinkingBlocks[0].thinking,
|
||||
"I already have this",
|
||||
"original thinking should be preserved"
|
||||
);
|
||||
const assistantMsg = result.messages.find((m) => m.role === "assistant");
|
||||
const thinkingBlocks =
|
||||
Array.isArray(assistantMsg.content) &&
|
||||
assistantMsg.content.filter((b) => b?.type === "thinking");
|
||||
assert.equal(thinkingBlocks?.length, 1, "should have exactly one thinking block (no duplicate)");
|
||||
assert.equal(
|
||||
thinkingBlocks[0].thinking,
|
||||
"I already have this",
|
||||
"original thinking should be preserved"
|
||||
);
|
||||
|
||||
clearReasoningCacheAll();
|
||||
});
|
||||
clearReasoningCacheAll();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user