mixdog 0.9.166 → 0.9.167

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 (221) hide show
  1. package/package.json +1 -1
  2. package/scripts/audit-usage-ledger.mjs +86 -0
  3. package/scripts/generate-voice-runtime-manifest.mjs +22 -17
  4. package/scripts/memory-chunk-quality.mjs +128 -0
  5. package/scripts/memory-layer2-check.mjs +133 -0
  6. package/scripts/model-catalog-audit.mjs +6 -0
  7. package/scripts/native-tool-download.mjs +14 -0
  8. package/scripts/prepare-native-assets.mjs +2 -7
  9. package/scripts/tool-failures.mjs +7 -2
  10. package/scripts/verify-release-assets.mjs +48 -90
  11. package/src/defaults/skills/browser-use/SKILL.md +33 -18
  12. package/src/defaults/skills/computer-use/SKILL.md +16 -8
  13. package/src/lib/keychain-cjs.cjs +0 -1
  14. package/src/lib/rules-builder.cjs +4 -1
  15. package/src/rules/agent/40-cycle1-agent.md +21 -13
  16. package/src/rules/lead/01-general.md +8 -3
  17. package/src/rules/shared/10-tool-workflow.md +20 -11
  18. package/src/rules/shared/30-exploration.md +11 -4
  19. package/src/rules/shared/40-editing.md +2 -3
  20. package/src/rules/shared/50-execution.md +6 -3
  21. package/src/rules/shared/60-verification.md +3 -2
  22. package/src/rules/shared/70-delivery.md +0 -4
  23. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +51 -45
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +3 -4
  25. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +44 -7
  26. package/src/runtime/agent/orchestrator/agent-trace.mjs +9 -0
  27. package/src/runtime/agent/orchestrator/config.mjs +9 -4
  28. package/src/runtime/agent/orchestrator/context/collect.mjs +25 -1055
  29. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +272 -0
  30. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +296 -0
  31. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +523 -0
  32. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +7 -2
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +13 -14
  34. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +214 -0
  35. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +220 -116
  36. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +74 -44
  37. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +191 -29
  38. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +13 -12
  39. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +1 -9
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +5 -2
  41. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +48 -25
  42. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +28 -2
  43. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -1
  44. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +1 -8
  45. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +32 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +21 -6
  47. package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +61 -13
  48. package/src/runtime/agent/orchestrator/providers/model-catalog-projection.mjs +6 -1
  49. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +123 -118
  50. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +54 -0
  51. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +5 -2
  52. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +34 -3
  53. package/src/runtime/agent/orchestrator/providers/openai-codex-endpoints.mjs +21 -0
  54. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +154 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +2 -1
  56. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +322 -0
  57. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +243 -702
  58. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +2 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-startup-prewarm.mjs +221 -0
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -2
  61. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +19 -0
  63. package/src/runtime/agent/orchestrator/providers/provider-model-identities.mjs +40 -0
  64. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +120 -710
  65. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +36 -15
  66. package/src/runtime/agent/orchestrator/session/cache/util.mjs +9 -0
  67. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +17 -4
  68. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +36 -31
  69. package/src/runtime/agent/orchestrator/session/compact.mjs +4 -5
  70. package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +6 -5
  71. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -6
  72. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +121 -0
  73. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -3
  74. package/src/runtime/agent/orchestrator/session/loop/diagnostics.mjs +74 -0
  75. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +44 -17
  76. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +266 -0
  77. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +111 -0
  78. package/src/runtime/agent/orchestrator/session/loop/request-projection.mjs +99 -0
  79. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +91 -0
  80. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +36 -0
  81. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +3 -3
  82. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  83. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +20 -11
  84. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +3 -5
  85. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -3
  86. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +39 -0
  87. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +3 -2
  88. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +21 -3
  89. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +2 -3
  91. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +25 -25
  92. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +62 -10
  93. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +17 -0
  94. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +61 -4
  95. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +13 -9
  96. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +12 -2
  97. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +3 -2
  98. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +8 -3
  99. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +69 -6
  100. package/src/runtime/agent/orchestrator/tools/builtin.mjs +14 -17
  101. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  102. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  104. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
  105. package/src/runtime/bridge-discovery.mjs +8 -0
  106. package/src/runtime/browser-bridge/action-schema.mjs +37 -43
  107. package/src/runtime/browser-bridge/client.mjs +111 -99
  108. package/src/runtime/browser-bridge/input-fields.mjs +187 -0
  109. package/src/runtime/browser-bridge/tool-defs.mjs +12 -227
  110. package/src/runtime/computer-bridge/action-schema.mjs +2 -46
  111. package/src/runtime/computer-bridge/client.mjs +69 -77
  112. package/src/runtime/computer-bridge/core-actions.mjs +7 -5
  113. package/src/runtime/computer-bridge/error-recovery.mjs +41 -37
  114. package/src/runtime/computer-bridge/tool-defs.mjs +1 -1
  115. package/src/runtime/github/commands.mjs +4 -1
  116. package/src/runtime/github/contract.mjs +1 -1
  117. package/src/runtime/github/tool.mjs +2 -2
  118. package/src/runtime/media/adapters/antigravity-image.mjs +72 -0
  119. package/src/runtime/media/adapters/gemini-image.mjs +20 -11
  120. package/src/runtime/media/auth.mjs +12 -0
  121. package/src/runtime/media/catalog.mjs +30 -10
  122. package/src/runtime/media/jobs.mjs +4 -0
  123. package/src/runtime/media/lanes.mjs +40 -31
  124. package/src/runtime/memory/index.mjs +2 -9
  125. package/src/runtime/memory/lib/compact-handoff.mjs +26 -18
  126. package/src/runtime/memory/lib/memory-chunk-audit.mjs +41 -0
  127. package/src/runtime/memory/lib/memory-chunk-quality.mjs +388 -0
  128. package/src/runtime/memory/lib/memory-cycle1.mjs +95 -394
  129. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +29 -4
  130. package/src/runtime/memory/lib/memory-recall-store.mjs +8 -12
  131. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -11
  132. package/src/runtime/memory/lib/memory.mjs +2 -0
  133. package/src/runtime/memory/lib/query-handlers.mjs +98 -144
  134. package/src/runtime/memory/lib/query-ranking.mjs +23 -9
  135. package/src/runtime/memory/lib/recall-format.mjs +37 -27
  136. package/src/runtime/memory/lib/session-ingest-runtime.mjs +14 -8
  137. package/src/runtime/memory/lib/session-ingest.mjs +45 -84
  138. package/src/runtime/memory/lib/transcript-ingest.mjs +19 -21
  139. package/src/runtime/memory/tool-defs.mjs +2 -2
  140. package/src/runtime/office/authoring/pptx-author-session.mjs +3 -7
  141. package/src/runtime/office/core/office-actions-batch.mjs +2 -8
  142. package/src/runtime/office/core/office-actions-open.mjs +124 -0
  143. package/src/runtime/office/core/office-actions-read.mjs +127 -0
  144. package/src/runtime/office/core/office-core.mjs +80 -1
  145. package/src/runtime/office/core/office-sessions.mjs +116 -138
  146. package/src/runtime/office/index.mjs +18 -271
  147. package/src/runtime/office/pdf/pdf-analysis.mjs +2 -2
  148. package/src/runtime/office/pdf/pdf-limits.mjs +2 -0
  149. package/src/runtime/office/tool-defs.mjs +7 -6
  150. package/src/runtime/shared/agent-route-config.mjs +8 -8
  151. package/src/runtime/shared/child-spawn-gate.mjs +10 -13
  152. package/src/runtime/shared/config.mjs +2 -7
  153. package/src/runtime/shared/env.mjs +3 -3
  154. package/src/runtime/shared/err-text.mjs +1 -1
  155. package/src/runtime/shared/extension-scopes.mjs +3 -2
  156. package/src/runtime/shared/heap-cap.mjs +10 -6
  157. package/src/runtime/shared/llm/cost.mjs +35 -10
  158. package/src/runtime/shared/llm/usage-accounting.mjs +11 -1
  159. package/src/runtime/shared/llm/usage-context.mjs +6 -0
  160. package/src/runtime/shared/llm/usage-ledger-import.mjs +5 -0
  161. package/src/runtime/shared/llm/usage-ledger-repair.mjs +109 -0
  162. package/src/runtime/shared/llm/usage-ledger.mjs +59 -20
  163. package/src/runtime/shared/llm/usage-pricing-refresh.mjs +26 -0
  164. package/src/runtime/shared/llm/usage-rollup.mjs +17 -15
  165. package/src/runtime/shared/native-asset.mjs +3 -4
  166. package/src/runtime/shared/object.mjs +4 -0
  167. package/src/runtime/shared/pg-schema-db.mjs +21 -0
  168. package/src/runtime/shared/plugin-metadata.mjs +3 -1
  169. package/src/runtime/shared/resource-admission.mjs +1 -6
  170. package/src/runtime/shared/schedules-db.mjs +8 -24
  171. package/src/runtime/shared/schema-value-error.d.mts +2 -0
  172. package/src/runtime/shared/schema-value-error.mjs +68 -0
  173. package/src/runtime/shared/singleton-owner.mjs +11 -24
  174. package/src/runtime/shared/staged-update.mjs +1 -10
  175. package/src/runtime/shared/tool-card-model.mjs +8 -5
  176. package/src/runtime/shared/tool-execution-contract.mjs +1 -1
  177. package/src/runtime/shared/tool-surface.mjs +1 -5
  178. package/src/runtime/shared/user-data-guard.mjs +1 -4
  179. package/src/runtime/shared/webhooks-db.mjs +8 -24
  180. package/src/runtime/web-search/index.mjs +32 -8
  181. package/src/runtime/web-search/tool-defs.mjs +4 -4
  182. package/src/session-runtime/config-lifecycle.mjs +34 -0
  183. package/src/session-runtime/context-status.mjs +19 -0
  184. package/src/session-runtime/cwd-plugins.mjs +12 -29
  185. package/src/session-runtime/goal-tasks.mjs +2 -2
  186. package/src/session-runtime/goal-tool-defs.mjs +3 -2
  187. package/src/session-runtime/internal-tool-executor.mjs +7 -0
  188. package/src/session-runtime/lifecycle-api.mjs +5 -11
  189. package/src/session-runtime/notification-bus.mjs +41 -0
  190. package/src/session-runtime/prewarm.mjs +22 -0
  191. package/src/session-runtime/runtime-bootstrap.mjs +90 -0
  192. package/src/session-runtime/runtime-core.mjs +88 -202
  193. package/src/session-runtime/runtime-feature-gates.mjs +37 -0
  194. package/src/session-runtime/session-lifecycle.mjs +5 -5
  195. package/src/session-runtime/session-turn-api.mjs +1 -12
  196. package/src/session-runtime/setup-tool/executor.mjs +13 -5
  197. package/src/session-runtime/setup-tool/tool-defs.mjs +52 -46
  198. package/src/session-runtime/tool-defs.mjs +73 -4
  199. package/src/session-runtime/usage-stats-api.mjs +4 -1
  200. package/src/session-runtime/warmup-schedulers.mjs +8 -2
  201. package/src/session-runtime/workflow.mjs +14 -0
  202. package/src/standalone/channel-session-router.mjs +3 -1
  203. package/src/standalone/channel-worker.mjs +18 -9
  204. package/src/standalone/daemon-crash-capture.mjs +534 -0
  205. package/src/standalone/daemon-telemetry.mjs +175 -0
  206. package/src/standalone/daemon.mjs +31 -21
  207. package/src/standalone/session-client.mjs +31 -23
  208. package/src/standalone/usage-dashboard-model.mjs +2 -0
  209. package/src/standalone/usage-stats-hours.mjs +18 -11
  210. package/src/standalone/usage-stats-model.mjs +39 -28
  211. package/src/standalone/usage-stats-period.mjs +43 -45
  212. package/src/tui/app/use-transcript-window.mjs +1 -1
  213. package/src/tui/components/StatusLine.jsx +13 -42
  214. package/src/tui/dist/index.mjs +256 -239
  215. package/src/tui/session/session-api-ext.mjs +44 -41
  216. package/src/tui/session/session-api.mjs +4 -0
  217. package/src/tui/session/tool-card-results.mjs +25 -78
  218. package/src/tui/session/tool-result-status.mjs +94 -1
  219. package/src/tui/session/turn-aggregate-cards.mjs +17 -54
  220. package/src/ui/statusline-format.mjs +6 -26
  221. package/src/workflows/default/WORKFLOW.md +5 -0
