github-router 0.3.288 → 0.3.289
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{attribution-settings-CofaqSzw.js → attribution-settings-Cmz2jt7P.js} +3 -3
- package/dist/{attribution-settings-CofaqSzw.js.map → attribution-settings-Cmz2jt7P.js.map} +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{claude-CoZKRNB8.js → claude-C-9xFI4b.js} +8 -7
- package/dist/claude-C-9xFI4b.js.map +1 -0
- package/dist/{codex-y2OyLIdv.js → codex-DRW0yb8x.js} +4 -4
- package/dist/{codex-y2OyLIdv.js.map → codex-DRW0yb8x.js.map} +1 -1
- package/dist/engine-iEqGdx6T.js +2 -0
- package/dist/{gate-discovery-LQZ-enJa.js → gate-discovery-Cz6kwIVG.js} +2 -2
- package/dist/{gate-discovery-LQZ-enJa.js.map → gate-discovery-Cz6kwIVG.js.map} +1 -1
- package/dist/{internal-stop-hook-DSbaDb_m.js → internal-stop-hook-Dvkppwo7.js} +2 -2
- package/dist/{internal-stop-hook-DSbaDb_m.js.map → internal-stop-hook-Dvkppwo7.js.map} +1 -1
- package/dist/main.js +5 -5
- package/dist/{peer-mcp-personas-B5Wp6wIn.js → peer-mcp-personas-Bd56EmiO.js} +385 -21
- package/dist/peer-mcp-personas-Bd56EmiO.js.map +1 -0
- package/dist/{provision-CUqPki1z.js → provision-B53wbHwa.js} +2 -2
- package/dist/{provision-CUqPki1z.js.map → provision-B53wbHwa.js.map} +1 -1
- package/dist/{serve-BASqoXb3.js → serve-aZCEYFe5.js} +5 -5
- package/dist/{serve-BASqoXb3.js.map → serve-aZCEYFe5.js.map} +1 -1
- package/dist/{server-setup-D5hilphf.js → server-setup-DlztZAGT.js} +246 -89
- package/dist/server-setup-DlztZAGT.js.map +1 -0
- package/dist/{start-Rfim4TeF.js → start-DwNiXv5N.js} +3 -3
- package/dist/{start-Rfim4TeF.js.map → start-DwNiXv5N.js.map} +1 -1
- package/dist/token-BGCjZwtj.js.map +1 -1
- package/package.json +2 -1
- package/dist/claude-CoZKRNB8.js.map +0 -1
- package/dist/engine-B5nVGH4b.js +0 -2
- package/dist/peer-mcp-personas-B5Wp6wIn.js.map +0 -1
- package/dist/server-setup-D5hilphf.js.map +0 -1
|
@@ -19497,6 +19497,354 @@ function currentInFlight() {
|
|
|
19497
19497
|
return inFlight$2;
|
|
19498
19498
|
}
|
|
19499
19499
|
//#endregion
|
|
19500
|
+
//#region src/lib/prompt-cache.ts
|
|
19501
|
+
/**
|
|
19502
|
+
* Conservative eligibility floor, in UTF-8 BYTES — never `.length`, which
|
|
19503
|
+
* counts UTF-16 code units and undercounts anything outside the BMP (an
|
|
19504
|
+
* emoji is 2 code units but 4 bytes). This is a proxy for "the prefix is
|
|
19505
|
+
* obviously large enough that marking it as a cache breakpoint is worth one
|
|
19506
|
+
* of the scarce marker slots," and it deliberately does NOT claim to be a
|
|
19507
|
+
* token count: byte-to-token density varies by tokenizer and content — CJK
|
|
19508
|
+
* text carries MORE tokens per byte than ASCII prose (undercounting risk is
|
|
19509
|
+
* the SAFE direction: we'd skip a marker that might have qualified), while a
|
|
19510
|
+
* long run of a repeated character or repeated whitespace carries FEWER
|
|
19511
|
+
* tokens per byte than either, since BPE merges long runs into very few
|
|
19512
|
+
* tokens (overcounting risk: a byte count clearing the floor doesn't
|
|
19513
|
+
* guarantee the real token count clears Anthropic's or Copilot's per-model
|
|
19514
|
+
* minimum). No fixed byte threshold can bound that adversarial case; this
|
|
19515
|
+
* value is chosen so ordinary Claude Code system prompts and tool schemas
|
|
19516
|
+
* (natural-language / JSON, not deliberately repetitive) reliably qualify,
|
|
19517
|
+
* while genuinely small prefixes never burn a marker for no benefit.
|
|
19518
|
+
*/
|
|
19519
|
+
const MIN_CACHEABLE_PREFIX_BYTES = 4096;
|
|
19520
|
+
const CACHE_KEY_NAMESPACE = "ghr-cache-v1";
|
|
19521
|
+
const CACHE_DIAGNOSTIC_LIMIT = 128;
|
|
19522
|
+
const GPT56_EXPLICIT_CACHE_MODELS = /* @__PURE__ */ new Set([
|
|
19523
|
+
"gpt-5.6-sol",
|
|
19524
|
+
"gpt-5.6-terra",
|
|
19525
|
+
"gpt-5.6-luna"
|
|
19526
|
+
]);
|
|
19527
|
+
const priorSignatures = /* @__PURE__ */ new Map();
|
|
19528
|
+
function nonNegativeInt(value) {
|
|
19529
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
|
|
19530
|
+
return Math.floor(value);
|
|
19531
|
+
}
|
|
19532
|
+
/**
|
|
19533
|
+
* Pick the first genuinely POSITIVE numeric candidate from an ordered,
|
|
19534
|
+
* priority-ranked list of usage-shape fields. `??`-chaining these fields is
|
|
19535
|
+
* wrong: it stops at the first field that is merely PRESENT, and a provider
|
|
19536
|
+
* surface that always populates a nested detail object with `0` as a
|
|
19537
|
+
* placeholder (while the real, positive count is reported only in a
|
|
19538
|
+
* lower-priority field, e.g. the top-level one) would have its explicit zero
|
|
19539
|
+
* silently shadow that populated count. Falling through zeros to find a real
|
|
19540
|
+
* positive value fixes that; when every candidate is zero, absent, or
|
|
19541
|
+
* non-numeric, this returns `0` — a genuine all-zero reading, never
|
|
19542
|
+
* `undefined` — so downstream `nonNegativeInt` always has a countable value.
|
|
19543
|
+
*/
|
|
19544
|
+
function firstPositive(...candidates) {
|
|
19545
|
+
for (const c of candidates) if (typeof c === "number" && Number.isFinite(c) && c > 0) return c;
|
|
19546
|
+
return 0;
|
|
19547
|
+
}
|
|
19548
|
+
function usageDetails(usage) {
|
|
19549
|
+
const input = usage.input_tokens_details ?? {};
|
|
19550
|
+
const prompt = usage.prompt_tokens_details ?? {};
|
|
19551
|
+
return {
|
|
19552
|
+
cached_tokens: firstPositive(input.cached_tokens, prompt.cached_tokens, usage.cache_read_input_tokens),
|
|
19553
|
+
cache_write_tokens: firstPositive(input.cache_write_tokens, input.cache_creation_tokens, prompt.cache_write_tokens, prompt.cache_creation_tokens, usage.cache_write_tokens, usage.cache_creation_input_tokens),
|
|
19554
|
+
cache_ttl_seconds: firstPositive(input.cache_ttl_seconds, prompt.cache_ttl_seconds, usage.cache_ttl_seconds)
|
|
19555
|
+
};
|
|
19556
|
+
}
|
|
19557
|
+
/**
|
|
19558
|
+
* OpenAI totals INCLUDE cached and cache-write tokens. Normalize them into
|
|
19559
|
+
* mutually exclusive buckets so Anthropic and Pi consumers do not count the
|
|
19560
|
+
* same input twice.
|
|
19561
|
+
*/
|
|
19562
|
+
function normalizeOpenAIUsage(usage) {
|
|
19563
|
+
if (!usage) return {
|
|
19564
|
+
totalInput: 0,
|
|
19565
|
+
uncachedInput: 0,
|
|
19566
|
+
output: 0,
|
|
19567
|
+
cacheRead: 0,
|
|
19568
|
+
cacheWrite: 0,
|
|
19569
|
+
totalTokens: 0
|
|
19570
|
+
};
|
|
19571
|
+
const totalInput = nonNegativeInt(usage.input_tokens ?? usage.prompt_tokens);
|
|
19572
|
+
const output = nonNegativeInt(usage.output_tokens ?? usage.completion_tokens);
|
|
19573
|
+
const details = usageDetails(usage);
|
|
19574
|
+
const cacheRead = Math.min(totalInput, nonNegativeInt(details.cached_tokens));
|
|
19575
|
+
const remaining = Math.max(0, totalInput - cacheRead);
|
|
19576
|
+
const cacheWrite = Math.min(remaining, nonNegativeInt(details.cache_write_tokens ?? details.cache_creation_tokens));
|
|
19577
|
+
const uncachedInput = Math.max(0, totalInput - cacheRead - cacheWrite);
|
|
19578
|
+
const reportedTotal = nonNegativeInt(usage.total_tokens);
|
|
19579
|
+
const cacheTtlSeconds = nonNegativeInt(details.cache_ttl_seconds);
|
|
19580
|
+
return {
|
|
19581
|
+
totalInput,
|
|
19582
|
+
uncachedInput,
|
|
19583
|
+
output,
|
|
19584
|
+
cacheRead,
|
|
19585
|
+
cacheWrite,
|
|
19586
|
+
totalTokens: Math.max(reportedTotal, totalInput + output),
|
|
19587
|
+
...cacheTtlSeconds > 0 ? { cacheTtlSeconds } : {}
|
|
19588
|
+
};
|
|
19589
|
+
}
|
|
19590
|
+
function hash(value) {
|
|
19591
|
+
return createHash("sha256").update(value).digest("hex");
|
|
19592
|
+
}
|
|
19593
|
+
function signatureFor(value) {
|
|
19594
|
+
return hash(typeof value === "string" ? value : JSON.stringify(value ?? null));
|
|
19595
|
+
}
|
|
19596
|
+
function serializedBytes(value) {
|
|
19597
|
+
return Buffer.byteLength(typeof value === "string" ? value : JSON.stringify(value ?? null));
|
|
19598
|
+
}
|
|
19599
|
+
function logCacheSignature(args) {
|
|
19600
|
+
if (parseBoolEnv(process.env.GH_ROUTER_LOG_CACHE) !== true) return;
|
|
19601
|
+
const key = `${args.endpoint}:${args.model}:${args.workload}`;
|
|
19602
|
+
const current = {
|
|
19603
|
+
system: signatureFor(args.system),
|
|
19604
|
+
tools: signatureFor(args.tools),
|
|
19605
|
+
messages: signatureFor(args.messages)
|
|
19606
|
+
};
|
|
19607
|
+
const previous = priorSignatures.get(key);
|
|
19608
|
+
let changed = "cold";
|
|
19609
|
+
if (previous) changed = previous.system !== current.system ? "system" : previous.tools !== current.tools ? "tools" : previous.messages !== current.messages ? "messages" : "none";
|
|
19610
|
+
priorSignatures.set(key, current);
|
|
19611
|
+
if (priorSignatures.size > CACHE_DIAGNOSTIC_LIMIT) {
|
|
19612
|
+
const oldest = priorSignatures.keys().next().value;
|
|
19613
|
+
if (oldest !== void 0) priorSignatures.delete(oldest);
|
|
19614
|
+
}
|
|
19615
|
+
consola.info(`cache-signature endpoint=${args.endpoint} model=${args.model} workload=${args.workload} changed=${changed} system_bytes=${serializedBytes(args.system ?? "")} tools_bytes=${serializedBytes(args.tools ?? [])} messages_bytes=${serializedBytes(args.messages ?? [])}`);
|
|
19616
|
+
}
|
|
19617
|
+
function hasResponsesBreakpoint(input) {
|
|
19618
|
+
const visit = (value) => {
|
|
19619
|
+
if (!value || typeof value !== "object") return false;
|
|
19620
|
+
if (Array.isArray(value)) return value.some(visit);
|
|
19621
|
+
const record = value;
|
|
19622
|
+
return record.prompt_cache_breakpoint !== void 0 || Object.values(record).some(visit);
|
|
19623
|
+
};
|
|
19624
|
+
return visit(input);
|
|
19625
|
+
}
|
|
19626
|
+
function gpt56ExplicitCacheEnabled(model) {
|
|
19627
|
+
if (parseBoolEnv(process.env.GH_ROUTER_DISABLE_GPT56_EXPLICIT_CACHE) === true) return false;
|
|
19628
|
+
return GPT56_EXPLICIT_CACHE_MODELS.has(model);
|
|
19629
|
+
}
|
|
19630
|
+
function responsesCacheKey(payload, opts, stablePrefix) {
|
|
19631
|
+
const digest = hash(JSON.stringify({
|
|
19632
|
+
namespace: CACHE_KEY_NAMESPACE,
|
|
19633
|
+
model: payload.model,
|
|
19634
|
+
workload: opts.workload,
|
|
19635
|
+
scope: opts.scope ?? "",
|
|
19636
|
+
stablePrefix,
|
|
19637
|
+
tools: payload.tools ?? []
|
|
19638
|
+
}));
|
|
19639
|
+
return `${CACHE_KEY_NAMESPACE}-${digest.slice(0, 48)}`;
|
|
19640
|
+
}
|
|
19641
|
+
/**
|
|
19642
|
+
* Add GPT-5.6 explicit caching only to router-owned REUSABLE-PREFIX payloads.
|
|
19643
|
+
* Public passthrough routes never call this helper, and existing caller
|
|
19644
|
+
* fields always win. Live shape acceptance is pinned by compatibility probe
|
|
19645
|
+
* `gpt56_explicit_cache_breakpoint`.
|
|
19646
|
+
*
|
|
19647
|
+
* **`"conversation"` is deliberately EXCLUDED and left untouched (a no-op),
|
|
19648
|
+
* same as `"passthrough"`/`"one-shot"`.** A live-verified regression: on a
|
|
19649
|
+
* growing multi-turn conversation (Claude Code's translated main loop and
|
|
19650
|
+
* the worker-agent loop, both of which pass `workload: "conversation"`),
|
|
19651
|
+
* marking only the stable SYSTEM block with an explicit breakpoint measured
|
|
19652
|
+
* substantially worse than leaving caching provider-managed and implicit.
|
|
19653
|
+
* Explicit mode is a distinct
|
|
19654
|
+
* caching strategy from Copilot's provider-managed automatic caching, not an
|
|
19655
|
+
* addition to it — turning it on for a request marks only the bytes an
|
|
19656
|
+
* explicit breakpoint names, and the REST of that request's prefix (here,
|
|
19657
|
+
* the entire un-marked growing message history) stops receiving automatic
|
|
19658
|
+
* prefix-growth caching too. Measured on `gpt-5.6-sol` with explicit mode
|
|
19659
|
+
* force-enabled for conversation workloads: turn 1 (cold)
|
|
19660
|
+
* `input_tokens=27038, cache_write=2031, cache_read=0`; turn 2
|
|
19661
|
+
* `input_tokens=27054, cache_read=2031`; turn 3 `input_tokens=27071,
|
|
19662
|
+
* cache_read=2031` — the ~2k-token system block cached once and never grew,
|
|
19663
|
+
* while the other ~25k tokens of accumulating history were recomputed from
|
|
19664
|
+
* scratch on every single turn. `"reusable-prefix"` calls (peer/advisor/
|
|
19665
|
+
* worker-tool/browser-compressor prefixes reused verbatim across many
|
|
19666
|
+
* DISCRETE calls, never a single request whose own history keeps growing)
|
|
19667
|
+
* do not have this failure mode and keep the explicit treatment below.
|
|
19668
|
+
*/
|
|
19669
|
+
function applyResponsesCachePolicy(payload, opts) {
|
|
19670
|
+
logCacheSignature({
|
|
19671
|
+
endpoint: "/responses",
|
|
19672
|
+
model: payload.model,
|
|
19673
|
+
workload: opts.workload,
|
|
19674
|
+
system: opts.stablePrefix ?? payload.instructions,
|
|
19675
|
+
tools: payload.tools,
|
|
19676
|
+
messages: payload.input
|
|
19677
|
+
});
|
|
19678
|
+
if (opts.workload !== "reusable-prefix" || !gpt56ExplicitCacheEnabled(payload.model) || payload.prompt_cache_key !== void 0 || payload.prompt_cache_options !== void 0 || hasResponsesBreakpoint(payload.input)) return payload;
|
|
19679
|
+
const stablePrefix = opts.stablePrefix ?? payload.instructions;
|
|
19680
|
+
const stableBytes = serializedBytes(stablePrefix ?? "") + serializedBytes(payload.tools ?? []);
|
|
19681
|
+
if (!stablePrefix || stableBytes < MIN_CACHEABLE_PREFIX_BYTES) return payload;
|
|
19682
|
+
const input = typeof payload.input === "string" ? [{
|
|
19683
|
+
role: "user",
|
|
19684
|
+
content: payload.input
|
|
19685
|
+
}] : [...payload.input];
|
|
19686
|
+
const stableContent = [{
|
|
19687
|
+
type: "input_text",
|
|
19688
|
+
text: stablePrefix,
|
|
19689
|
+
prompt_cache_breakpoint: { mode: "explicit" }
|
|
19690
|
+
}];
|
|
19691
|
+
let nextInput;
|
|
19692
|
+
let removeInstructions = false;
|
|
19693
|
+
if (payload.instructions === stablePrefix) {
|
|
19694
|
+
nextInput = [{
|
|
19695
|
+
role: "system",
|
|
19696
|
+
content: stableContent
|
|
19697
|
+
}, ...input];
|
|
19698
|
+
removeInstructions = true;
|
|
19699
|
+
} else {
|
|
19700
|
+
const stableSystemIndex = input.findIndex((item) => item.role === "system" && item.content === stablePrefix);
|
|
19701
|
+
if (stableSystemIndex < 0) return payload;
|
|
19702
|
+
nextInput = [...input];
|
|
19703
|
+
nextInput[stableSystemIndex] = {
|
|
19704
|
+
...nextInput[stableSystemIndex],
|
|
19705
|
+
content: stableContent
|
|
19706
|
+
};
|
|
19707
|
+
}
|
|
19708
|
+
const next = {
|
|
19709
|
+
...payload,
|
|
19710
|
+
input: nextInput,
|
|
19711
|
+
prompt_cache_key: responsesCacheKey(payload, opts, stablePrefix),
|
|
19712
|
+
prompt_cache_options: {
|
|
19713
|
+
mode: "explicit",
|
|
19714
|
+
ttl: "30m"
|
|
19715
|
+
}
|
|
19716
|
+
};
|
|
19717
|
+
if (removeInstructions) delete next.instructions;
|
|
19718
|
+
return next;
|
|
19719
|
+
}
|
|
19720
|
+
function itemHasCacheControl(value) {
|
|
19721
|
+
return !!value && typeof value === "object" && value.cache_control !== void 0;
|
|
19722
|
+
}
|
|
19723
|
+
function hasClaudeCacheControl(body) {
|
|
19724
|
+
if (itemHasCacheControl(body.system)) return true;
|
|
19725
|
+
if (Array.isArray(body.system) && body.system.some(itemHasCacheControl)) return true;
|
|
19726
|
+
if (Array.isArray(body.tools) && body.tools.some(itemHasCacheControl)) return true;
|
|
19727
|
+
if (!Array.isArray(body.messages)) return false;
|
|
19728
|
+
return body.messages.some((message) => {
|
|
19729
|
+
if (!message || typeof message !== "object") return false;
|
|
19730
|
+
const content = message.content;
|
|
19731
|
+
return itemHasCacheControl(content) || Array.isArray(content) && content.some(itemHasCacheControl);
|
|
19732
|
+
});
|
|
19733
|
+
}
|
|
19734
|
+
/**
|
|
19735
|
+
* UTF-8 byte length of the tools array alone — the eligibility floor for the
|
|
19736
|
+
* TOOL breakpoint. Marked on the last non-deferred tool, it caches only the
|
|
19737
|
+
* tools prefix (Claude's wire order is tools, then system, then messages), so
|
|
19738
|
+
* its own size — not the combined system+tools size — is what determines
|
|
19739
|
+
* whether that marker is worth spending. See `MIN_CACHEABLE_PREFIX_BYTES`.
|
|
19740
|
+
*/
|
|
19741
|
+
function claudeToolsPrefixBytes(body) {
|
|
19742
|
+
return serializedBytes(body.tools ?? []);
|
|
19743
|
+
}
|
|
19744
|
+
/**
|
|
19745
|
+
* UTF-8 byte length of tools + system combined — the eligibility floor for
|
|
19746
|
+
* the SYSTEM breakpoint. Marked on the last system text block, it caches
|
|
19747
|
+
* everything up to and including system (tools THEN system in wire order),
|
|
19748
|
+
* so the combined size is the right measure — checked SEPARATELY from the
|
|
19749
|
+
* tools-only floor above so a large system prompt behind tiny tools doesn't
|
|
19750
|
+
* smuggle a useless tools-only marker in under the combined total, and a
|
|
19751
|
+
* large tools array behind an empty system doesn't get double-counted as
|
|
19752
|
+
* "small" just because system alone is tiny.
|
|
19753
|
+
*/
|
|
19754
|
+
function claudeSystemPrefixBytes(body) {
|
|
19755
|
+
return claudeToolsPrefixBytes(body) + serializedBytes(body.system ?? "");
|
|
19756
|
+
}
|
|
19757
|
+
function markClaudeSystem(body) {
|
|
19758
|
+
if (typeof body.system === "string" && body.system.length > 0) {
|
|
19759
|
+
body.system = [{
|
|
19760
|
+
type: "text",
|
|
19761
|
+
text: body.system,
|
|
19762
|
+
cache_control: { type: "ephemeral" }
|
|
19763
|
+
}];
|
|
19764
|
+
return true;
|
|
19765
|
+
}
|
|
19766
|
+
if (!Array.isArray(body.system)) return false;
|
|
19767
|
+
for (let index = body.system.length - 1; index >= 0; index--) {
|
|
19768
|
+
const block = body.system[index];
|
|
19769
|
+
if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
|
|
19770
|
+
body.system[index] = {
|
|
19771
|
+
...block,
|
|
19772
|
+
cache_control: { type: "ephemeral" }
|
|
19773
|
+
};
|
|
19774
|
+
return true;
|
|
19775
|
+
}
|
|
19776
|
+
}
|
|
19777
|
+
return false;
|
|
19778
|
+
}
|
|
19779
|
+
function markClaudeTool(body) {
|
|
19780
|
+
if (!Array.isArray(body.tools)) return false;
|
|
19781
|
+
for (let index = body.tools.length - 1; index >= 0; index--) {
|
|
19782
|
+
const tool = body.tools[index];
|
|
19783
|
+
if (tool && typeof tool === "object" && tool.defer_loading !== true) {
|
|
19784
|
+
body.tools[index] = {
|
|
19785
|
+
...tool,
|
|
19786
|
+
cache_control: { type: "ephemeral" }
|
|
19787
|
+
};
|
|
19788
|
+
return true;
|
|
19789
|
+
}
|
|
19790
|
+
}
|
|
19791
|
+
return false;
|
|
19792
|
+
}
|
|
19793
|
+
/**
|
|
19794
|
+
* Apply the bounded Claude anchor policy to router-generated Messages bodies.
|
|
19795
|
+
* Caller-owned marker layouts are returned byte-for-byte unchanged.
|
|
19796
|
+
*
|
|
19797
|
+
* Marks at most TWO breakpoints — the last non-deferred tool and the stable
|
|
19798
|
+
* system boundary — each gated on its OWN eligibility check
|
|
19799
|
+
* (`claudeToolsPrefixBytes` / `claudeSystemPrefixBytes`) rather than one
|
|
19800
|
+
* combined check, so a large system prompt behind tiny tools doesn't also
|
|
19801
|
+
* mark a tools breakpoint too small to be worth a marker slot, and vice
|
|
19802
|
+
* versa. Anthropic's own hard ceiling is FOUR `cache_control` blocks per
|
|
19803
|
+
* request (probe `cache_control_marker_limit_5`); this policy only ever
|
|
19804
|
+
* spends up to two of them (`hasClaudeCacheControl` already refuses to run
|
|
19805
|
+
* at all once the caller has marked anything itself, so the two never
|
|
19806
|
+
* combine with a caller-owned marker to approach that ceiling).
|
|
19807
|
+
*
|
|
19808
|
+
* There used to be a third, message-level marking path gated on
|
|
19809
|
+
* `opts.workload === "conversation"`. It was removed as dead code: every
|
|
19810
|
+
* production call site of this function (`src/routes/mcp/handler.ts`,
|
|
19811
|
+
* `src/services/advisor/advisor.ts`) passes `workload: "reusable-prefix"`,
|
|
19812
|
+
* so the per-message branch never ran outside its own unit test, which gave
|
|
19813
|
+
* false confidence that production traffic exercised it. `CacheWorkload`
|
|
19814
|
+
* keeps `"conversation"` as a shared enum value — the Responses-side policy,
|
|
19815
|
+
* which has no per-message logic of its own, still uses it — so passing it
|
|
19816
|
+
* here remains type-valid; it now behaves identically to
|
|
19817
|
+
* `"reusable-prefix"`.
|
|
19818
|
+
*/
|
|
19819
|
+
function applyClaudeCachePolicy(rawBody, opts) {
|
|
19820
|
+
if (opts.workload === "passthrough" || opts.workload === "one-shot" || parseBoolEnv(process.env.GH_ROUTER_DISABLE_CLAUDE_CACHE_POLICY) === true) return rawBody;
|
|
19821
|
+
let parsed;
|
|
19822
|
+
try {
|
|
19823
|
+
parsed = JSON.parse(rawBody);
|
|
19824
|
+
} catch {
|
|
19825
|
+
return rawBody;
|
|
19826
|
+
}
|
|
19827
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return rawBody;
|
|
19828
|
+
const body = parsed;
|
|
19829
|
+
if (typeof body.model !== "string" || !body.model.startsWith("claude-") || hasClaudeCacheControl(body)) return rawBody;
|
|
19830
|
+
const toolsEligible = claudeToolsPrefixBytes(body) >= MIN_CACHEABLE_PREFIX_BYTES;
|
|
19831
|
+
const systemEligible = claudeSystemPrefixBytes(body) >= MIN_CACHEABLE_PREFIX_BYTES;
|
|
19832
|
+
if (!toolsEligible && !systemEligible) return rawBody;
|
|
19833
|
+
let markers = 0;
|
|
19834
|
+
if (toolsEligible && markClaudeTool(body)) markers++;
|
|
19835
|
+
if (systemEligible && markClaudeSystem(body)) markers++;
|
|
19836
|
+
if (markers === 0) return rawBody;
|
|
19837
|
+
logCacheSignature({
|
|
19838
|
+
endpoint: "/messages",
|
|
19839
|
+
model: body.model,
|
|
19840
|
+
workload: opts.workload,
|
|
19841
|
+
system: body.system,
|
|
19842
|
+
tools: body.tools,
|
|
19843
|
+
messages: body.messages
|
|
19844
|
+
});
|
|
19845
|
+
return JSON.stringify(body);
|
|
19846
|
+
}
|
|
19847
|
+
//#endregion
|
|
19500
19848
|
//#region src/lib/vision-preflight.ts
|
|
19501
19849
|
/**
|
|
19502
19850
|
* Outbound vision handling.
|
|
@@ -20421,7 +20769,7 @@ async function callViaChat(model, systemPrompt, userMessage, tool, signal) {
|
|
|
20421
20769
|
* Image parts use `input_image` (vs chat's `image_url`) — see
|
|
20422
20770
|
* `toResponsesContent`. */
|
|
20423
20771
|
async function callViaResponses(model, systemPrompt, userMessage, tool, signal) {
|
|
20424
|
-
const payload = {
|
|
20772
|
+
const payload = applyResponsesCachePolicy({
|
|
20425
20773
|
model,
|
|
20426
20774
|
stream: false,
|
|
20427
20775
|
input: [{
|
|
@@ -20441,7 +20789,10 @@ async function callViaResponses(model, systemPrompt, userMessage, tool, signal)
|
|
|
20441
20789
|
type: "function",
|
|
20442
20790
|
name: tool.name
|
|
20443
20791
|
}
|
|
20444
|
-
}
|
|
20792
|
+
}, {
|
|
20793
|
+
workload: "reusable-prefix",
|
|
20794
|
+
stablePrefix: systemPrompt
|
|
20795
|
+
});
|
|
20445
20796
|
const resp = await createResponses(payload, void 0, signal, true);
|
|
20446
20797
|
const output = Array.isArray(resp.output) ? resp.output : [];
|
|
20447
20798
|
for (const item of output) {
|
|
@@ -24491,6 +24842,10 @@ function neutralToolsToResponses(tools) {
|
|
|
24491
24842
|
/** Assemble the full Responses payload from the neutral request shape. */
|
|
24492
24843
|
function assembleResponsesPayload(opts) {
|
|
24493
24844
|
const input = [];
|
|
24845
|
+
if (opts.dynamicInstructions) input.push({
|
|
24846
|
+
role: "system",
|
|
24847
|
+
content: opts.dynamicInstructions
|
|
24848
|
+
});
|
|
24494
24849
|
for (const m of opts.messages) for (const item of neutralMessageToResponsesInput(m)) input.push(item);
|
|
24495
24850
|
const payload = {
|
|
24496
24851
|
model: opts.model,
|
|
@@ -24507,7 +24862,10 @@ function assembleResponsesPayload(opts) {
|
|
|
24507
24862
|
if (typeof opts.maxOutputTokens === "number" && opts.maxOutputTokens > 0) payload.max_output_tokens = Math.max(opts.maxOutputTokens, RESPONSES_MIN_MAX_OUTPUT_TOKENS);
|
|
24508
24863
|
if (opts.stopSequences && opts.stopSequences.length > 0) payload.stop = [...opts.stopSequences];
|
|
24509
24864
|
if (opts.parallelToolCalls === false) payload.parallel_tool_calls = false;
|
|
24510
|
-
return payload
|
|
24865
|
+
return opts.cachePolicy ? applyResponsesCachePolicy(payload, {
|
|
24866
|
+
...opts.cachePolicy,
|
|
24867
|
+
stablePrefix: opts.cachePolicy.stablePrefix ?? opts.instructions
|
|
24868
|
+
}) : payload;
|
|
24511
24869
|
}
|
|
24512
24870
|
//#endregion
|
|
24513
24871
|
//#region src/lib/worker-agent/context-budget.ts
|
|
@@ -25042,7 +25400,10 @@ function mapResponsesUsage(u) {
|
|
|
25042
25400
|
prompt_tokens: u.input_tokens ?? 0,
|
|
25043
25401
|
completion_tokens: u.output_tokens ?? 0,
|
|
25044
25402
|
total_tokens: u.total_tokens ?? 0,
|
|
25045
|
-
prompt_tokens_details: u.input_tokens_details
|
|
25403
|
+
prompt_tokens_details: u.input_tokens_details != null ? {
|
|
25404
|
+
cached_tokens: u.input_tokens_details.cached_tokens ?? 0,
|
|
25405
|
+
cache_write_tokens: u.input_tokens_details.cache_write_tokens ?? u.input_tokens_details.cache_creation_tokens ?? 0
|
|
25406
|
+
} : void 0
|
|
25046
25407
|
};
|
|
25047
25408
|
}
|
|
25048
25409
|
/**
|
|
@@ -25291,6 +25652,7 @@ function buildResponsesPayload(context, resolved) {
|
|
|
25291
25652
|
messages,
|
|
25292
25653
|
tools: piToolsToNeutral(context.tools),
|
|
25293
25654
|
reasoningEffort: resolved.thinking,
|
|
25655
|
+
cachePolicy: { workload: "conversation" },
|
|
25294
25656
|
stream: true
|
|
25295
25657
|
});
|
|
25296
25658
|
}
|
|
@@ -25513,12 +25875,13 @@ function emptyUsage() {
|
|
|
25513
25875
|
}
|
|
25514
25876
|
function deriveUsage(u) {
|
|
25515
25877
|
if (!u) return emptyUsage();
|
|
25878
|
+
const normalized = normalizeOpenAIUsage(u);
|
|
25516
25879
|
return {
|
|
25517
|
-
input:
|
|
25518
|
-
output:
|
|
25519
|
-
cacheRead:
|
|
25520
|
-
cacheWrite:
|
|
25521
|
-
totalTokens:
|
|
25880
|
+
input: normalized.uncachedInput,
|
|
25881
|
+
output: normalized.output,
|
|
25882
|
+
cacheRead: normalized.cacheRead,
|
|
25883
|
+
cacheWrite: normalized.cacheWrite,
|
|
25884
|
+
totalTokens: normalized.totalTokens,
|
|
25522
25885
|
cost: {
|
|
25523
25886
|
input: 0,
|
|
25524
25887
|
output: 0,
|
|
@@ -27839,7 +28202,7 @@ function jsonPathPreflightCap(body, scope) {
|
|
|
27839
28202
|
async function dispatchModelCall(args) {
|
|
27840
28203
|
const resolvedModel = resolveModel(args.model);
|
|
27841
28204
|
if (args.endpoint === "/v1/responses") {
|
|
27842
|
-
const payload = {
|
|
28205
|
+
const payload = applyResponsesCachePolicy({
|
|
27843
28206
|
model: resolvedModel,
|
|
27844
28207
|
instructions: args.instructions,
|
|
27845
28208
|
input: [{
|
|
@@ -27854,7 +28217,7 @@ async function dispatchModelCall(args) {
|
|
|
27854
28217
|
}],
|
|
27855
28218
|
stream: false,
|
|
27856
28219
|
reasoning: { effort: args.effort }
|
|
27857
|
-
};
|
|
28220
|
+
}, { workload: "reusable-prefix" });
|
|
27858
28221
|
return extractResponsesText(await withTransientRetry(() => createResponses(payload, void 0, args.signal), {
|
|
27859
28222
|
signal: args.signal,
|
|
27860
28223
|
label: resolvedModel
|
|
@@ -27862,7 +28225,7 @@ async function dispatchModelCall(args) {
|
|
|
27862
28225
|
}
|
|
27863
28226
|
if (args.endpoint === "/v1/messages") {
|
|
27864
28227
|
const maxTokens = args.effort === "low" ? 4096 : args.effort === "medium" ? 8192 : args.effort === "high" ? 16384 : 32768;
|
|
27865
|
-
const body = JSON.stringify({
|
|
28228
|
+
const body = applyClaudeCachePolicy(JSON.stringify({
|
|
27866
28229
|
model: resolvedModel,
|
|
27867
28230
|
max_tokens: maxTokens,
|
|
27868
28231
|
system: args.instructions,
|
|
@@ -27885,7 +28248,7 @@ async function dispatchModelCall(args) {
|
|
|
27885
28248
|
role: "user",
|
|
27886
28249
|
content: args.userText
|
|
27887
28250
|
}]
|
|
27888
|
-
});
|
|
28251
|
+
}), { workload: "reusable-prefix" });
|
|
27889
28252
|
return extractMessagesText(await (await withTransientRetry(() => createMessages(body, void 0, args.signal), {
|
|
27890
28253
|
signal: args.signal,
|
|
27891
28254
|
label: resolvedModel
|
|
@@ -29387,7 +29750,7 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
|
|
|
29387
29750
|
}
|
|
29388
29751
|
const conversationText = renderConversationAsText(conversation, maxUnits, measure);
|
|
29389
29752
|
if (advisorUsesResponses(resolvedAdvisorModel)) {
|
|
29390
|
-
const payload = {
|
|
29753
|
+
const payload = applyResponsesCachePolicy({
|
|
29391
29754
|
model: resolvedAdvisorModel,
|
|
29392
29755
|
instructions: advisorSystem,
|
|
29393
29756
|
input: [{
|
|
@@ -29399,7 +29762,7 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
|
|
|
29399
29762
|
}],
|
|
29400
29763
|
stream: false,
|
|
29401
29764
|
reasoning: { effort: advisorEffort }
|
|
29402
|
-
};
|
|
29765
|
+
}, { workload: "reusable-prefix" });
|
|
29403
29766
|
const response = await withTransientRetry(() => createResponses(payload, void 0, signal), {
|
|
29404
29767
|
signal,
|
|
29405
29768
|
label: resolvedAdvisorModel
|
|
@@ -29424,7 +29787,7 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
|
|
|
29424
29787
|
const advisorEntry = state.models?.data?.find((m) => m.id === resolvedAdvisorModel);
|
|
29425
29788
|
const limits = advisorEntry?.capabilities?.limits;
|
|
29426
29789
|
const maxTokens = limits?.max_non_streaming_output_tokens ?? limits?.max_output_tokens ?? ADVISOR_FALLBACK_MAX_OUTPUT_TOKENS;
|
|
29427
|
-
const advisorBody = JSON.stringify({
|
|
29790
|
+
const advisorBody = applyClaudeCachePolicy(JSON.stringify({
|
|
29428
29791
|
model: resolvedAdvisorModel,
|
|
29429
29792
|
max_tokens: maxTokens,
|
|
29430
29793
|
system: advisorSystem,
|
|
@@ -29437,7 +29800,7 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
|
|
|
29437
29800
|
thinking: { type: "adaptive" },
|
|
29438
29801
|
...advertisedEffortLadder(resolvedAdvisorModel) ? { output_config: { effort: advisorEffort } } : {}
|
|
29439
29802
|
} : {}
|
|
29440
|
-
});
|
|
29803
|
+
}), { workload: "reusable-prefix" });
|
|
29441
29804
|
const json = await (await withTransientRetry(() => createMessages(advisorBody, {}, signal), {
|
|
29442
29805
|
signal,
|
|
29443
29806
|
label: resolvedAdvisorModel
|
|
@@ -31715,7 +32078,7 @@ function advisorTool(getMessages) {
|
|
|
31715
32078
|
const release = acquireInFlightSlot();
|
|
31716
32079
|
if (!release) throw new Error(`advisor: MCP in-flight cap (${MAX_INFLIGHT_TOOLS_CALL}) saturated; retry shortly`);
|
|
31717
32080
|
try {
|
|
31718
|
-
const
|
|
32081
|
+
const payload = applyResponsesCachePolicy({
|
|
31719
32082
|
model: resolvedModel,
|
|
31720
32083
|
instructions: advisorSystem,
|
|
31721
32084
|
input: [{
|
|
@@ -31727,7 +32090,8 @@ function advisorTool(getMessages) {
|
|
|
31727
32090
|
}],
|
|
31728
32091
|
stream: false,
|
|
31729
32092
|
reasoning: { effort: ADVISOR_DEFAULT_EFFORT }
|
|
31730
|
-
},
|
|
32093
|
+
}, { workload: "reusable-prefix" });
|
|
32094
|
+
const text = extractResponsesText(await createResponses(payload, void 0, signal, true));
|
|
31731
32095
|
if (!text) throw new Error("advisor returned empty output");
|
|
31732
32096
|
return textResult(text);
|
|
31733
32097
|
} finally {
|
|
@@ -36111,6 +36475,6 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
|
|
|
36111
36475
|
return [...new Set(names)];
|
|
36112
36476
|
}
|
|
36113
36477
|
//#endregion
|
|
36114
|
-
export { handleMcpDelete as $,
|
|
36478
|
+
export { handleMcpDelete as $, UPSTREAM_INACTIVITY_TIMEOUT_MS as $t, satisfiesMinVersion as A, readResponseBodyCapped as At, rememberThinkingHistoryRepair as B, provisionTreeSitterAssets as Bt, availableToolCommands as C, getTokenCount as Ct, vscodeRipgrepPath as D, createResponses as Dt, toolbeltSkipSet as E, resolveMcpToolTimeoutMs as Et, injectAdvisorTool as F, colbertDegradedWarning as Ft, isControllerClosedError as G, toolbeltPathOverride as Gt, repairRejectedThinkingHistory as H, DEFINITION_OF_GREATNESS as Ht, isAdvisorRequested as I, provisionAndIndexColbert as It, relayAnthropicStream as J, DEFAULT_CLAUDE_MODEL_FALLBACKS as Jt, logStreamError as K, BUDGET_SMALL_FAST_CATALOG_ID as Kt, resolveAdvisorEffort as L, extractTarGzMember as Lt, ADVISOR_INTERNAL_TOOL_NAME as M, normalizeOpenAIUsage as Mt, ADVISOR_TOOL_INSTRUCTIONS as N, provisionBrowserAssets as Nt, TOOLBELT_TOOLS$1 as O, createChatCompletions as Ot, buildAdvisorStream as P, hasSupportedBrowserInstalled as Pt, clampEffort as Q, UPSTREAM_FETCH_TIMEOUT_MS as Qt, resolveAdvisorModel as R, extractZipMember as Rt, buildEnv as S, createMessages as St, toolbeltEnabled as T, warnOnTokenPriceDrift as Tt, buildAnthropicErrorEvent as U, shouldUseInsecureTls as Ut, repairKnownThinkingHistory as V, CONDENSED_OPERATING_SEQUENCE as Vt, buildOpenAIErrorEvent as W, collapsePathKeys as Wt, UNKNOWN_EFFORT_ANCHOR as X, DEFAULT_CODEX_MODEL_FALLBACKS as Xt, EFFORT_ORDER as Y, DEFAULT_CODEX_MODEL as Yt, bucketEffort as Z, DEFAULT_PORT as Zt, appendPlanReminder as _, scribeModel as _t, buildPeerAwarenessSnippet as a, upstreamMaxConnections as an, browseAgentEnabled as at, resolveWorkerRunOpts as b, shimDefaultsToXhigh as bt, personasFor as c, withOneMSuffixForLead as cn, fleetToolsEnabled as ct, EXPLORE_DEFAULT_MODEL as d, implementerFastModel as dt, generateRandomPort as en, handleMcpPost as et, EXPLORE_DEFAULT_THINKING as f, nativeSubagentModel as ft, TEST_DEFAULT_MODEL as g, scoutModel as gt, REVIEW_DEFAULT_MODEL as h, reviewerModel as ht, buildAgentPrompt as i, upstreamAllowH2 as in, brainstormModel as it, searchWeb as j, parseJsonOrDiagnose as jt, assetFor as k, MAX_RESPONSE_BODY_BYTES as kt, BROWSE_DEFAULT_MODEL as l, withInstallLock as ln, geminiAvailable as lt, PLAN_DEFAULT_MODEL as m, reviewerFastModel as mt, MCP_GROUPS as n, pickClaudeDefault as nn, agentToolsEnabled as nt, buildPeerAwarenessSummary as o, classifyMessagesRoute as on, browserCompoundToolsEnabled as ot, IMPLEMENT_DEFAULT_MODEL as p, resolveGeminiReviewModel as pt, readIteratorWithTimeout as q, BUDGET_SMALL_FAST_SLUG as qt, assertMcpToolSurfaceConsistent as r, resolveLeadSlugArg as rn, artifactToolsEnabled as rt, enumerateInjectedMcpToolNames as s, withOneMSuffix as sn, browserToolsEnabled as st, GROUP_META as t, isBudgetClaudeLead as tn, REVIEW_FAST_DEFAULT_MODEL as tt, DEFAULT_MODEL_CHAIN as u, generalPurposeFastModel as ut, resolveDefaultModel as v, standInToolEnabled as vt, buildToolbeltAwareness as w, assembleResponsesPayload as wt, runWorkerAgent as x, countTokens as xt, resolveModeDefaults as y, workerToolsEnabled as yt, formatThinkingRepairDecline as z, warmTreeSitterPool as zt };
|
|
36115
36479
|
|
|
36116
|
-
//# sourceMappingURL=peer-mcp-personas-
|
|
36480
|
+
//# sourceMappingURL=peer-mcp-personas-Bd56EmiO.js.map
|