@framers/agentos 0.10.21 → 0.10.23

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 (125) hide show
  1. package/README.md +1 -1
  2. package/dist/api/embedText.d.ts +9 -4
  3. package/dist/api/embedText.d.ts.map +1 -1
  4. package/dist/api/embedText.js +114 -8
  5. package/dist/api/embedText.js.map +1 -1
  6. package/dist/api/generateText.d.ts +14 -1
  7. package/dist/api/generateText.d.ts.map +1 -1
  8. package/dist/api/generateText.js +165 -20
  9. package/dist/api/generateText.js.map +1 -1
  10. package/dist/api/model.d.ts +25 -1
  11. package/dist/api/model.d.ts.map +1 -1
  12. package/dist/api/model.js +105 -3
  13. package/dist/api/model.js.map +1 -1
  14. package/dist/api/runtime/global-default.d.ts +9 -1
  15. package/dist/api/runtime/global-default.d.ts.map +1 -1
  16. package/dist/api/runtime/global-default.js.map +1 -1
  17. package/dist/api/runtime/provider-defaults.d.ts.map +1 -1
  18. package/dist/api/runtime/provider-defaults.js +11 -4
  19. package/dist/api/runtime/provider-defaults.js.map +1 -1
  20. package/dist/api/runtime/tool-emulation/history.d.ts +16 -0
  21. package/dist/api/runtime/tool-emulation/history.d.ts.map +1 -0
  22. package/dist/api/runtime/tool-emulation/history.js +89 -0
  23. package/dist/api/runtime/tool-emulation/history.js.map +1 -0
  24. package/dist/api/runtime/tool-emulation/index.d.ts +1 -0
  25. package/dist/api/runtime/tool-emulation/index.d.ts.map +1 -1
  26. package/dist/api/runtime/tool-emulation/index.js +1 -0
  27. package/dist/api/runtime/tool-emulation/index.js.map +1 -1
  28. package/dist/api/runtime/tool-emulation/loop.d.ts +6 -0
  29. package/dist/api/runtime/tool-emulation/loop.d.ts.map +1 -1
  30. package/dist/api/runtime/tool-emulation/loop.js +1 -0
  31. package/dist/api/runtime/tool-emulation/loop.js.map +1 -1
  32. package/dist/api/sessionTranscript.d.ts +40 -0
  33. package/dist/api/sessionTranscript.d.ts.map +1 -1
  34. package/dist/api/sessionTranscript.js +61 -1
  35. package/dist/api/sessionTranscript.js.map +1 -1
  36. package/dist/api/streamText.d.ts.map +1 -1
  37. package/dist/api/streamText.js +28 -7
  38. package/dist/api/streamText.js.map +1 -1
  39. package/dist/cognition/substrate/ConversationHistoryManager.d.ts.map +1 -1
  40. package/dist/cognition/substrate/ConversationHistoryManager.js +2 -0
  41. package/dist/cognition/substrate/ConversationHistoryManager.js.map +1 -1
  42. package/dist/cognition/substrate/GMI.d.ts.map +1 -1
  43. package/dist/cognition/substrate/GMI.js +3 -1
  44. package/dist/cognition/substrate/GMI.js.map +1 -1
  45. package/dist/cognition/substrate/IGMI.d.ts +5 -0
  46. package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
  47. package/dist/cognition/substrate/IGMI.js.map +1 -1
  48. package/dist/core/llm/PromptEngine.d.ts.map +1 -1
  49. package/dist/core/llm/PromptEngine.js +1 -0
  50. package/dist/core/llm/PromptEngine.js.map +1 -1
  51. package/dist/core/llm/providers/AIModelProviderManager.d.ts +20 -0
  52. package/dist/core/llm/providers/AIModelProviderManager.d.ts.map +1 -1
  53. package/dist/core/llm/providers/AIModelProviderManager.js +35 -5
  54. package/dist/core/llm/providers/AIModelProviderManager.js.map +1 -1
  55. package/dist/core/llm/providers/IProvider.d.ts +9 -0
  56. package/dist/core/llm/providers/IProvider.d.ts.map +1 -1
  57. package/dist/core/llm/providers/errors/GeminiProviderError.d.ts +11 -0
  58. package/dist/core/llm/providers/errors/GeminiProviderError.d.ts.map +1 -1
  59. package/dist/core/llm/providers/errors/GeminiProviderError.js.map +1 -1
  60. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts +27 -6
  61. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts.map +1 -1
  62. package/dist/core/llm/providers/implementations/AnthropicProvider.js +107 -19
  63. package/dist/core/llm/providers/implementations/AnthropicProvider.js.map +1 -1
  64. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts +1 -1
  65. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts.map +1 -1
  66. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js +70 -7
  67. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js.map +1 -1
  68. package/dist/core/llm/providers/implementations/GeminiCLIBridge.js +1 -1
  69. package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts.map +1 -1
  70. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js +56 -4
  71. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js.map +1 -1
  72. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts +20 -4
  73. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts.map +1 -1
  74. package/dist/core/llm/providers/implementations/GeminiProvider.js +352 -56
  75. package/dist/core/llm/providers/implementations/GeminiProvider.js.map +1 -1
  76. package/dist/core/llm/providers/implementations/MistralProvider.d.ts +11 -0
  77. package/dist/core/llm/providers/implementations/MistralProvider.d.ts.map +1 -1
  78. package/dist/core/llm/providers/implementations/MistralProvider.js +32 -2
  79. package/dist/core/llm/providers/implementations/MistralProvider.js.map +1 -1
  80. package/dist/core/llm/providers/implementations/OllamaProvider.js +7 -7
  81. package/dist/core/llm/providers/implementations/OllamaProvider.js.map +1 -1
  82. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts +61 -0
  83. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts.map +1 -1
  84. package/dist/core/llm/providers/implementations/OpenAIProvider.js +171 -29
  85. package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
  86. package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
  87. package/dist/core/llm/providers/implementations/OpenRouterProvider.js +14 -5
  88. package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
  89. package/dist/core/llm/providers/implementations/TogetherProvider.d.ts +3 -3
  90. package/dist/core/llm/providers/implementations/TogetherProvider.d.ts.map +1 -1
  91. package/dist/core/llm/providers/implementations/TogetherProvider.js +25 -13
  92. package/dist/core/llm/providers/implementations/TogetherProvider.js.map +1 -1
  93. package/dist/core/llm/providers/model-forced-tool-choice.d.ts +32 -15
  94. package/dist/core/llm/providers/model-forced-tool-choice.d.ts.map +1 -1
  95. package/dist/core/llm/providers/model-forced-tool-choice.js +33 -16
  96. package/dist/core/llm/providers/model-forced-tool-choice.js.map +1 -1
  97. package/dist/core/llm/providers/openrouter-only-params.d.ts +26 -7
  98. package/dist/core/llm/providers/openrouter-only-params.d.ts.map +1 -1
  99. package/dist/core/llm/providers/openrouter-only-params.js +58 -9
  100. package/dist/core/llm/providers/openrouter-only-params.js.map +1 -1
  101. package/dist/core/llm/providers/url-secrets.d.ts +42 -0
  102. package/dist/core/llm/providers/url-secrets.d.ts.map +1 -0
  103. package/dist/core/llm/providers/url-secrets.js +64 -0
  104. package/dist/core/llm/providers/url-secrets.js.map +1 -0
  105. package/dist/core/llm/streaming/StreamingBatcher.d.ts.map +1 -1
  106. package/dist/core/llm/streaming/StreamingBatcher.js +3 -0
  107. package/dist/core/llm/streaming/StreamingBatcher.js.map +1 -1
  108. package/dist/core/llm/streaming/StreamingReconstructor.d.ts +2 -0
  109. package/dist/core/llm/streaming/StreamingReconstructor.d.ts.map +1 -1
  110. package/dist/core/llm/streaming/StreamingReconstructor.js +12 -1
  111. package/dist/core/llm/streaming/StreamingReconstructor.js.map +1 -1
  112. package/dist/index.d.ts +1 -1
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +1 -1
  115. package/dist/index.js.map +1 -1
  116. package/dist/io/vision/providers/LLMVisionProvider.d.ts +2 -2
  117. package/dist/io/vision/providers/LLMVisionProvider.js +1 -1
  118. package/dist/io/vision/types.d.ts +1 -1
  119. package/dist/orchestration/pipeline/query/types.d.ts.map +1 -1
  120. package/dist/orchestration/pipeline/query/types.js +5 -4
  121. package/dist/orchestration/pipeline/query/types.js.map +1 -1
  122. package/dist/orchestration/planning/ProviderAssignmentEngine.d.ts.map +1 -1
  123. package/dist/orchestration/planning/ProviderAssignmentEngine.js +4 -3
  124. package/dist/orchestration/planning/ProviderAssignmentEngine.js.map +1 -1
  125. package/package.json +1 -1