@@ -37,10 +37,18 @@ import {
37
37
  DEFAULT_ANTIGRAVITY_MODEL,
38
38
  antigravityHeaders,
39
39
  ensureAccessToken,
40
+ ensureAntigravityVersion,
40
41
  hasAntigravityOAuthCredentials,
41
42
  loadTokens,
42
43
  _scrubTokens,
43
44
  } from './antigravity-oauth-tokens.mjs';
45
+ import {
46
+ antigravityModelCache,
47
+ antigravityQuotaWindows,
48
+ fetchAvailableModels,
49
+ normalizeAntigravityCatalog,
50
+ resolveAntigravityWireModel,
51
+ } from './antigravity-oauth-catalog.mjs';
44
52
 
45
53
  const CLAUDE_THINKING_BETA = 'interleaved-thinking-2025-05-14';
46
54
 
@@ -61,9 +69,16 @@ function antigravityError(res, text, endpoint) {
61
69
  }
62
70
  // Account verification is terminal and actionable: surface the URL Google
63
71
  // returns instead of a raw API body the user cannot act on.
64
- const validationUrl = /https:\/\/\S*(?:accounts|console)\.google\.com\/\S+/.exec(message || '');
72
+ // Google puts the link in the error details, not the message text.
73
+ const validationDetail = Array.isArray(detail?.details)
74
+ ? detail.details.find((entry) => entry?.reason === 'VALIDATION_REQUIRED' && typeof entry.metadata?.validation_url === 'string')
75
+ : null;
76
+ const validationUrl = validationDetail?.metadata.validation_url
77
+ || /https:\/\/\S*(?:accounts|console)\.google\.com\/\S+/.exec(message || '')?.[0]
78
+ || '';
65
79
  if (res.status === 403 && /VALIDATION_REQUIRED/i.test(text || '')) {
66
- err.message = `Antigravity requires account verification${validationUrl ? `: ${validationUrl[0]}` : ''}`;
80
+ err.message = `Antigravity requires account verification${validationUrl ? `: open ${validationUrl} , complete the check, then retry` : ''}`;
81
+ err.validationUrl = validationUrl || undefined;
67
82
  err.unsafeToRetry = true;
68
83
  }
