@ai-sdk/gateway 4.0.63 → 4.0.67
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/CHANGELOG.md +38 -0
- package/dist/index.d.ts +44 -9
- package/dist/index.js +42 -96
- package/dist/index.js.map +1 -1
- package/docs/00-ai-gateway.mdx +196 -2
- package/package.json +7 -7
- package/src/gateway-language-model-batch.ts +29 -126
- package/src/gateway-language-model-settings.ts +4 -6
- package/src/gateway-provider-metadata.ts +27 -0
- package/src/gateway-video-model-settings.ts +1 -0
- package/src/index.ts +4 -0
- package/src/tool/tako-search.ts +25 -10
package/docs/00-ai-gateway.mdx
CHANGED
|
@@ -207,6 +207,200 @@ const { text } = await generateText({
|
|
|
207
207
|
|
|
208
208
|
AI Gateway language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output) (see [AI SDK Core](/docs/ai-sdk-core)).
|
|
209
209
|
|
|
210
|
+
When an upstream provider reports a well-formed error after streaming has
|
|
211
|
+
started, AI SDK Core exposes it as a
|
|
212
|
+
[`StreamProviderError`](/docs/reference/ai-sdk-errors/ai-stream-provider-error).
|
|
213
|
+
Use its `type`, `code`, `statusCode`, and `isRetryable` metadata to classify
|
|
214
|
+
the failure without inspecting the Gateway's serialized error payload.
|
|
215
|
+
|
|
216
|
+
## Text Batches
|
|
217
|
+
|
|
218
|
+
<Note type="warning">
|
|
219
|
+
Text batch support is experimental and the API may change in patch releases.
|
|
220
|
+
</Note>
|
|
221
|
+
|
|
222
|
+
AI Gateway supports durable text batches through `experimental_startTextBatch`, `experimental_getBatchStatus`, and `experimental_getBatchResults`. See the [AI Gateway batch processing guide](https://vercel.com/docs/ai-gateway/models-and-providers/batch-processing) for supported models, limits, and the complete lifecycle.
|
|
223
|
+
|
|
224
|
+
Pass a publicly reachable HTTPS `webhookUrl` when starting a batch to receive a terminal notification instead of polling. AI Gateway sends `batch.completed`, `batch.failed`, or `batch.cancelled`. The event contains terminal status information but not batch results. Completion webhooks are an AI Gateway capability; direct Anthropic and OpenAI batch providers return an unsupported warning when this option is provided.
|
|
225
|
+
|
|
226
|
+
```ts filename="start-batch.ts"
|
|
227
|
+
import { randomUUID } from 'node:crypto';
|
|
228
|
+
import {
|
|
229
|
+
experimental_startTextBatch as startTextBatch,
|
|
230
|
+
type Experimental_TextBatchReference as TextBatchReference,
|
|
231
|
+
type GatewayProviderMetadata,
|
|
232
|
+
} from 'ai';
|
|
233
|
+
|
|
234
|
+
// The receiver uses this token to look up the expected batch and signing
|
|
235
|
+
// secret before trusting anything in the webhook body.
|
|
236
|
+
const token = randomUUID();
|
|
237
|
+
const model = 'anthropic/claude-haiku-4.5' as const;
|
|
238
|
+
|
|
239
|
+
// Reserve the token before starting so an early delivery finds a retryable
|
|
240
|
+
// pending record rather than an unknown callback.
|
|
241
|
+
await reserveBatchWebhook(token);
|
|
242
|
+
|
|
243
|
+
const batch = await startTextBatch({
|
|
244
|
+
model,
|
|
245
|
+
requests: [
|
|
246
|
+
{ id: 'france', prompt: 'What is the capital of France?' },
|
|
247
|
+
{ id: 'germany', prompt: 'What is the capital of Germany?' },
|
|
248
|
+
],
|
|
249
|
+
providerOptions: {
|
|
250
|
+
gateway: { idempotencyKey: token },
|
|
251
|
+
},
|
|
252
|
+
webhookUrl: `https://example.com/api/batch-webhook?token=${token}`,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const gatewayMetadata = batch.providerMetadata?.gateway as
|
|
256
|
+
| GatewayProviderMetadata
|
|
257
|
+
| undefined;
|
|
258
|
+
const signingSecret = gatewayMetadata?.asyncJob?.webhookSigningSecret;
|
|
259
|
+
|
|
260
|
+
if (typeof signingSecret !== 'string') {
|
|
261
|
+
throw new Error('AI Gateway did not return a webhook signing secret.');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Keep sensitive provider metadata out of the receiver queue and logs.
|
|
265
|
+
const batchReference = {
|
|
266
|
+
version: batch.version,
|
|
267
|
+
type: batch.type,
|
|
268
|
+
id: batch.id,
|
|
269
|
+
provider: batch.provider,
|
|
270
|
+
modelId: batch.modelId,
|
|
271
|
+
} satisfies TextBatchReference;
|
|
272
|
+
|
|
273
|
+
await completeBatchWebhookReservation(token, {
|
|
274
|
+
batch: batchReference,
|
|
275
|
+
model,
|
|
276
|
+
signingSecret,
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The signing secret is returned only in the start response. Reserve the callback token before submitting the batch, then persist the secret, model, and minimal `batch` reference before the process exits. The stable `providerOptions.gateway.idempotencyKey` makes retrying an ambiguous start safe. Reuse the reserved token when retrying; do not generate a new one. Delete the reservation after a definitive start failure. While the reservation is still pending, the receiver should return a retryable response so an early delivery is sent again after setup completes.
|
|
281
|
+
|
|
282
|
+
The receiver must verify the `x-ai-gateway-signature` header against a bounded raw request body before trusting the event:
|
|
283
|
+
|
|
284
|
+
```ts filename="app/api/batch-webhook/route.ts"
|
|
285
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
286
|
+
|
|
287
|
+
const MAX_AGE_SECONDS = 5 * 60;
|
|
288
|
+
const MAX_BODY_BYTES = 64 * 1024;
|
|
289
|
+
|
|
290
|
+
export async function POST(request: Request) {
|
|
291
|
+
const token = new URL(request.url).searchParams.get('token');
|
|
292
|
+
if (token == null) return new Response(null, { status: 401 });
|
|
293
|
+
|
|
294
|
+
const record = await loadBatchWebhook(token);
|
|
295
|
+
if (record == null) return new Response(null, { status: 401 });
|
|
296
|
+
if (record.state !== 'ready') return new Response(null, { status: 503 });
|
|
297
|
+
|
|
298
|
+
const declaredLength = request.headers.get('content-length');
|
|
299
|
+
if (declaredLength != null && Number(declaredLength) > MAX_BODY_BYTES) {
|
|
300
|
+
return new Response(null, { status: 413 });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const rawBody = await readBodyWithLimit(request, MAX_BODY_BYTES);
|
|
304
|
+
if (rawBody == null) return new Response(null, { status: 413 });
|
|
305
|
+
|
|
306
|
+
const signature = request.headers.get('x-ai-gateway-signature');
|
|
307
|
+
if (
|
|
308
|
+
signature == null ||
|
|
309
|
+
!verifySignature({
|
|
310
|
+
header: signature,
|
|
311
|
+
rawBody,
|
|
312
|
+
secret: record.signingSecret,
|
|
313
|
+
})
|
|
314
|
+
) {
|
|
315
|
+
return new Response(null, { status: 401 });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let event: { data?: { jobId?: string; status?: string } };
|
|
319
|
+
try {
|
|
320
|
+
event = JSON.parse(rawBody);
|
|
321
|
+
} catch {
|
|
322
|
+
return new Response(null, { status: 400 });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const jobId = event.data?.jobId;
|
|
326
|
+
const status = event.data?.status;
|
|
327
|
+
if (
|
|
328
|
+
jobId !== record.batch.id ||
|
|
329
|
+
(status !== 'cancelled' && status !== 'completed' && status !== 'failed')
|
|
330
|
+
) {
|
|
331
|
+
return new Response(null, { status: 401 });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// The header is not signed, so require it to match the signed event body.
|
|
335
|
+
const deliveryId = `${jobId}-${status}`;
|
|
336
|
+
if (request.headers.get('x-ai-gateway-idempotency-key') !== deliveryId) {
|
|
337
|
+
return new Response(null, { status: 401 });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
await enqueueBatchResultFetch({
|
|
341
|
+
batch: record.batch,
|
|
342
|
+
deliveryId,
|
|
343
|
+
model: record.model,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return new Response(null, { status: 204 });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function readBodyWithLimit(request: Request, maxBytes: number) {
|
|
350
|
+
if (request.body == null) return '';
|
|
351
|
+
|
|
352
|
+
const reader = request.body.getReader();
|
|
353
|
+
const decoder = new TextDecoder();
|
|
354
|
+
let body = '';
|
|
355
|
+
let size = 0;
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
while (true) {
|
|
359
|
+
const { done, value } = await reader.read();
|
|
360
|
+
if (done) return body + decoder.decode();
|
|
361
|
+
|
|
362
|
+
size += value.byteLength;
|
|
363
|
+
if (size > maxBytes) {
|
|
364
|
+
await reader.cancel();
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
body += decoder.decode(value, { stream: true });
|
|
369
|
+
}
|
|
370
|
+
} finally {
|
|
371
|
+
reader.releaseLock();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function verifySignature({
|
|
376
|
+
header,
|
|
377
|
+
rawBody,
|
|
378
|
+
secret,
|
|
379
|
+
}: {
|
|
380
|
+
header: string;
|
|
381
|
+
rawBody: string;
|
|
382
|
+
secret: string;
|
|
383
|
+
}) {
|
|
384
|
+
const timestamp = header.match(/(?:^|,)t=(\d+)/)?.[1];
|
|
385
|
+
const digest = header.match(/(?:^|,)v1=([0-9a-f]{64})(?:,|$)/)?.[1];
|
|
386
|
+
if (timestamp == null || digest == null) return false;
|
|
387
|
+
|
|
388
|
+
const expected = createHmac('sha256', secret)
|
|
389
|
+
.update(`${timestamp}.${rawBody}`, 'utf8')
|
|
390
|
+
.digest('hex');
|
|
391
|
+
const provided = Buffer.from(digest, 'hex');
|
|
392
|
+
const computed = Buffer.from(expected, 'hex');
|
|
393
|
+
if (provided.length !== computed.length) return false;
|
|
394
|
+
if (!timingSafeEqual(provided, computed)) return false;
|
|
395
|
+
|
|
396
|
+
return Math.abs(Date.now() / 1000 - Number(timestamp)) <= MAX_AGE_SECONDS;
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
The signature header has the form `t=<unix seconds>,v1=<hex digest>`, where `v1` is the HMAC-SHA256 digest of `"<t>.<raw body>"`. Verify a size-limited raw request body rather than a re-serialized object, use a timing-safe comparison, reject stale timestamps, and reject events whose `data.jobId` does not match the persisted `batch.id`.
|
|
401
|
+
|
|
402
|
+
AI Gateway expects a 2xx response within 10 seconds and retries failed deliveries. It does not follow redirects, so the callback endpoint itself must return the 2xx response. Retries carry the same `x-ai-gateway-idempotency-key` value (`<jobId>-<status>`), which receivers can use for deduplication. After verification, pass the persisted model and `batch` reference to `experimental_getBatchStatus` and `experimental_getBatchResults`. Delivery is best-effort, so periodically reconcile persisted batches by status as a fallback for a notification that exhausts its retries.
|
|
403
|
+
|
|
210
404
|
## Reranking Models
|
|
211
405
|
|
|
212
406
|
You can create reranking models using the `rerankingModel` method on the provider instance:
|
|
@@ -829,9 +1023,9 @@ The Tako Search tool supports these optional configuration options:
|
|
|
829
1023
|
- **includeContents** _boolean_ - Inline each page's extracted full text in `content`. **articleContentMaxChars** caps it, defaulting to 30,000 characters (maximum 1,000,000).
|
|
830
1024
|
- **sources.data** _object_ - Configure data results:
|
|
831
1025
|
- **count** _number_ - Maximum data results, 1-20. Defaults to 5.
|
|
832
|
-
- **includeContents** _boolean_ - Inline each card's underlying rows in `content.dataset` as typed, unit-labeled columns.
|
|
1026
|
+
- **includeContents** _boolean_ - Inline each card's underlying rows in `content.dataset` as typed, unit-labeled columns. This adds a data surcharge based on the row count and dataset source. To estimate cost, first search with `includeContents: false` and inspect `cards.content.export_pricing`. Because this applies to every returned card, use `count` and `maxRows` to control cost.
|
|
833
1027
|
- **contentFormat** _'json_compact' | 'json_records' | 'csv' | 'card_json'_ - Serialization for inlined card data. Defaults to `'json_compact'`.
|
|
834
|
-
- **maxRows** _number_ - Row cap
|
|
1028
|
+
- **maxRows** _number_ - Row cap per result. Omit to use the allowance in `cards.content.export_pricing`. A data surcharge applies per 1,000 exported rows; lower values reduce cost.
|
|
835
1029
|
- **nodeIds** _string[]_ - Data Graph node IDs to prioritize. Up to 20.
|
|
836
1030
|
- **strict** _boolean_ - Only return cards matching `nodeIds`. Requires at least one `nodeIds` value.
|
|
837
1031
|
- **location** _object_ - End-user `{ latitude, longitude }` coordinates for localized results.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/gateway",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.67",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -30,18 +30,18 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@ai-sdk/provider": "
|
|
35
|
-
"@
|
|
33
|
+
"@ai-sdk/provider": "4.0.8",
|
|
34
|
+
"@ai-sdk/provider-utils": "5.0.32",
|
|
35
|
+
"@vercel/oidc": "3.2.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
+
"@ai-sdk/test-server": "2.0.1",
|
|
38
39
|
"@types/node": "22.19.19",
|
|
40
|
+
"@vercel/ai-tsconfig": "0.0.0",
|
|
39
41
|
"tsup": "^8.5.1",
|
|
40
42
|
"tsx": "4.23.12",
|
|
41
43
|
"typescript": "5.8.3",
|
|
42
|
-
"zod": "3.25.76"
|
|
43
|
-
"@ai-sdk/test-server": "2.0.1",
|
|
44
|
-
"@vercel/ai-tsconfig": "0.0.0"
|
|
44
|
+
"zod": "3.25.76"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"zod": "^3.25.76 || ^4.1.8"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
APICallError,
|
|
3
2
|
type Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
|
|
4
3
|
type Experimental_BatchV4ItemResult as BatchV4ItemResult,
|
|
5
4
|
type Experimental_BatchV4OperationOptions as BatchV4OperationOptions,
|
|
@@ -15,9 +14,10 @@ import {
|
|
|
15
14
|
combineHeaders,
|
|
16
15
|
convertAsyncIteratorToReadableStream,
|
|
17
16
|
createJsonErrorResponseHandler,
|
|
17
|
+
createJsonLinesResponseHandler,
|
|
18
18
|
createJsonResponseHandler,
|
|
19
19
|
getErrorMessage,
|
|
20
|
-
|
|
20
|
+
normalizeBatchRequestCounts,
|
|
21
21
|
postJsonToApi,
|
|
22
22
|
resolve,
|
|
23
23
|
WORKFLOW_SERIALIZE,
|
|
@@ -63,6 +63,7 @@ export class GatewayBatchLanguageModel
|
|
|
63
63
|
providerOptions,
|
|
64
64
|
headers,
|
|
65
65
|
abortSignal,
|
|
66
|
+
webhookUrl,
|
|
66
67
|
}: BatchV4StartOptions<LanguageModelV4BatchRequest>): Promise<BatchV4StartResult> {
|
|
67
68
|
const resolvedHeaders = this.config.headers
|
|
68
69
|
? await resolve(this.config.headers)
|
|
@@ -84,6 +85,7 @@ export class GatewayBatchLanguageModel
|
|
|
84
85
|
: undefined,
|
|
85
86
|
),
|
|
86
87
|
body: {
|
|
88
|
+
...(webhookUrl != null && { callbackUrl: webhookUrl }),
|
|
87
89
|
modelId: this.modelId,
|
|
88
90
|
requests: requests.map(request => ({
|
|
89
91
|
id: request.id,
|
|
@@ -188,7 +190,7 @@ export class GatewayBatchLanguageModel
|
|
|
188
190
|
: undefined;
|
|
189
191
|
|
|
190
192
|
try {
|
|
191
|
-
const { value:
|
|
193
|
+
const { value: lines } = await postJsonToApi({
|
|
192
194
|
url: this.getBatchUrl('results'),
|
|
193
195
|
headers: combineHeaders(
|
|
194
196
|
resolvedHeaders,
|
|
@@ -197,28 +199,9 @@ export class GatewayBatchLanguageModel
|
|
|
197
199
|
await resolve(this.config.o11yHeaders),
|
|
198
200
|
),
|
|
199
201
|
body: { batchId },
|
|
200
|
-
successfulResponseHandler:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
requestBodyValues,
|
|
204
|
-
}: {
|
|
205
|
-
url: string;
|
|
206
|
-
requestBodyValues: unknown;
|
|
207
|
-
response: Response;
|
|
208
|
-
}) => {
|
|
209
|
-
if (response.body == null) {
|
|
210
|
-
throw new APICallError({
|
|
211
|
-
message: 'Batch results response body is empty',
|
|
212
|
-
url,
|
|
213
|
-
requestBodyValues,
|
|
214
|
-
statusCode: response.status,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
value: response.body,
|
|
219
|
-
responseHeaders: Object.fromEntries([...response.headers]),
|
|
220
|
-
};
|
|
221
|
-
},
|
|
202
|
+
successfulResponseHandler: createJsonLinesResponseHandler(
|
|
203
|
+
gatewayBatchItemResultLineSchema,
|
|
204
|
+
),
|
|
222
205
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
223
206
|
errorSchema: z.any(),
|
|
224
207
|
errorToMessage: data => getErrorMessage(data) ?? 'unknown error',
|
|
@@ -228,7 +211,7 @@ export class GatewayBatchLanguageModel
|
|
|
228
211
|
});
|
|
229
212
|
|
|
230
213
|
return convertAsyncIteratorToReadableStream(
|
|
231
|
-
|
|
214
|
+
convertGatewayBatchResultLines(lines),
|
|
232
215
|
);
|
|
233
216
|
} catch (error) {
|
|
234
217
|
if (isAbortOrTimeoutError(error)) {
|
|
@@ -340,7 +323,12 @@ function convertGatewayBatchStatus(body: {
|
|
|
340
323
|
expiresAt?: string | null;
|
|
341
324
|
providerMetadata?: Record<string, Record<string, unknown>> | null;
|
|
342
325
|
}): BatchV4Status {
|
|
343
|
-
const requestCounts =
|
|
326
|
+
const requestCounts = normalizeBatchRequestCounts({
|
|
327
|
+
total: body.requestCounts?.total,
|
|
328
|
+
pending: body.requestCounts?.pending,
|
|
329
|
+
completed: body.requestCounts?.completed,
|
|
330
|
+
failed: body.requestCounts?.failed,
|
|
331
|
+
});
|
|
344
332
|
|
|
345
333
|
return {
|
|
346
334
|
status: body.status,
|
|
@@ -365,112 +353,27 @@ function convertGatewayBatchStatus(body: {
|
|
|
365
353
|
}
|
|
366
354
|
|
|
367
355
|
/**
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
* full set is present rather than fabricating zeros.
|
|
371
|
-
*/
|
|
372
|
-
function convertGatewayBatchRequestCounts(
|
|
373
|
-
counts:
|
|
374
|
-
| {
|
|
375
|
-
total?: number | null;
|
|
376
|
-
pending?: number | null;
|
|
377
|
-
completed?: number | null;
|
|
378
|
-
failed?: number | null;
|
|
379
|
-
}
|
|
380
|
-
| null
|
|
381
|
-
| undefined,
|
|
382
|
-
): BatchV4Status['requestCounts'] | undefined {
|
|
383
|
-
if (
|
|
384
|
-
counts == null ||
|
|
385
|
-
typeof counts.total !== 'number' ||
|
|
386
|
-
typeof counts.pending !== 'number' ||
|
|
387
|
-
typeof counts.completed !== 'number' ||
|
|
388
|
-
typeof counts.failed !== 'number'
|
|
389
|
-
) {
|
|
390
|
-
return undefined;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return {
|
|
394
|
-
total: counts.total,
|
|
395
|
-
pending: counts.pending,
|
|
396
|
-
completed: counts.completed,
|
|
397
|
-
failed: counts.failed,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Incremental NDJSON line splitter for the batch results stream: buffers
|
|
403
|
-
* partial lines across chunks and flushes a trailing line without a final
|
|
404
|
-
* newline. Each non-empty line is one `BatchV4ItemResult` JSON object.
|
|
356
|
+
* Converts the minimally validated Gateway batch result lines. The Gateway
|
|
357
|
+
* already sanitizes the complete result objects server-side.
|
|
405
358
|
*
|
|
406
|
-
* @
|
|
407
|
-
* @yields One minimally-validated `BatchV4ItemResult` per non-empty line.
|
|
359
|
+
* @yields Each Gateway batch item result.
|
|
408
360
|
*/
|
|
409
|
-
async function*
|
|
410
|
-
|
|
361
|
+
async function* convertGatewayBatchResultLines(
|
|
362
|
+
lines: AsyncIterable<unknown>,
|
|
411
363
|
): AsyncGenerator<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (done) {
|
|
422
|
-
finished = true;
|
|
423
|
-
buffer += decoder.decode();
|
|
424
|
-
break;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
buffer += decoder.decode(value, { stream: true });
|
|
428
|
-
|
|
429
|
-
let lineEnd = buffer.indexOf('\n');
|
|
430
|
-
while (lineEnd !== -1) {
|
|
431
|
-
const line = buffer.slice(0, lineEnd).replace(/\r$/, '');
|
|
432
|
-
buffer = buffer.slice(lineEnd + 1);
|
|
433
|
-
|
|
434
|
-
if (line.trim().length > 0) {
|
|
435
|
-
yield await parseGatewayBatchResultLine(line);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
lineEnd = buffer.indexOf('\n');
|
|
364
|
+
for await (const line of lines) {
|
|
365
|
+
const item = line as BatchV4ItemResult<LanguageModelV4GenerateResult>;
|
|
366
|
+
|
|
367
|
+
// JSON carries `response.timestamp` as an ISO string; core expects a Date.
|
|
368
|
+
if (item.status === 'succeeded') {
|
|
369
|
+
const response = item.result?.response;
|
|
370
|
+
if (response !== undefined && typeof response.timestamp === 'string') {
|
|
371
|
+
response.timestamp = new Date(response.timestamp);
|
|
439
372
|
}
|
|
440
373
|
}
|
|
441
374
|
|
|
442
|
-
|
|
443
|
-
if (finalLine.trim().length > 0) {
|
|
444
|
-
yield await parseGatewayBatchResultLine(finalLine);
|
|
445
|
-
}
|
|
446
|
-
} finally {
|
|
447
|
-
if (!finished) {
|
|
448
|
-
await reader.cancel().catch(() => {});
|
|
449
|
-
}
|
|
450
|
-
reader.releaseLock();
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
async function parseGatewayBatchResultLine(
|
|
455
|
-
line: string,
|
|
456
|
-
): Promise<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
|
|
457
|
-
// Minimal validation (id + status); items pass through otherwise — the
|
|
458
|
-
// Gateway already sanitizes them server-side.
|
|
459
|
-
const parsed = await parseJSON({
|
|
460
|
-
text: line,
|
|
461
|
-
schema: gatewayBatchItemResultLineSchema,
|
|
462
|
-
});
|
|
463
|
-
const item =
|
|
464
|
-
parsed as unknown as BatchV4ItemResult<LanguageModelV4GenerateResult>;
|
|
465
|
-
// JSON carries `response.timestamp` as an ISO string; core expects a Date
|
|
466
|
-
// (`GeneratedFile`-style consumers call `.toISOString()`).
|
|
467
|
-
if (item.status === 'succeeded') {
|
|
468
|
-
const response = item.result?.response;
|
|
469
|
-
if (response !== undefined && typeof response.timestamp === 'string') {
|
|
470
|
-
response.timestamp = new Date(response.timestamp);
|
|
471
|
-
}
|
|
375
|
+
yield item;
|
|
472
376
|
}
|
|
473
|
-
return item;
|
|
474
377
|
}
|
|
475
378
|
|
|
476
379
|
const gatewayBatchItemResultLineSchema = z
|
|
@@ -47,7 +47,6 @@ export type GatewayModelId =
|
|
|
47
47
|
| 'anthropic/claude-sonnet-4.6'
|
|
48
48
|
| 'anthropic/claude-sonnet-5'
|
|
49
49
|
| 'arcee-ai/trinity-large-thinking'
|
|
50
|
-
| 'arcee-ai/trinity-mini'
|
|
51
50
|
| 'bytedance/seed-1.6'
|
|
52
51
|
| 'bytedance/seed-1.8'
|
|
53
52
|
| 'cohere/command-a'
|
|
@@ -103,13 +102,13 @@ export type GatewayModelId =
|
|
|
103
102
|
| 'minimax/minimax-m2.5'
|
|
104
103
|
| 'minimax/minimax-m2.5-highspeed'
|
|
105
104
|
| 'minimax/minimax-m2.7'
|
|
105
|
+
| 'minimax/minimax-m2.7-free'
|
|
106
106
|
| 'minimax/minimax-m2.7-highspeed'
|
|
107
107
|
| 'minimax/minimax-m3'
|
|
108
|
+
| 'minimax/minimax-m3-free'
|
|
108
109
|
| 'mistral/codestral'
|
|
109
110
|
| 'mistral/devstral-2'
|
|
110
111
|
| 'mistral/devstral-small-2'
|
|
111
|
-
| 'mistral/magistral-medium'
|
|
112
|
-
| 'mistral/magistral-small'
|
|
113
112
|
| 'mistral/ministral-14b'
|
|
114
113
|
| 'mistral/ministral-3b'
|
|
115
114
|
| 'mistral/ministral-8b'
|
|
@@ -133,7 +132,6 @@ export type GatewayModelId =
|
|
|
133
132
|
| 'nvidia/nemotron-3-super-120b-a12b'
|
|
134
133
|
| 'nvidia/nemotron-3-ultra-550b-a55b'
|
|
135
134
|
| 'nvidia/nemotron-3.5-lightning'
|
|
136
|
-
| 'nvidia/nemotron-3.5-lightning-free'
|
|
137
135
|
| 'nvidia/nemotron-nano-12b-v2-vl'
|
|
138
136
|
| 'nvidia/nemotron-nano-9b-v2'
|
|
139
137
|
| 'openai/gpt-3.5-turbo'
|
|
@@ -148,7 +146,6 @@ export type GatewayModelId =
|
|
|
148
146
|
| 'openai/gpt-4o-fast'
|
|
149
147
|
| 'openai/gpt-4o-mini'
|
|
150
148
|
| 'openai/gpt-4o-mini-fast'
|
|
151
|
-
| 'openai/gpt-4o-mini-search-preview'
|
|
152
149
|
| 'openai/gpt-5'
|
|
153
150
|
| 'openai/gpt-5-codex'
|
|
154
151
|
| 'openai/gpt-5-fast'
|
|
@@ -184,10 +181,10 @@ export type GatewayModelId =
|
|
|
184
181
|
| 'openai/gpt-5.6-terra-fast'
|
|
185
182
|
| 'openai/gpt-oss-120b'
|
|
186
183
|
| 'openai/gpt-oss-20b'
|
|
184
|
+
| 'openai/gpt-oss-safeguard-120b'
|
|
187
185
|
| 'openai/gpt-oss-safeguard-20b'
|
|
188
186
|
| 'openai/o1'
|
|
189
187
|
| 'openai/o3'
|
|
190
|
-
| 'openai/o3-deep-research'
|
|
191
188
|
| 'openai/o3-fast'
|
|
192
189
|
| 'openai/o3-mini'
|
|
193
190
|
| 'openai/o3-pro'
|
|
@@ -235,5 +232,6 @@ export type GatewayModelId =
|
|
|
235
232
|
| 'zai/glm-5.2'
|
|
236
233
|
| 'zai/glm-5.2-fast'
|
|
237
234
|
| 'zai/glm-5.3'
|
|
235
|
+
| 'zai/glm-5.3-flash'
|
|
238
236
|
| 'zai/glm-5v-turbo'
|
|
239
237
|
| (string & {});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { JSONValue } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Metadata for an asynchronous AI Gateway job.
|
|
5
|
+
*/
|
|
6
|
+
export type GatewayAsyncJobMetadata = {
|
|
7
|
+
readonly jobId: string;
|
|
8
|
+
readonly status: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Secret for verifying customer webhook deliveries. This is sensitive,
|
|
12
|
+
* start-response-only metadata and must not be logged or forwarded.
|
|
13
|
+
*/
|
|
14
|
+
readonly webhookSigningSecret?: string;
|
|
15
|
+
readonly [key: string]: JSONValue | undefined;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Shape of the inner `providerMetadata.gateway` object returned by AI Gateway
|
|
20
|
+
* operations.
|
|
21
|
+
*
|
|
22
|
+
* Additional fields may be added without a package update.
|
|
23
|
+
*/
|
|
24
|
+
export type GatewayProviderMetadata = {
|
|
25
|
+
readonly asyncJob?: GatewayAsyncJobMetadata;
|
|
26
|
+
readonly [key: string]: JSONValue | undefined;
|
|
27
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,10 @@ export type {
|
|
|
41
41
|
GatewayProvider,
|
|
42
42
|
GatewayProviderSettings,
|
|
43
43
|
} from './gateway-provider';
|
|
44
|
+
export type {
|
|
45
|
+
GatewayAsyncJobMetadata,
|
|
46
|
+
GatewayProviderMetadata,
|
|
47
|
+
} from './gateway-provider-metadata';
|
|
44
48
|
export type {
|
|
45
49
|
GatewayProviderOptions,
|
|
46
50
|
/** @deprecated Use `GatewayProviderOptions` instead. */
|
package/src/tool/tako-search.ts
CHANGED
|
@@ -14,15 +14,28 @@ export type TakoContentFormat =
|
|
|
14
14
|
| 'json_records';
|
|
15
15
|
|
|
16
16
|
export interface TakoDataSourceConfig {
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Maximum number of data results to return (1-20). When includeContents is
|
|
19
|
+
* true, each additional result adds its own data surcharge.
|
|
20
|
+
*/
|
|
18
21
|
count?: number;
|
|
19
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Inline rows for each data result. This adds a data surcharge based on row
|
|
24
|
+
* count and dataset source. To estimate cost, search with includeContents
|
|
25
|
+
* disabled and inspect cards.content.export_pricing. This applies to every
|
|
26
|
+
* returned card; limit sources.data.count and sources.data.maxRows to control
|
|
27
|
+
* cost.
|
|
28
|
+
*/
|
|
20
29
|
includeContents?: boolean;
|
|
21
30
|
/** Requested delivery mode for card data. Search cards are always inlined. */
|
|
22
31
|
mode?: 'inline' | 'url';
|
|
23
32
|
/** Serialization for inlined card data. */
|
|
24
33
|
contentFormat?: TakoContentFormat;
|
|
25
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Maximum rows to inline per result. Omit to use the allowance in
|
|
36
|
+
* cards.content.export_pricing. A data surcharge applies per 1,000 exported
|
|
37
|
+
* rows; lower values reduce cost.
|
|
38
|
+
*/
|
|
26
39
|
maxRows?: number;
|
|
27
40
|
/** Data Graph node IDs to prioritize. */
|
|
28
41
|
nodeIds?: string[];
|
|
@@ -190,7 +203,6 @@ export interface TakoCard {
|
|
|
190
203
|
relevance?: 'High' | 'Low' | 'Medium' | null;
|
|
191
204
|
content?: TakoResultContent | null;
|
|
192
205
|
exportable?: boolean;
|
|
193
|
-
relevance_score?: number | null;
|
|
194
206
|
nodes?: Array<{
|
|
195
207
|
id: string;
|
|
196
208
|
type: 'entity' | 'metric';
|
|
@@ -215,7 +227,6 @@ export interface TakoWebResult {
|
|
|
215
227
|
source_name?: string | null;
|
|
216
228
|
publish_date?: string | null;
|
|
217
229
|
content?: TakoResultContent | null;
|
|
218
|
-
citation_number?: number | null;
|
|
219
230
|
}
|
|
220
231
|
|
|
221
232
|
export interface TakoSearchResponse {
|
|
@@ -254,11 +265,15 @@ const takoDataSourceInputSchema = z.object({
|
|
|
254
265
|
count: z
|
|
255
266
|
.number()
|
|
256
267
|
.optional()
|
|
257
|
-
.describe(
|
|
268
|
+
.describe(
|
|
269
|
+
'Maximum number of data results to return (1-20). When include_contents is true, each additional result adds its own data surcharge.',
|
|
270
|
+
),
|
|
258
271
|
include_contents: z
|
|
259
272
|
.boolean()
|
|
260
273
|
.optional()
|
|
261
|
-
.describe(
|
|
274
|
+
.describe(
|
|
275
|
+
'Inline rows for each data result. This adds a data surcharge based on row count and dataset source. To estimate cost, search with include_contents disabled and inspect cards.content.export_pricing. This applies to every returned card; limit sources.data.count and sources.data.max_rows to control cost.',
|
|
276
|
+
),
|
|
262
277
|
mode: z
|
|
263
278
|
.enum(['inline', 'url'])
|
|
264
279
|
.optional()
|
|
@@ -272,7 +287,9 @@ const takoDataSourceInputSchema = z.object({
|
|
|
272
287
|
max_rows: z
|
|
273
288
|
.number()
|
|
274
289
|
.optional()
|
|
275
|
-
.describe(
|
|
290
|
+
.describe(
|
|
291
|
+
'Maximum rows to inline per result. Omit to use the allowance in cards.content.export_pricing. A data surcharge applies per 1,000 exported rows; lower values reduce cost.',
|
|
292
|
+
),
|
|
276
293
|
node_ids: z
|
|
277
294
|
.array(z.string())
|
|
278
295
|
.optional()
|
|
@@ -493,7 +510,6 @@ const takoCardSchema = z
|
|
|
493
510
|
relevance: z.enum(['High', 'Low', 'Medium']).nullish(),
|
|
494
511
|
content: takoResultContentSchema.nullish(),
|
|
495
512
|
exportable: z.boolean().optional(),
|
|
496
|
-
relevance_score: z.number().nullish(),
|
|
497
513
|
nodes: z
|
|
498
514
|
.array(
|
|
499
515
|
z.object({
|
|
@@ -525,7 +541,6 @@ const takoWebResultSchema = z
|
|
|
525
541
|
source_name: z.string().nullish(),
|
|
526
542
|
publish_date: z.string().nullish(),
|
|
527
543
|
content: takoResultContentSchema.nullish(),
|
|
528
|
-
citation_number: z.number().nullish(),
|
|
529
544
|
})
|
|
530
545
|
.passthrough();
|
|
531
546
|
|