@martinloop/mcp 0.7.0 → 0.8.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.
Files changed (73) hide show
  1. package/dist/package-version.d.ts +1 -1
  2. package/dist/package-version.js +1 -1
  3. package/dist/vendor/adapters/claude-cli.d.ts +11 -1
  4. package/dist/vendor/adapters/claude-cli.js +285 -71
  5. package/dist/vendor/adapters/cli-bridge.d.ts +12 -3
  6. package/dist/vendor/adapters/cli-bridge.js +129 -149
  7. package/dist/vendor/adapters/codex-cli.d.ts +3 -3
  8. package/dist/vendor/adapters/codex-cli.js +10 -24
  9. package/dist/vendor/adapters/codex-host.js +127 -132
  10. package/dist/vendor/adapters/index.d.ts +2 -1
  11. package/dist/vendor/adapters/index.js +2 -1
  12. package/dist/vendor/adapters/openai-compatible.js +88 -6
  13. package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
  14. package/dist/vendor/adapters/process-supervisor.js +541 -0
  15. package/dist/vendor/adapters/verifier-only.d.ts +10 -1
  16. package/dist/vendor/adapters/verifier-only.js +43 -1
  17. package/dist/vendor/contracts/external-outcome.d.ts +102 -0
  18. package/dist/vendor/contracts/external-outcome.js +226 -0
  19. package/dist/vendor/contracts/index.d.ts +8 -1
  20. package/dist/vendor/contracts/index.js +2 -0
  21. package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
  22. package/dist/vendor/contracts/swarm-hosted.js +629 -0
  23. package/dist/vendor/contracts/swarm.d.ts +449 -0
  24. package/dist/vendor/contracts/swarm.js +550 -0
  25. package/dist/vendor/core/external-outcome.d.ts +34 -0
  26. package/dist/vendor/core/external-outcome.js +515 -0
  27. package/dist/vendor/core/grounding.js +1 -1
  28. package/dist/vendor/core/index.d.ts +47 -1
  29. package/dist/vendor/core/index.js +53 -3
  30. package/dist/vendor/core/leash.d.ts +1 -0
  31. package/dist/vendor/core/leash.js +13 -0
  32. package/dist/vendor/core/persistence/integrity.d.ts +4 -0
  33. package/dist/vendor/core/persistence/integrity.js +19 -6
  34. package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
  35. package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
  36. package/dist/vendor/core/swarm/candidates.d.ts +78 -0
  37. package/dist/vendor/core/swarm/candidates.js +810 -0
  38. package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
  39. package/dist/vendor/core/swarm/evidence-projection.js +128 -0
  40. package/dist/vendor/core/swarm/evidence.d.ts +40 -0
  41. package/dist/vendor/core/swarm/evidence.js +575 -0
  42. package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
  43. package/dist/vendor/core/swarm/hosted-export.js +362 -0
  44. package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
  45. package/dist/vendor/core/swarm/hosted-projection.js +258 -0
  46. package/dist/vendor/core/swarm/index.d.ts +110 -0
  47. package/dist/vendor/core/swarm/index.js +1060 -0
  48. package/dist/vendor/core/swarm/integration.d.ts +76 -0
  49. package/dist/vendor/core/swarm/integration.js +615 -0
  50. package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
  51. package/dist/vendor/core/swarm/lifecycle.js +252 -0
  52. package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
  53. package/dist/vendor/core/swarm/live-runtime.js +1482 -0
  54. package/dist/vendor/core/swarm/live-store.d.ts +99 -0
  55. package/dist/vendor/core/swarm/live-store.js +860 -0
  56. package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
  57. package/dist/vendor/core/swarm/operational-reader.js +96 -0
  58. package/dist/vendor/core/swarm/operations.d.ts +29 -0
  59. package/dist/vendor/core/swarm/operations.js +128 -0
  60. package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
  61. package/dist/vendor/core/swarm/parent-receipt.js +489 -0
  62. package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
  63. package/dist/vendor/core/swarm/receipt-projection.js +120 -0
  64. package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
  65. package/dist/vendor/core/swarm/scheduler.js +555 -0
  66. package/dist/vendor/core/swarm/verification.d.ts +70 -0
  67. package/dist/vendor/core/swarm/verification.js +295 -0
  68. package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
  69. package/dist/vendor/core/swarm/workspaces.js +488 -0
  70. package/dist/vendor/core/verified-handoff.d.ts +4 -0
  71. package/dist/vendor/core/verified-handoff.js +4 -0
  72. package/package.json +1 -1
  73. package/server.json +2 -2
@@ -1 +1 @@
1
- export declare const MARTIN_MCP_PACKAGE_VERSION = "0.7.0";
1
+ export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.0";
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by build — do not edit by hand.
2
2
  // Source of truth: packages/mcp/package.json#version
3
- export const MARTIN_MCP_PACKAGE_VERSION = "0.7.0";
3
+ export const MARTIN_MCP_PACKAGE_VERSION = "0.8.0";
@@ -22,8 +22,10 @@ import { type SpawnLike } from "./cli-bridge.js";
22
22
  export type CliArgsBuilder = (prompt: string, request: MartinAdapterRequest) => string[];
23
23
  export type CliStdinBuilder = (prompt: string) => string | undefined;
