@framers/agentos 0.10.21 → 0.10.22

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 (74) hide show
  1. package/dist/api/embedText.d.ts +9 -4
  2. package/dist/api/embedText.d.ts.map +1 -1
  3. package/dist/api/embedText.js +114 -8
  4. package/dist/api/embedText.js.map +1 -1
  5. package/dist/api/runtime/provider-defaults.d.ts.map +1 -1
  6. package/dist/api/runtime/provider-defaults.js +6 -2
  7. package/dist/api/runtime/provider-defaults.js.map +1 -1
  8. package/dist/api/sessionTranscript.d.ts +6 -0
  9. package/dist/api/sessionTranscript.d.ts.map +1 -1
  10. package/dist/api/sessionTranscript.js.map +1 -1
  11. package/dist/api/streamText.d.ts.map +1 -1
  12. package/dist/api/streamText.js +1 -0
  13. package/dist/api/streamText.js.map +1 -1
  14. package/dist/cognition/substrate/ConversationHistoryManager.d.ts.map +1 -1
  15. package/dist/cognition/substrate/ConversationHistoryManager.js +2 -0
  16. package/dist/cognition/substrate/ConversationHistoryManager.js.map +1 -1
  17. package/dist/cognition/substrate/GMI.d.ts.map +1 -1
  18. package/dist/cognition/substrate/GMI.js +3 -1
  19. package/dist/cognition/substrate/GMI.js.map +1 -1
  20. package/dist/cognition/substrate/IGMI.d.ts +5 -0
  21. package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
  22. package/dist/cognition/substrate/IGMI.js.map +1 -1
  23. package/dist/core/llm/PromptEngine.d.ts.map +1 -1
  24. package/dist/core/llm/PromptEngine.js +1 -0
  25. package/dist/core/llm/PromptEngine.js.map +1 -1
  26. package/dist/core/llm/providers/IProvider.d.ts +9 -0
  27. package/dist/core/llm/providers/IProvider.d.ts.map +1 -1
  28. package/dist/core/llm/providers/errors/GeminiProviderError.d.ts +11 -0
  29. package/dist/core/llm/providers/errors/GeminiProviderError.d.ts.map +1 -1
  30. package/dist/core/llm/providers/errors/GeminiProviderError.js.map +1 -1
  31. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts +27 -6
  32. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts.map +1 -1
  33. package/dist/core/llm/providers/implementations/AnthropicProvider.js +78 -15
  34. package/dist/core/llm/providers/implementations/AnthropicProvider.js.map +1 -1
  35. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts +1 -1
  36. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts.map +1 -1
  37. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js +70 -7
  38. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js.map +1 -1
  39. package/dist/core/llm/providers/implementations/GeminiCLIBridge.js +1 -1
  40. package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts.map +1 -1
  41. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js +56 -4
  42. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js.map +1 -1
  43. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts +14 -4
  44. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts.map +1 -1
  45. package/dist/core/llm/providers/implementations/GeminiProvider.js +355 -39
  46. package/dist/core/llm/providers/implementations/GeminiProvider.js.map +1 -1
  47. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts +61 -0
  48. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts.map +1 -1
  49. package/dist/core/llm/providers/implementations/OpenAIProvider.js +164 -22
  50. package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
  51. package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
  52. package/dist/core/llm/providers/implementations/OpenRouterProvider.js +6 -2
  53. package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
  54. package/dist/core/llm/providers/model-forced-tool-choice.d.ts +32 -15
  55. package/dist/core/llm/providers/model-forced-tool-choice.d.ts.map +1 -1
  56. package/dist/core/llm/providers/model-forced-tool-choice.js +33 -16
  57. package/dist/core/llm/providers/model-forced-tool-choice.js.map +1 -1
  58. package/dist/core/llm/streaming/StreamingBatcher.d.ts.map +1 -1
  59. package/dist/core/llm/streaming/StreamingBatcher.js +3 -0
  60. package/dist/core/llm/streaming/StreamingBatcher.js.map +1 -1
  61. package/dist/core/llm/streaming/StreamingReconstructor.d.ts +2 -0
  62. package/dist/core/llm/streaming/StreamingReconstructor.d.ts.map +1 -1
  63. package/dist/core/llm/streaming/StreamingReconstructor.js +12 -1
  64. package/dist/core/llm/streaming/StreamingReconstructor.js.map +1 -1
  65. package/dist/io/vision/providers/LLMVisionProvider.d.ts +2 -2
  66. package/dist/io/vision/providers/LLMVisionProvider.js +1 -1
  67. package/dist/io/vision/types.d.ts +1 -1
  68. package/dist/orchestration/pipeline/query/types.d.ts.map +1 -1
  69. package/dist/orchestration/pipeline/query/types.js +3 -2
  70. package/dist/orchestration/pipeline/query/types.js.map +1 -1
  71. package/dist/orchestration/planning/ProviderAssignmentEngine.d.ts.map +1 -1
  72. package/dist/orchestration/planning/ProviderAssignmentEngine.js +2 -1
  73. package/dist/orchestration/planning/ProviderAssignmentEngine.js.map +1 -1
  74. package/package.json +1 -1
