@kenkaiiii/ggcoder 5.68.0 → 5.69.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/dist/app-sidecar.js +26 -11
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli.js +15 -14
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts +18 -0
- package/dist/core/agent-session.js +125 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/cache-diagnostics.d.ts +68 -0
- package/dist/core/cache-diagnostics.js +196 -0
- package/dist/core/cache-diagnostics.js.map +1 -0
- package/dist/core/cache-expiry.d.ts +87 -0
- package/dist/core/cache-expiry.js +111 -0
- package/dist/core/cache-expiry.js.map +1 -0
- package/dist/core/destructive-git-guard.d.ts +27 -0
- package/dist/core/destructive-git-guard.js +94 -54
- package/dist/core/destructive-git-guard.js.map +1 -1
- package/dist/core/injection-detect.d.ts +38 -0
- package/dist/core/injection-detect.js +232 -0
- package/dist/core/injection-detect.js.map +1 -0
- package/dist/core/mcp/client.d.ts +72 -0
- package/dist/core/mcp/client.js +264 -41
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/store.d.ts +6 -1
- package/dist/core/mcp/store.js +12 -1
- package/dist/core/mcp/store.js.map +1 -1
- package/dist/core/mcp/types.d.ts +18 -0
- package/dist/core/persistent-shell.d.ts +58 -6
- package/dist/core/persistent-shell.js +331 -49
- package/dist/core/persistent-shell.js.map +1 -1
- package/dist/core/progress/git-xp.js +8 -14
- package/dist/core/progress/git-xp.js.map +1 -1
- package/dist/core/subagent-manager.d.ts +9 -0
- package/dist/core/subagent-manager.js +7 -1
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-receipt.d.ts +54 -0
- package/dist/core/subagent-receipt.js +276 -0
- package/dist/core/subagent-receipt.js.map +1 -0
- package/dist/core/subagent-turn-record.d.ts +2 -0
- package/dist/core/subagent-turn-record.js.map +1 -1
- package/dist/core/verification-snapshot.js +3 -5
- package/dist/core/verification-snapshot.js.map +1 -1
- package/dist/core/workspace-guard.d.ts +18 -7
- package/dist/core/workspace-guard.js +227 -60
- package/dist/core/workspace-guard.js.map +1 -1
- package/dist/modes/subagent-worker-mode.js +18 -2
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/motion-agent/motion-check-tool.d.ts +7 -0
- package/dist/motion-agent/motion-check-tool.js +20 -1
- package/dist/motion-agent/motion-check-tool.js.map +1 -1
- package/dist/tools/bash.js +12 -4
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/read.js +3 -2
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/steroids.d.ts +1 -1
- package/dist/tools/subagent-control.js +34 -6
- package/dist/tools/subagent-control.js.map +1 -1
- package/dist/tools/subagent-shared.d.ts +13 -0
- package/dist/tools/subagent-shared.js +25 -0
- package/dist/tools/subagent-shared.js.map +1 -1
- package/dist/tools/subagent.js +14 -3
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/task-output.js +3 -2
- package/dist/tools/task-output.js.map +1 -1
- package/dist/tools/truncate.d.ts +21 -0
- package/dist/tools/truncate.js +187 -0
- package/dist/tools/truncate.js.map +1 -1
- package/dist/tools/ui-adopt.js +2 -0
- package/dist/tools/ui-adopt.js.map +1 -1
- package/dist/utils/git.d.ts +77 -0
- package/dist/utils/git.js +285 -21
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/github-ci.js +2 -1
- package/dist/utils/github-ci.js.map +1 -1
- package/dist/utils/github.js +11 -9
- package/dist/utils/github.js.map +1 -1
- package/dist/utils/process.d.ts +20 -0
- package/dist/utils/process.js +98 -0
- package/dist/utils/process.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { CacheRetention, PreparedContext, Usage } from "@kenkaiiii/gg-ai";
|
|
2
|
+
import { type CacheTouch } from "./cache-expiry.js";
|
|
3
|
+
export interface CacheRequestObservation {
|
|
4
|
+
provider: string;
|
|
5
|
+
model: string;
|
|
6
|
+
at: number;
|
|
7
|
+
cacheRetention: CacheRetention;
|
|
8
|
+
/** Request-shaping settings only; digested in memory, never logged. */
|
|
9
|
+
settings: unknown;
|
|
10
|
+
/** Route/account identity, digested in memory. Never pass credentials. */
|
|
11
|
+
route: unknown;
|
|
12
|
+
}
|
|
13
|
+
type HistoryEdit = "tool_prune" | "compaction";
|
|
14
|
+
export interface CacheContextReport {
|
|
15
|
+
stage: "prepared_context";
|
|
16
|
+
request: number;
|
|
17
|
+
changes: string[];
|
|
18
|
+
messageCount: number;
|
|
19
|
+
previousMessageCount: number;
|
|
20
|
+
/** Logical-message prefix, NOT a provider token offset or exact HTTP-body comparison. */
|
|
21
|
+
unchangedMessages: number;
|
|
22
|
+
imagesBefore: number;
|
|
23
|
+
imagesAfter: number;
|
|
24
|
+
imagesDropped: number;
|
|
25
|
+
pruneFreedTokensEstimate: number;
|
|
26
|
+
sincePreviousRequestMs: number | null;
|
|
27
|
+
requestedTtlExceeded: boolean;
|
|
28
|
+
/** Possible signed-thinking prefix mismatch; not a server-confirmed rejection. */
|
|
29
|
+
thinkingPrefixRiskBlocks: number;
|
|
30
|
+
/** Existing Anthropic encoding removes these signed blocks from settled turns. */
|
|
31
|
+
settledThinkingBlocks: number;
|
|
32
|
+
fingerprintMs: number;
|
|
33
|
+
}
|
|
34
|
+
export interface CacheUsageReport extends CacheContextReport {
|
|
35
|
+
promptTokens: number;
|
|
36
|
+
cacheRead: number;
|
|
37
|
+
cacheWrite: number;
|
|
38
|
+
comparison: "first_request" | "compaction" | "route_changed" | "cache_unreported" | "comparable";
|
|
39
|
+
/** Upper-bound proxy for repeated input not read from cache, not proven avoidable waste. */
|
|
40
|
+
reprocessedPromptTokensEstimate: number | null;
|
|
41
|
+
costStatus: "unavailable";
|
|
42
|
+
ttftMs: number | null;
|
|
43
|
+
providerDurationMs: number;
|
|
44
|
+
}
|
|
45
|
+
/** One per session. Retains only one completed snapshot and one in-flight snapshot, no payloads.
|
|
46
|
+
* Failed/retried attempts never replace the last successful usage baseline. */
|
|
47
|
+
export declare class CacheDiagnostics {
|
|
48
|
+
private completed;
|
|
49
|
+
private pending;
|
|
50
|
+
private edits;
|
|
51
|
+
private freedTokens;
|
|
52
|
+
private requests;
|
|
53
|
+
private reportedCache;
|
|
54
|
+
private lastTouch;
|
|
55
|
+
/** Last successful cache-using request (real turn or prewarm), or null. */
|
|
56
|
+
lastCacheTouch(): CacheTouch | null;
|
|
57
|
+
/** Record a successful cache-using request that bypassed prepare/complete (prewarm). */
|
|
58
|
+
noteCacheTouch(touch: CacheTouch): void;
|
|
59
|
+
reset(): void;
|
|
60
|
+
discardAttempt(): void;
|
|
61
|
+
noteEdit(edit: HistoryEdit, freedTokens?: number): void;
|
|
62
|
+
prepare(context: PreparedContext, input: CacheRequestObservation): CacheContextReport;
|
|
63
|
+
complete(usage: Usage, timing: {
|
|
64
|
+
ttftMs?: number;
|
|
65
|
+
providerDurationMs: number;
|
|
66
|
+
}): CacheUsageReport | undefined;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { resolveCacheTtl } from "./cache-expiry.js";
|
|
3
|
+
/** Digests never leave this instance. Canonical object keys avoid spurious edits on resume. */
|
|
4
|
+
function digest(value) {
|
|
5
|
+
const json = JSON.stringify(value, (_key, item) => item !== null && typeof item === "object" && !Array.isArray(item)
|
|
6
|
+
? Object.fromEntries(Object.entries(item).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
|
|
7
|
+
: item);
|
|
8
|
+
return createHash("sha256")
|
|
9
|
+
.update(json ?? "null")
|
|
10
|
+
.digest("hex");
|
|
11
|
+
}
|
|
12
|
+
/** Route fields the session passes as `route`; read structurally, never digested. */
|
|
13
|
+
function routeField(route, key) {
|
|
14
|
+
if (!route || typeof route !== "object")
|
|
15
|
+
return undefined;
|
|
16
|
+
const value = route[key];
|
|
17
|
+
return typeof value === "string" && value ? value : undefined;
|
|
18
|
+
}
|
|
19
|
+
function signedThinkingCount(message) {
|
|
20
|
+
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
21
|
+
return 0;
|
|
22
|
+
return message.content.filter((part) => (part.type === "thinking" && Boolean(part.signature?.trim())) ||
|
|
23
|
+
(part.type === "raw" &&
|
|
24
|
+
(part.data.type === "redacted_thinking" ||
|
|
25
|
+
(part.data.type === "thinking" && typeof part.data.signature === "string")))).length;
|
|
26
|
+
}
|
|
27
|
+
function promptTokens(usage) {
|
|
28
|
+
return usage.inputTokens + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
29
|
+
}
|
|
30
|
+
/** One per session. Retains only one completed snapshot and one in-flight snapshot, no payloads.
|
|
31
|
+
* Failed/retried attempts never replace the last successful usage baseline. */
|
|
32
|
+
export class CacheDiagnostics {
|
|
33
|
+
completed;
|
|
34
|
+
pending;
|
|
35
|
+
edits = new Set();
|
|
36
|
+
freedTokens = 0;
|
|
37
|
+
requests = 0;
|
|
38
|
+
reportedCache = false;
|
|
39
|
+
lastTouch = null;
|
|
40
|
+
/** Last successful cache-using request (real turn or prewarm), or null. */
|
|
41
|
+
lastCacheTouch() {
|
|
42
|
+
return this.lastTouch;
|
|
43
|
+
}
|
|
44
|
+
/** Record a successful cache-using request that bypassed prepare/complete (prewarm). */
|
|
45
|
+
noteCacheTouch(touch) {
|
|
46
|
+
this.lastTouch = { ...touch };
|
|
47
|
+
}
|
|
48
|
+
reset() {
|
|
49
|
+
this.lastTouch = null;
|
|
50
|
+
this.completed = undefined;
|
|
51
|
+
this.pending = undefined;
|
|
52
|
+
this.edits.clear();
|
|
53
|
+
this.freedTokens = 0;
|
|
54
|
+
this.requests = 0;
|
|
55
|
+
this.reportedCache = false;
|
|
56
|
+
}
|
|
57
|
+
discardAttempt() {
|
|
58
|
+
this.pending = undefined;
|
|
59
|
+
}
|
|
60
|
+
noteEdit(edit, freedTokens = 0) {
|
|
61
|
+
this.edits.add(edit);
|
|
62
|
+
this.freedTokens += Math.max(0, freedTokens);
|
|
63
|
+
}
|
|
64
|
+
prepare(context, input) {
|
|
65
|
+
const started = performance.now();
|
|
66
|
+
const previous = this.completed;
|
|
67
|
+
const snapshot = {
|
|
68
|
+
messages: context.messages.map((message) => digest({ role: message.role, content: message.content })),
|
|
69
|
+
settings: digest({
|
|
70
|
+
tools: context.tools,
|
|
71
|
+
settings: input.settings,
|
|
72
|
+
cacheRetention: input.cacheRetention,
|
|
73
|
+
}),
|
|
74
|
+
route: digest({ provider: input.provider, model: input.model, route: input.route }),
|
|
75
|
+
imagesDropped: context.imagesBefore - context.imagesAfter,
|
|
76
|
+
at: input.at,
|
|
77
|
+
touch: (() => {
|
|
78
|
+
const policy = resolveCacheTtl({
|
|
79
|
+
provider: input.provider,
|
|
80
|
+
model: input.model,
|
|
81
|
+
cacheRetention: input.cacheRetention,
|
|
82
|
+
baseUrl: routeField(input.route, "baseUrl"),
|
|
83
|
+
accountId: routeField(input.route, "accountId"),
|
|
84
|
+
});
|
|
85
|
+
return policy ? { provider: input.provider, model: input.model, policy } : null;
|
|
86
|
+
})(),
|
|
87
|
+
};
|
|
88
|
+
let unchanged = 0;
|
|
89
|
+
if (previous) {
|
|
90
|
+
while (unchanged < previous.messages.length &&
|
|
91
|
+
unchanged < snapshot.messages.length &&
|
|
92
|
+
previous.messages[unchanged] === snapshot.messages[unchanged])
|
|
93
|
+
unchanged++;
|
|
94
|
+
}
|
|
95
|
+
const changes = [...this.edits];
|
|
96
|
+
if (previous?.route !== undefined && previous.route !== snapshot.route)
|
|
97
|
+
changes.push("route_or_model");
|
|
98
|
+
const settingsChanged = previous !== undefined && previous.settings !== snapshot.settings;
|
|
99
|
+
if (settingsChanged)
|
|
100
|
+
changes.push("request_settings");
|
|
101
|
+
const historyChanged = previous !== undefined && unchanged < previous.messages.length;
|
|
102
|
+
if (historyChanged)
|
|
103
|
+
changes.push("history");
|
|
104
|
+
if (snapshot.imagesDropped !== (previous?.imagesDropped ?? 0))
|
|
105
|
+
changes.push("image_budget");
|
|
106
|
+
// The input is already clamped. On resume there is no previous snapshot, but removal of
|
|
107
|
+
// earlier images can still invalidate signed reasoning from the saved conversation.
|
|
108
|
+
let firstEdit = context.firstImageDropMessage ?? Number.POSITIVE_INFINITY;
|
|
109
|
+
if (historyChanged)
|
|
110
|
+
firstEdit = Math.min(firstEdit, unchanged);
|
|
111
|
+
if (settingsChanged)
|
|
112
|
+
firstEdit = -1;
|
|
113
|
+
let lastUser = -1;
|
|
114
|
+
context.messages.forEach((message, index) => {
|
|
115
|
+
if (message.role === "user")
|
|
116
|
+
lastUser = index;
|
|
117
|
+
});
|
|
118
|
+
let thinkingPrefixRiskBlocks = 0;
|
|
119
|
+
let settledThinkingBlocks = 0;
|
|
120
|
+
if (input.provider === "anthropic") {
|
|
121
|
+
context.messages.forEach((message, index) => {
|
|
122
|
+
const count = signedThinkingCount(message);
|
|
123
|
+
if (index >= firstEdit)
|
|
124
|
+
thinkingPrefixRiskBlocks += count;
|
|
125
|
+
if (index < lastUser)
|
|
126
|
+
settledThinkingBlocks += count;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const sincePreviousRequestMs = previous ? Math.max(0, input.at - previous.at) : null;
|
|
130
|
+
const report = {
|
|
131
|
+
stage: "prepared_context",
|
|
132
|
+
request: ++this.requests,
|
|
133
|
+
changes: changes.sort(),
|
|
134
|
+
messageCount: snapshot.messages.length,
|
|
135
|
+
previousMessageCount: previous?.messages.length ?? 0,
|
|
136
|
+
unchangedMessages: unchanged,
|
|
137
|
+
imagesBefore: context.imagesBefore,
|
|
138
|
+
imagesAfter: context.imagesAfter,
|
|
139
|
+
imagesDropped: snapshot.imagesDropped,
|
|
140
|
+
pruneFreedTokensEstimate: this.freedTokens,
|
|
141
|
+
sincePreviousRequestMs,
|
|
142
|
+
requestedTtlExceeded: input.cacheRetention !== "none" &&
|
|
143
|
+
sincePreviousRequestMs !== null &&
|
|
144
|
+
sincePreviousRequestMs > (input.cacheRetention === "long" ? 3_600_000 : 300_000),
|
|
145
|
+
thinkingPrefixRiskBlocks,
|
|
146
|
+
settledThinkingBlocks,
|
|
147
|
+
fingerprintMs: Math.round((performance.now() - started) * 100) / 100,
|
|
148
|
+
};
|
|
149
|
+
this.pending = { snapshot, report };
|
|
150
|
+
return report;
|
|
151
|
+
}
|
|
152
|
+
complete(usage, timing) {
|
|
153
|
+
if (!this.pending)
|
|
154
|
+
return undefined;
|
|
155
|
+
const { snapshot, report } = this.pending;
|
|
156
|
+
const previous = this.completed;
|
|
157
|
+
const cacheRead = usage.cacheRead ?? 0;
|
|
158
|
+
const cacheWrite = usage.cacheWrite ?? 0;
|
|
159
|
+
const routeChanged = previous !== undefined && previous.route !== snapshot.route;
|
|
160
|
+
const hasCache = cacheRead + cacheWrite > 0 || (!routeChanged && this.reportedCache);
|
|
161
|
+
const comparison = !previous?.usage
|
|
162
|
+
? "first_request"
|
|
163
|
+
: this.edits.has("compaction")
|
|
164
|
+
? "compaction"
|
|
165
|
+
: routeChanged
|
|
166
|
+
? "route_changed"
|
|
167
|
+
: !hasCache
|
|
168
|
+
? "cache_unreported"
|
|
169
|
+
: "comparable";
|
|
170
|
+
const reprocessed = comparison === "comparable" && previous?.usage
|
|
171
|
+
? Math.max(0, Math.min(promptTokens(previous.usage), promptTokens(usage)) - cacheRead)
|
|
172
|
+
: null;
|
|
173
|
+
this.completed = { ...snapshot, usage: { ...usage } };
|
|
174
|
+
// The provider refreshes the cached prefix when the request is processed, so
|
|
175
|
+
// the TTL clock restarts at the request's start time.
|
|
176
|
+
this.lastTouch = snapshot.touch ? { ...snapshot.touch, at: snapshot.at } : null;
|
|
177
|
+
this.pending = undefined;
|
|
178
|
+
this.edits.clear();
|
|
179
|
+
this.freedTokens = 0;
|
|
180
|
+
this.reportedCache = hasCache;
|
|
181
|
+
return {
|
|
182
|
+
...report,
|
|
183
|
+
promptTokens: promptTokens(usage),
|
|
184
|
+
cacheRead,
|
|
185
|
+
cacheWrite,
|
|
186
|
+
comparison,
|
|
187
|
+
reprocessedPromptTokensEstimate: reprocessed,
|
|
188
|
+
// GG's turn metrics currently have no authoritative effective-dated pricing. Do not
|
|
189
|
+
// turn a cache-write count (which includes new content) into fabricated dollar waste.
|
|
190
|
+
costStatus: "unavailable",
|
|
191
|
+
ttftMs: timing.ttftMs ?? null,
|
|
192
|
+
providerDurationMs: timing.providerDurationMs,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=cache-diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-diagnostics.js","sourceRoot":"","sources":["../../src/core/cache-diagnostics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { CacheRetention } from "@kenkaiiii/gg-ai";
|
|
2
|
+
/**
|
|
3
|
+
* Cold-prompt-cache detection (mirrors langchain-ai/deepagents#6477's
|
|
4
|
+
* `cold_cache.py`): providers keep the conversation prefix cached only for a
|
|
5
|
+
* TTL, so after a break the next message re-reads the whole context uncached.
|
|
6
|
+
*
|
|
7
|
+
* Every TTL below is derived from what gg-ai actually puts on the wire — never
|
|
8
|
+
* from a provider's marketing maximum we did not request. A provider/route we
|
|
9
|
+
* cannot pin to a request-side TTL returns `null` (no warning) rather than a
|
|
10
|
+
* guess.
|
|
11
|
+
*/
|
|
12
|
+
export interface CacheTtlPolicy {
|
|
13
|
+
ttlMs: number;
|
|
14
|
+
/**
|
|
15
|
+
* `expired`: the TTL is a documented/requested *maximum* — past it the entry
|
|
16
|
+
* is gone. `may_be_cold`: the TTL is a guaranteed *minimum* (GPT-5.6+ `30m`),
|
|
17
|
+
* so past it the prefix may still be warm.
|
|
18
|
+
*/
|
|
19
|
+
confidence: "expired" | "may_be_cold";
|
|
20
|
+
/** Where the number comes from, for logs/diagnostics. */
|
|
21
|
+
source: string;
|
|
22
|
+
}
|
|
23
|
+
export interface CacheTtlInput {
|
|
24
|
+
provider: string;
|
|
25
|
+
model: string;
|
|
26
|
+
cacheRetention: CacheRetention;
|
|
27
|
+
baseUrl?: string;
|
|
28
|
+
/** OpenAI OAuth (ChatGPT/Codex) routes carry an account id. */
|
|
29
|
+
accountId?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Resolve the cache TTL our provider code requests for this route. */
|
|
32
|
+
export declare function resolveCacheTtl(input: CacheTtlInput): CacheTtlPolicy | null;
|
|
33
|
+
/**
|
|
34
|
+
* Prefix size below which the notice is not worth an interruption. At 40k
|
|
35
|
+
* tokens a cold Anthropic re-read (1.25x write vs 0.1x cached read) costs ~12x
|
|
36
|
+
* the warm request, roughly a dime on Sonnet-class pricing; below that the delta
|
|
37
|
+
* is pennies — and compacting first itself re-reads the whole context once, so
|
|
38
|
+
* on small chats "Compact first" saves nothing.
|
|
39
|
+
*/
|
|
40
|
+
export declare const CACHE_EXPIRY_NOTICE_MIN_TOKENS = 40000;
|
|
41
|
+
export interface CacheTouch {
|
|
42
|
+
at: number;
|
|
43
|
+
provider: string;
|
|
44
|
+
model: string;
|
|
45
|
+
policy: CacheTtlPolicy;
|
|
46
|
+
}
|
|
47
|
+
export interface CacheExpiryStatus {
|
|
48
|
+
/** Owning session, so clients can key "dismissed once per expiry per chat". */
|
|
49
|
+
sessionId?: string;
|
|
50
|
+
provider: string;
|
|
51
|
+
ttlMs: number;
|
|
52
|
+
confidence: CacheTtlPolicy["confidence"];
|
|
53
|
+
ttlSource: string;
|
|
54
|
+
/** Last successful cache-using request (real turn or prewarm), epoch ms. */
|
|
55
|
+
lastRequestAt: number | null;
|
|
56
|
+
/** When the cached prefix lapses/lapsed, epoch ms; null when age is unknown. */
|
|
57
|
+
expiresAt: number | null;
|
|
58
|
+
expired: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* `idle`: TTL passed. `age_unknown`: resumed/reset session with no request in
|
|
61
|
+
* this process. `identity_changed`: provider/model switched since the last
|
|
62
|
+
* request, so the old prefix cannot be reused.
|
|
63
|
+
*/
|
|
64
|
+
reason: "idle" | "age_unknown" | "identity_changed" | null;
|
|
65
|
+
/** Tokens the next request would re-read (the current context). */
|
|
66
|
+
prefixTokens: number;
|
|
67
|
+
minTokens: number;
|
|
68
|
+
/** expired AND prefixTokens >= minTokens: worth showing the notice. */
|
|
69
|
+
notable: boolean;
|
|
70
|
+
/** No authoritative pricing exists in GG (turn metric cost is "unavailable"). */
|
|
71
|
+
estimatedExtraCostUsd: null;
|
|
72
|
+
}
|
|
73
|
+
export interface AssessCacheExpiryInput {
|
|
74
|
+
/** Policy for the route the NEXT request will take. */
|
|
75
|
+
current: {
|
|
76
|
+
provider: string;
|
|
77
|
+
model: string;
|
|
78
|
+
policy: CacheTtlPolicy | null;
|
|
79
|
+
};
|
|
80
|
+
lastTouch: CacheTouch | null;
|
|
81
|
+
now: number;
|
|
82
|
+
prefixTokens: number;
|
|
83
|
+
/** False for an empty chat — nothing to re-read. */
|
|
84
|
+
hasHistory: boolean;
|
|
85
|
+
minTokens?: number;
|
|
86
|
+
}
|
|
87
|
+
export declare function assessCacheExpiry(input: AssessCacheExpiryInput): CacheExpiryStatus | null;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const MINUTE = 60_000;
|
|
2
|
+
/** Resolve the cache TTL our provider code requests for this route. */
|
|
3
|
+
export function resolveCacheTtl(input) {
|
|
4
|
+
if (input.cacheRetention === "none")
|
|
5
|
+
return null;
|
|
6
|
+
if (input.provider === "anthropic") {
|
|
7
|
+
// gg-ai/src/providers/transform.ts `toAnthropicCacheControl`: "long" sends
|
|
8
|
+
// `ttl: "1h"` only on the first-party endpoint; everything else sends a bare
|
|
9
|
+
// `{type: "ephemeral"}`, which is Anthropic's 5-minute default.
|
|
10
|
+
const firstParty = !input.baseUrl || input.baseUrl.includes("api.anthropic.com");
|
|
11
|
+
if (input.cacheRetention === "long" && firstParty) {
|
|
12
|
+
return {
|
|
13
|
+
ttlMs: 60 * MINUTE,
|
|
14
|
+
confidence: "expired",
|
|
15
|
+
source: "anthropic cache_control ttl=1h (transform.ts toAnthropicCacheControl)",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
ttlMs: 5 * MINUTE,
|
|
20
|
+
confidence: "expired",
|
|
21
|
+
source: "anthropic cache_control ephemeral default 5m (transform.ts toAnthropicCacheControl)",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (input.provider === "openai") {
|
|
25
|
+
if (input.accountId) {
|
|
26
|
+
// gg-ai/src/providers/openai-codex.ts: the Codex backend rejects
|
|
27
|
+
// prompt_cache_retention; "Cache TTL on Codex is controlled server-side
|
|
28
|
+
// (~5-10 min in-memory)". Use the upper bound so we only warn once the
|
|
29
|
+
// entry is surely gone.
|
|
30
|
+
return {
|
|
31
|
+
ttlMs: 10 * MINUTE,
|
|
32
|
+
confidence: "expired",
|
|
33
|
+
source: "codex server-side in-memory cache ~5-10m (openai-codex.ts)",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (input.model.startsWith("gpt-5.6") || input.model.startsWith("gpt-6")) {
|
|
37
|
+
// gg-ai/src/providers/openai.ts sends prompt_cache_options.ttl = "30m";
|
|
38
|
+
// OpenAI documents 30 minutes as a guaranteed minimum.
|
|
39
|
+
return {
|
|
40
|
+
ttlMs: 30 * MINUTE,
|
|
41
|
+
confidence: "may_be_cold",
|
|
42
|
+
source: "openai prompt_cache_options ttl=30m (openai.ts)",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (input.cacheRetention === "long") {
|
|
46
|
+
// gg-ai/src/providers/openai.ts sends prompt_cache_retention = "24h".
|
|
47
|
+
return {
|
|
48
|
+
ttlMs: 24 * 60 * MINUTE,
|
|
49
|
+
confidence: "expired",
|
|
50
|
+
source: "openai prompt_cache_retention=24h (openai.ts)",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// No retention param sent → OpenAI's default in-memory retention, which
|
|
54
|
+
// its prompt-caching guide documents as up to one hour (a maximum).
|
|
55
|
+
return {
|
|
56
|
+
ttlMs: 60 * MINUTE,
|
|
57
|
+
confidence: "expired",
|
|
58
|
+
source: "openai default in_memory retention, max 1h (no param sent by openai.ts)",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Other providers (Gemini, GLM, Moonshot, OpenRouter, local, …): we request
|
|
62
|
+
// no TTL and have no documented window to anchor a warning on.
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Prefix size below which the notice is not worth an interruption. At 40k
|
|
67
|
+
* tokens a cold Anthropic re-read (1.25x write vs 0.1x cached read) costs ~12x
|
|
68
|
+
* the warm request, roughly a dime on Sonnet-class pricing; below that the delta
|
|
69
|
+
* is pennies — and compacting first itself re-reads the whole context once, so
|
|
70
|
+
* on small chats "Compact first" saves nothing.
|
|
71
|
+
*/
|
|
72
|
+
export const CACHE_EXPIRY_NOTICE_MIN_TOKENS = 40_000;
|
|
73
|
+
export function assessCacheExpiry(input) {
|
|
74
|
+
const { current, lastTouch, now } = input;
|
|
75
|
+
if (!input.hasHistory || !current.policy)
|
|
76
|
+
return null;
|
|
77
|
+
const minTokens = input.minTokens ?? CACHE_EXPIRY_NOTICE_MIN_TOKENS;
|
|
78
|
+
const prefixTokens = Math.max(0, Math.round(input.prefixTokens));
|
|
79
|
+
let reason = null;
|
|
80
|
+
let expiresAt = null;
|
|
81
|
+
// The entry lives by the TTL it was WRITTEN with, not the current setting.
|
|
82
|
+
let policy = current.policy;
|
|
83
|
+
if (!lastTouch) {
|
|
84
|
+
reason = "age_unknown";
|
|
85
|
+
}
|
|
86
|
+
else if (lastTouch.provider !== current.provider || lastTouch.model !== current.model) {
|
|
87
|
+
reason = "identity_changed";
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
policy = lastTouch.policy;
|
|
91
|
+
expiresAt = lastTouch.at + policy.ttlMs;
|
|
92
|
+
if (now >= expiresAt)
|
|
93
|
+
reason = "idle";
|
|
94
|
+
}
|
|
95
|
+
const expired = reason !== null;
|
|
96
|
+
return {
|
|
97
|
+
provider: current.provider,
|
|
98
|
+
ttlMs: policy.ttlMs,
|
|
99
|
+
confidence: policy.confidence,
|
|
100
|
+
ttlSource: policy.source,
|
|
101
|
+
lastRequestAt: lastTouch?.at ?? null,
|
|
102
|
+
expiresAt,
|
|
103
|
+
expired,
|
|
104
|
+
reason,
|
|
105
|
+
prefixTokens,
|
|
106
|
+
minTokens,
|
|
107
|
+
notable: expired && prefixTokens >= minTokens,
|
|
108
|
+
estimatedExtraCostUsd: null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=cache-expiry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-expiry.js","sourceRoot":"","sources":["../../src/core/cache-expiry.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,6 +39,33 @@ export interface DestructiveGitMatch {
|
|
|
39
39
|
/** branch -D targets; stash drop target. */
|
|
40
40
|
refs: string[];
|
|
41
41
|
}
|
|
42
|
+
/** Basename of a command word, `.exe` stripped (`/usr/bin/git`, `git.exe`). */
|
|
43
|
+
export declare function commandName(word: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Expand the home and working-directory forms a model writes in paths:
|
|
46
|
+
* `~`, `$HOME`/`${HOME}` (any case, which also covers PowerShell `$home`),
|
|
47
|
+
* `$env:USERPROFILE`/`$env:HOME`, cmd `%USERPROFILE%`, and `$PWD`/`${PWD}`. Other variables stay
|
|
48
|
+
* literal, so they resolve to a harmless relative path.
|
|
49
|
+
*/
|
|
50
|
+
export declare function expandShellPath(p: string, cwd: string): string;
|
|
51
|
+
/** One simple command found in a shell string, after unwrapping. */
|
|
52
|
+
export interface ShellInvocation {
|
|
53
|
+
/** {@link commandName} of the command word. */
|
|
54
|
+
name: string;
|
|
55
|
+
args: readonly string[];
|
|
56
|
+
/** Directory it would run in after any earlier `cd` in the same script. */
|
|
57
|
+
cwd: string;
|
|
58
|
+
/** Run by `xargs`, which appends operands read from stdin. */
|
|
59
|
+
viaXargs: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Every simple command in `command` whose name satisfies `wanted`, in source
|
|
63
|
+
* order, after splitting on `&&`/`;`/`||`/pipes, entering subshells,
|
|
64
|
+
* substitutions and `if`/`for`/`while` bodies, unwrapping `bash -c`/`eval`/
|
|
65
|
+
* `pwsh -c` scripts and exec wrappers (`sudo`, `env`, `timeout`, `xargs`, …),
|
|
66
|
+
* and following `cd`. Pure: nothing is executed.
|
|
67
|
+
*/
|
|
68
|
+
export declare function walkShell(command: string, cwd: string, wanted: (name: string) => boolean): ShellInvocation[];
|
|
42
69
|
/**
|
|
43
70
|
* Parse `command` and return every git invocation in it that can destroy
|
|
44
71
|
* work, with the directory each would run in. Pure apart from `existsSync`
|