24
24
  export interface AgentCliAdapterOptions {
25
- /** The executable to spawn (e.g. "claude", "codex"). */
25
+ /** Stable provider identity (e.g. "claude", "codex"). */
26
26
  command: string;
27
+ /** Exact executable to spawn when it differs from the provider identity. */
28
+ executionCommand?: string;
27
29
  /** Converts a prompt string into the argv array passed to spawn(). */
28
30
  argsBuilder: CliArgsBuilder;
29
31
  /** Optional stdin payload for CLIs that accept prompt input via stdin or `-`. */
@@ -57,6 +59,10 @@ export interface AgentCliAdapterOptions {
57
59
  * rather than only learning about an overspend after the process exits.
58
60
  */
59
61
  streamingUsageCap?: boolean;
62
+ /** Terminate after streamed provider usage reports request.context.remainingTokens was exceeded. */
63
+ streamingTokenCap?: boolean;
64
+ /** Provider detail-token fields are already included in input/output token totals. */
65
+ streamingUsageDetailsIncludedInTotals?: boolean;
60
66
  /** Test-only override for subprocess spawning. */
61
67
  spawnImpl?: SpawnLike;
62
68
  }
@@ -69,6 +75,8 @@ export interface ClaudeCliAdapterOptions {
69
75
  label?: string;
70
76
  /** Override the model passed via --model flag. */
71
77
  model?: string;
78
+ /** Enforce Claude Code's non-mutating plan permission mode. */
79
+ readOnly?: boolean;
72
80
  /** Extra args appended after core args (before prompt). */
73
81
  extraArgs?: string[];
74
82
  spawnImpl?: SpawnLike;
@@ -107,6 +115,8 @@ export interface GeminiCliAdapterOptions {
107
115
  label?: string;
108
116
  /** Explicit model override passed via --model. Omitted to preserve Gemini Auto. */
109
117
  model?: string;
118
+ /** Enforce Gemini's non-mutating plan approval mode. */
119
+ readOnly?: boolean;
110
120
  /** Approval mode for headless Gemini runs. Defaults to yolo for autonomous execution. */
111
121
  approvalMode?: "default" | "auto_edit" | "yolo" | "plan";
112
122
  /** Enable Gemini sandbox mode when the host is configured for it. Disabled by default. */
@@ -24,24 +24,76 @@ import { createAdapterCapabilities, normalizeStructuredErrors, normalizeUsage }
24
24
  // ---------------------------------------------------------------------------
25
25
  const BLENDED_INPUT_COST_PER_1K = 0.003; // $/1K input tokens
26
26
  const BLENDED_OUTPUT_COST_PER_1K = 0.012; // $/1K output tokens
27
- // USD per 1K tokens. Claude cache creation uses the documented default
28
- // five-minute rate; cache reads are priced independently from fresh input.
27
+ const PRICING_SNAPSHOT_2026_10_04 = "official-provider-pricing@2026-10-04";
28
+ // USD per 1K tokens. Rates are standard/on-demand text-token prices from
29
+ // official provider pricing pages as of 2026-10-04. Long-context tiers are
30
+ // selected from the estimated/observed input-token count where providers
31
+ // publish a threshold.
29
32
  const MODEL_PRICING = {
30
- "claude-opus-4-6": { inputPer1K: 0.005, cachedInputPer1K: 0.0005, cacheCreationInputPer1K: 0.00625, outputPer1K: 0.025 },
31
- "claude-sonnet-4-6": { inputPer1K: 0.003, cachedInputPer1K: 0.0003, cacheCreationInputPer1K: 0.00375, outputPer1K: 0.015 },
32
- "claude-haiku-4-5": { inputPer1K: 0.001, cachedInputPer1K: 0.0001, cacheCreationInputPer1K: 0.00125, outputPer1K: 0.005 },
33
- // OpenAI coding models
34
- "codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
35
- "gpt-5-codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
36
- "gpt-5.1-codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
37
- "gpt-5.1-codex-max": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
33
+ // Anthropic current + supported legacy models.
34
+ "claude-fable-5-1": { inputPer1K: 0.010, cachedInputPer1K: 0.00025, cacheCreationInputPer1K: 0.0125, outputPer1K: 0.050, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
35
+ "claude-opus-5-5": { inputPer1K: 0.004, cachedInputPer1K: 0.0002, cacheCreationInputPer1K: 0.005, outputPer1K: 0.020, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
36
+ "claude-sonnet-5-5": { inputPer1K: 0.002, cachedInputPer1K: 0.0002, cacheCreationInputPer1K: 0.0025, outputPer1K: 0.010, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
37
+ "claude-opus-4-6": { inputPer1K: 0.005, cachedInputPer1K: 0.0005, cacheCreationInputPer1K: 0.00625, outputPer1K: 0.025, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
38
+ "claude-sonnet-4-6": { inputPer1K: 0.003, cachedInputPer1K: 0.0003, cacheCreationInputPer1K: 0.00375, outputPer1K: 0.015, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
39
+ "claude-haiku-4-5": { inputPer1K: 0.001, cachedInputPer1K: 0.0001, cacheCreationInputPer1K: 0.00125, outputPer1K: 0.005, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
40
+ // OpenAI / Codex. GPT-6-family long-context pricing applies above 272K input tokens.
41
+ "gpt-6-astra": {
42
+ inputPer1K: 0.010, cachedInputPer1K: 0.001, cacheCreationInputPer1K: 0.0125, outputPer1K: 0.050,
43
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
44
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.020, cachedInputPer1K: 0.002, cacheCreationInputPer1K: 0.025, outputPer1K: 0.075 }
45
+ },
46
+ "gpt-6.1-sol": {
47
+ inputPer1K: 0.002, cachedInputPer1K: 0.0001, cacheCreationInputPer1K: 0.0025, outputPer1K: 0.010,
48
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
49
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.004, cachedInputPer1K: 0.0002, cacheCreationInputPer1K: 0.005, outputPer1K: 0.015 }
50
+ },
51
+ "gpt-6-sol": {
52
+ inputPer1K: 0.002, cachedInputPer1K: 0.0002, cacheCreationInputPer1K: 0.0025, outputPer1K: 0.010,
53
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
54
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.004, cachedInputPer1K: 0.0004, cacheCreationInputPer1K: 0.005, outputPer1K: 0.015 }
55
+ },
56
+ "gpt-6-luna": {
57
+ inputPer1K: 0.0001, cachedInputPer1K: 0.00001, cacheCreationInputPer1K: 0.000125, outputPer1K: 0.0005,
58
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
59
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.0002, cachedInputPer1K: 0.00002, cacheCreationInputPer1K: 0.00025, outputPer1K: 0.00075 }
60
+ },
61
+ "gpt-5.6-sol": {
62
+ inputPer1K: 0.004, cachedInputPer1K: 0.0004, cacheCreationInputPer1K: 0.005, outputPer1K: 0.020,
63
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
64
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.008, cachedInputPer1K: 0.0008, cacheCreationInputPer1K: 0.010, outputPer1K: 0.030 }
65
+ },
66
+ "gpt-5.3-codex": { inputPer1K: 0.00175, cachedInputPer1K: 0.000175, outputPer1K: 0.014, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
67
+ "codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010 },
68
+ "gpt-5-codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010 },
69
+ "gpt-5.1-codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010 },
70
+ "gpt-5.1-codex-max": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010 },
38
71
  "gpt-5.2-codex": { inputPer1K: 0.00175, cachedInputPer1K: 0.000175, outputPer1K: 0.014 },
39
- "codex-mini-latest": { inputPer1K: 0.0015, cachedInputPer1K: 0.000375, outputPer1K: 0.006 }
72
+ "codex-mini-latest": { inputPer1K: 0.0015, cachedInputPer1K: 0.000375, outputPer1K: 0.006 },
73
+ // Google Gemini current + common supported models.
74
+ "gemini-3.8-flash": { inputPer1K: 0.00075, cachedInputPer1K: 0.000075, outputPer1K: 0.00375, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
75
+ "gemini-3.7-flash": { inputPer1K: 0.00075, cachedInputPer1K: 0.000075, outputPer1K: 0.00375, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
76
+ "gemini-3.5-flash-lite": { inputPer1K: 0.0003, cachedInputPer1K: 0.00003, outputPer1K: 0.0025, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
77
+ "gemini-3.1-flash-lite": { inputPer1K: 0.00025, cachedInputPer1K: 0.000025, outputPer1K: 0.0015, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
78
+ "gemini-3.1-pro-preview": {
79
+ inputPer1K: 0.002, cachedInputPer1K: 0.0002, outputPer1K: 0.012, pricingVersion: PRICING_SNAPSHOT_2026_10_04,
80
+ longContext: { thresholdInputTokens: 200_000, inputPer1K: 0.004, cachedInputPer1K: 0.0004, outputPer1K: 0.018 }
81
+ },
82
+ "gemini-3-flash-preview": { inputPer1K: 0.0005, cachedInputPer1K: 0.00005, outputPer1K: 0.003, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
83
+ "gemini-2.5-pro": {
84
+ inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010, pricingVersion: PRICING_SNAPSHOT_2026_10_04,
85
+ longContext: { thresholdInputTokens: 200_000, inputPer1K: 0.0025, cachedInputPer1K: 0.00025, outputPer1K: 0.015 }
86
+ },
87
+ "gemini-2.5-flash": { inputPer1K: 0.0003, cachedInputPer1K: 0.00003, outputPer1K: 0.0025, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
88
+ "gemini-2.5-flash-lite": { inputPer1K: 0.0001, cachedInputPer1K: 0.00001, outputPer1K: 0.0004, pricingVersion: PRICING_SNAPSHOT_2026_10_04 }
40
89
  };
41
90
  const CLAUDE_MODEL_ALIASES = [
42
- { canonicalModelId: "claude-opus-4-6", aliases: [/^claude-opus-4-6-\d{8}$/u, /^claude-opus$/u] },
43
- { canonicalModelId: "claude-sonnet-4-6", aliases: [/^claude-sonnet-4-6-\d{8}$/u, /^claude-sonnet$/u] },
44
- { canonicalModelId: "claude-haiku-4-5", aliases: [/^claude-haiku-4-5-\d{8}$/u, /^claude-haiku$/u] }
91
+ { canonicalModelId: "claude-fable-5-1", aliases: [/^claude-fable-5-1-\d{8}$/u, /^claude-fable$/u, /^fable$/u] },
92
+ { canonicalModelId: "claude-opus-5-5", aliases: [/^claude-opus-5-5-\d{8}$/u, /^claude-opus$/u, /^opus$/u] },
93
+ { canonicalModelId: "claude-sonnet-5-5", aliases: [/^claude-sonnet-5-5-\d{8}$/u, /^claude-sonnet$/u, /^sonnet$/u] },
94
+ { canonicalModelId: "claude-opus-4-6", aliases: [/^claude-opus-4-6-\d{8}$/u] },
95
+ { canonicalModelId: "claude-sonnet-4-6", aliases: [/^claude-sonnet-4-6-\d{8}$/u] },
96
+ { canonicalModelId: "claude-haiku-4-5", aliases: [/^claude-haiku-4-5-\d{8}$/u, /^claude-haiku$/u, /^haiku$/u] }
45
97
  ];
46
98
  function resolveModelPricing(modelLabel) {
47
99
  const normalized = modelLabel?.trim().toLowerCase();
@@ -63,13 +115,28 @@ function resolveModelPricing(modelLabel) {
63
115
  }
64
116
  return { status: "unknown" };
65
117
  }
66
- function calculateUsageCost(usage, pricing) {
67
- return ((usage.inputTokens / 1000) * pricing.inputPer1K +
68
- (usage.cachedInputTokens / 1000) * (pricing.cachedInputPer1K ?? pricing.inputPer1K) +
69
- (usage.cacheCreationInputTokens / 1000) * (pricing.cacheCreationInputPer1K ?? pricing.inputPer1K) +
70
- (usage.outputTokens / 1000) * pricing.outputPer1K);
118
+ function effectivePricingForInput(pricing, inputTokens) {
119
+ const long = pricing.longContext;
120
+ if (!long || inputTokens <= long.thresholdInputTokens) {
121
+ return pricing;
122
+ }
123
+ return {
124
+ inputPer1K: long.inputPer1K,
125
+ cachedInputPer1K: long.cachedInputPer1K,
126
+ cacheCreationInputPer1K: long.cacheCreationInputPer1K,
127
+ outputPer1K: long.outputPer1K,
128
+ pricingVersion: pricing.pricingVersion
129
+ };
130
+ }
131
+ function calculateUsageCost(usage, pricing, contextInputTokens = usage.inputTokens + usage.cachedInputTokens + usage.cacheCreationInputTokens) {
132
+ const effective = effectivePricingForInput(pricing, contextInputTokens);
133
+ return ((usage.inputTokens / 1000) * effective.inputPer1K +
134
+ (usage.cachedInputTokens / 1000) * (effective.cachedInputPer1K ?? effective.inputPer1K) +
135
+ (usage.cacheCreationInputTokens / 1000) * (effective.cacheCreationInputPer1K ?? effective.inputPer1K) +
136
+ (usage.outputTokens / 1000) * effective.outputPer1K);
71
137
  }
72
138
  const EMBEDDED_PRICING_VERSION = "embedded-v1";
139
+ // Source snapshot: https://developers.openai.com/api/docs/models/gpt-6.1-sol
73
140
  function extractClaudeObservedModel(stdout) {
74
141
  for (const line of stdout.split(/\r?\n/u)) {
75
142
  try {
@@ -135,7 +202,9 @@ function extractUsage(parsed, modelLabel, modelSource) {
135
202
  : pricingResolution.pricing
136
203
  ? "static_catalog"
137
204
  : "none",
138
- ...(pricingResolution.pricing ? { pricingVersion: EMBEDDED_PRICING_VERSION } : {}),
205
+ ...(pricingResolution.pricing
206
+ ? { pricingVersion: pricingResolution.pricing.pricingVersion ?? EMBEDDED_PRICING_VERSION }
207
+ : {}),
139
208
  rawUsageAvailable: true,
140
209
  settledAt: new Date().toISOString()
141
210
  }
@@ -192,14 +261,20 @@ function extractCodexJsonlResult(stdout, modelLabel) {
192
261
  const cachedInputTokens = latestTurnCompleted.usage.cached_input_tokens ?? 0;
193
262
  const outputTokens = latestTurnCompleted.usage.output_tokens ?? 0;
194
263
  const reasoningOutputTokens = latestTurnCompleted.usage.reasoning_output_tokens ?? 0;
195
- const tokensIn = promptTokens + cachedInputTokens;
196
- const tokensOut = outputTokens + reasoningOutputTokens;
197
- const exactPricing = modelLabel ? MODEL_PRICING[modelLabel] : undefined;
264
+ // Codex exposes cached/reasoning token detail fields as subsets of the
265
+ // corresponding input/output totals. Do not add those details twice.
266
+ const tokensIn = promptTokens;
267
+ const tokensOut = outputTokens;
268
+ const pricingResolution = resolveModelPricing(modelLabel);
269
+ const exactPricing = pricingResolution.pricing;
198
270
  const pricing = exactPricing ?? MODEL_PRICING["codex"] ??
199
271
  { inputPer1K: BLENDED_INPUT_COST_PER_1K, outputPer1K: BLENDED_OUTPUT_COST_PER_1K };
200
- const actualUsd = (promptTokens / 1000) * pricing.inputPer1K +
201
- (cachedInputTokens / 1000) * (pricing.cachedInputPer1K ?? pricing.inputPer1K) +
202
- (tokensOut / 1000) * pricing.outputPer1K;
272
+ const actualUsd = calculateUsageCost({
273
+ inputTokens: Math.max(promptTokens - cachedInputTokens, 0),
274
+ cachedInputTokens,
275
+ cacheCreationInputTokens: 0,
276
+ outputTokens
277
+ }, pricing, promptTokens);
203
278
  return {
204
279
  summary,
205
280
  usage: normalizeUsage({
@@ -221,7 +296,7 @@ function extractCodexJsonlResult(stdout, modelLabel) {
221
296
  billingMode: "unknown",
222
297
  modelSource: modelLabel ? "explicit_override" : "agent_default",
223
298
  pricingSource: exactPricing ? "static_catalog" : "blended_fallback",
224
- pricingVersion: EMBEDDED_PRICING_VERSION,
299
+ pricingVersion: exactPricing?.pricingVersion ?? EMBEDDED_PRICING_VERSION,
225
300
  rawUsageAvailable: true,
226
301
  settledAt: new Date().toISOString()
227
302
  }
@@ -270,11 +345,16 @@ function extractGeminiJsonResult(stdout, modelLabel) {
270
345
  }
271
346
  const tokensIn = promptTokens + cachedInputTokens;
272
347
  const tokensOut = outputTokens + reasoningOutputTokens;
273
- const pricing = (modelLabel ? MODEL_PRICING[modelLabel] : undefined) ??
348
+ const pricingResolution = resolveModelPricing(modelLabel);
349
+ const exactPricing = pricingResolution.pricing;
350
+ const pricing = exactPricing ??
274
351
  { inputPer1K: BLENDED_INPUT_COST_PER_1K, outputPer1K: BLENDED_OUTPUT_COST_PER_1K };
275
- const actualUsd = (promptTokens / 1000) * pricing.inputPer1K +
276
- (cachedInputTokens / 1000) * (pricing.cachedInputPer1K ?? pricing.inputPer1K) +
277
- (tokensOut / 1000) * pricing.outputPer1K;
352
+ const actualUsd = calculateUsageCost({
353
+ inputTokens: promptTokens,
354
+ cachedInputTokens,
355
+ cacheCreationInputTokens: 0,
356
+ outputTokens: tokensOut
357
+ }, pricing, promptTokens + cachedInputTokens);
278
358
  return {
279
359
  summary,
280
360
  usage: normalizeUsage({
@@ -295,15 +375,15 @@ function extractGeminiJsonResult(stdout, modelLabel) {
295
375
  reasoningOutputTokens,
296
376
  billingMode: "unknown",
297
377
  modelSource: modelLabel ? "explicit_override" : "agent_default",
298
- pricingSource: "blended_fallback",
299
- pricingVersion: EMBEDDED_PRICING_VERSION,
378
+ pricingSource: exactPricing ? "static_catalog" : "blended_fallback",
379
+ pricingVersion: exactPricing?.pricingVersion ?? EMBEDDED_PRICING_VERSION,
300
380
  rawUsageAvailable: true,
301
381
  settledAt: new Date().toISOString()
302
382
  }
303
383
  })
304
384
  };
305
385
  }
306
- function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate) {
386
+ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate, capTokens, detailTokensIncludedInTotals = false, observedUsageGovernor) {
307
387
  let pricingResolution = resolveModelPricing(modelLabel);
308
388
  // Safety margin: terminate at 80% of cap to bound one-turn overshoot.
309
389
  // Without this, a single expensive turn can blow past the cap before the
@@ -333,54 +413,101 @@ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate)
333
413
  let usageEventSeen = false;
334
414
  let firstChunkAt;
335
415
  let finalResult;
336
- const checkBudgetExceeded = (terminate) => {
416
+ let finalObservationPublished = false;
417
+ const observedEventIdentities = new Set();
418
+ const checkBudgetExceeded = (terminate, final = false) => {
419
+ const cumulativeTokens = tokensIn + tokensOut;
420
+ if (observedUsageGovernor) {
421
+ if (final && finalObservationPublished)
422
+ return;
423
+ if (final)
424
+ finalObservationPublished = true;
425
+ try {
426
+ const decision = observedUsageGovernor({ cumulativeUsd, cumulativeTokens, turns, final });
427
+ if (decision.action === "terminate") {
428
+ terminate(`Parent observed-usage governor terminated the subprocess: ${decision.reason}.`);
429
+ }
430
+ }
431
+ catch (error) {
432
+ terminate(`Parent observed-usage governor failed closed: ${error instanceof Error ? error.message : String(error)}.`);
433
+ }
434
+ return;
435
+ }
436
+ if (capTokens !== undefined && capTokens > 0 && cumulativeTokens > capTokens) {
437
+ terminate(`Streaming token lease exceeded after ${String(turns)} turn(s): observed ${String(cumulativeTokens)} tokens ` +
438
+ `surpassed the per-attempt token cap ${String(capTokens)}. Subprocess terminated to prevent additional token usage.`);
439
+ return;
440
+ }
337
441
  if (capUsd > 0 && cumulativeUsd > effectiveCapUsd) {
338
442
  terminate(`Streaming usage cap exceeded after ${String(turns)} turn(s): cumulative cost ~$${cumulativeUsd.toFixed(4)} ` +
339
443
  `surpassed the per-attempt cap $${capUsd.toFixed(4)} (${String(Math.round(effectiveCapRatio * 100))}% threshold: $${effectiveCapUsd.toFixed(4)}). ` +
340
444
  `Subprocess terminated to bound runaway overspend.`);
341
445
  }
342
446
  };
343
- const extractUsageFromEvent = (event, terminate) => {
447
+ const extractUsageFromEvent = (event, terminate, final = false) => {
448
+ const nestedMessage = event.message && typeof event.message === "object"
449
+ ? event.message
450
+ : undefined;
451
+ const providerEventId = typeof event.id === "string"
452
+ ? event.id
453
+ : typeof nestedMessage?.id === "string" ? nestedMessage.id : undefined;
454
+ if (providerEventId) {
455
+ const identity = `${String(event.type ?? "event")}:${providerEventId}`;
456
+ if (observedEventIdentities.has(identity))
457
+ return;
458
+ observedEventIdentities.add(identity);
459
+ }
344
460
  if (event.type === "system" && event.subtype === "init" && typeof event.model === "string") {
345
461
  pricingResolution = resolveModelPricing(event.model);
346
462
  }
347
463
  // Check for authoritative total_cost_usd on ANY event — if Claude reports
348
464
  // cost exceeding cap, terminate immediately regardless of event type.
349
- if (typeof event.total_cost_usd === "number" && event.total_cost_usd > 0) {
350
- cumulativeUsd = event.total_cost_usd;
351
- checkBudgetExceeded(terminate);
352
- return;
353
- }
465
+ const authoritativeCost = typeof event.total_cost_usd === "number" && event.total_cost_usd >= 0;
466
+ if (authoritativeCost)
467
+ cumulativeUsd = Math.max(cumulativeUsd, event.total_cost_usd);
354
468
  // Extract usage from any event shape that carries it:
355
469
  // - { type: "assistant", message: { usage: { ... } } } (original format)
356
470
  // - { usage: { input_tokens, output_tokens, ... } } (top-level usage)
357
471
  // - { message: { usage: { ... } } } (nested without type check)
358
- const usage = (event.message && typeof event.message === "object" && "usage" in event.message
359
- ? event.message.usage
472
+ const usage = (nestedMessage && "usage" in nestedMessage
473
+ ? nestedMessage.usage
360
474
  : undefined) ??
361
475
  (event.usage && typeof event.usage === "object" ? event.usage : undefined);
362
476
  if (!usage || typeof usage !== "object") {
477
+ if (authoritativeCost || final)
478
+ checkBudgetExceeded(terminate, final);
363
479
  return;
364
480
  }
365
481
  const usageRecord = usage;
366
482
  const turnInputTokens = usageRecord.input_tokens ?? usageRecord.inputTokens ?? 0;
367
- const turnCachedInputTokens = usageRecord.cache_read_input_tokens ?? usageRecord.cacheReadInputTokens ?? 0;
483
+ const turnCachedInputTokens = usageRecord.cached_input_tokens ?? usageRecord.cachedInputTokens ??
484
+ usageRecord.cache_read_input_tokens ?? usageRecord.cacheReadInputTokens ?? 0;
368
485
  const turnCacheCreationInputTokens = usageRecord.cache_creation_input_tokens ?? usageRecord.cacheCreationInputTokens ?? 0;
369
- const turnTokensIn = turnInputTokens + turnCachedInputTokens + turnCacheCreationInputTokens;
370
- const turnTokensOut = usageRecord.output_tokens ?? usageRecord.outputTokens ?? 0;
486
+ const turnTokensIn = detailTokensIncludedInTotals
487
+ ? turnInputTokens
488
+ : turnInputTokens + turnCachedInputTokens + turnCacheCreationInputTokens;
489
+ const turnOutputTokens = usageRecord.output_tokens ?? usageRecord.outputTokens ?? 0;
490
+ const turnReasoningOutputTokens = usageRecord.reasoning_output_tokens ?? usageRecord.reasoningOutputTokens ?? 0;
491
+ const turnTokensOut = detailTokensIncludedInTotals
492
+ ? turnOutputTokens
493
+ : turnOutputTokens + turnReasoningOutputTokens;
371
494
  if (turnTokensIn === 0 && turnTokensOut === 0) {
495
+ if (authoritativeCost || final)
496
+ checkBudgetExceeded(terminate, final);
372
497
  return;
373
498
  }
374
499
  const turnUsd = pricingResolution.pricing
375
500
  ? calculateUsageCost({
376
- inputTokens: turnInputTokens,
501
+ inputTokens: detailTokensIncludedInTotals
502
+ ? Math.max(turnInputTokens - turnCachedInputTokens - turnCacheCreationInputTokens, 0)
503
+ : turnInputTokens,
377
504
  cachedInputTokens: turnCachedInputTokens,
378
505
  cacheCreationInputTokens: turnCacheCreationInputTokens,
379
506
  outputTokens: turnTokensOut
380
507
  }, pricingResolution.pricing)
381
508
  : undefined;
382
509
  const remainingBudgetBeforeTurn = Math.max(capUsd - cumulativeUsd, 0);
383
- if (turnUsd !== undefined &&
510
+ if (!observedUsageGovernor && !final && !authoritativeCost && turnUsd !== undefined &&
384
511
  capUsd > 0 &&
385
512
  remainingBudgetBeforeTurn > 0 &&
386
513
  turnUsd > remainingBudgetBeforeTurn * 0.5) {
@@ -397,10 +524,10 @@ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate)
397
524
  tokensOut += turnTokensOut;
398
525
  turns += 1;
399
526
  usageEventSeen = true;
400
- if (turnUsd !== undefined) {
527
+ if (!authoritativeCost && turnUsd !== undefined) {
401
528
  cumulativeUsd += turnUsd;
402
529
  }
403
- checkBudgetExceeded(terminate);
530
+ checkBudgetExceeded(terminate, final);
404
531
  };
405
532
  const ingestLine = (line, terminate) => {
406
533
  const trimmed = line.trim();
@@ -414,10 +541,23 @@ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate)
414
541
  catch {
415
542
  return;
416
543
  }
417
- extractUsageFromEvent(event, terminate);
418
544
  if (event.type === "result") {
419
545
  finalResult = event;
546
+ // Claude's final result usage is an aggregate repeat of usage already
547
+ // emitted on assistant events. Count it only when no incremental usage
548
+ // was observed, otherwise token leases are falsely doubled.
549
+ if (!usageEventSeen) {
550
+ extractUsageFromEvent(event, terminate, true);
551
+ }
552
+ else {
553
+ if (typeof event.total_cost_usd === "number" && event.total_cost_usd >= 0) {
554
+ cumulativeUsd = Math.max(cumulativeUsd, event.total_cost_usd);
555
+ }
556
+ checkBudgetExceeded(terminate, true);
557
+ }
558
+ return;
420
559
  }
560
+ extractUsageFromEvent(event, terminate);
421
561
  };
422
562
  return {
423
563
  onChunk: (chunk, terminate) => {
@@ -582,10 +722,12 @@ export function createAgentCliAdapter(options) {
582
722
  // watch per-turn usage events as they arrive and kill the subprocess the
583
723
  // instant projected spend crosses what remains — bounding the worst case
584
724
  // to roughly one turn's overshoot rather than the entire runaway session.
585
- const streamingUsage = options.streamingUsageCap && request.context.remainingBudgetUsd > 0
586
- ? createStreamingUsageInspector(request.context.remainingBudgetUsd, options.model ?? options.command, estimatedUsage.tokensIn)
725
+ const streamingUsage = options.streamingUsageCap &&
726
+ (request.context.remainingBudgetUsd > 0 ||
727
+ (options.streamingTokenCap && (request.context.remainingTokens ?? 0) > 0))
728
+ ? createStreamingUsageInspector(request.context.remainingBudgetUsd, options.model ?? options.command, estimatedUsage.tokensIn, options.streamingTokenCap ? request.context.remainingTokens : undefined, options.streamingUsageDetailsIncludedInTotals, request.observedUsageGovernor)
587
729
  : undefined;
588
- const agentResult = await runSubprocess(options.command, args, {
730
+ const agentResult = await runSubprocess(options.executionCommand ?? options.command, args, {
589
731
  cwd: workingDirectory,
590
732
  timeoutMs: executionTimeoutMs,
591
733
  spawnImpl: options.spawnImpl,
@@ -747,8 +889,12 @@ export function createAgentCliAdapter(options) {
747
889
  const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, verificationStack, options.spawnImpl, {
748
890
  runId: request.loopId,
749
891
  workspaceId: request.workspaceId,
892
+ attemptId: request.attemptId,
750
893
  cwd: workingDirectory,
751
- });
894
+ ...(request.context.runsRoot ? { runsRoot: request.context.runsRoot } : {}),
895
+ ...(request.context.executionProfile ? { executionProfile: request.context.executionProfile } : {}),
896
+ ...(request.context.allowedNetworkDomains?.length ? { allowedNetworkDomains: [...request.context.allowedNetworkDomains] } : {}),
897
+ }, request.signal);
752
898
  const verificationConfigured = verification.binding.commands.length > 0;
753
899
  // Check for zero-diff (agent ran but made no file changes)
754
900
  const postRunChangedFiles = gitRepoRoot
@@ -759,11 +905,12 @@ export function createAgentCliAdapter(options) {
759
905
  // Extract structured errors from stderr/stdout for better failure context
760
906
  const structuredErrors = normalizeStructuredErrors(extractStructuredErrors(agentResult.stderr, agentResult.stdout));
761
907
  const rawExecutionArtifacts = gitRepoRoot
762
- ? await readGitExecutionArtifacts(gitRepoRoot, 5000, options.spawnImpl)
908
+ ? await readGitExecutionArtifacts(gitRepoRoot, 5000, options.spawnImpl, agentChangedFiles)
763
909
  : undefined;
764
910
  const executionArtifacts = rawExecutionArtifacts
765
911
  ? {
766
912
  ...(agentChangedFiles.length > 0 ? { changedFiles: agentChangedFiles } : {}),
913
+ ...(rawExecutionArtifacts.patch ? { patch: rawExecutionArtifacts.patch } : {}),
767
914
  ...(baselineChangedFiles.size === 0 && rawExecutionArtifacts.diffStats
768
915
  ? { diffStats: rawExecutionArtifacts.diffStats }
769
916
  : {})
@@ -821,8 +968,11 @@ export function createAgentCliAdapter(options) {
821
968
  const scopeBlock = scopeViolations.length > 0
822
969
  ? `\n Scope violations: ${scopeViolations.join(", ")}`
823
970
  : "";
824
- // Write PROGRESS.md to help the next attempt re-anchor on the original objective
825
- if (repoRoot) {
971
+ // PROGRESS.md is legacy adapter-local retry state. Never create it in a
972
+ // scope-constrained workspace: doing so manufactures an undeclared
973
+ // product change and causes the Core leash to hide the provider's real
974
+ // failure behind surface_write_not_allowed.
975
+ if (repoRoot && (request.context.allowedPaths?.length ?? 0) === 0) {
826
976
  try {
827
977
  const { writeFile, readFile, appendFile: appendFs } = await import("node:fs/promises");
828
978
  const progressPath = `${repoRoot}/PROGRESS.md`;
@@ -882,6 +1032,33 @@ export function createAgentCliAdapter(options) {
882
1032
  // ---------------------------------------------------------------------------
883
1033
  // Pre-configured: Claude CLI
884
1034
  // ---------------------------------------------------------------------------
1035
+ const CLAUDE_PERMISSION_CONTROL_ARGS = new Set([
1036
+ "--allow-dangerously-skip-permissions",
1037
+ "--allowed-tools",
1038
+ "--allowedTools",
1039
+ "--dangerously-skip-permissions",
1040
+ "--permission-mode"
1041
+ ]);
1042
+ function assertClaudePermissionControlsAreGoverned(extraArgs) {
1043
+ const overridesPermissionControl = extraArgs.some((arg) => {
1044
+ const [flag] = arg.split("=", 1);
1045
+ return flag !== undefined && CLAUDE_PERMISSION_CONTROL_ARGS.has(flag);
1046
+ });
1047
+ if (overridesPermissionControl) {
1048
+ throw new Error("Claude permission controls cannot be overridden via extraArgs.");
1049
+ }
1050
+ }
1051
+ function hostOwnsVerification(request) {
1052
+ return request.context.verificationExecutionOwner === "host_only";
1053
+ }
1054
+ function buildClaudeVerifierAllowedTools(request) {
1055
+ if (hostOwnsVerification(request))
1056
+ return [];
1057
+ const commands = [...new Set(request.context.verificationPlan.filter((command) => command.trim().length > 0))];
1058
+ return commands.length > 0
1059
+ ? ["--allowedTools", ...commands.map((command) => `Bash(${command})`)]
1060
+ : [];
1061
+ }
885
1062
  /**
886
1063
  * Spawns `claude --output-format stream-json --verbose --print "<prompt>" [extraArgs]`.
887
1064
  *
@@ -900,6 +1077,7 @@ export function createAgentCliAdapter(options) {
900
1077
  export function createClaudeCliAdapter(options = {}) {
901
1078
  const modelArgs = options.model ? ["--model", options.model] : [];
902
1079
  const extraArgs = options.extraArgs ?? [];
1080
+ assertClaudePermissionControlsAreGoverned(extraArgs);
903
1081
  return createAgentCliAdapter({
904
1082
  command: "claude",
905
1083
  adapterIdSuffix: "claude",
@@ -912,20 +1090,26 @@ export function createClaudeCliAdapter(options = {}) {
912
1090
  verifyTimeoutMs: options.verifyTimeoutMs,
913
1091
  supportsJsonOutput: true,
914
1092
  streamingUsageCap: true,
1093
+ streamingTokenCap: true,
915
1094
  spawnImpl: options.spawnImpl,
916
1095
  argsBuilder: (_prompt, request) => [
917
1096
  "--output-format",
918
1097
  "stream-json",
919
1098
  "--verbose",
920
1099
  "--print",
921
- "--dangerously-skip-permissions",
1100
+ ...(options.readOnly
1101
+ ? ["--permission-mode", "plan"]
1102
+ : ["--permission-mode", "acceptEdits"]),
1103
+ ...buildClaudeVerifierAllowedTools(request),
922
1104
  // Subprocess isolation strategy:
923
1105
  // --bare: skips hooks (prevents SessionEnd/hook failures causing non-zero exits),
924
1106
  // MCP server loading, LSP, CLAUDE.md discovery, and background prefetches.
925
1107
  // Requires ANTHROPIC_API_KEY (OAuth/keychain auth not available in bare mode).
926
1108
  // --strict-mcp-config: fallback when ANTHROPIC_API_KEY is not set — still
927
1109
  // prevents parent MCP servers from being inherited by the subprocess.
928
- ...(process.env["ANTHROPIC_API_KEY"] ? ["--bare"] : ["--strict-mcp-config"]),
1110
+ ...(process.env["ANTHROPIC_API_KEY"]
1111
+ ? ["--bare"]
1112
+ : ["--strict-mcp-config", "--setting-sources", "project", "--disable-slash-commands"]),
929
1113
  // NOTE: --max-tokens does not exist in the claude CLI. Token cap enforcement
930
1114
  // is handled at the MartinLoop layer via streamingUsageCap, not via subprocess flags.
931
1115
  ...modelArgs,
@@ -988,10 +1172,13 @@ export function createCodexCliAdapter(options = {}) {
988
1172
  * npm install -g @google/gemini-cli
989
1173
  */
990
1174
  export function createGeminiCliAdapter(options = {}) {
991
- const approvalMode = options.approvalMode ?? "yolo";
992
- if (approvalMode !== "yolo") {
1175
+ const approvalMode = options.readOnly ? "plan" : options.approvalMode ?? "yolo";
1176
+ if (!options.readOnly && approvalMode !== "yolo") {
993
1177
  throw new Error("governed-autonomous Gemini execution requires yolo approval mode; interactive downgrade rejected.");
994
1178
  }
1179
+ if (options.readOnly && options.approvalMode !== undefined && options.approvalMode !== "plan") {
1180
+ throw new Error("read-only Gemini execution requires plan approval mode.");
1181
+ }
995
1182
  const extraArgs = options.extraArgs ?? [];
996
1183
  if (extraArgs.some((arg) => arg === "--approval-mode" || arg.startsWith("--approval-mode="))) {
997
1184
  throw new Error("Gemini approval mode cannot be overridden through extraArgs.");
@@ -1034,15 +1221,32 @@ export function createGeminiCliAdapter(options = {}) {
1034
1221
  function buildPrompt(request) {
1035
1222
  const lines = [];
1036
1223
  const mutationMode = request.context.mutationMode ?? "edit";
1224
+ const verificationIsHostOwned = hostOwnsVerification(request);
1037
1225
  lines.push("You are running in autonomous agentic mode.");
1038
- lines.push("MAKE ALL REQUIRED FILE EDITS NOW. Do not ask for confirmation. Do not ask clarifying questions.");
1039
- lines.push("Do not explain what you found without also making the changes. Edit the files and complete the task.");
1040
- lines.push("");
1041
- lines.push("If PROGRESS.md exists in your working directory, read it first for context from prior attempts.");
1042
- lines.push("If it does not exist, proceed with the objective below.");
1226
+ if (mutationMode === "read_only") {
1227
+ lines.push("READ-ONLY EXECUTION. Do not create, edit, delete, rename, or move files.");
1228
+ lines.push("Inspect and verify only; report evidence without changing the workspace.");
1229
+ }
1230
+ else {
1231
+ lines.push("MAKE ALL REQUIRED FILE EDITS NOW. Do not ask for confirmation. Do not ask clarifying questions.");
1232
+ lines.push("Do not explain what you found without also making the changes. Edit the files and complete the task.");
1233
+ }
1043
1234
  lines.push("");
1044
- lines.push("Complete the following coding task. Make all necessary file changes.");
1045
- lines.push("When you are done, the verification commands listed below must pass.");
1235
+ if ((request.context.allowedPaths?.length ?? 0) === 0) {
1236
+ lines.push("If PROGRESS.md exists in your working directory, read it first for context from prior attempts.");
1237
+ lines.push("If it does not exist, proceed with the objective below.");
1238
+ lines.push("");
1239
+ }
1240
+ lines.push(mutationMode === "read_only"
1241
+ ? "Complete the following inspection task without making file changes."
1242
+ : "Complete the following coding task. Make all necessary file changes.");
1243
+ if (verificationIsHostOwned) {
1244
+ lines.push("MartinLoop owns verification and runs it after provider completion.");
1245
+ lines.push("Do not execute these verifier commands yourself.");
1246
+ }
1247
+ else {
1248
+ lines.push("When you are done, the verification commands listed below must pass.");
1249
+ }
1046
1250
  lines.push("");
1047
1251
  lines.push("OBJECTIVE:");
1048
1252
  lines.push(sanitizeForPrompt(request.context.objective));
@@ -1068,7 +1272,9 @@ function buildPrompt(request) {
1068
1272
  lines.push("");
1069
1273
  }
1070
1274
  if (request.context.verificationPlan.length > 0) {
1071
- lines.push("VERIFICATION (all commands must exit with code 0):");
1275
+ lines.push(verificationIsHostOwned
1276
+ ? "HOST-OWNED VERIFICATION (success conditions only; do not execute):"
1277
+ : "VERIFICATION (all commands must exit with code 0):");
1072
1278
  for (const cmd of request.context.verificationPlan) {
1073
1279
  lines.push(` ${cmd}`);
1074
1280
  }
@@ -1116,6 +1322,9 @@ function buildPrompt(request) {
1116
1322
  lines.push("");
1117
1323
  }
1118
1324
  lines.push(`FOCUS: ${sanitizeForPrompt(request.context.focus)}`);
1325
+ if (verificationIsHostOwned && request.context.verificationPlan.length > 0) {
1326
+ lines.push("FINAL BOUNDARY: stop after the scoped work and report it; MartinLoop will execute verification.");
1327
+ }
1119
1328
  return lines.join("\n");
1120
1329
  }
1121
1330
  // ---------------------------------------------------------------------------
@@ -1159,7 +1368,12 @@ function estimatePromptCost(promptText, model, providerCommand) {
1159
1368
  if (!pricing) {
1160
1369
  return undefined;
1161
1370
  }
1162
- return (inputTokens / 1000) * pricing.inputPer1K + (outputTokens / 1000) * pricing.outputPer1K;
1371
+ return calculateUsageCost({
1372
+ inputTokens,
1373
+ cachedInputTokens: 0,
1374
+ cacheCreationInputTokens: 0,
1375
+ outputTokens
1376
+ }, pricing, inputTokens);
1163
1377
  }
1164
1378
  function estimateUsage(promptText, model, providerCommand) {
1165
1379
  const inputTokens = Math.ceil(promptText.length / 3.5);