@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.
- package/dist/api/embedText.d.ts +9 -4
- package/dist/api/embedText.d.ts.map +1 -1
- package/dist/api/embedText.js +114 -8
- package/dist/api/embedText.js.map +1 -1
- package/dist/api/runtime/provider-defaults.d.ts.map +1 -1
- package/dist/api/runtime/provider-defaults.js +6 -2
- package/dist/api/runtime/provider-defaults.js.map +1 -1
- package/dist/api/sessionTranscript.d.ts +6 -0
- package/dist/api/sessionTranscript.d.ts.map +1 -1
- package/dist/api/sessionTranscript.js.map +1 -1
- package/dist/api/streamText.d.ts.map +1 -1
- package/dist/api/streamText.js +1 -0
- package/dist/api/streamText.js.map +1 -1
- package/dist/cognition/substrate/ConversationHistoryManager.d.ts.map +1 -1
- package/dist/cognition/substrate/ConversationHistoryManager.js +2 -0
- package/dist/cognition/substrate/ConversationHistoryManager.js.map +1 -1
- package/dist/cognition/substrate/GMI.d.ts.map +1 -1
- package/dist/cognition/substrate/GMI.js +3 -1
- package/dist/cognition/substrate/GMI.js.map +1 -1
- package/dist/cognition/substrate/IGMI.d.ts +5 -0
- package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
- package/dist/cognition/substrate/IGMI.js.map +1 -1
- package/dist/core/llm/PromptEngine.d.ts.map +1 -1
- package/dist/core/llm/PromptEngine.js +1 -0
- package/dist/core/llm/PromptEngine.js.map +1 -1
- package/dist/core/llm/providers/IProvider.d.ts +9 -0
- package/dist/core/llm/providers/IProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/errors/GeminiProviderError.d.ts +11 -0
- package/dist/core/llm/providers/errors/GeminiProviderError.d.ts.map +1 -1
- package/dist/core/llm/providers/errors/GeminiProviderError.js.map +1 -1
- package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts +27 -6
- package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/AnthropicProvider.js +78 -15
- package/dist/core/llm/providers/implementations/AnthropicProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts +1 -1
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js +70 -7
- package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/GeminiCLIBridge.js +1 -1
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.js +56 -4
- package/dist/core/llm/providers/implementations/GeminiCLIProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/GeminiProvider.d.ts +14 -4
- package/dist/core/llm/providers/implementations/GeminiProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/GeminiProvider.js +355 -39
- package/dist/core/llm/providers/implementations/GeminiProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts +61 -0
- package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/OpenAIProvider.js +164 -22
- package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js +6 -2
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
- package/dist/core/llm/providers/model-forced-tool-choice.d.ts +32 -15
- package/dist/core/llm/providers/model-forced-tool-choice.d.ts.map +1 -1
- package/dist/core/llm/providers/model-forced-tool-choice.js +33 -16
- package/dist/core/llm/providers/model-forced-tool-choice.js.map +1 -1
- package/dist/core/llm/streaming/StreamingBatcher.d.ts.map +1 -1
- package/dist/core/llm/streaming/StreamingBatcher.js +3 -0
- package/dist/core/llm/streaming/StreamingBatcher.js.map +1 -1
- package/dist/core/llm/streaming/StreamingReconstructor.d.ts +2 -0
- package/dist/core/llm/streaming/StreamingReconstructor.d.ts.map +1 -1
- package/dist/core/llm/streaming/StreamingReconstructor.js +12 -1
- package/dist/core/llm/streaming/StreamingReconstructor.js.map +1 -1
- package/dist/io/vision/providers/LLMVisionProvider.d.ts +2 -2
- package/dist/io/vision/providers/LLMVisionProvider.js +1 -1
- package/dist/io/vision/types.d.ts +1 -1
- package/dist/orchestration/pipeline/query/types.d.ts.map +1 -1
- package/dist/orchestration/pipeline/query/types.js +3 -2
- package/dist/orchestration/pipeline/query/types.js.map +1 -1
- package/dist/orchestration/planning/ProviderAssignmentEngine.d.ts.map +1 -1
- package/dist/orchestration/planning/ProviderAssignmentEngine.js +2 -1
- package/dist/orchestration/planning/ProviderAssignmentEngine.js.map +1 -1
- 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
|
-
/**
|
|
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-
|
|
266
|
+
modelId: 'gemini-flash-latest',
|
|
87
267
|
providerId: 'gemini',
|
|
88
|
-
displayName: 'Gemini
|
|
89
|
-
description: '
|
|
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:
|
|
94
|
-
pricePer1MTokensOutput:
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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
|
-
*
|
|
466
|
-
*
|
|
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., "
|
|
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
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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:
|
|
780
|
+
data: vectors.map((embedding, index) => ({
|
|
486
781
|
object: 'embedding',
|
|
487
|
-
embedding
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
// -------------------------------------------------------------------------
|