@sayknow-cli/coding-agent 0.3.12 → 0.3.15
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/types/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +2 -2
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/daemon-cli.d.ts +2 -0
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/malloc-env-guard.d.ts +65 -0
- package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +3 -1
- package/dist/types/cli/session-picker.d.ts +4 -3
- package/dist/types/cli/update-cli.d.ts +22 -3
- package/dist/types/cli.d.ts +5 -0
- package/dist/types/commands/daemon.d.ts +6 -2
- package/dist/types/commands/launch.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/commands/web-search.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +4 -4
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -9
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -9
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -9
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +3 -3
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/commit/shared-llm.d.ts +9 -9
- package/dist/types/config/keybindings.d.ts +134 -129
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +35 -35
- package/dist/types/config/models-config-schema.d.ts +111 -111
- package/dist/types/config/settings-schema.d.ts +56 -16
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +79 -6
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/daemon/control-types.d.ts +10 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +12 -12
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/edit/modes/patch.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/goals/tools/goal-tool.d.ts +6 -6
- package/dist/types/i18n/messages/en.d.ts +8 -8
- package/dist/types/internal-urls/local-protocol.d.ts +13 -9
- package/dist/types/lsp/index.d.ts +9 -9
- package/dist/types/lsp/types.d.ts +9 -9
- package/dist/types/main.d.ts +66 -2
- package/dist/types/modes/acp/acp-agent.d.ts +19 -2
- package/dist/types/modes/acp/acp-mode.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
- package/dist/types/modes/components/pet-selector.d.ts +11 -0
- package/dist/types/modes/components/sayknow-pet-widget.d.ts +59 -0
- package/dist/types/modes/components/session-selector.d.ts +22 -9
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- package/dist/types/modes/components/tool-execution.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +4 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +24 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
- package/dist/types/modes/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
- package/dist/types/modes/theme/defaults/index.d.ts +568 -568
- package/dist/types/modes/types.d.ts +28 -2
- package/dist/types/modes/utils/abort-message.d.ts +1 -1
- package/dist/types/modes/utils/context-usage.d.ts +4 -19
- package/dist/types/modes/utils/irc-message.d.ts +38 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
- package/dist/types/notifications/config.d.ts +34 -0
- package/dist/types/notifications/daemon-paths.d.ts +7 -0
- package/dist/types/notifications/index.d.ts +27 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
- package/dist/types/notifications/notification-service.d.ts +144 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
- package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +130 -3
- package/dist/types/notifications/telegram-reference.d.ts +19 -2
- package/dist/types/notifications/topic-registry.d.ts +24 -4
- package/dist/types/registry/agent-registry.d.ts +2 -0
- package/dist/types/rlm/index.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +40 -3
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/dist/types/session/provider-safety-stop.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +124 -4
- package/dist/types/session/session-storage.d.ts +155 -13
- package/dist/types/setup/credential-auto-import.d.ts +2 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/skc-runtime/state-schema.d.ts +19 -19
- package/dist/types/skc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/task/agents.d.ts +2 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +35 -27
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/browser.d.ts +28 -28
- package/dist/types/tools/computer.d.ts +20 -20
- package/dist/types/tools/cron.d.ts +2 -2
- package/dist/types/tools/debug.d.ts +40 -40
- package/dist/types/tools/gh.d.ts +10 -10
- package/dist/types/tools/image-gen.d.ts +2 -2
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/monitor.d.ts +6 -6
- package/dist/types/tools/skill-discovery.d.ts +4 -4
- package/dist/types/tools/subagent.d.ts +12 -12
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/tools/todo-write.d.ts +8 -8
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/dist/types/web/insane/bridge.d.ts +9 -9
- package/dist/types/web/search/index.d.ts +4 -4
- package/package.json +8 -8
- package/scripts/g004-tmux-smoke.ts +196 -36
- package/src/cli/daemon-cli.ts +19 -29
- package/src/cli/malloc-env-guard.test.ts +104 -0
- package/src/cli/malloc-env-guard.ts +149 -0
- package/src/cli/notify-cli.ts +78 -14
- package/src/cli/session-picker.ts +23 -40
- package/src/cli/update-cli.ts +56 -37
- package/src/cli.ts +51 -10
- package/src/commands/daemon.ts +19 -5
- package/src/commands/harness.ts +515 -47
- package/src/commands/launch.ts +11 -3
- package/src/commands/notify.ts +5 -1
- package/src/commands/session.ts +6 -1
- package/src/config/keybindings.ts +5 -0
- package/src/config/model-profile-activation.ts +1 -1
- package/src/config/model-profiles.ts +14 -14
- package/src/config/settings-schema.ts +41 -1
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +2958 -430
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/daemon/control-types.ts +11 -0
- package/src/daemon/operator-contract.ts +121 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/skc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +301 -80
- package/src/modes/acp/acp-agent.ts +730 -100
- package/src/modes/acp/acp-mode.ts +29 -2
- package/src/modes/components/assistant-message.ts +32 -3
- package/src/modes/components/bash-execution.ts +43 -13
- package/src/modes/components/custom-editor.ts +5 -0
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/hook-selector.ts +12 -0
- package/src/modes/components/irc-sidebar.ts +206 -0
- package/src/modes/components/pet-selector.ts +55 -0
- package/src/modes/components/sayknow-pet-widget.ts +360 -0
- package/src/modes/components/session-selector.ts +222 -221
- package/src/modes/components/settings-selector.ts +10 -0
- package/src/modes/components/status-line/segments.ts +14 -7
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/components/user-message.ts +26 -9
- package/src/modes/controllers/command-controller.ts +28 -2
- package/src/modes/controllers/event-controller.ts +159 -36
- package/src/modes/controllers/extension-ui-controller.ts +37 -18
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +199 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
- package/src/modes/types.ts +34 -2
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/irc-message.ts +138 -0
- package/src/modes/utils/ui-helpers.ts +216 -50
- package/src/notifications/config.ts +44 -0
- package/src/notifications/daemon-paths.ts +8 -0
- package/src/notifications/index.ts +535 -75
- package/src/notifications/lifecycle-control-runtime.ts +930 -77
- package/src/notifications/notification-service.ts +710 -0
- package/src/notifications/rate-limit-pool.ts +46 -12
- package/src/notifications/telegram-daemon-control.ts +27 -2
- package/src/notifications/telegram-daemon.ts +635 -94
- package/src/notifications/telegram-reference.ts +73 -13
- package/src/notifications/topic-registry.ts +86 -9
- package/src/prompts/system/irc-peer-roster.md +1 -0
- package/src/prompts/system/subagent-system-prompt.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/registry/agent-registry.ts +3 -0
- package/src/sdk.ts +52 -1
- package/src/secrets/obfuscator.ts +4 -2
- package/src/session/agent-session.ts +670 -217
- package/src/session/cache-economics.ts +275 -0
- package/src/session/context-estimation.ts +91 -0
- package/src/session/provider-safety-stop.ts +9 -0
- package/src/session/session-dump-format.ts +6 -0
- package/src/session/session-manager.ts +809 -39
- package/src/session/session-storage.ts +582 -38
- package/src/skc-runtime/session-state-sidecar.ts +756 -135
- package/src/skc-runtime/tmux-common.ts +26 -7
- package/src/skc-runtime/tmux-gc.ts +52 -9
- package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/skc-runtime/tmux-sessions.ts +871 -68
- package/src/slash-commands/builtin-registry.ts +153 -9
- package/src/slash-commands/helpers/context-report.ts +26 -4
- package/src/task/executor.ts +12 -3
- package/src/task/index.ts +8 -0
- package/src/task/receipt.ts +7 -2
- package/src/task/types.ts +26 -0
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/browser/tab-supervisor.ts +63 -6
- package/src/tools/browser.ts +8 -3
- package/src/tools/index.ts +81 -6
- package/src/tools/sqlite-reader.ts +8 -4
- package/src/tui/output-block.ts +18 -6
- package/src/utils/external-editor.ts +6 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import type { Usage } from "@sayknow-cli/ai";
|
|
2
|
+
|
|
3
|
+
const TOKENS_PER_MILLION = 1_000_000;
|
|
4
|
+
const MATERIAL_COST_USD = 0.01;
|
|
5
|
+
const MATERIAL_CACHE_TOKENS = 10_000;
|
|
6
|
+
const EXPENSIVE_MISS_COST_USD = 0.05;
|
|
7
|
+
const LARGE_INPUT_TOKENS = 20_000;
|
|
8
|
+
const LARGE_CACHE_WRITE_TOKENS = 25_000;
|
|
9
|
+
const TRANSCRIPT_WARNING_CAP = 3;
|
|
10
|
+
|
|
11
|
+
export interface CacheEconomicsPricing {
|
|
12
|
+
input: number;
|
|
13
|
+
cacheRead: number;
|
|
14
|
+
cacheWrite: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CacheEconomicsModelCost extends CacheEconomicsPricing {
|
|
18
|
+
output: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CacheEconomicsBasis =
|
|
22
|
+
| { kind: "persisted-aggregate"; costBreakdown: Usage["cost"] }
|
|
23
|
+
| { kind: "current-model-estimate"; pricing: CacheEconomicsPricing };
|
|
24
|
+
|
|
25
|
+
export interface CacheEconomicsUsage {
|
|
26
|
+
input: number;
|
|
27
|
+
output?: number;
|
|
28
|
+
cacheRead: number;
|
|
29
|
+
cacheWrite: number;
|
|
30
|
+
total?: number;
|
|
31
|
+
cost?: {
|
|
32
|
+
input?: number;
|
|
33
|
+
output?: number;
|
|
34
|
+
cacheRead?: number;
|
|
35
|
+
cacheWrite?: number;
|
|
36
|
+
total?: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CacheMissCostSummary {
|
|
41
|
+
inputTokens: number;
|
|
42
|
+
cacheReadTokens: number;
|
|
43
|
+
cacheWriteTokens: number;
|
|
44
|
+
inputCostUsd: number;
|
|
45
|
+
cacheReadCostUsd: number;
|
|
46
|
+
cacheWriteCostUsd: number;
|
|
47
|
+
cacheHitRate: number | undefined;
|
|
48
|
+
missPremiumUsd: number | undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* How a detected cache-miss pattern is attributed, per issue #2020.
|
|
53
|
+
*
|
|
54
|
+
* - `actionable`: usage evidence points to a user-controllable cause and SKC
|
|
55
|
+
* has a concrete remediation.
|
|
56
|
+
* - `diagnostic-only`: a miss pattern is observed but the cause is not
|
|
57
|
+
* determinable from usage alone; describe what is unknown, assert no cause.
|
|
58
|
+
* - `provider-side-suspected`: the provider returned no cache activity, so the
|
|
59
|
+
* miss cannot be attributed to the user's prompt; not user-actionable.
|
|
60
|
+
*/
|
|
61
|
+
export type CacheMissAttribution = "actionable" | "diagnostic-only" | "provider-side-suspected";
|
|
62
|
+
|
|
63
|
+
export interface CacheBehaviorWarning {
|
|
64
|
+
code: "expensive_cache_miss" | "cache_write_spike" | "provider_side_cache_miss";
|
|
65
|
+
attribution: CacheMissAttribution;
|
|
66
|
+
reason: string;
|
|
67
|
+
/** Concrete remediation — present only when `attribution` is `actionable`. */
|
|
68
|
+
nextStep?: string;
|
|
69
|
+
/** What SKC cannot determine — present for non-actionable attributions. */
|
|
70
|
+
unknown?: string;
|
|
71
|
+
costUsd: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface CacheWarningBuildState {
|
|
75
|
+
warningsEmitted: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function finiteNonNegative(value: unknown): value is number {
|
|
79
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function positiveFinite(value: number | undefined): value is number {
|
|
83
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function currentModelBucketCost(
|
|
87
|
+
tokens: number,
|
|
88
|
+
persistedCostUsd: number | undefined,
|
|
89
|
+
pricePerMillionTokens: number | undefined,
|
|
90
|
+
): number | undefined {
|
|
91
|
+
if (persistedCostUsd !== undefined) {
|
|
92
|
+
return finiteNonNegative(persistedCostUsd) ? persistedCostUsd : undefined;
|
|
93
|
+
}
|
|
94
|
+
if (!positiveFinite(tokens) || !positiveFinite(pricePerMillionTokens)) return 0;
|
|
95
|
+
return (tokens / TOKENS_PER_MILLION) * pricePerMillionTokens;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function persistedAggregateCosts(costBreakdown: Usage["cost"] | undefined): [number, number, number] | undefined {
|
|
99
|
+
if (!costBreakdown) return undefined;
|
|
100
|
+
const { input, cacheRead, cacheWrite, output, total } = costBreakdown;
|
|
101
|
+
if (![input, cacheRead, cacheWrite, output, total].every(finiteNonNegative)) return undefined;
|
|
102
|
+
return [input, cacheRead, cacheWrite];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function hasMaterialEvidence(usage: CacheEconomicsUsage, costs: readonly number[]): boolean {
|
|
106
|
+
const tokenEvidence = usage.input + usage.cacheRead + usage.cacheWrite >= MATERIAL_CACHE_TOKENS;
|
|
107
|
+
const costEvidence = costs.some(cost => cost >= MATERIAL_COST_USD);
|
|
108
|
+
return tokenEvidence || costEvidence;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function computeCacheMissCostSummary(
|
|
112
|
+
usage: CacheEconomicsUsage | undefined,
|
|
113
|
+
basis: CacheEconomicsBasis,
|
|
114
|
+
): CacheMissCostSummary | undefined {
|
|
115
|
+
if (!usage) return undefined;
|
|
116
|
+
|
|
117
|
+
const costs =
|
|
118
|
+
basis.kind === "persisted-aggregate"
|
|
119
|
+
? persistedAggregateCosts(basis.costBreakdown)
|
|
120
|
+
: [
|
|
121
|
+
currentModelBucketCost(usage.input, usage.cost?.input, basis.pricing.input),
|
|
122
|
+
currentModelBucketCost(usage.cacheRead, usage.cost?.cacheRead, basis.pricing.cacheRead),
|
|
123
|
+
currentModelBucketCost(usage.cacheWrite, usage.cost?.cacheWrite, basis.pricing.cacheWrite),
|
|
124
|
+
];
|
|
125
|
+
if (!costs) return undefined;
|
|
126
|
+
const [inputCostUsd, cacheReadCostUsd, cacheWriteCostUsd] = costs;
|
|
127
|
+
if (inputCostUsd === undefined || cacheReadCostUsd === undefined || cacheWriteCostUsd === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
if (usage.input <= 0 && cacheWriteCostUsd <= 0) return undefined;
|
|
130
|
+
if (!hasMaterialEvidence(usage, [inputCostUsd, cacheReadCostUsd, cacheWriteCostUsd])) return undefined;
|
|
131
|
+
if (inputCostUsd <= 0 && cacheWriteCostUsd <= 0) return undefined;
|
|
132
|
+
|
|
133
|
+
const totalReusableInput = usage.input + usage.cacheRead;
|
|
134
|
+
const cacheHitRate = totalReusableInput > 0 ? usage.cacheRead / totalReusableInput : undefined;
|
|
135
|
+
const missPremiumUsd =
|
|
136
|
+
basis.kind === "current-model-estimate" &&
|
|
137
|
+
positiveFinite(basis.pricing.input) &&
|
|
138
|
+
positiveFinite(basis.pricing.cacheRead) &&
|
|
139
|
+
basis.pricing.input > basis.pricing.cacheRead &&
|
|
140
|
+
usage.input > 0
|
|
141
|
+
? ((basis.pricing.input - basis.pricing.cacheRead) * usage.input) / TOKENS_PER_MILLION
|
|
142
|
+
: undefined;
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
inputTokens: usage.input,
|
|
146
|
+
cacheReadTokens: usage.cacheRead,
|
|
147
|
+
cacheWriteTokens: usage.cacheWrite,
|
|
148
|
+
inputCostUsd,
|
|
149
|
+
cacheReadCostUsd,
|
|
150
|
+
cacheWriteCostUsd,
|
|
151
|
+
cacheHitRate,
|
|
152
|
+
missPremiumUsd: positiveFinite(missPremiumUsd) ? missPremiumUsd : undefined,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function formatUsd(cost: number): string {
|
|
157
|
+
return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function formatPercent(value: number): string {
|
|
161
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function formatCacheMissSummaryLines(summary: CacheMissCostSummary): string[] {
|
|
165
|
+
const lines = [`Uncached Input Cost: ${formatUsd(summary.inputCostUsd)}`];
|
|
166
|
+
if (summary.missPremiumUsd !== undefined) {
|
|
167
|
+
lines.push(`Estimated Miss Premium: ${formatUsd(summary.missPremiumUsd)} vs cache-read pricing`);
|
|
168
|
+
}
|
|
169
|
+
if (summary.cacheHitRate !== undefined) {
|
|
170
|
+
lines.push(`Cache Hit Rate: ${formatPercent(summary.cacheHitRate)}`);
|
|
171
|
+
}
|
|
172
|
+
if (summary.cacheWriteCostUsd >= MATERIAL_COST_USD) {
|
|
173
|
+
lines.push(`Cache Write Cost: ${formatUsd(summary.cacheWriteCostUsd)}`);
|
|
174
|
+
}
|
|
175
|
+
return lines;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function buildCacheBehaviorWarning(
|
|
179
|
+
usage: Usage | undefined,
|
|
180
|
+
model: { cost: CacheEconomicsModelCost } | undefined | null,
|
|
181
|
+
): CacheBehaviorWarning | undefined {
|
|
182
|
+
const summary = model
|
|
183
|
+
? computeCacheMissCostSummary(usage, { kind: "current-model-estimate", pricing: model.cost })
|
|
184
|
+
: undefined;
|
|
185
|
+
if (!summary) return undefined;
|
|
186
|
+
if (
|
|
187
|
+
summary.inputTokens >= LARGE_INPUT_TOKENS &&
|
|
188
|
+
(summary.cacheHitRate === undefined || summary.cacheHitRate < 0.25) &&
|
|
189
|
+
summary.inputCostUsd >= EXPENSIVE_MISS_COST_USD
|
|
190
|
+
) {
|
|
191
|
+
// Attribute by observable cache activity rather than asserting a cause (#2020).
|
|
192
|
+
const noCacheActivity = summary.cacheReadTokens <= 0 && summary.cacheWriteTokens <= 0;
|
|
193
|
+
if (noCacheActivity) {
|
|
194
|
+
// The provider returned zero cache read/write tokens for a large, costly
|
|
195
|
+
// prompt: the miss cannot be traced to the user's prefix, and keeping a
|
|
196
|
+
// stable prefix cannot help when nothing is being cached at all.
|
|
197
|
+
return {
|
|
198
|
+
code: "provider_side_cache_miss",
|
|
199
|
+
attribution: "provider-side-suspected",
|
|
200
|
+
reason: `large uncached input with no cache activity (${formatUsd(summary.inputCostUsd)})`,
|
|
201
|
+
unknown:
|
|
202
|
+
"the provider reported no cache reads or writes, so SKC cannot tell whether prompt caching is unsupported, missing cache-control fields, or provider-side eviction",
|
|
203
|
+
costUsd: summary.inputCostUsd,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (summary.cacheWriteTokens > 0) {
|
|
207
|
+
// Writes are landing but reuse is low: the cacheable prefix is churning
|
|
208
|
+
// between turns, which is user-controllable.
|
|
209
|
+
return {
|
|
210
|
+
code: "expensive_cache_miss",
|
|
211
|
+
attribution: "actionable",
|
|
212
|
+
reason: `large uncached input with low cache reuse (${formatUsd(summary.inputCostUsd)})`,
|
|
213
|
+
nextStep: "keep the stable prefix; avoid rereading unchanged context before the next turn",
|
|
214
|
+
costUsd: summary.inputCostUsd,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// Reads happened but no writes this turn: part of the prefix was reused, yet
|
|
218
|
+
// the large new input was not cached. From usage alone SKC cannot tell new
|
|
219
|
+
// content apart from a provider-side gap, so it reports without asserting.
|
|
220
|
+
return {
|
|
221
|
+
code: "expensive_cache_miss",
|
|
222
|
+
attribution: "diagnostic-only",
|
|
223
|
+
reason: `large uncached input with partial cache reuse (${formatUsd(summary.inputCostUsd)})`,
|
|
224
|
+
unknown:
|
|
225
|
+
"the uncached tokens may be genuinely new content or a provider-side gap; usage alone cannot attribute a single cause",
|
|
226
|
+
costUsd: summary.inputCostUsd,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (
|
|
230
|
+
summary.cacheWriteTokens >= LARGE_CACHE_WRITE_TOKENS &&
|
|
231
|
+
summary.cacheWriteCostUsd >= EXPENSIVE_MISS_COST_USD &&
|
|
232
|
+
// Only claim reuse is insufficient when reads actually fail to cover the
|
|
233
|
+
// writes; a large write that is being read back is healthy, not a spike.
|
|
234
|
+
summary.cacheReadTokens < summary.cacheWriteTokens
|
|
235
|
+
) {
|
|
236
|
+
return {
|
|
237
|
+
code: "cache_write_spike",
|
|
238
|
+
attribution: "actionable",
|
|
239
|
+
reason: `large cache write without enough matching reads (${formatUsd(summary.cacheWriteCostUsd)})`,
|
|
240
|
+
nextStep: "make the next turn reuse the same context; avoid changing system or tool prefixes",
|
|
241
|
+
costUsd: summary.cacheWriteCostUsd,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Render a cache warning as a single transcript line. Actionable attributions
|
|
249
|
+
* carry a concrete next step; non-actionable ones state what is unknown and,
|
|
250
|
+
* for provider-side patterns, that the miss is not user-actionable — never
|
|
251
|
+
* asserting a cause or blaming the provider (#2020).
|
|
252
|
+
*/
|
|
253
|
+
export function formatCacheWarningLine(warning: CacheBehaviorWarning): string {
|
|
254
|
+
if (warning.attribution === "actionable" && warning.nextStep) {
|
|
255
|
+
return `Cache warning: ${warning.reason}; next step: ${warning.nextStep}.`;
|
|
256
|
+
}
|
|
257
|
+
if (warning.attribution === "provider-side-suspected") {
|
|
258
|
+
const detail = warning.unknown ? ` — ${warning.unknown}` : "";
|
|
259
|
+
return `Cache notice: ${warning.reason}; provider-side suspected / not user-actionable${detail}.`;
|
|
260
|
+
}
|
|
261
|
+
const detail = warning.unknown ? `; ${warning.unknown}` : "";
|
|
262
|
+
return `Cache notice: ${warning.reason}${detail}.`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function buildCacheEconomicsWarning(
|
|
266
|
+
usage: Usage | undefined,
|
|
267
|
+
model: { cost: CacheEconomicsModelCost } | undefined | null,
|
|
268
|
+
state: CacheWarningBuildState,
|
|
269
|
+
): string | undefined {
|
|
270
|
+
if (state.warningsEmitted >= TRANSCRIPT_WARNING_CAP) return undefined;
|
|
271
|
+
const warning = buildCacheBehaviorWarning(usage, model);
|
|
272
|
+
if (!warning) return undefined;
|
|
273
|
+
state.warningsEmitted += 1;
|
|
274
|
+
return formatCacheWarningLine(warning);
|
|
275
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-neutral fixed-context (non-message) token estimation.
|
|
3
|
+
*
|
|
4
|
+
* Lives in the session layer so load-bearing session/compaction behavior
|
|
5
|
+
* (`AgentSession` context estimates) never depends on a presentation module.
|
|
6
|
+
* UI surfaces (`modes/utils/context-usage.ts`, status line) import from here
|
|
7
|
+
* as well, so every surface reports the same numbers.
|
|
8
|
+
*/
|
|
9
|
+
import { estimateTextTokensHeuristic } from "@sayknow-cli/agent-core/compaction";
|
|
10
|
+
import type { Skill } from "../extensibility/skills";
|
|
11
|
+
import type { Tool } from "../tools";
|
|
12
|
+
import type { AgentSession } from "./agent-session";
|
|
13
|
+
|
|
14
|
+
export function estimateSkillsTokens(skills: readonly Skill[]): number {
|
|
15
|
+
const fragments: string[] = [];
|
|
16
|
+
for (const skill of skills) {
|
|
17
|
+
// "- name: description\n" wire framing tokenizes ~identically to the
|
|
18
|
+
// concatenated form, so encode each piece separately and sum.
|
|
19
|
+
fragments.push(skill.name, skill.description);
|
|
20
|
+
}
|
|
21
|
+
return estimateTextTokensHeuristic(fragments);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function estimateToolSchemaTokens(
|
|
25
|
+
tools: ReadonlyArray<Pick<Tool, "name" | "description" | "parameters">>,
|
|
26
|
+
): number {
|
|
27
|
+
const fragments: string[] = [];
|
|
28
|
+
for (const tool of tools) {
|
|
29
|
+
fragments.push(tool.name, tool.description);
|
|
30
|
+
try {
|
|
31
|
+
fragments.push(JSON.stringify(tool.parameters ?? {}));
|
|
32
|
+
} catch {
|
|
33
|
+
// Schema may contain functions or cycles; ignore.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return estimateTextTokensHeuristic(fragments);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compute just the NON-MESSAGE token total: system prompt (with its skills
|
|
41
|
+
* section subtracted, since skills are tokenized separately) + system context
|
|
42
|
+
* (the rest of the system-prompt array) + tools + skills.
|
|
43
|
+
*
|
|
44
|
+
* Exposed so callers like `StatusLineComponent` can cache the non-message
|
|
45
|
+
* total separately from the message total. Non-message inputs (skills,
|
|
46
|
+
* tools, system prompt) change rarely; the message list grows on every
|
|
47
|
+
* streaming turn. Splitting the two lets the caller refresh each on its own
|
|
48
|
+
* cadence — non-message recomputed only when the inputs identity changes,
|
|
49
|
+
* messages walked incrementally as new entries append.
|
|
50
|
+
*/
|
|
51
|
+
export function computeNonMessageTokens(session: AgentSession): number {
|
|
52
|
+
const parts = computeNonMessageBreakdown(session);
|
|
53
|
+
return (
|
|
54
|
+
parts.systemPromptTokens + parts.systemContextTokens + parts.rulesTokens + parts.toolsTokens + parts.skillsTokens
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Shared helper for the four non-message token totals. Single source of truth
|
|
60
|
+
* for `computeNonMessageTokens` (session estimates + status-line incremental
|
|
61
|
+
* cache) and `computeContextBreakdown` (/context panel). The split avoids
|
|
62
|
+
* drift between the surfaces — they MUST report the same numbers.
|
|
63
|
+
*/
|
|
64
|
+
export function computeNonMessageBreakdown(session: AgentSession): {
|
|
65
|
+
rulesTokens: number;
|
|
66
|
+
skillsTokens: number;
|
|
67
|
+
toolsTokens: number;
|
|
68
|
+
systemContextTokens: number;
|
|
69
|
+
systemPromptTokens: number;
|
|
70
|
+
} {
|
|
71
|
+
const skillsTokens = estimateSkillsTokens(session.skills ?? []);
|
|
72
|
+
const toolsTokens = estimateToolSchemaTokens(session.agent?.state?.tools ?? []);
|
|
73
|
+
const systemPromptParts = session.systemPrompt ?? [];
|
|
74
|
+
const rulesTokens = estimateRulesTokens(systemPromptParts);
|
|
75
|
+
const systemContextTokens = estimateTextTokensHeuristic(systemPromptParts.slice(1));
|
|
76
|
+
const systemPromptTokens = Math.max(
|
|
77
|
+
0,
|
|
78
|
+
estimateTextTokensHeuristic(systemPromptParts[0] ?? "") - skillsTokens - rulesTokens,
|
|
79
|
+
);
|
|
80
|
+
return { rulesTokens, skillsTokens, toolsTokens, systemContextTokens, systemPromptTokens };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function estimateRulesTokens(systemPromptParts: readonly string[]): number {
|
|
84
|
+
const fragments: string[] = [];
|
|
85
|
+
for (const part of systemPromptParts) {
|
|
86
|
+
for (const match of part.matchAll(/<rules>[\s\S]*?<\/rules>/g)) {
|
|
87
|
+
fragments.push(match[0]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return fragments.length === 0 ? 0 : estimateTextTokensHeuristic(fragments);
|
|
91
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recognizes legacy provider safety-stop labels persisted before `errorKind`.
|
|
3
|
+
* Keep this anchored and conservative so incidental transient-error prose stays retryable.
|
|
4
|
+
*/
|
|
5
|
+
export function isLegacyProviderSafetyStopMessage(errorMessage: string): boolean {
|
|
6
|
+
return /^(?:refusal(?: \([^()\s\r\n](?:[^()\r\n]*[^()\s\r\n])?\))?(?:: \S(?:[^\r\n]*\S)?)?|content flagged by safety filters|blocked under anthropic's usage policy\.|provider finish_reason: content_filter)[ \t]*(?![\s\S])/i.test(
|
|
7
|
+
errorMessage,
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { AgentMessage, ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
5
5
|
import { INTENT_FIELD } from "@sayknow-cli/agent-core";
|
|
6
6
|
import type { AssistantMessage, Model } from "@sayknow-cli/ai";
|
|
7
|
+
import { buildCacheEconomicsWarning, type CacheWarningBuildState } from "./cache-economics";
|
|
7
8
|
import {
|
|
8
9
|
type BashExecutionMessage,
|
|
9
10
|
type BranchSummaryMessage,
|
|
@@ -137,6 +138,7 @@ export function formatSessionDumpText(options: FormatSessionDumpTextOptions): st
|
|
|
137
138
|
lines.push("\n");
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
const cacheWarningState: CacheWarningBuildState = { warningsEmitted: 0 };
|
|
140
142
|
for (const msg of options.messages) {
|
|
141
143
|
if (msg.role === "user" || msg.role === "developer") {
|
|
142
144
|
lines.push(msg.role === "developer" ? "## Developer\n" : "## User\n");
|
|
@@ -172,6 +174,10 @@ export function formatSessionDumpText(options: FormatSessionDumpTextOptions): st
|
|
|
172
174
|
lines.push("<" + "/invoke>\n");
|
|
173
175
|
}
|
|
174
176
|
}
|
|
177
|
+
const cacheWarning = buildCacheEconomicsWarning(assistantMsg.usage, model, cacheWarningState);
|
|
178
|
+
if (cacheWarning) {
|
|
179
|
+
lines.push(cacheWarning);
|
|
180
|
+
}
|
|
175
181
|
lines.push("");
|
|
176
182
|
} else if (msg.role === "toolResult") {
|
|
177
183
|
lines.push(`### Tool Result: ${msg.toolName}`);
|