@@ -1,9 +1,24 @@
1
1
  // File: backend/agentos/core/llm/providers/implementations/GeminiProvider.ts
2
2
  import { stripOpenRouterOnlyParams } from '../openrouter-only-params.js';
3
+ import { redactUrlSecrets } from '../url-secrets.js';
3
4
  import { GeminiProviderError } from '../errors/GeminiProviderError.js';
4
5
  import { ApiKeyPool } from '../../../providers/ApiKeyPool.js';
5
6
  import { computeRetryBackoffMs } from './retry-backoff.js';
6
7
  import { sanitizeGeminiResponseSchema } from './geminiResponseSchema.js';
8
+ /**
9
+ * Most requests `models/{model}:batchEmbedContents` accepts in one call. The
10
+ * API returns HTTP 400 "at most 100 requests can be in one batch" above this
11
+ * (probed 2026-09-29 on gemini-embedding-001).
12
+ */
13
+ const GEMINI_EMBED_BATCH_LIMIT = 100;
14
+ /**
15
+ * Placeholder signature Google documents for replaying a function call whose
16
+ * real signature is unavailable, such as a call made by another provider in a
17
+ * fallback chain. Gemini 3 returns HTTP 400 for a replayed call with no
18
+ * signature and accepts this value, and Gemini 2.5 accepts it too (probed
19
+ * 2026-09-29).
20
+ */
21
+ const GEMINI_SKIP_THOUGHT_SIGNATURE = 'skip_thought_signature_validator';
7
22
  /**
8
23
  * Thinking levels each model accepts, keyed by exact model id and limited to
9
24
  * what was verified against the live API (2026-09-30). The set differs per
@@ -48,12 +63,30 @@ const warnedRetiredGeminiModels = new Set();
48
63
  const GEMINI_LONG_CONTEXT_PRICING = {
49
64
  'gemini-3.1-pro-preview': { abovePromptTokens: 200000, input: 4.00, output: 18.00 },
50
65
  'gemini-pro-latest': { abovePromptTokens: 200000, input: 4.00, output: 18.00 },
66
+ 'gemini-2.5-pro': { abovePromptTokens: 200000, input: 2.50, output: 15.00 },
51
67
  };
52
68
  // ---------------------------------------------------------------------------
53
69
  // Known model catalog
54
70
  // ---------------------------------------------------------------------------
55
- /** Static catalog of well-known Gemini models and their metadata. */
71
+ /**
72
+ * Static catalog of well-known Gemini models and their metadata.
73
+ *
74
+ * Ids and token limits come from a live GET /v1beta/models probe and rates from
75
+ * ai.google.dev/gemini-api/docs/pricing, both on 2026-09-29. `getModelInfo` is
76
+ * an exact-id lookup over this array, so a model missing here resolves to
77
+ * undefined and its calls carry no context limit and no price.
78
+ *
79
+ * Tiers advance independently. Flash has reached 3.8 while Pro is at
80
+ * 3.1-preview, the only Pro id served. `gemini-3-pro-preview` appears in
81
+ * Google's doc index and is absent from the live listing.
82
+ *
83
+ * Rates are the standard tier. Prompts over 200K tokens on 3.1 Pro and 2.5
84
+ * Pro bill at the tier in GEMINI_LONG_CONTEXT_PRICING, and Gemini 3.6 / 3.7 /
85
+ * 3.8 Flash list at promotional rates through 2026-12-31 and meter at the
86
+ * post-promotion rate.
87
+ */
56
88
  const GEMINI_MODELS = [
89
+ // --- Pro ---
57
90
  {
58
91
  modelId: 'gemini-3.1-pro-preview',
59
92
  providerId: 'gemini',
@@ -68,6 +101,107 @@ const GEMINI_MODELS = [
68
101
  supportsStreaming: true,
69
102
  status: 'active',
70
103
  },
104
+ // --- Flash. 3.6, 3.7 and 3.8 list at $0.75 / $3.75 through 2026-12-31 and
105
+ // $1.50 / $7.50 from 2027-01-01. They meter at $1.50 / $7.50, as
106
+ // claude-sonnet-5 meters at its sticker rate in the Anthropic catalog, so
107
+ // cost rollups stay conservative and stay right after the promotion. ---
108
+ {
109
+ modelId: 'gemini-3.8-flash',
110
+ providerId: 'gemini',
111
+ displayName: 'Gemini 3.8 Flash',
112
+ description: 'Newest Flash model. Fast and low-cost, with reasoning and multimodal input.',
113
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
114
+ contextWindowSize: 1048576,
115
+ outputTokenLimit: 65536,
116
+ pricePer1MTokensInput: 1.50,
117
+ pricePer1MTokensOutput: 7.50,
118
+ supportsStreaming: true,
119
+ status: 'active',
120
+ },
121
+ {
122
+ modelId: 'gemini-3.7-flash',
123
+ providerId: 'gemini',
124
+ displayName: 'Gemini 3.7 Flash',
125
+ description: 'Flash model with reasoning and multimodal input.',
126
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
127
+ contextWindowSize: 1048576,
128
+ outputTokenLimit: 65536,
129
+ pricePer1MTokensInput: 1.50,
130
+ pricePer1MTokensOutput: 7.50,
131
+ supportsStreaming: true,
132
+ status: 'active',
133
+ },
134
+ {
135
+ modelId: 'gemini-3.6-flash',
136
+ providerId: 'gemini',
137
+ displayName: 'Gemini 3.6 Flash',
138
+ description: 'Flash model with reasoning and multimodal input.',
139
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
140
+ contextWindowSize: 1048576,
141
+ outputTokenLimit: 65536,
142
+ pricePer1MTokensInput: 1.50,
143
+ pricePer1MTokensOutput: 7.50,
144
+ supportsStreaming: true,
145
+ status: 'active',
146
+ },
147
+ {
148
+ modelId: 'gemini-3.5-flash',
149
+ providerId: 'gemini',
150
+ displayName: 'Gemini 3.5 Flash',
151
+ description: 'Flash model with reasoning and multimodal input, at standard pricing.',
152
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
153
+ contextWindowSize: 1048576,
154
+ outputTokenLimit: 65536,
155
+ pricePer1MTokensInput: 1.50,
156
+ pricePer1MTokensOutput: 9.00,
157
+ supportsStreaming: true,
158
+ status: 'active',
159
+ },
160
+ {
161
+ modelId: 'gemini-3-flash-preview',
162
+ providerId: 'gemini',
163
+ displayName: 'Gemini 3 Flash Preview',
164
+ description: 'First Gemini 3 Flash preview.',
165
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
166
+ contextWindowSize: 1048576,
167
+ outputTokenLimit: 65536,
168
+ pricePer1MTokensInput: 0.50,
169
+ pricePer1MTokensOutput: 3.00,
170
+ supportsStreaming: true,
171
+ status: 'active',
172
+ },
173
+ // --- Flash-Lite ---
174
+ {
175
+ modelId: 'gemini-3.5-flash-lite',
176
+ providerId: 'gemini',
177
+ displayName: 'Gemini 3.5 Flash-Lite',
178
+ description: 'Lowest-cost Gemini 3.5 tier, for high-volume and latency-sensitive work.',
179
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
180
+ contextWindowSize: 1048576,
181
+ outputTokenLimit: 65536,
182
+ pricePer1MTokensInput: 0.30,
183
+ pricePer1MTokensOutput: 2.50,
184
+ supportsStreaming: true,
185
+ status: 'active',
186
+ },
187
+ {
188
+ modelId: 'gemini-3.1-flash-lite',
189
+ providerId: 'gemini',
190
+ displayName: 'Gemini 3.1 Flash-Lite',
191
+ description: 'Lowest-cost Gemini 3.1 tier. Google lists a shutdown date of 2027-05-07.',
192
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
193
+ contextWindowSize: 1048576,
194
+ outputTokenLimit: 65536,
195
+ pricePer1MTokensInput: 0.25,
196
+ pricePer1MTokensOutput: 1.50,
197
+ supportsStreaming: true,
198
+ status: 'active',
199
+ },
200
+ // --- Floating aliases. Google can repoint these without changing the id, so
201
+ // a price here can go stale unnoticed. They are listed because an unlisted id
202
+ // resolves to undefined and meters at zero. generateContent reported these
203
+ // targets on 2026-09-29 (response modelVersion): pro-latest to 3.1 Pro,
204
+ // flash-latest to 3.8 Flash, flash-lite-latest to 3.5 Flash-Lite. ---
71
205
  {
72
206
  modelId: 'gemini-pro-latest',
73
207
  providerId: 'gemini',
@@ -83,23 +217,37 @@ const GEMINI_MODELS = [
83
217
  status: 'active',
84
218
  },
85
219
  {
86
- modelId: 'gemini-2.5-flash',
220
+ modelId: 'gemini-flash-latest',
87
221
  providerId: 'gemini',
88
- displayName: 'Gemini 2.5 Flash',
89
- description: 'Fast, cost-effective model with strong reasoning and multimodal capabilities.',
222
+ displayName: 'Gemini Flash (latest)',
223
+ description: 'Floating alias for the current Gemini Flash tier.',
90
224
  capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
91
225
  contextWindowSize: 1048576,
92
226
  outputTokenLimit: 65536,
93
- pricePer1MTokensInput: 0.15,
94
- pricePer1MTokensOutput: 0.60,
227
+ pricePer1MTokensInput: 1.50,
228
+ pricePer1MTokensOutput: 7.50,
95
229
  supportsStreaming: true,
96
230
  status: 'active',
97
231
  },
232
+ {
233
+ modelId: 'gemini-flash-lite-latest',
234
+ providerId: 'gemini',
235
+ displayName: 'Gemini Flash-Lite (latest)',
236
+ description: 'Floating alias for the current Gemini Flash-Lite tier.',
237
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
238
+ contextWindowSize: 1048576,
239
+ outputTokenLimit: 65536,
240
+ pricePer1MTokensInput: 0.30,
241
+ pricePer1MTokensOutput: 2.50,
242
+ supportsStreaming: true,
243
+ status: 'active',
244
+ },
245
+ // --- Gemini 2.5 ---
98
246
  {
99
247
  modelId: 'gemini-2.5-pro',
100
248
  providerId: 'gemini',
101
249
  displayName: 'Gemini 2.5 Pro',
102
- description: 'Most capable Gemini model for complex reasoning and analysis.',
250
+ description: 'Previous-generation Pro model.',
103
251
  capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
104
252
  contextWindowSize: 1048576,
105
253
  outputTokenLimit: 65536,
@@ -108,31 +256,93 @@ const GEMINI_MODELS = [
108
256
  supportsStreaming: true,
109
257
  status: 'active',
110
258
  },
259
+ {
260
+ modelId: 'gemini-2.5-flash',
261
+ providerId: 'gemini',
262
+ displayName: 'Gemini 2.5 Flash',
263
+ description: 'Previous-generation Flash model with reasoning and multimodal input.',
264
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
265
+ contextWindowSize: 1048576,
266
+ outputTokenLimit: 65536,
267
+ pricePer1MTokensInput: 0.30,
268
+ pricePer1MTokensOutput: 2.50,
269
+ supportsStreaming: true,
270
+ status: 'active',
271
+ },
272
+ {
273
+ modelId: 'gemini-2.5-flash-lite',
274
+ providerId: 'gemini',
275
+ displayName: 'Gemini 2.5 Flash-Lite',
276
+ description: 'Cheapest Gemini model served.',
277
+ capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
278
+ contextWindowSize: 1048576,
279
+ outputTokenLimit: 65536,
280
+ pricePer1MTokensInput: 0.10,
281
+ pricePer1MTokensOutput: 0.40,
282
+ supportsStreaming: true,
283
+ status: 'active',
284
+ },
285
+ // --- Embeddings. Both ids accept embedContent and batchEmbedContents and
286
+ // return 3072-dimension vectors (probed 2026-09-29). Google's pricing page
287
+ // lists gemini-embedding-2 at $0.20 per 1M text tokens and carries no price
288
+ // for gemini-embedding-001, so that row leaves its price unset: an unset
289
+ // price reads as unknown to a caller, where 0 would read as free. ---
290
+ {
291
+ modelId: 'gemini-embedding-2',
292
+ providerId: 'gemini',
293
+ displayName: 'Gemini Embedding 2',
294
+ description: 'Multimodal embedding model mapping text, images, video, audio and PDFs into one vector space.',
295
+ capabilities: ['embeddings'],
296
+ contextWindowSize: 8192,
297
+ inputTokenLimit: 8192,
298
+ embeddingDimension: 3072,
299
+ pricePer1MTokensInput: 0.20,
300
+ pricePer1MTokensTotal: 0.20,
301
+ supportsStreaming: false,
302
+ status: 'active',
303
+ },
304
+ {
305
+ modelId: 'gemini-embedding-001',
306
+ providerId: 'gemini',
307
+ displayName: 'Gemini Embedding 001',
308
+ description: 'Text embedding model. An index built with it needs it for queries too, since vectors from different embedding models are not comparable.',
309
+ capabilities: ['embeddings'],
310
+ contextWindowSize: 2048,
311
+ inputTokenLimit: 2048,
312
+ embeddingDimension: 3072,
313
+ supportsStreaming: false,
314
+ status: 'active',
315
+ },
316
+ // --- Retired by Google. generateContent returns HTTP 404 for both ids
317
+ // ("This model models/gemini-2.0-flash is no longer available" for 2.0
318
+ // Flash), probed 2026-09-29 with a gemini-2.5-flash-lite control returning
319
+ // 200. The rows stay as `deprecated` so getModelInfo still answers for a
320
+ // caller holding either id. ---
111
321
  {
112
322
  modelId: 'gemini-2.0-flash',
113
323
  providerId: 'gemini',
114
- displayName: 'Gemini 2.0 Flash',
115
- description: 'Previous-generation fast model with strong performance.',
324
+ displayName: 'Gemini 2.0 Flash (retired)',
325
+ description: 'Retired by Google and no longer served. Google names gemini-3.8-flash as its replacement.',
116
326
  capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
117
327
  contextWindowSize: 1048576,
118
328
  outputTokenLimit: 8192,
119
329
  pricePer1MTokensInput: 0.10,
120
330
  pricePer1MTokensOutput: 0.40,
121
331
  supportsStreaming: true,
122
- status: 'active',
332
+ status: 'deprecated',
123
333
  },
124
334
  {
125
335
  modelId: 'gemini-1.5-pro',
126
336
  providerId: 'gemini',
127
- displayName: 'Gemini 1.5 Pro',
128
- description: 'Stable model with 2M context window for long-document tasks.',
337
+ displayName: 'Gemini 1.5 Pro (retired)',
338
+ description: 'Retired by Google and no longer served. Use gemini-3.1-pro-preview.',
129
339
  capabilities: ['chat', 'tool_use', 'vision_input', 'json_mode'],
130
340
  contextWindowSize: 2097152,
131
341
  outputTokenLimit: 8192,
132
342
  pricePer1MTokensInput: 1.25,
133
343
  pricePer1MTokensOutput: 5.00,
134
344
  supportsStreaming: true,
135
- status: 'active',
345
+ status: 'deprecated',
136
346
  },
137
347
  ];
138
348
  // ---------------------------------------------------------------------------
@@ -355,6 +565,9 @@ export class GeminiProvider {
355
565
  }
356
566
  const parts = candidate.content?.parts ?? [];
357
567
  for (const part of parts) {
568
+ // A thought summary is the model's reasoning; it stays out of the answer.
569
+ if (part.thought)
570
+ continue;
358
571
  if (part.text !== undefined) {
359
572
  // Text delta
360
573
  accumulatedContent += part.text;
@@ -378,6 +591,7 @@ export class GeminiProvider {
378
591
  toolCallAccum.set(idx, {
379
592
  name: part.functionCall.name,
380
593
  args: part.functionCall.args,
594
+ thoughtSignature: part.thoughtSignature,
381
595
  });
382
596
  yield {
383
597
  id: responseId,
@@ -398,6 +612,7 @@ export class GeminiProvider {
398
612
  // Gemini delivers complete args, so emit them as a single delta
399
613
  arguments_delta: JSON.stringify(part.functionCall.args),
400
614
  },
615
+ ...(part.thoughtSignature ? { thoughtSignature: part.thoughtSignature } : {}),
401
616
  }],
402
617
  };
403
618
  }
@@ -462,10 +677,20 @@ export class GeminiProvider {
462
677
  /**
463
678
  * Generates embeddings using Gemini's embedding models.
464
679
  *
465
- * Uses the `models/{model}:embedContent` endpoint. Currently Gemini
466
- * supports embedding one text at a time, so we batch sequentially.
680
+ * Sends the texts through `models/{model}:batchEmbedContents` in chunks of
681
+ * at most {@link GEMINI_EMBED_BATCH_LIMIT}, one request per chunk, and
682
+ * returns the vectors in input order. gemini-embedding-001 and
683
+ * gemini-embedding-2 both accept batchEmbedContents (probed 2026-09-29),
684
+ * although neither lists it in `supportedGenerationMethods`.
685
+ * `options.dimensions` is sent as `outputDimensionality`, which both models
686
+ * honor (768 and 1536 probed 2026-09-29); without it they return 3072.
687
+ * Usage sums each call's `usageMetadata.promptTokenCount`, which
688
+ * gemini-embedding-2 reports and gemini-embedding-001 omits, so the older
689
+ * model's calls count zero tokens (probed 2026-09-30). When a later batch
690
+ * fails, the thrown error carries the earlier batches' usage as
691
+ * `partialUsage`, since those calls were billed.
467
692
  *
468
- * @param {string} modelId - Embedding model (e.g., "text-embedding-004").
693
+ * @param {string} modelId - Embedding model (e.g., "gemini-embedding-001").
469
694
  * @param {string[]} texts - Input texts to embed.
470
695
  * @param {ProviderEmbeddingOptions} [options] - Optional embedding parameters.
471
696
  * @returns {Promise<ProviderEmbeddingResponse>} Embedding vectors.
@@ -473,25 +698,46 @@ export class GeminiProvider {
473
698
  */
474
699
  async generateEmbeddings(modelId, texts, options) {
475
700
  this.ensureInitialized();
476
- // Gemini's batch embedding endpoint
477
701
  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 });
702
+ const pricePer1M = GEMINI_MODELS.find(m => m.modelId === modelId)?.pricePer1MTokensInput;
703
+ const usageFor = (tokens) => ({
704
+ prompt_tokens: tokens,
705
+ total_tokens: tokens,
706
+ ...(pricePer1M !== undefined && tokens > 0 ? { costUSD: (tokens / 1000000) * pricePer1M } : {}),
707
+ });
708
+ const vectors = [];
709
+ let promptTokens = 0;
710
+ for (let start = 0; start < texts.length; start += GEMINI_EMBED_BATCH_LIMIT) {
711
+ const requests = texts.slice(start, start + GEMINI_EMBED_BATCH_LIMIT).map(text => ({
712
+ model: `models/${modelId}`,
713
+ content: { parts: [{ text }] },
714
+ ...(options?.dimensions ? { outputDimensionality: options.dimensions } : {}),
715
+ }));
716
+ let apiResponse;
717
+ try {
718
+ apiResponse = await this.makeApiRequest(endpoint, { requests });
719
+ }
720
+ catch (error) {
721
+ // The batches before this one were billed; keep their usage on the
722
+ // error so the caller can still record it.
723
+ if (start > 0 && error instanceof GeminiProviderError) {
724
+ error.partialUsage = usageFor(promptTokens);
725
+ }
726
+ throw error;
727
+ }
728
+ for (const emb of apiResponse.embeddings)
729
+ vectors.push(emb.values);
730
+ promptTokens += apiResponse.usageMetadata?.promptTokenCount ?? 0;
731
+ }
483
732
  return {
484
733
  object: 'list',
485
- data: apiResponse.embeddings.map((emb, index) => ({
734
+ data: vectors.map((embedding, index) => ({
486
735
  object: 'embedding',
487
- embedding: emb.values,
736
+ embedding,
488
737
  index,
489
738
  })),
490
739
  model: modelId,
491
- usage: {
492
- prompt_tokens: 0, // Gemini does not report embedding token counts
493
- total_tokens: 0,
494
- },
740
+ usage: usageFor(promptTokens),
495
741
  };
496
742
  }
497
743
  // -------------------------------------------------------------------------
@@ -656,7 +902,9 @@ export class GeminiProvider {
656
902
  // otherwise the provider-neutral `effort` becomes the model's thinking
657
903
  // level when the model is known to take that level. Thinking shares the
658
904
  // maxOutputTokens cap: room for it on a rescue hop comes from
659
- // FallbackProviderEntry.maxTokensHeadroom, not from this provider.
905
+ // FallbackProviderEntry.maxTokensHeadroom, not from this provider. The
906
+ // shared `thinking` option is Anthropic's on-switch and is not translated:
907
+ // on a fallback hop it would turn "thinking on" into a thinking-token cap.
660
908
  const customThinking = options.customModelParams?.thinkingConfig;
661
909
  if (customThinking && typeof customThinking === 'object') {
662
910
  generationConfig.thinkingConfig = customThinking;
@@ -696,12 +944,19 @@ export class GeminiProvider {
696
944
  * - `assistant` -> `model` (Gemini uses "model" instead of "assistant")
697
945
  * - `tool` -> `user` with `functionResponse` parts
698
946
  *
947
+ * Consecutive tool results share one `user` turn, because Gemini requires
948
+ * the responses to a turn of parallel function calls in a single content
949
+ * (one functionResponse part per call). A tool result without `name`, as
950
+ * `generateText` records them, takes the function name of the call with the
951
+ * same id.
952
+ *
699
953
  * @param {ChatMessage[]} messages - IProvider-format messages.
700
954
  * @returns {GeminiContent[]} Gemini-format content array.
701
955
  * @private
702
956
  */
703
957
  convertMessages(messages) {
704
958
  const contents = [];
959
+ const toolNameByCallId = new Map();
705
960
  for (const msg of messages) {
706
961
  if (msg.role === 'assistant') {
707
962
  // --- Assistant messages map to "model" role ---
@@ -710,9 +965,16 @@ export class GeminiProvider {
710
965
  if (typeof msg.content === 'string' && msg.content) {
711
966
  parts.push({ text: msg.content });
712
967
  }
713
- // Convert tool_calls to functionCall parts
968
+ // Convert tool_calls to functionCall parts. Each call's thought
969
+ // signature is replayed verbatim. A turn with no signature at all (a
970
+ // call made by another provider, or history that dropped it) gets the
971
+ // documented placeholder on its first call, which is where Gemini puts
972
+ // its own; without one Gemini 3 rejects the request with HTTP 400.
714
973
  if (msg.tool_calls?.length) {
715
- for (const tc of msg.tool_calls) {
974
+ const turnHasSignature = msg.tool_calls.some(tc => tc.thoughtSignature);
975
+ msg.tool_calls.forEach((tc, i) => {
976
+ if (tc.id)
977
+ toolNameByCallId.set(tc.id, tc.function.name);
716
978
  let parsedArgs;
717
979
  try {
718
980
  parsedArgs = typeof tc.function.arguments === 'string'
@@ -722,10 +984,17 @@ export class GeminiProvider {
722
984
  catch {
723
985
  parsedArgs = {};
724
986
  }
725
- parts.push({
987
+ const part = {
726
988
  functionCall: { name: tc.function.name, args: parsedArgs },
727
- });
728
- }
989
+ };
990
+ if (tc.thoughtSignature) {
991
+ part.thoughtSignature = tc.thoughtSignature;
992
+ }
993
+ else if (!turnHasSignature && i === 0) {
994
+ part.thoughtSignature = GEMINI_SKIP_THOUGHT_SIGNATURE;
995
+ }
996
+ parts.push(part);
997
+ });
729
998
  }
730
999
  // Ensure at least one part — Gemini requires non-empty parts
731
1000
  if (parts.length === 0) {
@@ -737,25 +1006,42 @@ export class GeminiProvider {
737
1006
  // --- Tool result messages become user-role functionResponse ---
738
1007
  // Gemini expects tool results as functionResponse parts in a user turn.
739
1008
  let responseData;
740
- try {
741
- const raw = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content ?? '');
742
- responseData = typeof msg.content === 'string'
743
- ? JSON.parse(raw)
744
- : { result: raw };
1009
+ if (typeof msg.content === 'string') {
1010
+ let parsed;
1011
+ try {
1012
+ parsed = JSON.parse(msg.content);
1013
+ }
1014
+ catch {
1015
+ // If the tool result isn't valid JSON, wrap it
1016
+ parsed = msg.content;
1017
+ }
1018
+ // functionResponse.response is a JSON object (a protobuf Struct);
1019
+ // Gemini rejects a string, number, array or null there with HTTP
1020
+ // 400, so such a result is wrapped.
1021
+ responseData = parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
1022
+ ? parsed
1023
+ : { result: parsed };
745
1024
  }
746
- catch {
747
- // If the tool result isn't valid JSON, wrap it
748
- responseData = { result: typeof msg.content === 'string' ? msg.content : String(msg.content) };
1025
+ else {
1026
+ responseData = { result: JSON.stringify(msg.content ?? '') };
1027
+ }
1028
+ const responsePart = {
1029
+ functionResponse: {
1030
+ name: msg.name
1031
+ || (msg.tool_call_id ? toolNameByCallId.get(msg.tool_call_id) : undefined)
1032
+ || 'unknown',
1033
+ response: responseData,
1034
+ },
1035
+ };
1036
+ const previous = contents[contents.length - 1];
1037
+ if (previous?.role === 'user'
1038
+ && previous.parts.length > 0
1039
+ && previous.parts.every(part => part.functionResponse)) {
1040
+ previous.parts.push(responsePart);
1041
+ }
1042
+ else {
1043
+ contents.push({ role: 'user', parts: [responsePart] });
749
1044
  }
750
- contents.push({
751
- role: 'user',
752
- parts: [{
753
- functionResponse: {
754
- name: msg.name || 'unknown',
755
- response: responseData,
756
- },
757
- }],
758
- });
759
1045
  }
760
1046
  else {
761
1047
  // --- User messages ---
@@ -830,8 +1116,10 @@ export class GeminiProvider {
830
1116
  const candidate = apiResponse.candidates?.[0];
831
1117
  const parts = candidate?.content?.parts ?? [];
832
1118
  // Collect text from all text parts
1119
+ // Thought-summary parts (present only with includeThoughts) are the
1120
+ // model's reasoning and stay out of the answer.
833
1121
  const textParts = parts
834
- .filter(p => p.text !== undefined)
1122
+ .filter(p => p.text !== undefined && !p.thought)
835
1123
  .map(p => p.text);
836
1124
  const fullText = textParts.join('');
837
1125
  // Collect function calls and convert to OpenAI-style tool_calls
@@ -844,6 +1132,7 @@ export class GeminiProvider {
844
1132
  name: p.functionCall.name,
845
1133
  arguments: JSON.stringify(p.functionCall.args ?? {}),
846
1134
  },
1135
+ ...(p.thoughtSignature ? { thoughtSignature: p.thoughtSignature } : {}),
847
1136
  }));
848
1137
  const hasToolCalls = toolCalls.length > 0;
849
1138
  const finishReason = this.mapFinishReason(candidate?.finishReason ?? null);
@@ -948,6 +1237,7 @@ export class GeminiProvider {
948
1237
  name: tc.name,
949
1238
  arguments: JSON.stringify(tc.args ?? {}),
950
1239
  },
1240
+ ...(tc.thoughtSignature ? { thoughtSignature: tc.thoughtSignature } : {}),
951
1241
  }));
952
1242
  }
953
1243
  /**
@@ -1010,7 +1300,8 @@ export class GeminiProvider {
1010
1300
  */
1011
1301
  async makeApiRequest(endpoint, body, requestTimeoutOverride) {
1012
1302
  // 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}`;
1303
+ const apiKey = this.keyPool?.hasKeys ? this.keyPool.next() : this.config.apiKey;
1304
+ const url = `${this.config.baseURL}${endpoint}?key=${apiKey}`;
1014
1305
  const headers = {
1015
1306
  'Content-Type': 'application/json',
1016
1307
  'User-Agent': 'AgentOS/1.0 (GeminiProvider)',
@@ -1070,7 +1361,9 @@ export class GeminiProvider {
1070
1361
  lastError = new GeminiProviderError(`Request timed out after ${effectiveTimeout}ms.`, 'REQUEST_TIMEOUT');
1071
1362
  }
1072
1363
  else {
1073
- lastError = new GeminiProviderError(error instanceof Error ? error.message : 'Network or unknown error', 'NETWORK_ERROR');
1364
+ lastError = new GeminiProviderError(error instanceof Error
1365
+ ? redactUrlSecrets(error.message, this.config.baseURL, [apiKey])
1366
+ : 'Network or unknown error', 'NETWORK_ERROR');
1074
1367
  }
1075
1368
  if (attempt === this.config.maxRetries - 1)
1076
1369
  break;
@@ -1095,7 +1388,8 @@ export class GeminiProvider {
1095
1388
  */
1096
1389
  async makeStreamRequest(endpoint, body, requestTimeoutOverride) {
1097
1390
  // 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}`;
1391
+ const apiKey = this.keyPool?.hasKeys ? this.keyPool.next() : this.config.apiKey;
1392
+ const url = `${this.config.baseURL}${endpoint}?alt=sse&key=${apiKey}`;
1099
1393
  const headers = {
1100
1394
  'Content-Type': 'application/json',
1101
1395
  'User-Agent': 'AgentOS/1.0 (GeminiProvider)',
@@ -1128,7 +1422,9 @@ export class GeminiProvider {
1128
1422
  clearTimeout(timeoutId);
1129
1423
  if (error instanceof GeminiProviderError)
1130
1424
  throw error;
1131
- throw new GeminiProviderError(error instanceof Error ? error.message : 'Failed to connect to Gemini stream.', 'STREAM_CONNECTION_FAILED');
1425
+ throw new GeminiProviderError(error instanceof Error
1426
+ ? redactUrlSecrets(error.message, this.config.baseURL, [apiKey])
1427
+ : 'Failed to connect to Gemini stream.', 'STREAM_CONNECTION_FAILED');
1132
1428
  }
1133
1429
  }
1134
1430
  // -------------------------------------------------------------------------