experimental-a2 0.18.0 → 0.19.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 +39 -0
- package/dist/{actor-Bjce9_5U.d.ts → actor-C7b-1arn.d.ts} +3 -3
- package/dist/{actor-Bjce9_5U.d.ts.map → actor-C7b-1arn.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-react.d.ts +1 -1
- package/dist/actor-react.js +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +2 -2
- package/dist/{ai-DrUaMyRt.d.ts → ai-Cz3hqVhk.d.ts} +34 -17
- package/dist/ai-Cz3hqVhk.d.ts.map +1 -0
- package/dist/ai-client.d.ts +1 -1
- package/dist/ai-client.js +2 -2
- package/dist/ai-control-CvLbW10B.js +46 -0
- package/dist/ai-control-CvLbW10B.js.map +1 -0
- package/dist/ai-fx.d.ts +1 -1
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +122 -44
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-BuLh_EYi.js → ai-harness-BuuC-gJ3.js} +79 -15
- package/dist/ai-harness-BuuC-gJ3.js.map +1 -0
- package/dist/{ai-harness-DvsPeJeB.d.ts → ai-harness-CV6rXbB3.d.ts} +2 -2
- package/dist/ai-harness-CV6rXbB3.d.ts.map +1 -0
- package/dist/{ai-BpOGm-zB.js → ai-mMiWul1T.js} +117 -78
- package/dist/ai-mMiWul1T.js.map +1 -0
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.d.ts.map +1 -1
- package/dist/ai-pi.js +114 -23
- package/dist/ai-pi.js.map +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +3 -3
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +127 -55
- package/dist/ai-server.js.map +1 -1
- package/dist/ai-tool-input-DmSqMUQi.js +40 -0
- package/dist/ai-tool-input-DmSqMUQi.js.map +1 -0
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +1 -1
- package/dist/{client-BReKqwZC.js → client-BhHLoNKF.js} +3 -3
- package/dist/{client-BReKqwZC.js.map → client-BhHLoNKF.js.map} +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/{internal-B2T3U4Rq.js → internal-CscSY7K_.js} +4 -2
- package/dist/{internal-B2T3U4Rq.js.map → internal-CscSY7K_.js.map} +1 -1
- package/dist/react.js +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +3 -3
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +7 -7
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +10 -14
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-Dw_k_7kO.js → server-D-Y4pqwu.js} +37 -11
- package/dist/server-D-Y4pqwu.js.map +1 -0
- package/dist/{server-En7cbi3v.d.ts → server-DYAATFuT.d.ts} +11 -5
- package/dist/server-DYAATFuT.d.ts.map +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/{session-clear-Bo-XTFEy.d.ts → session-clear-C-QwTXV1.d.ts} +2 -2
- package/dist/session-clear-C-QwTXV1.d.ts.map +1 -0
- package/dist/{store-Djdv0bnZ.d.ts → store-CyLUy86v.d.ts} +23 -4
- package/dist/store-CyLUy86v.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.js +25 -6
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-B5FcwJfo.js → store-polling-7WJV0YWP.js} +2 -2
- package/dist/{store-polling-B5FcwJfo.js.map → store-polling-7WJV0YWP.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +55 -25
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-BAwS8RWn.js → store-presence-polling-B1ixeqIS.js} +3 -3
- package/dist/{store-presence-polling-BAwS8RWn.js.map → store-presence-polling-B1ixeqIS.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +3 -3
- package/dist/{store-redis-notify-BRITti_o.js → store-redis-notify-CM0AfLY0.js} +88 -37
- package/dist/store-redis-notify-CM0AfLY0.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.js +35 -20
- package/dist/store-sqlite.js.map +1 -1
- package/dist/{wire-rSUglYo8.js → wire-BKxH1vbP.js} +2 -2
- package/dist/{wire-rSUglYo8.js.map → wire-BKxH1vbP.js.map} +1 -1
- package/docs/agents/03-conversations.mdx +27 -15
- package/docs/agents/04-harnesses.mdx +51 -5
- package/docs/contract/03-sessions.mdx +1 -1
- package/docs/index.mdx +3 -3
- package/docs/reference/02-server.mdx +12 -7
- package/docs/reference/04-ai.mdx +41 -23
- package/docs/reference/05-adapters.mdx +6 -6
- package/docs/server/01-handlers.mdx +1 -1
- package/docs/server/02-timers.mdx +1 -1
- package/docs/server/03-cancellation.mdx +3 -2
- package/docs/server/05-production.mdx +4 -3
- package/docs/server/06-guarantees.mdx +20 -11
- package/examples/playground/DEPLOY.md +11 -4
- package/examples/playground/app/a0/[appId]/page.tsx +2 -8
- package/examples/playground/app/a0/a0.css +244 -5
- package/examples/playground/app/a0/app-view.tsx +2 -1
- package/examples/playground/app/a0/attachments.ts +133 -0
- package/examples/playground/app/a0/chat.test.tsx +504 -0
- package/examples/playground/app/a0/chat.tsx +623 -63
- package/examples/playground/app/a0/context-menu.tsx +38 -11
- package/examples/playground/app/a0/devtools.tsx +2 -8
- package/examples/playground/app/a0/model.test.ts +28 -0
- package/examples/playground/app/a0/model.ts +30 -0
- package/examples/playground/app/a0/server.ts +13 -19
- package/examples/playground/app/a0/uploads/route.ts +29 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +2 -1
- package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +1 -0
- package/examples/playground/app/agent/model.test.ts +12 -3
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +1 -5
- package/examples/playground/next.config.ts +7 -4
- package/examples/playground/package.json +3 -1
- package/package.json +1 -1
- package/src/ai-agent.ts +14 -3
- package/src/ai-client-state.ts +21 -9
- package/src/ai-context-schema.ts +28 -4
- package/src/ai-context.ts +83 -3
- package/src/ai-control-server.ts +70 -43
- package/src/ai-control-state.ts +3 -4
- package/src/ai-control.ts +59 -10
- package/src/ai-coordinator.ts +7 -6
- package/src/ai-fx.ts +184 -67
- package/src/ai-harness.ts +153 -29
- package/src/ai-message-shape.ts +41 -0
- package/src/ai-pi.ts +161 -46
- package/src/ai-stored-state.ts +1 -3
- package/src/ai-tool-input.ts +45 -0
- package/src/ai.ts +116 -77
- package/src/internal.ts +6 -0
- package/src/libfx.d.ts +8 -1
- package/src/scheduler-qstash.ts +5 -5
- package/src/scheduler-vercel.ts +15 -27
- package/src/server.ts +58 -12
- package/src/store-memory.ts +50 -9
- package/src/store-postgres.ts +79 -28
- package/src/store-redis-core.ts +112 -31
- package/src/store-sqlite.ts +61 -28
- package/src/store.ts +22 -3
- package/dist/ai-BpOGm-zB.js.map +0 -1
- package/dist/ai-DrUaMyRt.d.ts.map +0 -1
- package/dist/ai-harness-BuLh_EYi.js.map +0 -1
- package/dist/ai-harness-DvsPeJeB.d.ts.map +0 -1
- package/dist/server-Dw_k_7kO.js.map +0 -1
- package/dist/server-En7cbi3v.d.ts.map +0 -1
- package/dist/session-clear-Bo-XTFEy.d.ts.map +0 -1
- package/dist/store-Djdv0bnZ.d.ts.map +0 -1
- package/dist/store-redis-notify-BRITti_o.js.map +0 -1
- package/examples/playground/app/a0/no-photon.cjs +0 -4
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { UIMessage } from 'ai'
|
|
2
|
+
|
|
3
|
+
export const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
4
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
5
|
+
|
|
6
|
+
export const isJSONCompatible = (
|
|
7
|
+
value: unknown,
|
|
8
|
+
seen: Set<object> = new Set(),
|
|
9
|
+
): boolean => {
|
|
10
|
+
if (
|
|
11
|
+
value === null ||
|
|
12
|
+
typeof value === 'string' ||
|
|
13
|
+
typeof value === 'boolean'
|
|
14
|
+
) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === 'number') return Number.isFinite(value)
|
|
18
|
+
if (typeof value === 'undefined') return true
|
|
19
|
+
if (typeof value !== 'object') return false
|
|
20
|
+
if (seen.has(value)) return false
|
|
21
|
+
seen.add(value)
|
|
22
|
+
const valid = Array.isArray(value)
|
|
23
|
+
? value.every((item) => isJSONCompatible(item, seen))
|
|
24
|
+
: Object.getPrototypeOf(value) === Object.prototype &&
|
|
25
|
+
Object.values(value).every((item) => isJSONCompatible(item, seen))
|
|
26
|
+
seen.delete(value)
|
|
27
|
+
return valid
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Whether a value has an AI SDK `UIMessage`'s shape. */
|
|
31
|
+
export const isUIMessage = (value: unknown): value is UIMessage =>
|
|
32
|
+
isRecord(value) &&
|
|
33
|
+
typeof value['id'] === 'string' &&
|
|
34
|
+
(value['role'] === 'user' ||
|
|
35
|
+
value['role'] === 'assistant' ||
|
|
36
|
+
value['role'] === 'system') &&
|
|
37
|
+
Array.isArray(value['parts']) &&
|
|
38
|
+
value['parts'].every(
|
|
39
|
+
(part) => isRecord(part) && typeof part['type'] === 'string',
|
|
40
|
+
) &&
|
|
41
|
+
isJSONCompatible(value)
|
package/src/ai-pi.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
AssistantMessage,
|
|
15
15
|
AssistantMessageEvent,
|
|
16
16
|
AssistantMessageEventStream,
|
|
17
|
+
ImageContent,
|
|
17
18
|
JsonObject,
|
|
18
19
|
TextContent,
|
|
19
20
|
ThinkingContent,
|
|
@@ -50,6 +51,8 @@ import {
|
|
|
50
51
|
gatewayCredential,
|
|
51
52
|
type Harness,
|
|
52
53
|
type HarnessGeneration,
|
|
54
|
+
harnessImages,
|
|
55
|
+
type HarnessInput,
|
|
53
56
|
type HarnessRead,
|
|
54
57
|
type HarnessSandbox,
|
|
55
58
|
type HarnessStep,
|
|
@@ -59,6 +62,7 @@ import {
|
|
|
59
62
|
type RecordedGeneration,
|
|
60
63
|
saveHarness,
|
|
61
64
|
} from './ai-harness.ts'
|
|
65
|
+
import { type SchemaInput, schemaInput } from './ai-tool-input.ts'
|
|
62
66
|
import type { CompactionPartData, ToolResultPayload } from './ai.ts'
|
|
63
67
|
import { boundedCache } from './bounded-cache.ts'
|
|
64
68
|
import { ambientToolScopeStorage } from './internal.ts'
|
|
@@ -110,11 +114,14 @@ type PiModules = {
|
|
|
110
114
|
ai: typeof import('@earendil-works/pi-ai')
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* A content block without the text the transcript already holds. A tool call
|
|
119
|
+
* keeps its arguments only where the transcript's input is not the model's.
|
|
120
|
+
*/
|
|
114
121
|
type StoredBlock =
|
|
115
122
|
| (Omit<TextContent, 'text'> & { ref: string })
|
|
116
123
|
| (Omit<ThinkingContent, 'thinking'> & { ref: string })
|
|
117
|
-
| Omit<ToolCall, 'arguments'>
|
|
124
|
+
| (Omit<ToolCall, 'arguments'> & Partial<Pick<ToolCall, 'arguments'>>)
|
|
118
125
|
|
|
119
126
|
/** What a step's transcript parts do not hold of Pi's response. */
|
|
120
127
|
type StoredResponse = Omit<AssistantMessage, 'content'> & {
|
|
@@ -126,11 +133,14 @@ type PiRecord =
|
|
|
126
133
|
| { call: 'step'; response: StoredResponse }
|
|
127
134
|
| { call: 'compaction'; response: AssistantMessage }
|
|
128
135
|
|
|
136
|
+
/** A user message as Pi takes it. */
|
|
137
|
+
type PiInput = { text: string; images: ImageContent[] }
|
|
138
|
+
|
|
129
139
|
type RecordedCall = {
|
|
130
140
|
kind: 'step' | 'compaction'
|
|
131
141
|
response: AssistantMessage
|
|
132
142
|
/** Steering to queue while serving this call. */
|
|
133
|
-
steers:
|
|
143
|
+
steers: PiInput[]
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
type Saved = {
|
|
@@ -228,7 +238,8 @@ async function runStep(
|
|
|
228
238
|
fail(state, diverged(`call ${state.next} is not a ${kind}`))
|
|
229
239
|
state.next++
|
|
230
240
|
// Steering queued while a response streams enters after its tools.
|
|
231
|
-
for (const text of recorded.steers)
|
|
241
|
+
for (const { text, images } of recorded.steers)
|
|
242
|
+
await session!.steer(text, images.length === 0 ? undefined : images)
|
|
232
243
|
replay(out, recorded.response)
|
|
233
244
|
return
|
|
234
245
|
}
|
|
@@ -248,10 +259,13 @@ async function runStep(
|
|
|
248
259
|
const upstream = realStream(model, context, {
|
|
249
260
|
...options,
|
|
250
261
|
headers: { ...options?.headers, ...gatewayHeaders(credential) },
|
|
262
|
+
...(step.speed === 'fast'
|
|
263
|
+
? { onPayload: fastTier(options?.onPayload) }
|
|
264
|
+
: {}),
|
|
251
265
|
})
|
|
252
266
|
const live = kind === 'step' ? liveStep() : undefined
|
|
253
267
|
for await (const event of upstream) {
|
|
254
|
-
live?.(event)
|
|
268
|
+
await live?.(event)
|
|
255
269
|
if (event.type === 'done' || event.type === 'error') {
|
|
256
270
|
const response = event.type === 'done' ? event.message : event.error
|
|
257
271
|
if (kind === 'compaction') state.compactions.push(response)
|
|
@@ -267,11 +281,39 @@ async function runStep(
|
|
|
267
281
|
}
|
|
268
282
|
}
|
|
269
283
|
|
|
284
|
+
/**
|
|
285
|
+
* A live call's input as Pi's loop, then the AI SDK step, read it. Pi
|
|
286
|
+
* answers a call to no tool, or whose arguments it cannot prepare or
|
|
287
|
+
* validate, itself. The agent's tool reads the arguments Pi accepts through
|
|
288
|
+
* its own schema; A2 runs Pi's own tools from the model's arguments, as
|
|
289
|
+
* Pi's loop does.
|
|
290
|
+
*/
|
|
291
|
+
const callInput = async (block: ToolCall): Promise<SchemaInput> => {
|
|
292
|
+
const definition = tools.find((tool) => tool.name === block.name)
|
|
293
|
+
if (definition === undefined)
|
|
294
|
+
return { success: false, errorText: `Tool ${block.name} not found` }
|
|
295
|
+
let args: unknown
|
|
296
|
+
try {
|
|
297
|
+
const prepared = definition.prepareArguments?.(block.arguments)
|
|
298
|
+
args = modules.ai.validateToolArguments(
|
|
299
|
+
definition,
|
|
300
|
+
prepared === undefined
|
|
301
|
+
? block
|
|
302
|
+
: { ...block, arguments: prepared as JsonObject },
|
|
303
|
+
)
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return { success: false, errorText: asError(error).message }
|
|
306
|
+
}
|
|
307
|
+
if (builtinOf(step.tools, block.name) !== undefined)
|
|
308
|
+
return { success: true, value: block.arguments }
|
|
309
|
+
return schemaInput({ tools: step.tools, toolName: block.name, value: args })
|
|
310
|
+
}
|
|
311
|
+
|
|
270
312
|
/** Streams one live step response as UI chunks and records it. */
|
|
271
313
|
const liveStep = () => {
|
|
272
314
|
const ref = (index: number) => `pi:${step.requestIndex}:${index}`
|
|
273
315
|
const started = new Set<string>()
|
|
274
|
-
return (event: AssistantMessageEvent): void => {
|
|
316
|
+
return async (event: AssistantMessageEvent): Promise<void> => {
|
|
275
317
|
switch (event.type) {
|
|
276
318
|
case 'start':
|
|
277
319
|
emit({ type: 'start-step' })
|
|
@@ -325,6 +367,8 @@ async function runStep(
|
|
|
325
367
|
case 'done':
|
|
326
368
|
case 'error': {
|
|
327
369
|
const response = event.type === 'done' ? event.message : event.error
|
|
370
|
+
// Calls whose transcript input is not the model's arguments.
|
|
371
|
+
const changed = new Set<string>()
|
|
328
372
|
if (event.type === 'done') {
|
|
329
373
|
for (const block of response.content) {
|
|
330
374
|
if (block.type !== 'toolCall') continue
|
|
@@ -335,26 +379,41 @@ async function runStep(
|
|
|
335
379
|
toolName: block.name,
|
|
336
380
|
})
|
|
337
381
|
// Pi does not run the calls of a cut-off response.
|
|
338
|
-
|
|
382
|
+
const input: SchemaInput =
|
|
339
383
|
response.stopReason === 'length'
|
|
340
384
|
? {
|
|
341
|
-
|
|
342
|
-
toolCallId: block.id,
|
|
343
|
-
toolName: block.name,
|
|
344
|
-
input: block.arguments,
|
|
385
|
+
success: false,
|
|
345
386
|
errorText:
|
|
346
387
|
'The response hit the output token limit before this call was complete.',
|
|
347
388
|
}
|
|
348
|
-
:
|
|
389
|
+
: await callInput(block)
|
|
390
|
+
if (
|
|
391
|
+
input.success &&
|
|
392
|
+
JSON.stringify(input.value) !== JSON.stringify(block.arguments)
|
|
393
|
+
)
|
|
394
|
+
changed.add(block.id)
|
|
395
|
+
emit(
|
|
396
|
+
input.success
|
|
397
|
+
? {
|
|
349
398
|
type: 'tool-input-available',
|
|
350
399
|
toolCallId: block.id,
|
|
351
400
|
toolName: block.name,
|
|
401
|
+
input: input.value,
|
|
402
|
+
}
|
|
403
|
+
: {
|
|
404
|
+
type: 'tool-input-error',
|
|
405
|
+
toolCallId: block.id,
|
|
406
|
+
toolName: block.name,
|
|
352
407
|
input: block.arguments,
|
|
408
|
+
errorText: input.errorText,
|
|
353
409
|
},
|
|
354
410
|
)
|
|
355
411
|
}
|
|
356
412
|
}
|
|
357
|
-
record({
|
|
413
|
+
record({
|
|
414
|
+
call: 'step',
|
|
415
|
+
response: storedResponse(response, ref, changed),
|
|
416
|
+
})
|
|
358
417
|
completion = {
|
|
359
418
|
finishReason: finishReason(response.stopReason),
|
|
360
419
|
usage: modelUsage(response.usage),
|
|
@@ -498,9 +557,10 @@ async function runStep(
|
|
|
498
557
|
}
|
|
499
558
|
for (const plan of read.turns) {
|
|
500
559
|
if (step.signal.aborted) return
|
|
560
|
+
const prompt = await piInput({ input: plan.prompt, signal: step.signal })
|
|
501
561
|
const state: TurnState = {
|
|
502
562
|
plan,
|
|
503
|
-
calls: recordedTurn(plan),
|
|
563
|
+
calls: await recordedTurn({ turn: plan, signal: step.signal }),
|
|
504
564
|
next: 0,
|
|
505
565
|
liveStep: false,
|
|
506
566
|
over: false,
|
|
@@ -508,8 +568,9 @@ async function runStep(
|
|
|
508
568
|
compactions: [],
|
|
509
569
|
}
|
|
510
570
|
turn = state
|
|
511
|
-
const running = session.prompt(
|
|
571
|
+
const running = session.prompt(prompt.text, {
|
|
512
572
|
expandPromptTemplates: false,
|
|
573
|
+
...(prompt.images.length === 0 ? {} : { images: prompt.images }),
|
|
513
574
|
})
|
|
514
575
|
if (plan.end !== 'live') {
|
|
515
576
|
await running.catch((error: unknown) => {
|
|
@@ -573,17 +634,43 @@ async function runStep(
|
|
|
573
634
|
// ------------------------------------------------------------ the plan
|
|
574
635
|
|
|
575
636
|
/** A turn's recorded model calls, in the order Pi made them. */
|
|
576
|
-
function recordedTurn(
|
|
637
|
+
async function recordedTurn({
|
|
638
|
+
turn,
|
|
639
|
+
signal,
|
|
640
|
+
}: {
|
|
641
|
+
turn: HarnessTurn
|
|
642
|
+
signal: AbortSignal
|
|
643
|
+
}): Promise<RecordedCall[]> {
|
|
577
644
|
const calls: RecordedCall[] = []
|
|
578
|
-
turn.generations.
|
|
645
|
+
for (const [index, generation] of turn.generations.entries()) {
|
|
579
646
|
calls.push(...recordedCalls(generation))
|
|
580
647
|
const steers = turn.steers.get(index)
|
|
581
648
|
const last = calls.findLast((call) => call.kind === 'step')
|
|
582
|
-
if (steers !== undefined && last !== undefined)
|
|
583
|
-
|
|
649
|
+
if (steers !== undefined && last !== undefined)
|
|
650
|
+
for (const input of steers)
|
|
651
|
+
last.steers.push(await piInput({ input, signal }))
|
|
652
|
+
}
|
|
584
653
|
return calls
|
|
585
654
|
}
|
|
586
655
|
|
|
656
|
+
async function piInput({
|
|
657
|
+
input,
|
|
658
|
+
signal,
|
|
659
|
+
}: {
|
|
660
|
+
input: HarnessInput
|
|
661
|
+
signal: AbortSignal
|
|
662
|
+
}): Promise<PiInput> {
|
|
663
|
+
const images = await harnessImages({ harness: 'pi()', input, signal })
|
|
664
|
+
return {
|
|
665
|
+
text: input.text,
|
|
666
|
+
images: images.map((image) => ({
|
|
667
|
+
type: 'image',
|
|
668
|
+
data: image.data,
|
|
669
|
+
mimeType: image.mediaType,
|
|
670
|
+
})),
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
587
674
|
function recordedCalls(generation: RecordedGeneration): RecordedCall[] {
|
|
588
675
|
const texts = new Map<string, string>()
|
|
589
676
|
const inputs = new Map<string, unknown>()
|
|
@@ -620,6 +707,7 @@ function recordedCalls(generation: RecordedGeneration): RecordedCall[] {
|
|
|
620
707
|
function storedResponse(
|
|
621
708
|
response: AssistantMessage,
|
|
622
709
|
ref: (index: number) => string,
|
|
710
|
+
changed: ReadonlySet<string>,
|
|
623
711
|
): StoredResponse {
|
|
624
712
|
return {
|
|
625
713
|
...response,
|
|
@@ -634,6 +722,7 @@ function storedResponse(
|
|
|
634
722
|
return { ...rest, ref: ref(index) }
|
|
635
723
|
}
|
|
636
724
|
case 'toolCall': {
|
|
725
|
+
if (changed.has(block.id)) return block
|
|
637
726
|
const { arguments: _, ...rest } = block
|
|
638
727
|
return rest
|
|
639
728
|
}
|
|
@@ -653,7 +742,8 @@ function restoredResponse(
|
|
|
653
742
|
if (block.type === 'toolCall')
|
|
654
743
|
return {
|
|
655
744
|
...block,
|
|
656
|
-
arguments:
|
|
745
|
+
arguments:
|
|
746
|
+
block.arguments ?? ((inputs.get(block.id) ?? {}) as JsonObject),
|
|
657
747
|
}
|
|
658
748
|
const { ref, ...rest } = block
|
|
659
749
|
const text = texts.get(ref) ?? ''
|
|
@@ -826,6 +916,15 @@ async function recordedResult(
|
|
|
826
916
|
return { ...kept, details: { ...details, truncation } }
|
|
827
917
|
}
|
|
828
918
|
|
|
919
|
+
/** The Pi tool a name in the agent's tool set runs, unless the agent replaced it. */
|
|
920
|
+
const builtinOf = (tools: ToolSet, name: string): Builtin | undefined =>
|
|
921
|
+
BUILTINS.find(
|
|
922
|
+
(candidate) =>
|
|
923
|
+
candidate === name &&
|
|
924
|
+
Object.hasOwn(tools, name) &&
|
|
925
|
+
tools[name] === builtinTools[name],
|
|
926
|
+
)
|
|
927
|
+
|
|
829
928
|
// Each definition is typed by its parameters; a session takes any of them.
|
|
830
929
|
const untyped = (definition: object) => definition as ToolDefinition
|
|
831
930
|
|
|
@@ -876,32 +975,28 @@ async function sessionTools(
|
|
|
876
975
|
) => Promise<AgentToolResult<unknown>>,
|
|
877
976
|
): Promise<ToolDefinition[]> {
|
|
878
977
|
return Promise.all(
|
|
879
|
-
Object.entries(tools)
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
execute: (toolCallId, _params, signal) =>
|
|
902
|
-
result(toolCallId, signal, 'tool'),
|
|
903
|
-
}
|
|
904
|
-
}),
|
|
978
|
+
Object.entries(tools).map(async ([name, tool]): Promise<ToolDefinition> => {
|
|
979
|
+
const builtin = builtinOf(tools, name)
|
|
980
|
+
if (builtin !== undefined)
|
|
981
|
+
// Pi's own definition, for its prompt; A2 already ran the call.
|
|
982
|
+
return Object.assign(builtinDefinition(modules, builtin, cwd), {
|
|
983
|
+
execute: (
|
|
984
|
+
toolCallId: string,
|
|
985
|
+
_params: unknown,
|
|
986
|
+
signal?: AbortSignal,
|
|
987
|
+
) => result(toolCallId, signal, 'pi'),
|
|
988
|
+
})
|
|
989
|
+
return {
|
|
990
|
+
name,
|
|
991
|
+
label: name,
|
|
992
|
+
description:
|
|
993
|
+
typeof tool.description === 'string' ? tool.description : '',
|
|
994
|
+
parameters: (await asSchema(tool.inputSchema)
|
|
995
|
+
.jsonSchema) as ToolDefinition['parameters'],
|
|
996
|
+
execute: (toolCallId, _params, signal) =>
|
|
997
|
+
result(toolCallId, signal, 'tool'),
|
|
998
|
+
}
|
|
999
|
+
}),
|
|
905
1000
|
)
|
|
906
1001
|
}
|
|
907
1002
|
|
|
@@ -1168,6 +1263,26 @@ function thinkingLevel(step: HarnessStep): {
|
|
|
1168
1263
|
return { thinkingLevel: step.reasoning === 'none' ? 'off' : step.reasoning }
|
|
1169
1264
|
}
|
|
1170
1265
|
|
|
1266
|
+
type PayloadHook = NonNullable<
|
|
1267
|
+
NonNullable<Parameters<ModelRuntime['streamSimple']>[2]>['onPayload']
|
|
1268
|
+
>
|
|
1269
|
+
|
|
1270
|
+
/** Asks AI Gateway for the model's fast tier, on the payload Pi's own hook returns. */
|
|
1271
|
+
function fastTier(inner: PayloadHook | undefined): PayloadHook {
|
|
1272
|
+
return async (payload, model) => {
|
|
1273
|
+
const body = ((await inner?.(payload, model)) ?? payload) as {
|
|
1274
|
+
providerOptions?: { gateway?: object }
|
|
1275
|
+
}
|
|
1276
|
+
return {
|
|
1277
|
+
...body,
|
|
1278
|
+
providerOptions: {
|
|
1279
|
+
...body.providerOptions,
|
|
1280
|
+
gateway: { ...body.providerOptions?.gateway, speed: 'fast' },
|
|
1281
|
+
},
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1171
1286
|
function gatewayHeaders(credential: {
|
|
1172
1287
|
token: string
|
|
1173
1288
|
method: string
|
package/src/ai-stored-state.ts
CHANGED
|
@@ -211,9 +211,7 @@ const storeState = <S extends AIStoredView>(state: S): AIStoredState => {
|
|
|
211
211
|
if (
|
|
212
212
|
value === undefined ||
|
|
213
213
|
(typeof value === 'string' && value.length > 80) ||
|
|
214
|
-
(field !== undefined &&
|
|
215
|
-
payloadFields.has(field) &&
|
|
216
|
-
!(field === 'input' && isRecord(value) && 'message' in value)) ||
|
|
214
|
+
(field !== undefined && payloadFields.has(field)) ||
|
|
217
215
|
(isRecord(value) && Object.hasOwn(value, '$a2Value'))
|
|
218
216
|
)
|
|
219
217
|
return intern(value)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asSchema,
|
|
3
|
+
InvalidToolInputError,
|
|
4
|
+
type ToolSet,
|
|
5
|
+
TypeValidationError,
|
|
6
|
+
} from 'ai'
|
|
7
|
+
|
|
8
|
+
export type SchemaInput =
|
|
9
|
+
{ success: true; value: unknown } | { success: false; errorText: string }
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A call's input through its tool's schema, as the AI SDK step reads it: the
|
|
13
|
+
* schema's output with its defaults, or the AI SDK's error text. A name the
|
|
14
|
+
* tool set does not own has no schema.
|
|
15
|
+
*/
|
|
16
|
+
export async function schemaInput({
|
|
17
|
+
tools,
|
|
18
|
+
toolName,
|
|
19
|
+
value,
|
|
20
|
+
}: {
|
|
21
|
+
tools: ToolSet
|
|
22
|
+
toolName: string
|
|
23
|
+
value: unknown
|
|
24
|
+
}): Promise<SchemaInput> {
|
|
25
|
+
const schema = Object.hasOwn(tools, toolName)
|
|
26
|
+
? asSchema(tools[toolName]!.inputSchema)
|
|
27
|
+
: undefined
|
|
28
|
+
if (schema?.validate === undefined) return { success: true, value }
|
|
29
|
+
let cause: unknown
|
|
30
|
+
try {
|
|
31
|
+
const result = await schema.validate(value)
|
|
32
|
+
if (result.success) return { success: true, value: result.value }
|
|
33
|
+
cause = result.error
|
|
34
|
+
} catch (error) {
|
|
35
|
+
cause = error
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
errorText: new InvalidToolInputError({
|
|
40
|
+
toolName,
|
|
41
|
+
toolInput: JSON.stringify(value),
|
|
42
|
+
cause: TypeValidationError.wrap({ value, cause }),
|
|
43
|
+
}).message,
|
|
44
|
+
}
|
|
45
|
+
}
|