69
84
  return err;
@@ -90,6 +105,28 @@ function antigravityFailoverEligible(err) {
90
105
  return status === 0;
91
106
  }
92
107
 
108
+ async function storedAuth(options) {
109
+ const tokens = await ensureAccessToken(options);
110
+ return { accessToken: tokens.access_token, projectId: tokens.project_id, email: tokens.email || '' };
111
+ }
112
+
113
+ // A retired wire id answers with one plain-text notice and no finishReason.
114
+ // That is a terminal answer about the model, not a truncated stream.
115
+ const RETIRED_MODEL_NOTICE = /\bno longer (?:available|supported)\b/i;
116
+
117
+ function retiredModelError(err, streamedText, model) {
118
+ if (!(err?.code === 'TRUNCATED_STREAM' && /no finishReason/.test(String(err?.message || '')))) return null;
119
+ const text = String(streamedText || '').trim();
120
+ if (!RETIRED_MODEL_NOTICE.test(text)) return null;
121
+ return Object.assign(new Error(`Antigravity retired ${model}: ${text}`), {
122
+ code: 'MODEL_RETIRED',
123
+ status: 404,
124
+ httpStatus: 404,
125
+ unsafeToRetry: true,
126
+ modelRetired: true,
127
+ });
128
+ }
129
+
93
130
  export class AntigravityOAuthProvider {
94
131
  // usageMetadata.promptTokenCount is the total, cached tokens included.
95
132
  static inputExcludesCache = false;
@@ -101,7 +138,12 @@ export class AntigravityOAuthProvider {
101
138
  this._preconnect = typeof config.preconnectFn === 'function' ? config.preconnectFn : preconnect;
102
139
  // AuthStorage equivalent: refreshes the token and resolves the project.
103
140
  // Injectable so wire-shape tests do not need a real credential store.
104
- this._ensureAuth = typeof config.ensureAuthFn === 'function' ? config.ensureAuthFn : ensureAccessToken;
141
+ // The store keeps snake_case fields; requests read the camelCase view.
142
+ this._ensureAuth = typeof config.ensureAuthFn === 'function' ? config.ensureAuthFn : storedAuth;
143
+ // Client version for the hub identity header, discovered once per process.
144
+ this._ensureVersion = typeof config.ensureVersionFn === 'function'
145
+ ? config.ensureVersionFn
146
+ : () => ensureAntigravityVersion({ fetchFn: this._fetch });
105
147
  // Remember the endpoint that last answered so a session stops paying the
106
148
  // failover cost on every turn.
107
149
  this._lastGoodEndpoint = null;
@@ -123,20 +165,37 @@ export class AntigravityOAuthProvider {
123
165
  }
124
166
 
125
167
  async send(messages, model, tools, sendOpts = {}) {
126
- const opts = sendOpts || {};
127
- const signal = opts.signal || null;
128
- const useModel = model || DEFAULT_ANTIGRAVITY_MODEL;
168
+ const signal = sendOpts?.signal || null;
169
+ // Picker ids name a tier family; the wire id carries the chosen effort.
170
+ const route = resolveAntigravityWireModel(model || DEFAULT_ANTIGRAVITY_MODEL, sendOpts?.effort);
171
+ const useModel = route.model;
172
+ const opts = route.effort === sendOpts?.effort
173
+ ? (sendOpts || {})
174
+ : { ...sendOpts, effort: route.effort, thinkingLevel: route.effort == null ? null : sendOpts?.thinkingLevel };
129
175
  const onToolCall = typeof opts.onToolCall === 'function' ? opts.onToolCall : null;
130
- const onTextDelta = typeof opts.onTextDelta === 'function' ? opts.onTextDelta : null;
176
+ // Streamed text is kept so a retirement notice can be told apart from
177
+ // a truncated stream when the gateway omits the finishReason.
178
+ let streamedText = '';
179
+ const onTextDelta = typeof opts.onTextDelta === 'function'
180
+ ? (text) => { if (typeof text === 'string') streamedText += text; opts.onTextDelta(text); }
181
+ : null;
131
182
  const onStreamDelta = typeof opts.onStreamDelta === 'function' ? opts.onStreamDelta : null;
132
183
  if (signal?.aborted) {
133
184
  const reason = signal.reason;
134
185
  throw reason instanceof Error ? reason : new Error('Antigravity request aborted');
135
186
  }
136
187
 
137
- const auth = await this._ensureAuth({ fetchFn: this._fetch });
188
+ this._preconnect(this._endpointOrder()[0]);
189
+ // Normalize history while version discovery and token refresh run.
190
+ // The authenticated project is bound only after those tasks finish.
191
+ const [, auth, request] = await Promise.all([
192
+ this._ensureVersion(),
193
+ this._ensureAuth({ fetchFn: this._fetch }),
194
+ Promise.resolve().then(() => this._buildBody(messages, useModel, tools, opts)),
195
+ ]);
138
196
  this._projectId = auth.projectId;
139
- const body = JSON.stringify(this._buildBody(messages, useModel, tools, opts));
197
+ request.project = auth.projectId;
198
+ const body = JSON.stringify(request);
140
199
  const headers = {
141
200
  Authorization: `Bearer ${auth.accessToken}`,
142
201
  'Content-Type': 'application/json',
@@ -146,6 +205,42 @@ export class AntigravityOAuthProvider {
146
205
  };
147
206
 
148
207
  let textLeakGuard = null;
208
+ let terminalFailure = null;
209
+ let streamedParts = [];
210
+ let streamedNativeToolCalls = [];
211
+ const seenNativeToolIds = new Set();
212
+ const emittedToolIds = new Set();
213
+ const dispatchToolCall = onToolCall ? (call) => {
214
+ // A failure already observed in this chunk must win over both
215
+ // native calls and calls recovered from its text.
216
+ if (terminalFailure || emittedToolIds.has(call.id)) return;
217
+ emittedToolIds.add(call.id);
218
+ onToolCall(call);
219
+ } : null;
220
+ const onChunk = (chunk) => {
221
+ const candidate = chunk?.candidates?.[0];
222
+ const finishReason = candidate?.finishReason
223
+ || (chunk?.promptFeedback?.blockReason ? `PROMPT_${chunk.promptFeedback.blockReason}` : null);
224
+ if (finishReason && String(finishReason).replace(/^FINISH_REASON_/, '') !== 'STOP') {
225
+ terminalFailure ||= finishReason;
226
+ }
227
+ if (!onToolCall) return;
228
+ const parts = candidate?.content?.parts ?? [];
229
+ streamedParts.push(...parts);
230
+ if (terminalFailure || !parts.some((part) => part?.functionCall)) return;
231
+ // Parse against the turn's parts so anonymous call IDs keep the
232
+ // same ordinal as final parsing, even across separate SSE chunks.
233
+ const fresh = (parseToolCalls(streamedParts) || []).filter((call) => {
234
+ if (seenNativeToolIds.has(call.id)) return false;
235
+ seenNativeToolIds.add(call.id);
236
+ return true;
237
+ });
238
+ const calls = textLeakGuard?.enabled
239
+ ? textLeakGuard.filterNativeToolCalls(fresh)
240
+ : fresh;
241
+ if (calls?.length) streamedNativeToolCalls.push(...calls);
242
+ emitGeminiToolCalls(calls, dispatchToolCall);
243
+ };
149
244
  const passthrough = createPassthroughSignal(signal);
150
245
  const endpoints = this._endpointOrder();
151
246
  let lastErr = null;
@@ -185,21 +280,45 @@ export class AntigravityOAuthProvider {
185
280
  textLeakGuard = createGeminiTextLeakGuard({
186
281
  knownToolNames: tools?.map((t) => t.name).filter(Boolean) ?? [],
187
282
  onTextDelta,
188
- onToolCall,
283
+ onToolCall: dispatchToolCall,
189
284
  onStreamDelta,
190
285
  });
191
- return await consumeGeminiRestStreamResponse(res, {
192
- signal: attemptSignal,
193
- onStreamDelta,
194
- onTextDelta,
195
- textLeakGuard,
196
- label: 'Antigravity streamGenerateContent',
197
- // Cloud Code Assist nests the Gemini payload under
198
- // `response`; in-band error events stay top level.
199
- unwrapChunk: (chunk) => (chunk && typeof chunk === 'object' && chunk.response
200
- ? chunk.response
201
- : chunk),
202
- });
286
+ streamedText = '';
287
+ terminalFailure = null;
288
+ streamedParts = [];
289
+ streamedNativeToolCalls = [];
290
+ seenNativeToolIds.clear();
291
+ emittedToolIds.clear();
292
+ try {
293
+ return await consumeGeminiRestStreamResponse(res, {
294
+ signal: attemptSignal,
295
+ onStreamDelta,
296
+ onTextDelta,
297
+ onChunk,
298
+ textLeakGuard,
299
+ label: 'Antigravity streamGenerateContent',
300
+ // Cloud Code Assist nests the Gemini payload under
301
+ // `response`; in-band error events stay top level.
302
+ unwrapChunk: (chunk) => (chunk && typeof chunk === 'object' && chunk.response
303
+ ? chunk.response
304
+ : chunk),
305
+ });
306
+ } catch (streamErr) {
307
+ const error = retiredModelError(streamErr, streamedText, useModel) || streamErr;
308
+ // Native calls now run before EOF. Preserve
309
+ // their history and prohibit resampling after
310
+ // a tool callback, including on another host.
311
+ if (emittedToolIds.size) {
312
+ error.emittedToolCall = true;
313
+ error.unsafeToRetry = true;
314
+ const leaked = textLeakGuard.getLeakedToolCalls();
315
+ error.partialToolCalls = [...streamedNativeToolCalls, ...leaked];
316
+ const replay = createProviderReplay('antigravity', leaked.length ? [] : streamedParts);
317
+ if (replay) replay.requestContext = { model: useModel };
318
+ if (replay) error.partialProviderReplay = replay;
319
+ }
320
+ throw error;
321
+ }
203
322
  },
204
323
  {
205
324
  signal: passthrough.signal,
@@ -273,19 +392,23 @@ export class AntigravityOAuthProvider {
273
392
  const leakedToolCalls = textLeakGuard?.getLeakedToolCalls() ?? [];
274
393
  const providerReplay = createProviderReplay(
275
394
  'antigravity',
276
- leakedToolCalls.length ? [] : responseParts,
395
+ leakedToolCalls.length || rawContent !== content ? [] : responseParts,
277
396
  );
278
- let nativeToolCalls = parseToolCalls(responseParts);
279
- if (textLeakGuard?.enabled) nativeToolCalls = textLeakGuard.filterNativeToolCalls(nativeToolCalls);
397
+ // Thought signatures are only valid for the model family that minted
398
+ // them; the request builder consults this when the route changes.
399
+ if (providerReplay) providerReplay.requestContext = { model: useModel };
400
+ let nativeToolCalls = onToolCall
401
+ ? (streamedNativeToolCalls.length ? streamedNativeToolCalls : undefined)
402
+ : parseToolCalls(responseParts);
403
+ if (!onToolCall && textLeakGuard?.enabled) nativeToolCalls = textLeakGuard.filterNativeToolCalls(nativeToolCalls);
280
404
  let toolCalls = nativeToolCalls;
281
405
  if (leakedToolCalls.length) {
282
406
  toolCalls = toolCalls?.length ? [...toolCalls, ...leakedToolCalls] : leakedToolCalls;
283
407
  }
284
408
  const citations = collectGeminiGroundingSources(candidate);
285
- emitGeminiToolCalls(nativeToolCalls, onToolCall);
286
409
 
287
410
  const promptBlockReason = response.promptFeedback?.blockReason || null;
288
- const finishReason = candidate?.finishReason || (promptBlockReason ? `PROMPT_${promptBlockReason}` : null);
411
+ const finishReason = terminalFailure || candidate?.finishReason || (promptBlockReason ? `PROMPT_${promptBlockReason}` : null);
289
412
  const normalizedFinish = String(finishReason || '').replace(/^FINISH_REASON_/, '');
290
413
  if (finishReason && normalizedFinish !== 'STOP') {
291
414
  throw Object.assign(new Error(`Antigravity response incomplete: finishReason=${finishReason}`), {
@@ -299,6 +422,7 @@ export class AntigravityOAuthProvider {
299
422
  providerMetadata,
300
423
  model: useModel,
301
424
  rawUsage: response.usageMetadata || null,
425
+ ...(emittedToolIds.size ? { emittedToolCall: true, unsafeToRetry: true } : {}),
302
426
  });
303
427
  }
304
428
 
@@ -337,12 +461,50 @@ export class AntigravityOAuthProvider {
337
461
  };
338
462
  }
339
463
 
340
- // The gateway publishes no public catalog endpoint, so the curated list in
341
- // the tokens module is the source of truth.
464
+ async _fetchRawModels(signal = null) {
465
+ await this._ensureVersion();
466
+ const auth = await this._ensureAuth({ fetchFn: this._fetch });
467
+ return fetchAvailableModels({
468
+ accessToken: auth.accessToken,
469
+ projectId: auth.projectId,
470
+ fetchFn: this._fetch,
471
+ signal,
472
+ });
473
+ }
474
+
475
+ // Live catalog with a disk cache; the curated list covers an offline or
476
+ // signed-out daemon so the picker never goes empty.
342
477
  async listModels() {
478
+ const cached = antigravityModelCache.loadSync();
479
+ if (cached) return cached;
480
+ try {
481
+ const models = normalizeAntigravityCatalog(await this._fetchRawModels());
482
+ if (models.length) {
483
+ antigravityModelCache.save(models);
484
+ return models;
485
+ }
486
+ } catch (err) {
487
+ console.warn(`[antigravity-oauth] catalog refresh failed, using the curated list: ${err?.message || err}`);
488
+ }
343
489
  return ANTIGRAVITY_MODELS;
344
490
  }
345
491
 
492
+ async _refreshModelCache() {
493
+ const models = normalizeAntigravityCatalog(await this._fetchRawModels());
494
+ if (!models.length) throw new Error('[antigravity-oauth] fetchAvailableModels listed no chat models');
495
+ antigravityModelCache.save(models);
496
+ return models;
497
+ }
498
+
499
+ async getUsageSnapshot() {
500
+ return {
501
+ provider: this.name,
502
+ model: null,
503
+ source: 'antigravity-models',
504
+ quotaWindows: antigravityQuotaWindows(await this._fetchRawModels()),
505
+ };
506
+ }
507
+
346
508
  async isAvailable() {
347
509
  try {
348
510
  if (!hasAntigravityOAuthCredentials()) return false;
@@ -10,10 +10,19 @@ export function isAntigravityClaude(model) {
10
10
  return /^claude-/i.test(String(model || ''));
11
11
  }
12
12
 
13
+ // A replay recorded by the other model family (Claude vs Gemini) carries
14
+ // signatures the target model rejects; rebuild it from the plain history
15
+ // with the recovery sentinel instead.
16
+ function replayFamilyMatches(message, model) {
17
+ const recorded = message?.providerReplay?.requestContext?.model;
18
+ if (typeof recorded !== 'string' || !recorded) return true;
19
+ return isAntigravityClaude(recorded) === isAntigravityClaude(model);
20
+ }
21
+
13
22
  function signatureSafeMessages(messages, model) {
14
23
  return messages.map(message => {
15
24
  if (message?.role !== 'assistant') return message;
16
- const ownParts = providerReplayItems(message, 'antigravity');
25
+ const ownParts = replayFamilyMatches(message, model) ? providerReplayItems(message, 'antigravity') : undefined;
17
26
  // Repair tool signatures only after the complete history establishes
18
27
  // the active turn, not while normalizing an isolated older message.
19
28
  const parts = ownParts || toGeminiContents([message], model, { repairToolSignatures: false })[0]?.parts;
@@ -60,20 +69,12 @@ export function buildAntigravityRequest(messages, model, tools, opts = {}, proje
60
69
  thinkingConfig.thinkingBudget = value;
61
70
  }
62
71
  } else {
72
+ // Tiered wire ids (gemini-3.8-flash-high) already encode the thinking
73
+ // level; the provider resolves them from the catalog and clears the
74
+ // effort, so only bare ids reach this field.
63
75
  thinkingConfig = geminiThinkingConfig(model, opts, {
64
76
  includeThoughts: /^gemini-3/i.test(model) ? true : undefined,
65
77
  });
66
- // This gateway selects Gemini Pro thinking tiers by model ID; Flash
67
- // uses the bare model ID plus the ordinary thinkingLevel field.
68
- if (thinkingConfig?.thinkingLevel && /^gemini-3(?:\.\d+)?-pro(?:-|$)/i.test(model)) {
69
- const level = thinkingConfig.thinkingLevel;
70
- if (!['low', 'high'].includes(level)) {
71
- throw new TypeError('Antigravity Gemini Pro supports low/high model tiers.');
72
- }
73
- model = `${model.replace(/-(?:low|medium|high)$/i, '')}-${level}`;
74
- } else if (thinkingConfig?.thinkingLevel && /^gemini-3(?:\.\d+)?-flash(?:-|$)/i.test(model)) {
75
- model = model.replace(/-(?:minimal|low|medium|high)$/i, '');
76
- }
77
78
  }
78
79
  const generationConfig = {
79
80
  ...(claude ? { maxOutputTokens: CLAUDE_MAX_OUTPUT_TOKENS } : {}),
@@ -4,7 +4,6 @@ const SCHEMA_ANNOTATIONS = new Set([
4
4
  '$schema',
5
5
  'default',
6
6
  'deprecated',
7
- 'description',
8
7
  'examples',
9
8
  'readOnly',
10
9
  'title',
@@ -105,13 +104,6 @@ function flattenCursorTopLevelCompoundSchema(schema) {
105
104
  };
106
105
  }
107
106
 
108
- function conciseDescription(value) {
109
- const normalized = String(value || '').replace(/\s+/g, ' ').trim();
110
- if (normalized.length <= 120) return normalized;
111
- const sentence = normalized.match(/^.{24,117}?[.!?](?:\s|$)/)?.[0]?.trim();
112
- return sentence || `${normalized.slice(0, 117)}...`;
113
- }
114
-
115
107
  export function prepareCursorToolDefinition(tool) {
116
108
  const fn = tool?.function || tool || {};
117
109
  const inputSchema = fn.parameters && typeof fn.parameters === 'object'
@@ -119,7 +111,7 @@ export function prepareCursorToolDefinition(tool) {
119
111
  : { type: 'object', properties: {} };
120
112
  return {
121
113
  name: fn.name,
122
- description: conciseDescription(fn.description),
114
+ description: String(fn.description || ''),
123
115
  inputSchema,
124
116
  };
125
117
  }
@@ -30,8 +30,11 @@ const SCHEMAS = {
30
30
  ['mimeType', 7, 'string'],
31
31
  ['data', 8, 'bytes'],
32
32
  ]),
33
- UserMessageAction: fields([['userMessage', 1, 'UserMessage']]),
34
- ResumeAction: fields([]),
33
+ UserMessageAction: fields([
34
+ ['userMessage', 1, 'UserMessage'],
35
+ ['requestContext', 2, 'RequestContext'],
36
+ ]),
37
+ ResumeAction: fields([['requestContext', 2, 'RequestContext']]),
35
38
  ConversationAction: fields([
36
39
  ['userMessageAction', 1, 'UserMessageAction'],
37
40
  ['resumeAction', 2, 'ResumeAction'],
@@ -1,5 +1,6 @@
1
1
  import crypto, { createHash } from 'node:crypto';
2
2
  import http2 from 'node:http2';
3
+ import { isDeepStrictEqual } from 'node:util';
3
4
  import {
4
5
  AUTO_MODEL,
5
6
  FALLBACK_MODELS,
@@ -380,6 +381,7 @@ function buildToolDefinitions(tools = []) {
380
381
  description: prepared.description,
381
382
  inputSchema: encodeJsonValue(prepared.inputSchema),
382
383
  inputSchemaJson: JSON.stringify(prepared.inputSchema),
384
+ inputSchemaObject: prepared.inputSchema,
383
385
  providerIdentifier: 'mixdog',
384
386
  toolName: prepared.name,
385
387
  };
@@ -405,6 +407,33 @@ function requestModelParameters(body) {
405
407
  .filter((entry) => entry.id);
406
408
  }
407
409
 
410
+ function buildRequestContext(tools, cloudRule) {
411
+ return {
412
+ tools,
413
+ mcpInstructions: tools.length ? [{
414
+ serverName: 'mixdog',
415
+ instructions: 'Use the tools provided by the mixdog MCP server for this task. '
416
+ + 'Follow their descriptions and input schemas. Prefer them over Cursor native tools; '
417
+ + 'native tools do not directly access the Mixdog environment. '
418
+ + 'If a needed tool is not available, use the provided tool discovery mechanism rather than inventing a tool.',
419
+ }] : [],
420
+ ...(cloudRule ? { cloudRule } : {}),
421
+ fileContents: {},
422
+ };
423
+ }
424
+
425
+ function canReuseRun(active, { tools, cloudRule, modelParameters, maxMode }) {
426
+ const toolContract = (definitions) => new Map(definitions.map((tool) => [
427
+ tool.name,
428
+ { description: tool.description, inputSchema: tool.inputSchemaObject },
429
+ ]));
430
+ const parameters = (values) => new Map(values.map(({ id, value }) => [id, value]));
431
+ return (active.cloudRule || '') === (cloudRule || '')
432
+ && isDeepStrictEqual(toolContract(active.tools), toolContract(tools))
433
+ && isDeepStrictEqual(parameters(active.modelParameters || []), parameters(modelParameters))
434
+ && (active.maxMode === true) === maxMode;
435
+ }
436
+
408
437
  const conversations = new Map();
409
438
  const activeRuns = new Map();
410
439
  const MEMORY_TTL_MS = 30 * 60_000;
@@ -522,11 +551,8 @@ function storeBlob(conversation, bytes) {
522
551
 
523
552
  function buildRunRequest({ model, modelParameters = [], maxMode = false, systems, history, userText, userImages = [], tools, conversation }) {
524
553
  assertCursorUserImages(userImages);
525
- const prompts = systems.length ? systems : ['You are a helpful assistant.'];
554
+ const requestContext = buildRequestContext(tools, systems.join('\n\n') || undefined);
526
555
  const rootPromptMessagesJson = [];
527
- for (const content of prompts) {
528
- rootPromptMessagesJson.push(storeBlob(conversation, textEncoder.encode(JSON.stringify({ role: 'system', content }))));
529
- }
530
556
  for (const entry of history) {
531
557
  rootPromptMessagesJson.push(storeBlob(conversation, textEncoder.encode(JSON.stringify(entry))));
532
558
  }
@@ -535,6 +561,7 @@ function buildRunRequest({ model, modelParameters = [], maxMode = false, systems
535
561
  const action = userText || userImages.length
536
562
  ? {
537
563
  userMessageAction: {
564
+ requestContext,
538
565
  userMessage: {
539
566
  text: userText,
540
567
  messageId: crypto.randomUUID(),
@@ -550,7 +577,7 @@ function buildRunRequest({ model, modelParameters = [], maxMode = false, systems
550
577
  },
551
578
  },
552
579
  }
553
- : { resumeAction: {} };
580
+ : { resumeAction: { requestContext } };
554
581
  return encodeMessage('AgentClientMessage', {
555
582
  runRequest: {
556
583
  conversationState: stateBytes,
@@ -560,13 +587,12 @@ function buildRunRequest({ model, modelParameters = [], maxMode = false, systems
560
587
  maxMode: maxMode === true,
561
588
  parameters: modelParameters,
562
589
  },
563
- mcpTools: { mcpTools: tools },
564
590
  conversationId: conversation.id,
565
591
  // NOTE: AgentRunRequest.customSystemPrompt (field 8) is a dead
566
592
  // channel on the current cloud endpoint: the server maps it to an
567
593
  // internal `--system-prompt` agent flag its binary rejects with
568
- // invalid_argument (400). Harness parity is carried by the
569
- // requestContext cloudRule instead (see handleExecMessage).
594
+ // invalid_argument (400). Send the harness once through the
595
+ // action's requestContext.cloudRule, not through root history too.
570
596
  },
571
597
  });
572
598
  }
@@ -671,15 +697,7 @@ function handleExecMessage(bridge, exec, tools, cloudRule, onToolCall) {
671
697
  if (exec.requestContextArgs) {
672
698
  sendExecResult(bridge, exec, 'requestContextResult', {
673
699
  success: {
674
- requestContext: {
675
- tools,
676
- mcpInstructions: [],
677
- // Cursor's rules channel: without it the server-side agent
678
- // treats the client as ruleless and applies only its own
679
- // harness policy (user report: policies ignored vs direct).
680
- ...(cloudRule ? { cloudRule } : {}),
681
- fileContents: {},
682
- },
700
+ requestContext: buildRequestContext(tools, cloudRule),
683
701
  },
684
702
  });
685
703
  return;
@@ -989,6 +1007,8 @@ function createStreamResponse({
989
1007
  tools,
990
1008
  cloudRule,
991
1009
  model,
1010
+ modelParameters = [],
1011
+ maxMode = false,
992
1012
  key,
993
1013
  // Owning session, carried into the stored run so a session close can find
994
1014
  // and tear down the pending batch's bridge/heartbeat.
@@ -1072,6 +1092,8 @@ function createStreamResponse({
1072
1092
  conversation,
1073
1093
  tools,
1074
1094
  cloudRule,
1095
+ modelParameters,
1096
+ maxMode,
1075
1097
  sessionId,
1076
1098
  pending: state.pending,
1077
1099
  sawTurnEnded: state.sawTurnEnded,
@@ -1361,8 +1383,12 @@ export async function handleChatCompletion(body, accessToken) {
1361
1383
  const maxMode = body.mixdog_max_mode === true;
1362
1384
  const sessionId = String(body.mixdog_session_id || '');
1363
1385
  const key = runKey(model, body.messages, sessionId);
1386
+ const selectedTools = selectToolsForChoice(body.tools, body.tool_choice);
1387
+ const toolDefinitions = buildToolDefinitions(selectedTools);
1388
+ const cloudRule = parsed.systems.join('\n\n') || undefined;
1364
1389
  const active = activeRuns.get(key);
1365
- if (active && parsed.toolResults.length && active.bridge.alive) {
1390
+ if (active && parsed.toolResults.length && active.bridge.alive
1391
+ && canReuseRun(active, { tools: toolDefinitions, cloudRule, modelParameters, maxMode })) {
1366
1392
  forgetActiveRun(key, active);
1367
1393
  return resumeRun(active, parsed.toolResults, parsed.userText, model, key);
1368
1394
  }
@@ -1373,8 +1399,6 @@ export async function handleChatCompletion(body, accessToken) {
1373
1399
  }
1374
1400
  const convKey = conversationKey(body.messages, sessionId);
1375
1401
  const conversation = getConversation(convKey);
1376
- const selectedTools = selectToolsForChoice(body.tools, body.tool_choice);
1377
- const toolDefinitions = buildToolDefinitions(selectedTools);
1378
1402
  const runInput = {
1379
1403
  model,
1380
1404
  modelParameters,
@@ -1392,12 +1416,11 @@ export async function handleChatCompletion(body, accessToken) {
1392
1416
  bridge,
1393
1417
  heartbeat,
1394
1418
  conversation,
1395
- tools: toolDefinitions.map((definition, index) => ({
1396
- ...definition,
1397
- inputSchemaObject: selectedTools[index]?.function?.parameters || {},
1398
- })),
1399
- cloudRule: parsed.systems.join('\n\n') || undefined,
1419
+ tools: toolDefinitions,
1420
+ cloudRule,
1400
1421
  model,
1422
+ modelParameters,
1423
+ maxMode,
1401
1424
  key,
1402
1425
  sessionId,
1403
1426
  restart: ({ fromCheckpoint }) => startRun(
@@ -526,12 +526,36 @@ function convertSchema(schema) {
526
526
  return result;
527
527
  }
528
528
 
529
+ // Gemini's legacy Schema requires typed values. An unconstrained JSON value
530
+ // (Office operation values, for example) cannot be replaced with a string or
531
+ // an untyped Schema node. FunctionDeclaration.parametersJsonSchema is the
532
+ // documented, mutually exclusive representation for the original JSON Schema.
533
+ function needsJsonParameters(schema) {
534
+ if (typeof schema === 'boolean') return true;
535
+ if (!schema || typeof schema !== 'object') return false;
536
+ // The legacy dialect has no reference vocabulary; preserve both pointers
537
+ // and their definitions together, including recursive MCP contracts.
538
+ if (['$ref', '$defs', 'definitions'].some((key) => Object.hasOwn(schema, key))) return true;
539
+ const constraints = Object.keys(schema)
540
+ .filter((key) => !['description', 'title', 'default', 'examples', '$comment'].includes(key));
541
+ if (!constraints.length || (schema.type === 'array' && !schema.items)) return true;
542
+ const children = [
543
+ ...Object.values(schema.properties || {}),
544
+ ...(schema.items ? [schema.items] : []),
545
+ ...(typeof schema.additionalProperties === 'object' ? [schema.additionalProperties] : []),
546
+ ...(schema.anyOf || []), ...(schema.oneOf || []), ...(schema.allOf || []),
547
+ ];
548
+ return children.some(needsJsonParameters);
549
+ }
550
+
529
551
  export function toGeminiTools(tools) {
530
552
  return {
531
553
  functionDeclarations: tools.map((t) => ({
532
554
  name: t.name,
533
555
  description: t.description,
534
- parameters: convertSchema(t.inputSchema),
556
+ ...(needsJsonParameters(t.inputSchema)
557
+ ? { parametersJsonSchema: structuredClone(t.inputSchema) }
558
+ : { parameters: convertSchema(t.inputSchema) }),
535
559
  })),
536
560
  };
537
561
  }
@@ -832,7 +856,9 @@ export function parseToolCalls(parts) {
832
856
  // deterministic id only for older responses that omit it.
833
857
  id: typeof fc.id === 'string' && fc.id ? fc.id : `gemini_${idHash}`,
834
858
  name: fc.name,
835
- arguments: (fc.args ?? {}),
859
+ // Execution may normalize nested arguments in place. Keep native
860
+ // replay and anonymous ID inputs independent of that working copy.
861
+ arguments: structuredClone(fc.args ?? {}),
836
862
  };
837
863
  if (typeof sig === 'string' && sig && sig.length <= 16_384) {
838
864
  call.thoughtSignature = sig;
@@ -352,7 +352,7 @@ export function createGeminiTextLeakGuard({ knownToolNames, onTextDelta, onToolC
352
352
  // deeper. Cloud Code Assist (Antigravity) streams `{ response: { candidates … } }`
353
353
  // while the public API streams the candidates directly; everything downstream —
354
354
  // aggregation, leak guard, watchdogs — stays shared.
355
- export async function consumeGeminiRestStreamResponse(response, { signal, onStreamDelta, onTextDelta, textLeakGuard, label, unwrapChunk = null }) {
355
+ export async function consumeGeminiRestStreamResponse(response, { signal, onStreamDelta, onTextDelta, textLeakGuard, label, unwrapChunk = null, onChunk = null }) {
356
356
  const unwrap = typeof unwrapChunk === 'function'
357
357
  ? (chunk) => { try { return unwrapChunk(chunk); } catch { return chunk; } }
358
358
  : (chunk) => chunk;
@@ -467,6 +467,7 @@ export async function consumeGeminiRestStreamResponse(response, { signal, onStre
467
467
  clearFirstByteTimer();
468
468
  }
469
469
  allChunks.push(parsed);
470
+ onChunk?.(parsed);
470
471
  try { onStreamDelta?.(geminiChunkProgressKind(parsed)); } catch {}
471
472
  if (!sawFunctionCall && geminiChunkHasFunctionCall(parsed)) sawFunctionCall = true;
472
473
  if (onTextDelta || textLeakGuard) {
@@ -494,6 +495,7 @@ export async function consumeGeminiRestStreamResponse(response, { signal, onStre
494
495
  clearFirstByteTimer();
495
496
  }
496
497
  allChunks.push(parsed);
498
+ onChunk?.(parsed);
497
499
  try { onStreamDelta?.(geminiChunkProgressKind(parsed)); } catch {}
498
500
  if (!sawFunctionCall && geminiChunkHasFunctionCall(parsed)) sawFunctionCall = true;
499
501
  if (onTextDelta || textLeakGuard) {