experimental-a2 0.14.0 → 0.15.0
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 +17 -0
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +39 -12
- package/dist/ai-server.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -0
- package/docs/guides/06-ai-agents.mdx +22 -3
- package/docs/reference/01-api.mdx +25 -0
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/ai-server.ts +48 -11
- package/src/idempotent-id.ts +11 -0
- package/src/index.ts +1 -0
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,8 @@ import { a as ContractEvent, c as PresenceDefs, d as PresenceSnapshot, f as Redu
|
|
|
2
2
|
import { i as Event } from "./store-D_yhNdPz.js";
|
|
3
3
|
import { i as NonRetriableError, n as A2ErrorCode, r as A2ServerErrorCode, t as A2Error } from "./errors-DvhSXnxk.js";
|
|
4
4
|
import { i as A2Telemetry, n as A2SpanHandle, r as A2SpanName, t as A2AttributeValue } from "./telemetry-CpeclqB2.js";
|
|
5
|
-
|
|
5
|
+
//#region src/idempotent-id.d.ts
|
|
6
|
+
declare function idempotentId(...parts: string[]): Promise<string>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { type A2AttributeValue, A2Error, type A2ErrorCode, type A2ServerErrorCode, type A2SpanHandle, type A2SpanName, type A2Telemetry, type AppendInput, type Contract, type ContractEvent, type Event, type EventBatchFactory, type EventDefs, NonRetriableError, type PresenceDefs, type PresenceMap, type PresencePatch, type PresenceSnapshot, type Reducer, type ReducerBuilder, type ReducerOptions, type StandardSchemaV1, contract, idempotentId };
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/idempotent-id.ts"],"mappings":";;;;;iBAAsB,gBAAgB,kBAAkB"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
import { t as contract } from "./contract-CKRg_E4q.js";
|
|
2
2
|
import { n as NonRetriableError, t as A2Error } from "./errors-DCk6ch5n.js";
|
|
3
|
-
|
|
3
|
+
//#region src/idempotent-id.ts
|
|
4
|
+
async function idempotentId(...parts) {
|
|
5
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(parts)));
|
|
6
|
+
const bytes = new Uint8Array(digest).slice(0, 16);
|
|
7
|
+
bytes[6] = bytes[6] & 15 | 128;
|
|
8
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
9
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
10
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { A2Error, NonRetriableError, contract, idempotentId };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/idempotent-id.ts"],"sourcesContent":["export async function idempotentId(...parts: string[]): Promise<string> {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(JSON.stringify(parts)),\n )\n const bytes = new Uint8Array(digest).slice(0, 16)\n bytes[6] = (bytes[6]! & 0x0f) | 0x80\n bytes[8] = (bytes[8]! & 0x3f) | 0x80\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`\n}\n"],"mappings":";;;AAAA,eAAsB,aAAa,GAAG,OAAkC;CACtE,MAAM,SAAS,MAAM,OAAO,OAAO,OACjC,WACA,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,UAAU,KAAK,CAAC,CAChD;CACA,MAAM,QAAQ,IAAI,WAAW,MAAM,CAAC,CAAC,MAAM,GAAG,EAAE;CAChD,MAAM,KAAM,MAAM,KAAM,KAAQ;CAChC,MAAM,KAAM,MAAM,KAAM,KAAQ;CAChC,MAAM,MAAM,MAAM,KAAK,QAAQ,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;CAC7E,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE;AAC7G"}
|
|
@@ -67,11 +67,30 @@ and continuation between steps. Responses have no step limit by default. Set
|
|
|
67
67
|
`maxSteps` to a positive integer to limit one complete assistant response.
|
|
68
68
|
Individual providers decide which model settings they support. `generation`
|
|
69
69
|
therefore excludes `stopWhen`, tool execution callbacks, tool callers, sandbox
|
|
70
|
-
execution, and the tool approval secret.
|
|
71
|
-
`toolsContext`,
|
|
72
|
-
durable execution context later, from the handler that runs it. Model-step
|
|
70
|
+
execution, and the tool approval secret. A2 preserves all other supplied
|
|
71
|
+
settings, including `toolsContext`, when calling the AI SDK. A local tool
|
|
72
|
+
reads its durable execution context later, from the handler that runs it. Model-step
|
|
73
73
|
timeouts are supported; tool-execution timeouts are not.
|
|
74
74
|
|
|
75
|
+
For AI Gateway models, the default generator sets
|
|
76
|
+
`generation.providerOptions.gateway.caching` to `'auto'`. A2 merges this
|
|
77
|
+
default with your provider options, with caller-supplied values taking
|
|
78
|
+
precedence. Other Gateway options and provider namespaces are preserved.
|
|
79
|
+
The same default applies to automatic compaction. A custom `generate`
|
|
80
|
+
function receives your original settings and owns its caching policy.
|
|
81
|
+
|
|
82
|
+
Gateway calls also send an `x-session-affinity` header derived from a SHA-256
|
|
83
|
+
hash of the agent name and session ID. The value stays stable across turns,
|
|
84
|
+
retries, worker restarts, and automatic compaction. No raw session identifier
|
|
85
|
+
is sent in this header. A caller-supplied `generation.headers` entry takes
|
|
86
|
+
precedence, regardless of header casing; an explicit `undefined` suppresses
|
|
87
|
+
A2's default header. Set overrides here, since A2's per-call default can
|
|
88
|
+
override headers configured on the model itself.
|
|
89
|
+
Affinity helps supporting providers reuse their cache; it does not select
|
|
90
|
+
Gateway's provider or guarantee a cache hit.
|
|
91
|
+
See [Gateway automatic caching](https://vercel.com/docs/ai-gateway/models-and-providers/automatic-caching)
|
|
92
|
+
for provider support and cache-write costs.
|
|
93
|
+
|
|
75
94
|
### Store assistant metadata
|
|
76
95
|
|
|
77
96
|
Extend the agent's `UIMessage` type to keep generation status and usage on the
|
|
@@ -63,6 +63,31 @@ The shared base for A2 failures, discriminated by `code`. The session
|
|
|
63
63
|
client exposes its `A2ClientError` subclass. See
|
|
64
64
|
[Errors](/reference/errors).
|
|
65
65
|
|
|
66
|
+
### `idempotentId(...parts)`
|
|
67
|
+
|
|
68
|
+
`idempotentId(...parts: string[]): Promise<string>` derives a stable UUIDv8
|
|
69
|
+
from an ordered tuple of strings. It runs on the server and in the browser.
|
|
70
|
+
|
|
71
|
+
```ts shared/request-id.ts
|
|
72
|
+
import { idempotentId } from 'experimental-a2'
|
|
73
|
+
|
|
74
|
+
export const id = await idempotentId('orders', 'shop-7', 'request-42')
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Pass the result as an event's `id` when retries should converge on the same
|
|
78
|
+
event. The same tuple produces the same ID across runtimes and A2 versions.
|
|
79
|
+
String boundaries, order, and case matter; strings are not normalized.
|
|
80
|
+
Empty strings and an empty tuple are valid inputs.
|
|
81
|
+
|
|
82
|
+
Every argument has the same role. A prefix such as `'orders'` is a namespace
|
|
83
|
+
by convention. Include enough context to distinguish operations across
|
|
84
|
+
contracts and sessions, since event IDs are globally unique within a store.
|
|
85
|
+
|
|
86
|
+
The helper performs no I/O. A2 preserves explicit IDs and keeps its automatic
|
|
87
|
+
ID generation unchanged. Changing an existing application's ID recipe changes
|
|
88
|
+
retry identity, so retries of previously committed requests need to retain
|
|
89
|
+
their original IDs.
|
|
90
|
+
|
|
66
91
|
## `experimental-a2/server`
|
|
67
92
|
|
|
68
93
|
### `handler(entry)`
|
package/package.json
CHANGED
package/src/ai-server.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { createControlRuntime, ControlCancelled } from './ai-control-server.ts'
|
|
12
12
|
import type { ControlCommit } from './ai-control.ts'
|
|
13
13
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
14
|
+
import { createHash } from 'node:crypto'
|
|
14
15
|
import { asSchema, convertToModelMessages } from 'ai'
|
|
15
16
|
import type {
|
|
16
17
|
FinishReason,
|
|
@@ -336,10 +337,38 @@ async function generateWithAISDK<
|
|
|
336
337
|
M extends UIMessage,
|
|
337
338
|
D extends AIEventDefs<M> & EventDefs,
|
|
338
339
|
T extends ToolSet,
|
|
339
|
-
>(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
>(options: {
|
|
341
|
+
agentName: string
|
|
342
|
+
context: AgentGenerateContext<M, D, T>
|
|
343
|
+
messageMetadata: AgentMessageMetadata<M, D, T> | undefined
|
|
344
|
+
}): Promise<GenerationSource> {
|
|
345
|
+
const { agentName, context, messageMetadata } = options
|
|
346
|
+
const headers = context.generation.headers ?? {}
|
|
347
|
+
const generation =
|
|
348
|
+
gatewayModelId(context.model) === undefined
|
|
349
|
+
? context.generation
|
|
350
|
+
: {
|
|
351
|
+
...context.generation,
|
|
352
|
+
headers: Object.keys(headers).some(
|
|
353
|
+
(key) => key.toLowerCase() === 'x-session-affinity',
|
|
354
|
+
)
|
|
355
|
+
? headers
|
|
356
|
+
: {
|
|
357
|
+
...headers,
|
|
358
|
+
'x-session-affinity': createHash('sha256')
|
|
359
|
+
.update(
|
|
360
|
+
JSON.stringify([agentName, context.request.sessionId]),
|
|
361
|
+
)
|
|
362
|
+
.digest('hex'),
|
|
363
|
+
},
|
|
364
|
+
providerOptions: {
|
|
365
|
+
...context.generation.providerOptions,
|
|
366
|
+
gateway: {
|
|
367
|
+
caching: 'auto',
|
|
368
|
+
...context.generation.providerOptions?.['gateway'],
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
}
|
|
343
372
|
const step = await generateAISDKStep<M, T>({
|
|
344
373
|
model: context.model,
|
|
345
374
|
tools: context.tools,
|
|
@@ -350,11 +379,12 @@ async function generateWithAISDK<
|
|
|
350
379
|
...(context.instructions === undefined
|
|
351
380
|
? {}
|
|
352
381
|
: { instructions: context.instructions }),
|
|
353
|
-
settings:
|
|
382
|
+
settings: generation,
|
|
354
383
|
...(messageMetadata === undefined
|
|
355
384
|
? {}
|
|
356
385
|
: {
|
|
357
|
-
messageMetadata: ({ part }) =>
|
|
386
|
+
messageMetadata: ({ part }) =>
|
|
387
|
+
messageMetadata({ ...context, generation, part }),
|
|
358
388
|
}),
|
|
359
389
|
})
|
|
360
390
|
return {
|
|
@@ -457,6 +487,7 @@ const summarize = async <
|
|
|
457
487
|
D extends AIEventDefs<M> & EventDefs,
|
|
458
488
|
T extends ToolSet,
|
|
459
489
|
>(options: {
|
|
490
|
+
agentName: string
|
|
460
491
|
context: AgentGenerateContext<M, D, T>
|
|
461
492
|
instructions: string | undefined
|
|
462
493
|
}): Promise<{ summary: string; usage?: LanguageModelUsage }> => {
|
|
@@ -498,8 +529,9 @@ const summarize = async <
|
|
|
498
529
|
return [name, definition]
|
|
499
530
|
}),
|
|
500
531
|
) as T
|
|
501
|
-
const source = await generateWithAISDK(
|
|
502
|
-
|
|
532
|
+
const source = await generateWithAISDK({
|
|
533
|
+
agentName: options.agentName,
|
|
534
|
+
context: {
|
|
503
535
|
...options.context,
|
|
504
536
|
generation,
|
|
505
537
|
tools,
|
|
@@ -509,8 +541,8 @@ const summarize = async <
|
|
|
509
541
|
],
|
|
510
542
|
responseMessageId: `${options.context.generationId}:summary`,
|
|
511
543
|
},
|
|
512
|
-
undefined,
|
|
513
|
-
)
|
|
544
|
+
messageMetadata: undefined,
|
|
545
|
+
})
|
|
514
546
|
let summary = ''
|
|
515
547
|
let calledTool = false
|
|
516
548
|
let finish: AgentGenerationFinish | undefined
|
|
@@ -1663,6 +1695,7 @@ export function createHandlers<
|
|
|
1663
1695
|
? {
|
|
1664
1696
|
messages: [] as M[],
|
|
1665
1697
|
...(await summarize({
|
|
1698
|
+
agentName: options.agent.contract.name,
|
|
1666
1699
|
context: baseContext,
|
|
1667
1700
|
instructions: policy.instructions,
|
|
1668
1701
|
})),
|
|
@@ -1765,7 +1798,11 @@ export function createHandlers<
|
|
|
1765
1798
|
const custom = options.generate !== undefined
|
|
1766
1799
|
const source =
|
|
1767
1800
|
options.generate === undefined
|
|
1768
|
-
? await generateWithAISDK(
|
|
1801
|
+
? await generateWithAISDK({
|
|
1802
|
+
agentName: options.agent.contract.name,
|
|
1803
|
+
context: generateContext,
|
|
1804
|
+
messageMetadata: options.messageMetadata,
|
|
1805
|
+
})
|
|
1769
1806
|
: { stream: await options.generate(generateContext) }
|
|
1770
1807
|
const updates = consumeGeneration({
|
|
1771
1808
|
source,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export async function idempotentId(...parts: string[]): Promise<string> {
|
|
2
|
+
const digest = await crypto.subtle.digest(
|
|
3
|
+
'SHA-256',
|
|
4
|
+
new TextEncoder().encode(JSON.stringify(parts)),
|
|
5
|
+
)
|
|
6
|
+
const bytes = new Uint8Array(digest).slice(0, 16)
|
|
7
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x80
|
|
8
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80
|
|
9
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')
|
|
10
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`
|
|
11
|
+
}
|