@@ -4,6 +4,67 @@ import { GeminiProviderError } from '../errors/GeminiProviderError.js';
4
4
  import { ApiKeyPool } from '../../../providers/ApiKeyPool.js';
5
5
  import { computeRetryBackoffMs } from './retry-backoff.js';
6
6
  import { sanitizeGeminiResponseSchema } from './geminiResponseSchema.js';
7
+ /**
8
+ * Most requests `models/{model}:batchEmbedContents` accepts in one call. The
9
+ * API returns HTTP 400 "at most 100 requests can be in one batch" above this
10
+ * (probed 2026-09-29 on gemini-embedding-001).
11
+ */
12
+ const GEMINI_EMBED_BATCH_LIMIT = 100;
13
+ /**
14
+ * Placeholder signature Google documents for replaying a function call whose
15
+ * real signature is unavailable, such as a call made by another provider in a
16
+ * fallback chain. Gemini 3 returns HTTP 400 for a replayed call with no
17
+ * signature and accepts this value, and Gemini 2.5 accepts it too (probed
18
+ * 2026-09-29).
19
+ */
20
+ const GEMINI_SKIP_THOUGHT_SIGNATURE = 'skip_thought_signature_validator';
21
+ /**
22
+ * The credentials part of a base URL, read from the raw string: the text
23
+ * between the authority start (after a leading `scheme://` or `//`, else the
24
+ * start of a scheme-less URL) and the last `@`. The last `@` is used because an email-style username
25
+ * carries its own `@`, and the raw string because a URL that fails to parse
26
+ * (for example with a `/` in the password) still appears verbatim in fetch's
27
+ * error.
28
+ *
29
+ * @param baseURL Configured base URL.
30
+ * @returns The credentials substring, or undefined when there is none.
31
+ */
32
+ function baseUrlCredentials(baseURL) {
33
+ if (!baseURL)
34
+ return undefined;
35
+ // Only a leading scheme or `//` opens the authority; a `//` later in the
36
+ // path must not move the start past the credentials.
37
+ const scheme = /^[a-z][a-z\d+.-]*:\/\//i.exec(baseURL);
38
+ const start = scheme ? scheme[0].length : baseURL.startsWith('//') ? 2 : 0;
39
+ const at = baseURL.lastIndexOf('@');
40
+ return at > start ? baseURL.slice(start, at) : undefined;
41
+ }
42
+ /**
43
+ * Masks the secrets a request URL carries out of an error message: the API
44
+ * key the request sent as `key=` and any credentials in the base URL. The
45
+ * known values are replaced literally, since a pattern cannot tell where
46
+ * arbitrary credentials end. A fetch that rejects its URL (relative,
47
+ * scheme-less or credential-bearing) names the whole URL in its error.
48
+ *
49
+ * @param message Error message that may contain the request URL.
50
+ * @param apiKey The key the request carried.
51
+ * @param baseURL Configured base URL.
52
+ * @returns The message with those secrets replaced by `[redacted]`.
53
+ */
54
+ function redactUrlSecrets(message, apiKey, baseURL) {
55
+ // Longest first, so a key that also appears inside the credentials cannot
56
+ // split them before the whole credentials string is replaced.
57
+ const secrets = [baseUrlCredentials(baseURL), apiKey]
58
+ .filter((secret) => Boolean(secret))
59
+ .sort((a, b) => b.length - a.length);
60
+ let out = message;
61
+ for (const secret of secrets)
62
+ out = out.split(secret).join('[redacted]');
63
+ // Backstops for any other key= value or //user:password@ credentials.
64
+ return out
65
+ .replace(/([?&]key=)[^&\s"')]+/g, '$1[redacted]')
66
+ .replace(/(\/\/)[^/\s@"')]+@/g, '$1[redacted]@');
67
+ }
7
68
  /**
8
69
  * Thinking levels each model accepts, keyed by exact model id and limited to
9
70
  * what was verified against the live API (2026-09-30). The set differs per
@@ -48,12 +109,30 @@ const warnedRetiredGeminiModels = new Set();
48
109
  const GEMINI_LONG_CONTEXT_PRICING = {
49
110
  'gemini-3.1-pro-preview': { abovePromptTokens: 200000, input: 4.00, output: 18.00 },
50
111
  'gemini-pro-latest': { abovePromptTokens: 200000, input: 4.00, output: 18.00 },
112
+ 'gemini-2.5-pro': { abovePromptTokens: 200000, input: 2.50, output: 15.00 },
51
113
  };
52
114
  // ---------------------------------------------------------------------------
53
115
  // Known model catalog
54
116
  // ---------------------------------------------------------------------------
55
- /** Static catalog of well-known Gemini models and their metadata. */
117
+ /**
118
+ * Static catalog of well-known Gemini models and their metadata.
119
+ *
120
+ * Ids and token limits come from a live GET /v1beta/models probe and rates from
121
+ * ai.google.dev/gemini-api/docs/pricing, both on 2026-09-29. `getModelInfo` is
122
+ * an exact-id lookup over this array, so a model missing here resolves to
123
+ * undefined and its calls carry no context limit and no price.
124
+ *
125
+ * Tiers advance independently. Flash has reached 3.8 while Pro is at
126
+ * 3.1-preview, the only Pro id served. `gemini-3-pro-preview` appears in
127
+ * Google's doc index and is absent from the live listing.
128
+ *
129
+ * Rates are the standard tier. Prompts over 200K tokens on 3.1 Pro and 2.5
130
+ * Pro bill at the tier in GEMINI_LONG_CONTEXT_PRICING, and Gemini 3.6 / 3.7 /
131
+ * 3.8 Flash list at promotional rates through 2026-12-31 and meter at the
132
+ * post-promotion rate.
133
+ */
56
134
  const GEMINI_MODELS = [
135
+ // --- Pro ---
57
136
  {
58
137
  modelId: 'gemini-3.1-pro-preview',
59
138
  providerId: 'gemini',
@@ -68,6 +147,107 @@ const GEMINI_MODELS = [
68
147
  supportsStreaming: true,
69
148
  status: 'active',
70
149
  },
150
+ // --- Flash. 3.6, 3.7 and 3.8 list at $0.75 / $3.75 through 2026-12-31 and
151
+ // $1.50 / $7.50 from 2027-01-01. They meter at $1.50 / $7.50, as
152
+ // claude-sonnet-5 meters at its sticker rate in the Anthropic catalog, so
153
+ // cost rollups stay conservative and stay right after the promotion. ---
154
+ {
155
+ modelId: 'gemini-3.8-flash',
156
+ providerId: 'gemini',
157
+ displayName: 'Gemini 3.8 Flash',
158
+ description: 'Newest Flash model. Fast and low-cost, with reasoning and multimodal input.',
159
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
160
+ contextWindowSize: 1048576,
161
+ outputTokenLimit: 65536,
162
+ pricePer1MTokensInput: 1.50,
163
+ pricePer1MTokensOutput: 7.50,
164
+ supportsStreaming: true,
165
+ status: 'active',
166
+ },
167
+ {
168
+ modelId: 'gemini-3.7-flash',
169
+ providerId: 'gemini',
170
+ displayName: 'Gemini 3.7 Flash',
171
+ description: 'Flash model with reasoning and multimodal input.',
172
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
173
+ contextWindowSize: 1048576,
174
+ outputTokenLimit: 65536,
175
+ pricePer1MTokensInput: 1.50,
176
+ pricePer1MTokensOutput: 7.50,
177
+ supportsStreaming: true,
178
+ status: 'active',
179
+ },
180
+ {
181
+ modelId: 'gemini-3.6-flash',
182
+ providerId: 'gemini',
183
+ displayName: 'Gemini 3.6 Flash',
184
+ description: 'Flash model with reasoning and multimodal input.',
185
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
186
+ contextWindowSize: 1048576,
187
+ outputTokenLimit: 65536,
188
+ pricePer1MTokensInput: 1.50,
189
+ pricePer1MTokensOutput: 7.50,
190
+ supportsStreaming: true,
191
+ status: 'active',
192
+ },
193
+ {
194
+ modelId: 'gemini-3.5-flash',
195
+ providerId: 'gemini',
196
+ displayName: 'Gemini 3.5 Flash',
197
+ description: 'Flash model with reasoning and multimodal input, at standard pricing.',
198
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
199
+ contextWindowSize: 1048576,
200
+ outputTokenLimit: 65536,
201
+ pricePer1MTokensInput: 1.50,
202
+ pricePer1MTokensOutput: 9.00,
203
+ supportsStreaming: true,
204
+ status: 'active',
205
+ },
206
+ {
207
+ modelId: 'gemini-3-flash-preview',
208
+ providerId: 'gemini',
209
+ displayName: 'Gemini 3 Flash Preview',
210
+ description: 'First Gemini 3 Flash preview.',
211
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
212
+ contextWindowSize: 1048576,
213
+ outputTokenLimit: 65536,
214
+ pricePer1MTokensInput: 0.50,
215
+ pricePer1MTokensOutput: 3.00,
216
+ supportsStreaming: true,
217
+ status: 'active',
218
+ },
219
+ // --- Flash-Lite ---
220
+ {
221
+ modelId: 'gemini-3.5-flash-lite',
222
+ providerId: 'gemini',
223
+ displayName: 'Gemini 3.5 Flash-Lite',
224
+ description: 'Lowest-cost Gemini 3.5 tier, for high-volume and latency-sensitive work.',
225
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
226
+ contextWindowSize: 1048576,
227
+ outputTokenLimit: 65536,
228
+ pricePer1MTokensInput: 0.30,
229
+ pricePer1MTokensOutput: 2.50,
230
+ supportsStreaming: true,
231
+ status: 'active',
232
+ },
233
+ {
234
+ modelId: 'gemini-3.1-flash-lite',
235
+ providerId: 'gemini',
236
+ displayName: 'Gemini 3.1 Flash-Lite',
237
+ description: 'Lowest-cost Gemini 3.1 tier. Google lists a shutdown date of 2027-05-07.',
238
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
239
+ contextWindowSize: 1048576,
240
+ outputTokenLimit: 65536,
241
+ pricePer1MTokensInput: 0.25,
242
+ pricePer1MTokensOutput: 1.50,
243
+ supportsStreaming: true,
244
+ status: 'active',
245
+ },
246
+ // --- Floating aliases. Google can repoint these without changing the id, so
247
+ // a price here can go stale unnoticed. They are listed because an unlisted id
248
+ // resolves to undefined and meters at zero. generateContent reported these
249
+ // targets on 2026-09-29 (response modelVersion): pro-latest to 3.1 Pro,
250
+ // flash-latest to 3.8 Flash, flash-lite-latest to 3.5 Flash-Lite. ---
71
251
  {
72
252
  modelId: 'gemini-pro-latest',
73
253
  providerId: 'gemini',
@@ -83,23 +263,37 @@ const GEMINI_MODELS = [
83
263
  status: 'active',
84
264
  },
85
265
  {
86
- modelId: 'gemini-2.5-flash',
266
+ modelId: 'gemini-flash-latest',
87
267
  providerId: 'gemini',
88
- displayName: 'Gemini 2.5 Flash',
89
- description: 'Fast, cost-effective model with strong reasoning and multimodal capabilities.',
268
+ displayName: 'Gemini Flash (latest)',
269
+ description: 'Floating alias for the current Gemini Flash tier.',
90
270
  capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
91
271
  contextWindowSize: 1048576,
92
272
  outputTokenLimit: 65536,
93
- pricePer1MTokensInput: 0.15,
94
- pricePer1MTokensOutput: 0.60,
273
+ pricePer1MTokensInput: 1.50,
274
+ pricePer1MTokensOutput: 7.50,
95
275
  supportsStreaming: true,
96
276
  status: 'active',
97
277
  },
278
+ {
279
+ modelId: 'gemini-flash-lite-latest',
280
+ providerId: 'gemini',
281
+ displayName: 'Gemini Flash-Lite (latest)',
282
+ description: 'Floating alias for the current Gemini Flash-Lite tier.',
283
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
284
+ contextWindowSize: 1048576,
285
+ outputTokenLimit: 65536,
286
+ pricePer1MTokensInput: 0.30,
287
+ pricePer1MTokensOutput: 2.50,
288
+ supportsStreaming: true,
289
+ status: 'active',
290
+ },
291
+ // --- Gemini 2.5 ---
98
292
  {
99
293
  modelId: 'gemini-2.5-pro',
100
294
  providerId: 'gemini',
101
295
  displayName: 'Gemini 2.5 Pro',
102
- description: 'Most capable Gemini model for complex reasoning and analysis.',
296
+ description: 'Previous-generation Pro model.',
103
297
  capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
104
298
  contextWindowSize: 1048576,
105
299
  outputTokenLimit: 65536,
@@ -108,31 +302,93 @@ const GEMINI_MODELS = [
108
302
  supportsStreaming: true,
109
303
  status: 'active',
110
304
  },
305
+ {
306
+ modelId: 'gemini-2.5-flash',
307
+ providerId: 'gemini',
308
+ displayName: 'Gemini 2.5 Flash',
309
+ description: 'Previous-generation Flash model with reasoning and multimodal input.',
310
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
311
+ contextWindowSize: 1048576,
312
+ outputTokenLimit: 65536,
313
+ pricePer1MTokensInput: 0.30,
314
+ pricePer1MTokensOutput: 2.50,
315
+ supportsStreaming: true,
316
+ status: 'active',
317
+ },
318
+ {
319
+ modelId: 'gemini-2.5-flash-lite',
320
+ providerId: 'gemini',
321
+ displayName: 'Gemini 2.5 Flash-Lite',
322
+ description: 'Cheapest Gemini model served.',
323
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
324
+ contextWindowSize: 1048576,
325
+ outputTokenLimit: 65536,
326
+ pricePer1MTokensInput: 0.10,
327
+ pricePer1MTokensOutput: 0.40,
328
+ supportsStreaming: true,
329
+ status: 'active',
330
+ },
331
+ // --- Embeddings. Both ids accept embedContent and batchEmbedContents and
332
+ // return 3072-dimension vectors (probed 2026-09-29). Google's pricing page
333
+ // lists gemini-embedding-2 at $0.20 per 1M text tokens and carries no price
334
+ // for gemini-embedding-001, so that row leaves its price unset: an unset
335
+ // price reads as unknown to a caller, where 0 would read as free. ---
336
+ {
337
+ modelId: 'gemini-embedding-2',
338
+ providerId: 'gemini',
339
+ displayName: 'Gemini Embedding 2',
340
+ description: 'Multimodal embedding model mapping text, images, video, audio and PDFs into one vector space.',
341
+ capabilities: ['embeddings'],
342
+ contextWindowSize: 8192,
343
+ inputTokenLimit: 8192,
344
+ embeddingDimension: 3072,
345
+ pricePer1MTokensInput: 0.20,
346
+ pricePer1MTokensTotal: 0.20,
347
+ supportsStreaming: false,
348
+ status: 'active',
349
+ },
350
+ {
351
+ modelId: 'gemini-embedding-001',
352
+ providerId: 'gemini',
353
+ displayName: 'Gemini Embedding 001',
354
+ description: 'Text embedding model. An index built with it needs it for queries too, since vectors from different embedding models are not comparable.',
355
+ capabilities: ['embeddings'],
356
+ contextWindowSize: 2048,
357
+ inputTokenLimit: 2048,
358
+ embeddingDimension: 3072,
359
+ supportsStreaming: false,
360
+ status: 'active',
361
+ },
362
+ // --- Retired by Google. generateContent returns HTTP 404 for both ids
363
+ // ("This model models/gemini-2.0-flash is no longer available" for 2.0
364
+ // Flash), probed 2026-09-29 with a gemini-2.5-flash-lite control returning
365
+ // 200. The rows stay as `deprecated` so getModelInfo still answers for a
366
+ // caller holding either id. ---
111
367
  {
112
368
  modelId: 'gemini-2.0-flash',
113
369
  providerId: 'gemini',
114
- displayName: 'Gemini 2.0 Flash',
115
- description: 'Previous-generation fast model with strong performance.',
370
+ displayName: 'Gemini 2.0 Flash (retired)',
371
+ description: 'Retired by Google and no longer served. Google names gemini-3.8-flash as its replacement.',
116
372
  capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
117
373
  contextWindowSize: 1048576,
118
374
  outputTokenLimit: 8192,
119
375
  pricePer1MTokensInput: 0.10,
120
376
  pricePer1MTokensOutput: 0.40,
121
377
  supportsStreaming: true,
122
- status: 'active',
378
+ status: 'deprecated',
123
379
  },
124
380
  {
125
381
  modelId: 'gemini-1.5-pro',
126
382
  providerId: 'gemini',
127
- displayName: 'Gemini 1.5 Pro',
128
- description: 'Stable model with 2M context window for long-document tasks.',
383
+ displayName: 'Gemini 1.5 Pro (retired)',
384
+ description: 'Retired by Google and no longer served. Use gemini-3.1-pro-preview.',
129
385
  capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
130
386
  contextWindowSize: 2097152,
131
387
  outputTokenLimit: 8192,
132
388
  pricePer1MTokensInput: 1.25,
133
389
  pricePer1MTokensOutput: 5.00,
134
390
  supportsStreaming: true,
135
- status: 'active',
391
+ status: 'deprecated',
136
392
  },
137
393
  ];
138
394
  // ---------------------------------------------------------------------------
@@ -355,6 +611,9 @@ export class GeminiProvider {
355
611
  }
356
612
  const parts = candidate.content?.parts ?? [];
357
613
  for (const part of parts) {
614
+ // A thought summary is the model's reasoning; it stays out of the answer.
615
+ if (part.thought)
616
+ continue;
358
617
  if (part.text !== undefined) {
359
618
  // Text delta
360
619
  accumulatedContent += part.text;
@@ -378,6 +637,7 @@ export class GeminiProvider {
378
637
  toolCallAccum.set(idx, {
379
638
  name: part.functionCall.name,
380
639
  args: part.functionCall.args,
640
+ thoughtSignature: part.thoughtSignature,
381
641
  });
382
642
  yield {
383
643
  id: responseId,
@@ -398,6 +658,7 @@ export class GeminiProvider {
398
658
  // Gemini delivers complete args, so emit them as a single delta
399
659
  arguments_delta: JSON.stringify(part.functionCall.args),
400
660
  },
661
+ ...(part.thoughtSignature ? { thoughtSignature: part.thoughtSignature } : {}),
401
662
  }],
402
663
  };
403
664
  }
@@ -462,10 +723,20 @@ export class GeminiProvider {
462
723
  /**
463
724
  * Generates embeddings using Gemini's embedding models.
464
725
  *
465
- * Uses the `models/{model}:embedContent` endpoint. Currently Gemini
466
- * supports embedding one text at a time, so we batch sequentially.
726
+ * Sends the texts through `models/{model}:batchEmbedContents` in chunks of
727
+ * at most {@link GEMINI_EMBED_BATCH_LIMIT}, one request per chunk, and
728
+ * returns the vectors in input order. gemini-embedding-001 and
729
+ * gemini-embedding-2 both accept batchEmbedContents (probed 2026-09-29),
730
+ * although neither lists it in `supportedGenerationMethods`.
731
+ * `options.dimensions` is sent as `outputDimensionality`, which both models
732
+ * honor (768 and 1536 probed 2026-09-29); without it they return 3072.
733
+ * Usage sums each call's `usageMetadata.promptTokenCount`, which
734
+ * gemini-embedding-2 reports and gemini-embedding-001 omits, so the older
735
+ * model's calls count zero tokens (probed 2026-09-30). When a later batch
736
+ * fails, the thrown error carries the earlier batches' usage as
737
+ * `partialUsage`, since those calls were billed.
467
738
  *
468
- * @param {string} modelId - Embedding model (e.g., "text-embedding-004").
739
+ * @param {string} modelId - Embedding model (e.g., "gemini-embedding-001").
469
740
  * @param {string[]} texts - Input texts to embed.
470
741
  * @param {ProviderEmbeddingOptions} [options] - Optional embedding parameters.
471
742
  * @returns {Promise<ProviderEmbeddingResponse>} Embedding vectors.
@@ -473,25 +744,46 @@ export class GeminiProvider {
473
744
  */
474
745
  async generateEmbeddings(modelId, texts, options) {
475
746
  this.ensureInitialized();
476
- // Gemini's batch embedding endpoint
477
747
  const endpoint = `/models/${modelId}:batchEmbedContents`;
478
- const requests = texts.map(text => ({
479
- model: `models/${modelId}`,
480
- content: { parts: [{ text }] },
481
- }));
482
- const apiResponse = await this.makeApiRequest(endpoint, { requests });
748
+ const pricePer1M = GEMINI_MODELS.find(m => m.modelId === modelId)?.pricePer1MTokensInput;
749
+ const usageFor = (tokens) => ({
750
+ prompt_tokens: tokens,
751
+ total_tokens: tokens,
752
+ ...(pricePer1M !== undefined && tokens > 0 ? { costUSD: (tokens / 1000000) * pricePer1M } : {}),
753
+ });
754
+ const vectors = [];
755
+ let promptTokens = 0;
756
+ for (let start = 0; start < texts.length; start += GEMINI_EMBED_BATCH_LIMIT) {
757
+ const requests = texts.slice(start, start + GEMINI_EMBED_BATCH_LIMIT).map(text => ({
758
+ model: `models/${modelId}`,
759
+ content: { parts: [{ text }] },
760
+ ...(options?.dimensions ? { outputDimensionality: options.dimensions } : {}),
761
+ }));
762
+ let apiResponse;
763
+ try {
764
+ apiResponse = await this.makeApiRequest(endpoint, { requests });
765
+ }
766
+ catch (error) {
767
+ // The batches before this one were billed; keep their usage on the
768
+ // error so the caller can still record it.
769
+ if (start > 0 && error instanceof GeminiProviderError) {
770
+ error.partialUsage = usageFor(promptTokens);
771
+ }
772
+ throw error;
773
+ }
774
+ for (const emb of apiResponse.embeddings)
775
+ vectors.push(emb.values);
776
+ promptTokens += apiResponse.usageMetadata?.promptTokenCount ?? 0;
777
+ }
483
778
  return {
484
779
  object: 'list',
485
- data: apiResponse.embeddings.map((emb, index) => ({
780
+ data: vectors.map((embedding, index) => ({
486
781
  object: 'embedding',
487
- embedding: emb.values,
782
+ embedding,
488
783
  index,
489
784
  })),
490
785
  model: modelId,
491
- usage: {
492
- prompt_tokens: 0, // Gemini does not report embedding token counts
493
- total_tokens: 0,
494
- },
786
+ usage: usageFor(promptTokens),
495
787
  };
496
788
  }
497
789
  // -------------------------------------------------------------------------
@@ -656,7 +948,9 @@ export class GeminiProvider {
656
948
  // otherwise the provider-neutral `effort` becomes the model's thinking
657
949
  // level when the model is known to take that level. Thinking shares the
658
950
  // maxOutputTokens cap: room for it on a rescue hop comes from
659
- // FallbackProviderEntry.maxTokensHeadroom, not from this provider.
951
+ // FallbackProviderEntry.maxTokensHeadroom, not from this provider. The
952
+ // shared `thinking` option is Anthropic's on-switch and is not translated:
953
+ // on a fallback hop it would turn "thinking on" into a thinking-token cap.
660
954
  const customThinking = options.customModelParams?.thinkingConfig;
661
955
  if (customThinking && typeof customThinking === 'object') {
662
956
  generationConfig.thinkingConfig = customThinking;
@@ -710,9 +1004,14 @@ export class GeminiProvider {
710
1004
  if (typeof msg.content === 'string' && msg.content) {
711
1005
  parts.push({ text: msg.content });
712
1006
  }
713
- // Convert tool_calls to functionCall parts
1007
+ // Convert tool_calls to functionCall parts. Each call's thought
1008
+ // signature is replayed verbatim. A turn with no signature at all (a
1009
+ // call made by another provider, or history that dropped it) gets the
1010
+ // documented placeholder on its first call, which is where Gemini puts
1011
+ // its own; without one Gemini 3 rejects the request with HTTP 400.
714
1012
  if (msg.tool_calls?.length) {
715
- for (const tc of msg.tool_calls) {
1013
+ const turnHasSignature = msg.tool_calls.some(tc => tc.thoughtSignature);
1014
+ msg.tool_calls.forEach((tc, i) => {
716
1015
  let parsedArgs;
717
1016
  try {
718
1017
  parsedArgs = typeof tc.function.arguments === 'string'
@@ -722,10 +1021,17 @@ export class GeminiProvider {
722
1021
  catch {
723
1022
  parsedArgs = {};
724
1023
  }
725
- parts.push({
1024
+ const part = {
726
1025
  functionCall: { name: tc.function.name, args: parsedArgs },
727
- });
728
- }
1026
+ };
1027
+ if (tc.thoughtSignature) {
1028
+ part.thoughtSignature = tc.thoughtSignature;
1029
+ }
1030
+ else if (!turnHasSignature && i === 0) {
1031
+ part.thoughtSignature = GEMINI_SKIP_THOUGHT_SIGNATURE;
1032
+ }
1033
+ parts.push(part);
1034
+ });
729
1035
  }
730
1036
  // Ensure at least one part — Gemini requires non-empty parts
731
1037
  if (parts.length === 0) {
@@ -830,8 +1136,10 @@ export class GeminiProvider {
830
1136
  const candidate = apiResponse.candidates?.[0];
831
1137
  const parts = candidate?.content?.parts ?? [];
832
1138
  // Collect text from all text parts
1139
+ // Thought-summary parts (present only with includeThoughts) are the
1140
+ // model's reasoning and stay out of the answer.
833
1141
  const textParts = parts
834
- .filter(p => p.text !== undefined)
1142
+ .filter(p => p.text !== undefined && !p.thought)
835
1143
  .map(p => p.text);
836
1144
  const fullText = textParts.join('');
837
1145
  // Collect function calls and convert to OpenAI-style tool_calls
@@ -844,6 +1152,7 @@ export class GeminiProvider {
844
1152
  name: p.functionCall.name,
845
1153
  arguments: JSON.stringify(p.functionCall.args ?? {}),
846
1154
  },
1155
+ ...(p.thoughtSignature ? { thoughtSignature: p.thoughtSignature } : {}),
847
1156
  }));
848
1157
  const hasToolCalls = toolCalls.length > 0;
849
1158
  const finishReason = this.mapFinishReason(candidate?.finishReason ?? null);
@@ -948,6 +1257,7 @@ export class GeminiProvider {
948
1257
  name: tc.name,
949
1258
  arguments: JSON.stringify(tc.args ?? {}),
950
1259
  },
1260
+ ...(tc.thoughtSignature ? { thoughtSignature: tc.thoughtSignature } : {}),
951
1261
  }));
952
1262
  }
953
1263
  /**
@@ -1010,7 +1320,8 @@ export class GeminiProvider {
1010
1320
  */
1011
1321
  async makeApiRequest(endpoint, body, requestTimeoutOverride) {
1012
1322
  // API key is passed as query parameter — Gemini's auth convention
1013
- const url = `${this.config.baseURL}${endpoint}?key=${this.keyPool?.hasKeys ? this.keyPool.next() : this.config.apiKey}`;
1323
+ const apiKey = this.keyPool?.hasKeys ? this.keyPool.next() : this.config.apiKey;
1324
+ const url = `${this.config.baseURL}${endpoint}?key=${apiKey}`;
1014
1325
  const headers = {
1015
1326
  'Content-Type': 'application/json',
1016
1327
  'User-Agent': 'AgentOS/1.0 (GeminiProvider)',
@@ -1070,7 +1381,9 @@ export class GeminiProvider {
1070
1381
  lastError = new GeminiProviderError(`Request timed out after ${effectiveTimeout}ms.`, 'REQUEST_TIMEOUT');
1071
1382
  }
1072
1383
  else {
1073
- lastError = new GeminiProviderError(error instanceof Error ? error.message : 'Network or unknown error', 'NETWORK_ERROR');
1384
+ lastError = new GeminiProviderError(error instanceof Error
1385
+ ? redactUrlSecrets(error.message, apiKey, this.config.baseURL)
1386
+ : 'Network or unknown error', 'NETWORK_ERROR');
1074
1387
  }
1075
1388
  if (attempt === this.config.maxRetries - 1)
1076
1389
  break;
@@ -1095,7 +1408,8 @@ export class GeminiProvider {
1095
1408
  */
1096
1409
  async makeStreamRequest(endpoint, body, requestTimeoutOverride) {
1097
1410
  // Both alt=sse and key= are query params
1098
- const url = `${this.config.baseURL}${endpoint}?alt=sse&key=${this.keyPool?.hasKeys ? this.keyPool.next() : this.config.apiKey}`;
1411
+ const apiKey = this.keyPool?.hasKeys ? this.keyPool.next() : this.config.apiKey;
1412
+ const url = `${this.config.baseURL}${endpoint}?alt=sse&key=${apiKey}`;
1099
1413
  const headers = {
1100
1414
  'Content-Type': 'application/json',
1101
1415
  'User-Agent': 'AgentOS/1.0 (GeminiProvider)',
@@ -1128,7 +1442,9 @@ export class GeminiProvider {
1128
1442
  clearTimeout(timeoutId);
1129
1443
  if (error instanceof GeminiProviderError)
1130
1444
  throw error;
1131
- throw new GeminiProviderError(error instanceof Error ? error.message : 'Failed to connect to Gemini stream.', 'STREAM_CONNECTION_FAILED');
1445
+ throw new GeminiProviderError(error instanceof Error
1446
+ ? redactUrlSecrets(error.message, apiKey, this.config.baseURL)
1447
+ : 'Failed to connect to Gemini stream.', 'STREAM_CONNECTION_FAILED');
1132
1448
  }
1133
1449
  }
1134
1450
  // -------------------------------------------------------------------------