context-doctor 0.31.0 → 0.32.0
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/README.md +25 -25
- package/dist/autoclear.d.ts +58 -7
- package/dist/autoclear.js +155 -30
- package/dist/autopilot.d.ts +23 -3
- package/dist/autopilot.js +100 -29
- package/dist/cache.d.ts +6 -2
- package/dist/cache.js +48 -23
- package/dist/cacheplan.d.ts +5 -3
- package/dist/cacheplan.js +46 -8
- package/dist/calibration.d.ts +7 -2
- package/dist/calibration.js +14 -3
- package/dist/ci.js +25 -5
- package/dist/cli.js +61 -26
- package/dist/coldresume.d.ts +32 -7
- package/dist/coldresume.js +148 -34
- package/dist/compactquality.js +34 -12
- package/dist/compactwindow.js +59 -34
- package/dist/config.d.ts +6 -0
- package/dist/config.js +4 -1
- package/dist/cursor.d.ts +9 -0
- package/dist/cursor.js +53 -22
- package/dist/dashboard.d.ts +10 -0
- package/dist/dashboard.js +27 -2
- package/dist/diff.js +10 -3
- package/dist/doctor.js +133 -18
- package/dist/embed.d.ts +9 -0
- package/dist/embed.js +40 -5
- package/dist/experiment.d.ts +1 -0
- package/dist/experiment.js +6 -3
- package/dist/hook.js +23 -4
- package/dist/impact.d.ts +32 -0
- package/dist/impact.js +45 -6
- package/dist/install.d.ts +34 -2
- package/dist/install.js +223 -30
- package/dist/ledger.js +9 -2
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2 -2
- package/dist/mcpschema.d.ts +16 -1
- package/dist/mcpschema.js +95 -24
- package/dist/optimize.js +149 -64
- package/dist/overhead.js +57 -27
- package/dist/pack.d.ts +11 -0
- package/dist/pack.js +75 -12
- package/dist/parse.js +42 -18
- package/dist/pricing.d.ts +57 -5
- package/dist/pricing.js +94 -8
- package/dist/proxy.d.ts +13 -2
- package/dist/proxy.js +258 -43
- package/dist/reads.d.ts +1 -1
- package/dist/reads.js +61 -24
- package/dist/report.d.ts +10 -0
- package/dist/report.js +12 -4
- package/dist/savings.d.ts +20 -8
- package/dist/savings.js +79 -34
- package/dist/session.d.ts +37 -0
- package/dist/session.js +143 -6
- package/dist/split.js +6 -1
- package/dist/statusline.d.ts +3 -1
- package/dist/statusline.js +29 -17
- package/dist/subagentpay.d.ts +5 -2
- package/dist/subagentpay.js +50 -16
- package/dist/subagents.d.ts +7 -3
- package/dist/subagents.js +28 -8
- package/dist/tasks.d.ts +22 -7
- package/dist/tasks.js +47 -20
- package/dist/wrap.d.ts +6 -0
- package/dist/wrap.js +142 -6
- package/package.json +17 -2
package/dist/parse.js
CHANGED
|
@@ -51,6 +51,23 @@ function flattenContent(content) {
|
|
|
51
51
|
hasBinary = true;
|
|
52
52
|
text += `[${b.type}]`;
|
|
53
53
|
break;
|
|
54
|
+
// OpenAI's media blocks (Chat Completions and Responses). Stringified,
|
|
55
|
+
// a 300 KB base64 data URL counted as ~75k text tokens and tripped the
|
|
56
|
+
// base64 finding; it is an attachment, counted like Anthropic's above.
|
|
57
|
+
case "image_url":
|
|
58
|
+
case "input_image":
|
|
59
|
+
hasBinary = true;
|
|
60
|
+
text += "[image]";
|
|
61
|
+
break;
|
|
62
|
+
case "input_audio":
|
|
63
|
+
hasBinary = true;
|
|
64
|
+
text += "[audio]";
|
|
65
|
+
break;
|
|
66
|
+
case "file":
|
|
67
|
+
case "input_file":
|
|
68
|
+
hasBinary = true;
|
|
69
|
+
text += "[file]";
|
|
70
|
+
break;
|
|
54
71
|
case "thinking":
|
|
55
72
|
// Count the thinking text but NOT the signature — it is an opaque
|
|
56
73
|
// base64 blob the API requires, not something the user can trim.
|
|
@@ -71,7 +88,8 @@ function normalizeMessage(rawInput, index) {
|
|
|
71
88
|
const raw = rawInput && typeof rawInput === "object" ? rawInput : {};
|
|
72
89
|
const role = String(raw.role ?? "user");
|
|
73
90
|
const flat = flattenContent(raw.content);
|
|
74
|
-
|
|
91
|
+
// OpenAI's "developer" role is the system prompt under its newer name (o1 onward).
|
|
92
|
+
let kind = flat.kind ?? (role === "developer" ? "system" : ["system", "user", "assistant"].includes(role) ? role : "other");
|
|
75
93
|
let toolName = flat.toolName;
|
|
76
94
|
let text = flat.text;
|
|
77
95
|
let toolCallText = flat.toolCallText;
|
|
@@ -93,30 +111,36 @@ function normalizeMessage(rawInput, index) {
|
|
|
93
111
|
}
|
|
94
112
|
return { index, role, kind, text, toolName, toolCallText, hasBinary: flat.hasBinary, isError: flat.isError };
|
|
95
113
|
}
|
|
114
|
+
/** Raw text: the whole input as one user message, so profiling still works for raw prompts. */
|
|
115
|
+
function rawText(input, parseError) {
|
|
116
|
+
// If it LOOKS like JSON, the user handed us a broken conversation file and
|
|
117
|
+
// deserves to be told, not given a report about a single 9-token "message".
|
|
118
|
+
const head = input.trimStart()[0];
|
|
119
|
+
const parseWarning = input.trim() === ""
|
|
120
|
+
? "Input is empty — nothing to profile."
|
|
121
|
+
: parseError && (head === "{" || head === "[")
|
|
122
|
+
? `Input starts like JSON but does not parse (${parseError.message}). Profiling it as raw text, which is almost certainly not what you want.`
|
|
123
|
+
: undefined;
|
|
124
|
+
return {
|
|
125
|
+
sourceFormat: "text",
|
|
126
|
+
parseWarning,
|
|
127
|
+
// Empty input has no message: reporting "1 message, ~4 tokens" for it
|
|
128
|
+
// would be inventing content that is not there.
|
|
129
|
+
messages: input.trim() === "" ? [] : [{ index: 0, role: "user", kind: "user", text: input, hasBinary: false }],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
96
132
|
export function parseConversation(input) {
|
|
97
133
|
let data;
|
|
98
134
|
try {
|
|
99
135
|
data = JSON.parse(input);
|
|
100
136
|
}
|
|
101
137
|
catch (e) {
|
|
102
|
-
|
|
103
|
-
// works for raw prompts. But if it LOOKS like JSON, the user handed us a
|
|
104
|
-
// broken conversation file and deserves to be told, not given a report
|
|
105
|
-
// about a single 9-token "message".
|
|
106
|
-
const head = input.trimStart()[0];
|
|
107
|
-
const parseWarning = input.trim() === ""
|
|
108
|
-
? "Input is empty — nothing to profile."
|
|
109
|
-
: head === "{" || head === "["
|
|
110
|
-
? `Input starts like JSON but does not parse (${e.message}). Profiling it as raw text, which is almost certainly not what you want.`
|
|
111
|
-
: undefined;
|
|
112
|
-
return {
|
|
113
|
-
sourceFormat: "text",
|
|
114
|
-
parseWarning,
|
|
115
|
-
// Empty input has no message: reporting "1 message, ~4 tokens" for it
|
|
116
|
-
// would be inventing content that is not there.
|
|
117
|
-
messages: input.trim() === "" ? [] : [{ index: 0, role: "user", kind: "user", text: input, hasBinary: false }],
|
|
118
|
-
};
|
|
138
|
+
return rawText(input, e);
|
|
119
139
|
}
|
|
140
|
+
// A prompt can happen to be valid JSON ("2024", a quoted sentence, null):
|
|
141
|
+
// that is still a raw prompt, not a conversation with no messages.
|
|
142
|
+
if (data === null || typeof data !== "object")
|
|
143
|
+
return rawText(input);
|
|
120
144
|
if (Array.isArray(data)) {
|
|
121
145
|
const looksLikeMessages = data.length === 0 || data.some((m) => m && typeof m === "object" && "role" in m);
|
|
122
146
|
return {
|
package/dist/pricing.d.ts
CHANGED
|
@@ -1,21 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-model pricing for cost estimation, USD per million tokens.
|
|
3
3
|
*
|
|
4
|
-
* Anthropic prices are
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* easy to update: PRs welcome
|
|
4
|
+
* Anthropic prices are first-party API list rates, checked against
|
|
5
|
+
* Anthropic's price list in October 2026. OpenAI/Google figures are
|
|
6
|
+
* approximate public list prices and drift over time — all cost output is
|
|
7
|
+
* labeled as an estimate. One table, one file, easy to update: PRs welcome
|
|
8
|
+
* when prices change.
|
|
8
9
|
*/
|
|
9
10
|
export interface ModelPricing {
|
|
10
11
|
/** USD per 1M input tokens. */
|
|
11
12
|
inputPerM: number;
|
|
12
13
|
/** USD per 1M output tokens (unused by the profiler, kept for reference). */
|
|
13
14
|
outputPerM: number;
|
|
14
|
-
/** USD per 1M cached input tokens read (
|
|
15
|
+
/** USD per 1M cached input tokens read (2.5-10% of input, depending on the Claude model). */
|
|
15
16
|
cacheReadPerM: number;
|
|
16
17
|
}
|
|
18
|
+
/** Anthropic cache writes, relative to the input price: 5-minute TTL and 1-hour TTL. */
|
|
19
|
+
export declare const CACHE_WRITE_5M = 1.25;
|
|
20
|
+
export declare const CACHE_WRITE_1H = 2;
|
|
17
21
|
export declare function pricingFor(model?: string): ModelPricing | undefined;
|
|
18
22
|
export declare function inputCostUsd(tokens: number, pricing: ModelPricing): number;
|
|
23
|
+
/** `usage` as the API returns it and Claude Code records it on each reply. */
|
|
24
|
+
export interface RecordedUsage {
|
|
25
|
+
input_tokens?: unknown;
|
|
26
|
+
cache_creation_input_tokens?: unknown;
|
|
27
|
+
cache_read_input_tokens?: unknown;
|
|
28
|
+
output_tokens?: unknown;
|
|
29
|
+
/** The cache writes split by TTL; absent in older transcripts. */
|
|
30
|
+
cache_creation?: {
|
|
31
|
+
ephemeral_5m_input_tokens?: unknown;
|
|
32
|
+
ephemeral_1h_input_tokens?: unknown;
|
|
33
|
+
} | null;
|
|
34
|
+
}
|
|
35
|
+
export interface UsageCost {
|
|
36
|
+
/** Uncached input. */
|
|
37
|
+
inputUsd: number;
|
|
38
|
+
/** Cache writes, each TTL at its own price. */
|
|
39
|
+
writeUsd: number;
|
|
40
|
+
readUsd: number;
|
|
41
|
+
outputUsd: number;
|
|
42
|
+
/** input + writes + reads: what the prompt cost. */
|
|
43
|
+
promptUsd: number;
|
|
44
|
+
totalUsd: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A request's cache writes by TTL. Claude Code's main thread writes almost
|
|
48
|
+
* everything at the 1-hour TTL (2x input) and subagents at 5 minutes
|
|
49
|
+
* (1.25x), so a single 1.25x multiplier underpriced most writes by 37%.
|
|
50
|
+
* Usage recorded without the split counts as 5-minute writes, the API's
|
|
51
|
+
* default TTL.
|
|
52
|
+
*/
|
|
53
|
+
export declare function cacheWriteSplit(usage: RecordedUsage | undefined | null): {
|
|
54
|
+
fiveMinute: number;
|
|
55
|
+
oneHour: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The write price, relative to input, of a mix of 5-minute and 1-hour
|
|
59
|
+
* writes. Simulations that charge a write that never happened (a cold
|
|
60
|
+
* re-read, a compaction's fresh context) use the session's own mix, since
|
|
61
|
+
* that is the TTL its harness would have written at; 1.25x when nothing was
|
|
62
|
+
* written to learn from.
|
|
63
|
+
*/
|
|
64
|
+
export declare function writeMultiplier(fiveMinute: number, oneHour: number): number;
|
|
65
|
+
/**
|
|
66
|
+
* What one recorded request cost: uncached input, cache writes priced by
|
|
67
|
+
* TTL, cache reads, output. The one place recorded usage is priced, so every
|
|
68
|
+
* report agrees with the others and with the bill.
|
|
69
|
+
*/
|
|
70
|
+
export declare function usageCost(usage: RecordedUsage | undefined | null, pricing: ModelPricing): UsageCost;
|
|
19
71
|
/**
|
|
20
72
|
* Rough time-to-first-token impact of input size. Prompt processing on major
|
|
21
73
|
* providers runs on the order of tens of thousands of tokens per second;
|
package/dist/pricing.js
CHANGED
|
@@ -1,16 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-model pricing for cost estimation, USD per million tokens.
|
|
3
3
|
*
|
|
4
|
-
* Anthropic prices are
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* easy to update: PRs welcome
|
|
4
|
+
* Anthropic prices are first-party API list rates, checked against
|
|
5
|
+
* Anthropic's price list in October 2026. OpenAI/Google figures are
|
|
6
|
+
* approximate public list prices and drift over time — all cost output is
|
|
7
|
+
* labeled as an estimate. One table, one file, easy to update: PRs welcome
|
|
8
|
+
* when prices change.
|
|
8
9
|
*/
|
|
10
|
+
/** Anthropic cache writes, relative to the input price: 5-minute TTL and 1-hour TTL. */
|
|
11
|
+
export const CACHE_WRITE_5M = 1.25;
|
|
12
|
+
export const CACHE_WRITE_1H = 2;
|
|
13
|
+
const rate = (inputPerM, outputPerM, cacheReadPerM) => ({ inputPerM, outputPerM, cacheReadPerM });
|
|
14
|
+
/**
|
|
15
|
+
* One Claude version, whatever wraps the id: a date (-20251101, @20251101),
|
|
16
|
+
* "[1m]", a Bedrock prefix. Prices differ between a major and its minor
|
|
17
|
+
* releases (Opus 5 is $5 in, Opus 5.5 is $4), so neither may match the
|
|
18
|
+
* other: a minor is one or two digits after the major, a date suffix is
|
|
19
|
+
* eight, and that is what the lookaheads tell apart.
|
|
20
|
+
*/
|
|
21
|
+
function claude(families, major, minor) {
|
|
22
|
+
return minor === undefined
|
|
23
|
+
? new RegExp(`claude-(?:${families})-${major}(?![.\\d]|-\\d{1,2}(?!\\d))`, "i")
|
|
24
|
+
: new RegExp(`claude-(?:${families})-${major}[-.]${minor}(?!\\d)`, "i");
|
|
25
|
+
}
|
|
9
26
|
const PRICING = [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
[
|
|
13
|
-
[
|
|
27
|
+
// Specific Claude versions first: the family rows below are only a
|
|
28
|
+
// fallback for ids this table does not know yet.
|
|
29
|
+
[claude("fable|mythos", 5, 1), rate(10, 50, 0.25)],
|
|
30
|
+
[claude("fable|mythos", 5), rate(10, 50, 1)],
|
|
31
|
+
[claude("opus", 5, 5), rate(4, 20, 0.2)],
|
|
32
|
+
[claude("opus", 5), rate(5, 25, 0.5)],
|
|
33
|
+
[claude("opus", 4, 8), rate(5, 25, 0.5)],
|
|
34
|
+
[claude("opus", 4, 7), rate(5, 25, 0.5)],
|
|
35
|
+
[claude("opus", 4, 6), rate(5, 25, 0.5)],
|
|
36
|
+
[claude("opus", 4, 5), rate(5, 25, 0.5)],
|
|
37
|
+
[claude("opus", 4, 1), rate(15, 75, 1.5)],
|
|
38
|
+
[claude("opus", 4), rate(15, 75, 1.5)],
|
|
39
|
+
[/claude-3-opus/i, rate(15, 75, 1.5)],
|
|
40
|
+
[claude("sonnet", 5, 5), rate(2, 10, 0.2)],
|
|
41
|
+
[claude("sonnet", 5), rate(2, 10, 0.2)],
|
|
42
|
+
// Haiku 5.5's rates hold for prompts up to 100k tokens; longer prompts
|
|
43
|
+
// bill higher, a tier this per-token table does not model.
|
|
44
|
+
[claude("haiku", 5, 5), rate(0.1, 0.5, 0.01)],
|
|
45
|
+
[claude("haiku", 4, 5), rate(1, 5, 0.1)],
|
|
46
|
+
[/claude-3-5-haiku/i, rate(0.8, 4, 0.08)],
|
|
47
|
+
[/claude-3-haiku/i, rate(0.25, 1.25, 0.03)],
|
|
48
|
+
// Sonnet 4.6, 4.5, 4 and the 3.x Sonnets are all $3 / $15 / $0.30: the family row.
|
|
49
|
+
[/claude.*fable|claude.*mythos/i, rate(10, 50, 1)],
|
|
50
|
+
[/claude.*opus/i, rate(5, 25, 0.5)],
|
|
51
|
+
[/claude.*sonnet/i, rate(3, 15, 0.3)],
|
|
52
|
+
[/claude.*haiku/i, rate(1, 5, 0.1)],
|
|
14
53
|
[/gpt-5.*(mini|nano)/i, { inputPerM: 0.25, outputPerM: 2, cacheReadPerM: 0.025 }],
|
|
15
54
|
[/gpt-5/i, { inputPerM: 1.25, outputPerM: 10, cacheReadPerM: 0.125 }],
|
|
16
55
|
[/gpt-4o-mini/i, { inputPerM: 0.15, outputPerM: 0.6, cacheReadPerM: 0.075 }],
|
|
@@ -32,6 +71,53 @@ export function pricingFor(model) {
|
|
|
32
71
|
export function inputCostUsd(tokens, pricing) {
|
|
33
72
|
return (tokens / 1_000_000) * pricing.inputPerM;
|
|
34
73
|
}
|
|
74
|
+
/** A token count from recorded usage: numbers and numeric strings, anything else is 0. */
|
|
75
|
+
function count(v) {
|
|
76
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : Number.NaN;
|
|
77
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A request's cache writes by TTL. Claude Code's main thread writes almost
|
|
81
|
+
* everything at the 1-hour TTL (2x input) and subagents at 5 minutes
|
|
82
|
+
* (1.25x), so a single 1.25x multiplier underpriced most writes by 37%.
|
|
83
|
+
* Usage recorded without the split counts as 5-minute writes, the API's
|
|
84
|
+
* default TTL.
|
|
85
|
+
*/
|
|
86
|
+
export function cacheWriteSplit(usage) {
|
|
87
|
+
const total = count(usage?.cache_creation_input_tokens);
|
|
88
|
+
const split = usage?.cache_creation;
|
|
89
|
+
if (!split || typeof split !== "object")
|
|
90
|
+
return { fiveMinute: total, oneHour: 0 };
|
|
91
|
+
const all = total || count(split.ephemeral_5m_input_tokens) + count(split.ephemeral_1h_input_tokens);
|
|
92
|
+
const oneHour = Math.min(all, count(split.ephemeral_1h_input_tokens));
|
|
93
|
+
return { fiveMinute: all - oneHour, oneHour };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The write price, relative to input, of a mix of 5-minute and 1-hour
|
|
97
|
+
* writes. Simulations that charge a write that never happened (a cold
|
|
98
|
+
* re-read, a compaction's fresh context) use the session's own mix, since
|
|
99
|
+
* that is the TTL its harness would have written at; 1.25x when nothing was
|
|
100
|
+
* written to learn from.
|
|
101
|
+
*/
|
|
102
|
+
export function writeMultiplier(fiveMinute, oneHour) {
|
|
103
|
+
const total = fiveMinute + oneHour;
|
|
104
|
+
return total > 0 ? (fiveMinute * CACHE_WRITE_5M + oneHour * CACHE_WRITE_1H) / total : CACHE_WRITE_5M;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* What one recorded request cost: uncached input, cache writes priced by
|
|
108
|
+
* TTL, cache reads, output. The one place recorded usage is priced, so every
|
|
109
|
+
* report agrees with the others and with the bill.
|
|
110
|
+
*/
|
|
111
|
+
export function usageCost(usage, pricing) {
|
|
112
|
+
const { fiveMinute, oneHour } = cacheWriteSplit(usage);
|
|
113
|
+
const perM = (tokens, usd) => (tokens * usd) / 1_000_000;
|
|
114
|
+
const inputUsd = perM(count(usage?.input_tokens), pricing.inputPerM);
|
|
115
|
+
const writeUsd = perM(fiveMinute * CACHE_WRITE_5M + oneHour * CACHE_WRITE_1H, pricing.inputPerM);
|
|
116
|
+
const readUsd = perM(count(usage?.cache_read_input_tokens), pricing.cacheReadPerM);
|
|
117
|
+
const outputUsd = perM(count(usage?.output_tokens), pricing.outputPerM);
|
|
118
|
+
const promptUsd = inputUsd + writeUsd + readUsd;
|
|
119
|
+
return { inputUsd, writeUsd, readUsd, outputUsd, promptUsd, totalUsd: promptUsd + outputUsd };
|
|
120
|
+
}
|
|
35
121
|
/**
|
|
36
122
|
* Rough time-to-first-token impact of input size. Prompt processing on major
|
|
37
123
|
* providers runs on the order of tens of thousands of tokens per second;
|
package/dist/proxy.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface ProxyOptions extends OptimizeOptions {
|
|
|
48
48
|
googleUpstream?: string;
|
|
49
49
|
}
|
|
50
50
|
/** Reported by /health so `autopilot status` can tell an outdated service from a current one. */
|
|
51
|
-
export declare const PROXY_VERSION = "0.
|
|
51
|
+
export declare const PROXY_VERSION = "0.32.0";
|
|
52
52
|
/**
|
|
53
53
|
* Remove a leading `/t/<token>` from a request path, or return undefined when
|
|
54
54
|
* the prefix is absent or the token differs. The comparison is constant time
|
|
@@ -61,7 +61,12 @@ export declare function stripToken(url: string, token: string): string | undefin
|
|
|
61
61
|
* Code under autopilot, an SDK) also lists models, uploads files, counts
|
|
62
62
|
* tokens. Until 0.24 those got a 404 and broke the feature for that client.
|
|
63
63
|
* Unknown paths go to Anthropic when the request says it is one (Anthropic
|
|
64
|
-
* clients always send anthropic-version, or x-api-key
|
|
64
|
+
* clients always send anthropic-version, or x-api-key; OAuth calls send
|
|
65
|
+
* anthropic-beta; Anthropic keys and tokens start sk-ant-), else to OpenAI.
|
|
66
|
+
* Under autopilot the default flips: Claude Code is the client it is
|
|
67
|
+
* installed for, and a Bearer-only request from it (its OAuth token) sent to
|
|
68
|
+
* OpenAI would hand an Anthropic credential to another company. There only a
|
|
69
|
+
* request carrying an OpenAI-style key (sk-..., not sk-ant-...) goes to OpenAI.
|
|
65
70
|
* Google's Gemini API (GOOGLE_GEMINI_BASE_URL) is recognised by its key
|
|
66
71
|
* header, its /v1beta paths, or a `model:method` path such as
|
|
67
72
|
* /v1/models/gemini-3-pro:generateContent.
|
|
@@ -108,4 +113,10 @@ export interface RouteConfig {
|
|
|
108
113
|
keepRecent?: number;
|
|
109
114
|
maxToolResultTokens?: number;
|
|
110
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Which account a request belongs to, as a salted hash of its credential: the
|
|
118
|
+
* proxy remembers per account whether the API refuses edited history, and
|
|
119
|
+
* never keeps the key itself.
|
|
120
|
+
*/
|
|
121
|
+
export declare function accountKey(headers: http.IncomingHttpHeaders): string;
|
|
111
122
|
export declare function startProxy(opts?: ProxyOptions): http.Server;
|