@cerebro-labs/agentnava-sdk 0.46.1 → 0.50.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/README.md +11 -0
- package/agent.d.ts +49 -33
- package/agent.js +41 -43
- package/agent.js.map +1 -1
- package/conversation.d.ts +3 -1
- package/conversation.js +6 -1
- package/conversation.js.map +1 -1
- package/index.d.ts +1 -1
- package/index.js.map +1 -1
- package/load-agent-dir.js +2 -21
- package/load-agent-dir.js.map +1 -1
- package/package.json +1 -1
- package/project.d.ts +1 -1
- package/project.js +11 -1
- package/project.js.map +1 -1
- package/turn.d.ts +6 -2
- package/turn.js +85 -19
- package/turn.js.map +1 -1
- package/types.d.ts +79 -21
- package/types.js +5 -1
- package/types.js.map +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
package/turn.js
CHANGED
|
@@ -20,37 +20,50 @@ import { AgentNavaError } from "./errors.js";
|
|
|
20
20
|
* Frames are `data: {json}` separated by blank lines. Anything else on the wire
|
|
21
21
|
* (comments, retry directives, a stray blank) is ignored rather than treated as
|
|
22
22
|
* an error: a proxy is allowed to insert keep-alive comments and a stream that
|
|
23
|
-
* died because of one would be maddening to debug.
|
|
24
|
-
|
|
23
|
+
* died because of one would be maddening to debug.
|
|
24
|
+
*
|
|
25
|
+
* `turnId` is the turn the API named in its X-Turn-ID header. It becomes the
|
|
26
|
+
* first event, `start`, yielded inside the same cleanup as the rest, so a caller
|
|
27
|
+
* who stops right there still releases the connection. */
|
|
28
|
+
export async function* parseSSE(body, turnId) {
|
|
25
29
|
const reader = body.getReader();
|
|
26
30
|
const decoder = new TextDecoder();
|
|
27
31
|
let buffer = '';
|
|
28
32
|
let completed = false;
|
|
29
33
|
let assembled = '';
|
|
30
34
|
let finalReply;
|
|
35
|
+
let finalWidgets;
|
|
31
36
|
let terminal = false;
|
|
32
|
-
const
|
|
37
|
+
const steps = new Map();
|
|
38
|
+
const eventsForLine = (line) => {
|
|
33
39
|
const wire = parseLine(line);
|
|
34
40
|
if (!wire)
|
|
35
|
-
return
|
|
41
|
+
return [];
|
|
36
42
|
// Text being final does not prove the turn completed. Persistence or the
|
|
37
43
|
// observer can still fail before the runtime's terminal done marker.
|
|
38
44
|
if (wire.type === 'message-end') {
|
|
39
45
|
finalReply = typeof wire.final === 'string' ? wire.final : '';
|
|
40
|
-
|
|
46
|
+
const widgets = Array.isArray(wire.widgets) ? wire.widgets.flatMap((w) => widgetOf(w) ?? []) : [];
|
|
47
|
+
finalWidgets = widgets.length > 0 ? widgets : undefined;
|
|
48
|
+
return [];
|
|
41
49
|
}
|
|
42
50
|
if (wire.type === 'done') {
|
|
43
51
|
terminal = true;
|
|
44
|
-
return { type: 'done', reply: finalReply ?? assembled };
|
|
52
|
+
return [{ type: 'done', reply: finalReply ?? assembled, ...(finalWidgets && { widgets: finalWidgets }) }];
|
|
45
53
|
}
|
|
46
54
|
const event = translate(wire);
|
|
47
|
-
if (event
|
|
55
|
+
if (!event)
|
|
56
|
+
return [];
|
|
57
|
+
if (event.type === 'text')
|
|
48
58
|
assembled += event.text;
|
|
49
|
-
if (event
|
|
59
|
+
if (event.type === 'error')
|
|
50
60
|
terminal = true;
|
|
51
|
-
|
|
61
|
+
const progress = event.type === 'tool' ? trackStep(steps, wire) : undefined;
|
|
62
|
+
return progress ? [event, progress] : [event];
|
|
52
63
|
};
|
|
53
64
|
try {
|
|
65
|
+
if (turnId)
|
|
66
|
+
yield { type: 'start', turnId };
|
|
54
67
|
while (true) {
|
|
55
68
|
const { done, value } = await reader.read();
|
|
56
69
|
if (done) {
|
|
@@ -64,8 +77,7 @@ export async function* parseSSE(body) {
|
|
|
64
77
|
while (nl !== -1) {
|
|
65
78
|
const line = buffer.slice(0, nl).replace(/\r$/, '');
|
|
66
79
|
buffer = buffer.slice(nl + 1);
|
|
67
|
-
const event
|
|
68
|
-
if (event)
|
|
80
|
+
for (const event of eventsForLine(line))
|
|
69
81
|
yield event;
|
|
70
82
|
if (terminal)
|
|
71
83
|
return;
|
|
@@ -73,9 +85,8 @@ export async function* parseSSE(body) {
|
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
// A final frame with no trailing newline still counts.
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
yield last;
|
|
88
|
+
for (const event of eventsForLine(buffer.replace(/\r$/, '')))
|
|
89
|
+
yield event;
|
|
79
90
|
if (!terminal)
|
|
80
91
|
yield { type: 'error', message: 'The response stream ended before the turn finished.', retryable: false };
|
|
81
92
|
}
|
|
@@ -104,6 +115,50 @@ export async function* parseSSE(body) {
|
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
}
|
|
118
|
+
/** A widget off the wire, or undefined for anything that is not one. */
|
|
119
|
+
function widgetOf(raw) {
|
|
120
|
+
const w = raw;
|
|
121
|
+
if (!w || typeof w.surfaceId !== 'string' || !Array.isArray(w.components))
|
|
122
|
+
return undefined;
|
|
123
|
+
return {
|
|
124
|
+
surfaceId: w.surfaceId,
|
|
125
|
+
at: typeof w.at === 'number' && w.at >= 0 ? w.at : 0,
|
|
126
|
+
components: w.components,
|
|
127
|
+
closed: w.closed !== false,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const STEP_KINDS = new Set(['read', 'write', 'edit', 'search', 'shell', 'web', 'data', 'task', 'plan', 'other']);
|
|
131
|
+
/**
|
|
132
|
+
* Update the turn's steps from one tool event, and return the whole list when
|
|
133
|
+
* it changed.
|
|
134
|
+
*
|
|
135
|
+
* The steps are the ones the portal's Working card shows: one per tool call a
|
|
136
|
+
* person should see, in the order they started, in the words the API put on
|
|
137
|
+
* the call. The API's labels come from the same mapping the portal uses
|
|
138
|
+
* (packages/backend-contract/src/tool-steps.ts), so a developer's own app and
|
|
139
|
+
* the portal list the same steps in the same words.
|
|
140
|
+
*
|
|
141
|
+
* Built from tool events on every runtime. The runtime's own `progress`
|
|
142
|
+
* snapshot, which only some runtimes send, is not used, so a turn never
|
|
143
|
+
* reports two different lists.
|
|
144
|
+
*
|
|
145
|
+
* An API from before labels sends none. A tool's identifier is not something
|
|
146
|
+
* to show a person, so such a turn has no steps rather than raw names.
|
|
147
|
+
*/
|
|
148
|
+
function trackStep(steps, wire) {
|
|
149
|
+
if (wire.step === false || !wire.label || !wire.toolId)
|
|
150
|
+
return undefined;
|
|
151
|
+
const id = wire.toolId;
|
|
152
|
+
const known = steps.get(id);
|
|
153
|
+
if (wire.type === 'tool-start' && known)
|
|
154
|
+
return undefined;
|
|
155
|
+
// A kind this version does not know yet still gets a step, as 'other'.
|
|
156
|
+
const kind = STEP_KINDS.has(wire.kind) ? wire.kind : 'other';
|
|
157
|
+
const status = wire.type === 'tool-start' ? 'active' : wire.ok === false ? 'error' : 'done';
|
|
158
|
+
steps.set(id, known ? { ...known, status } : { id, label: wire.label, kind, status });
|
|
159
|
+
// Copies, so a caller who keeps one list and edits it cannot change the next.
|
|
160
|
+
return { type: 'progress', steps: [...steps.values()].map((step) => ({ ...step })) };
|
|
161
|
+
}
|
|
107
162
|
/**
|
|
108
163
|
* The API's event vocabulary, as the SDK's.
|
|
109
164
|
*
|
|
@@ -119,6 +174,7 @@ export async function* parseSSE(body) {
|
|
|
119
174
|
*
|
|
120
175
|
* Returns undefined for a frame with nothing to report, including an event type
|
|
121
176
|
* this version does not know. A new event must never break an old client.
|
|
177
|
+
* Steps (`progress`) are built beside this, from the tool events (trackStep).
|
|
122
178
|
*/
|
|
123
179
|
function translate(wire) {
|
|
124
180
|
switch (wire.type) {
|
|
@@ -128,14 +184,23 @@ function translate(wire) {
|
|
|
128
184
|
? { type: 'text', text: wire.delta }
|
|
129
185
|
: undefined;
|
|
130
186
|
case 'tool-start':
|
|
131
|
-
return { type: 'tool', name: wire.name ?? 'tool', state: 'start' };
|
|
187
|
+
return { type: 'tool', name: wire.name ?? 'tool', state: 'start', ...(wire.label && { label: wire.label }) };
|
|
132
188
|
case 'tool-end':
|
|
133
189
|
return {
|
|
134
190
|
type: 'tool',
|
|
135
191
|
name: wire.name ?? 'tool',
|
|
136
192
|
state: 'end',
|
|
193
|
+
...(wire.label && { label: wire.label }),
|
|
137
194
|
...(wire.ok !== undefined && { ok: wire.ok }),
|
|
138
195
|
};
|
|
196
|
+
case 'widget': {
|
|
197
|
+
const widget = widgetOf(wire.widget);
|
|
198
|
+
return widget ? { type: 'widget', widget } : undefined;
|
|
199
|
+
}
|
|
200
|
+
case 'file':
|
|
201
|
+
// A file the agent wrote in the conversation's drive. Its path is what
|
|
202
|
+
// conversation.readFile() takes.
|
|
203
|
+
return typeof wire.path === 'string' && wire.path ? { type: 'file', path: wire.path } : undefined;
|
|
139
204
|
case 'error':
|
|
140
205
|
return {
|
|
141
206
|
type: 'error',
|
|
@@ -217,7 +282,7 @@ class TurnImpl {
|
|
|
217
282
|
}
|
|
218
283
|
this.consumed = how;
|
|
219
284
|
}
|
|
220
|
-
async
|
|
285
|
+
async open() {
|
|
221
286
|
const res = await this.response;
|
|
222
287
|
if (!res.body) {
|
|
223
288
|
throw new AgentNavaError({
|
|
@@ -226,13 +291,13 @@ class TurnImpl {
|
|
|
226
291
|
status: res.status,
|
|
227
292
|
});
|
|
228
293
|
}
|
|
229
|
-
return res.body;
|
|
294
|
+
return { body: res.body, turnId: res.headers.get('x-turn-id') };
|
|
230
295
|
}
|
|
231
296
|
/** Drain the stream and resolve to the final reply. */
|
|
232
297
|
async settle() {
|
|
233
298
|
// parseSSE withholds the final answer until wire done confirms completion.
|
|
234
299
|
// EOF is an interruption even if some or all of the reply already arrived.
|
|
235
|
-
for await (const event of parseSSE(await this.
|
|
300
|
+
for await (const event of parseSSE((await this.open()).body)) {
|
|
236
301
|
if (event.type === 'done')
|
|
237
302
|
return event.reply;
|
|
238
303
|
if (event.type === 'error') {
|
|
@@ -279,7 +344,8 @@ class TurnImpl {
|
|
|
279
344
|
}
|
|
280
345
|
async *[Symbol.asyncIterator]() {
|
|
281
346
|
this.claim('iterated');
|
|
282
|
-
|
|
347
|
+
const { body, turnId } = await this.open();
|
|
348
|
+
for await (const event of parseSSE(body, turnId)) {
|
|
283
349
|
yield event.type === 'error' && this.recoveryMessage
|
|
284
350
|
? { ...event, message: `${event.message} ${this.recoveryMessage}` }
|
|
285
351
|
: event;
|
package/turn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn.js","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"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","sourcesContent":["// One turn: `Promise<string> & AsyncIterable<TurnEvent>`.\n//\n// A caller who wants the answer writes `await conversation.ask('...')`. A caller\n// who wants to render it as it arrives writes `for await (const e of\n// conversation.ask('...'))`. One method, two shapes, because making the caller\n// choose `ask` or `askStream` up front means changing the call site later just\n// to show a spinner.\n//\n// THE MESSAGE IS SENT WHEN ask() IS CALLED, not when the result is awaited or\n// iterated. A lazy promise would mean `const t = ask(...)` did nothing and\n// `await t` twice asked twice; sending eagerly makes `ask` the verb it reads as,\n// and calling it twice is then obviously two questions.\n//\n// Awaiting AND iterating the same turn throws rather than replaying. The events\n// are a live stream over a socket, not a stored list, so a second consumer would\n// silently receive nothing at all, which is far worse than an error.\n\nimport { AgentNavaError } from './errors.ts';\nimport type { TurnEvent } from './types.ts';\n\n/** What `conversation.ask()` returns. */\nexport type Turn = Promise<string> & AsyncIterable<TurnEvent>;\n\n/** Parse an SSE body into the events the API sends.\n *\n * Frames are `data: {json}` separated by blank lines. Anything else on the wire\n * (comments, retry directives, a stray blank) is ignored rather than treated as\n * an error: a proxy is allowed to insert keep-alive comments and a stream that\n * died because of one would be maddening to debug. */\nexport async function* parseSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<TurnEvent> {\n\tconst reader = body.getReader();\n\tconst decoder = new TextDecoder();\n\tlet buffer = '';\n\tlet completed = false;\n\tlet assembled = '';\n\tlet finalReply: string | undefined;\n\tlet terminal = false;\n\tconst eventForLine = (line: string): TurnEvent | undefined => {\n\t\tconst wire = parseLine(line);\n\t\tif (!wire) return undefined;\n\t\t// Text being final does not prove the turn completed. Persistence or the\n\t\t// observer can still fail before the runtime's terminal done marker.\n\t\tif (wire.type === 'message-end') {\n\t\t\tfinalReply = typeof wire.final === 'string' ? wire.final : '';\n\t\t\treturn undefined;\n\t\t}\n\t\tif (wire.type === 'done') {\n\t\t\tterminal = true;\n\t\t\treturn { type: 'done', reply: finalReply ?? assembled };\n\t\t}\n\t\tconst event = translate(wire);\n\t\tif (event?.type === 'text') assembled += event.text;\n\t\tif (event?.type === 'error') terminal = true;\n\t\treturn event;\n\t};\n\ttry {\n\t\twhile (true) {\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (done) {\n\t\t\t\tcompleted = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t// Split on newlines, keeping the last partial line in the buffer. A\n\t\t\t// frame can be split across two network reads at any byte.\n\t\t\tlet nl = buffer.indexOf('\\n');\n\t\t\twhile (nl !== -1) {\n\t\t\t\tconst line = buffer.slice(0, nl).replace(/\\r$/, '');\n\t\t\t\tbuffer = buffer.slice(nl + 1);\n\t\t\t\tconst event = eventForLine(line);\n\t\t\t\tif (event) yield event;\n\t\t\t\tif (terminal) return;\n\t\t\t\tnl = buffer.indexOf('\\n');\n\t\t\t}\n\t\t}\n\t\t// A final frame with no trailing newline still counts.\n\t\tconst last = eventForLine(buffer.replace(/\\r$/, ''));\n\t\tif (last) yield last;\n\t\tif (!terminal) yield { type: 'error', message: 'The response stream ended before the turn finished.', retryable: false };\n\t} catch (error) {\n\t\t// Explicit cancellation keeps its AbortError identity. A lost transport\n\t\t// is an interrupted observer, never evidence that asking again is safe.\n\t\tif (error instanceof Error && error.name === 'AbortError') throw error;\n\t\tyield { type: 'error', message: `The response stream was interrupted: ${error instanceof Error ? error.message : String(error)}`, retryable: false };\n\t} finally {\n\t\t// A final reply, a wire error, or a caller's early break can stop before\n\t\t// EOF. Releasing the lock alone leaves the HTTP body and abort listener\n\t\t// alive; cancel unread data before releasing ownership of the reader.\n\t\ttry {\n\t\t\t// Cleanup can be slow or never settle. Initiate it before releasing\n\t\t\t// ownership, but do not delay the reply/error or iterator return.\n\t\t\tif (!completed) void reader.cancel().catch(() => {});\n\t\t} catch {\n\t\t\t// Cleanup must not replace the reply or the original stream/wire error.\n\t\t} finally {\n\t\t\treader.releaseLock();\n\t\t}\n\t}\n}\n\n/** One frame as it arrives on the wire, before translation. */\ninterface WireEvent {\n\ttype: string;\n\tdelta?: string;\n\tfinal?: string;\n\tname?: string;\n\tok?: boolean;\n\tmessage?: string;\n\tretryable?: boolean;\n\tpath?: string;\n}\n\n/**\n * The API's event vocabulary, as the SDK's.\n *\n * The wire says `message-delta` / `message-end` / `tool-start` / `tool-end`, and\n * design/sdk-design.md says `text` / `done` / `tool`. Both are right for their\n * own audience: the wire shape is what apps/web's chat renders and has rendered\n * for a long time, and the SDK shape is what reads well at a call site\n * (`if (e.type === 'text')`).\n *\n * So this translates, which is the same job the SDK already does for drive\n * entries and connection rows. It is NOT a rename waiting to happen: changing\n * the wire would break a working chat UI to save a switch statement.\n *\n * Returns undefined for a frame with nothing to report, including an event type\n * this version does not know. A new event must never break an old client.\n */\nfunction translate(wire: WireEvent): TurnEvent | undefined {\n\tswitch (wire.type) {\n\t\tcase 'message-delta':\n\t\t\t// A FRAGMENT. Append, do not assemble.\n\t\t\treturn typeof wire.delta === 'string' && wire.delta.length > 0\n\t\t\t\t? { type: 'text', text: wire.delta }\n\t\t\t\t: undefined;\n\t\tcase 'tool-start':\n\t\t\treturn { type: 'tool', name: wire.name ?? 'tool', state: 'start' };\n\t\tcase 'tool-end':\n\t\t\treturn {\n\t\t\t\ttype: 'tool',\n\t\t\t\tname: wire.name ?? 'tool',\n\t\t\t\tstate: 'end',\n\t\t\t\t...(wire.ok !== undefined && { ok: wire.ok }),\n\t\t\t};\n\t\tcase 'error':\n\t\t\treturn {\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: wire.message ?? 'the turn failed',\n\t\t\t\tretryable: wire.retryable === true,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction parseLine(line: string): WireEvent | undefined {\n\tif (!line.startsWith('data:')) return undefined;\n\tconst payload = line.slice(5).trim();\n\tif (!payload || payload === '[DONE]') return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(payload) as WireEvent | null;\n\t\treturn parsed && typeof parsed.type === 'string' ? parsed : undefined;\n\t} catch {\n\t\t// A frame we cannot parse is dropped rather than thrown. A malformed frame\n\t\t// must not break a stream that is otherwise fine.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Build the dual-shaped result.\n *\n * `response` is already in flight when this is called: the caller passes the\n * running request, so the turn is sent whether or not anyone ever consumes it.\n *\n * A THENABLE, not a Promise subclass, and that was measured rather than assumed.\n * A subclass whose base promise never settles deadlocks, because `await` can\n * settle against the base's own state instead of the overridden `then`. A\n * subclass whose base resolves immediately fixes the deadlock and passes\n * `instanceof Promise`, but then anything reading the promise's INTERNAL state\n * sees \"resolved\" for a turn whose `then` rejects, which is a lie that tooling\n * believes. A thenable is honest: `await`, `Promise.all` and `.then/.catch/\n * .finally` are all defined in terms of `then` and all work, and anything that\n * demands a real Promise says so out loud instead of reporting a wrong answer.\n */\nclass TurnImpl implements AsyncIterable<TurnEvent> {\n\tprivate consumed?: 'awaited' | 'iterated';\n\tprivate settled?: Promise<string>;\n\n\tconstructor(private readonly response: Promise<Response>, private readonly recoveryMessage = '') {\n\t\t// ask() starts eagerly, while consuming its thenable may happen later.\n\t\t// Observe early rejection now; keep the ORIGINAL promise so a later\n\t\t// await/iteration still receives that same error.\n\t\tvoid response.catch(() => {});\n\t}\n\n\t/**\n\t * Record how this turn is being consumed, and refuse a second, different way.\n\t *\n\t * AWAITING TWICE IS ALLOWED, deliberately. A promise handing the same value to\n\t * two awaits is exactly what a promise is, and `drain()` memoises so the second\n\t * await costs nothing. Refusing it would break `Promise.all([turn, turn])` and\n\t * every library that attaches a `.catch()` and then awaits the same value.\n\t *\n\t * ITERATING TWICE IS NOT, and neither is mixing the two. The events are a live\n\t * stream over a socket, not a stored list: a second iterator would receive\n\t * nothing at all and report an empty turn, which is worse than an error.\n\t */\n\tprivate claim(how: 'awaited' | 'iterated'): void {\n\t\tif (how === 'awaited' && this.consumed === 'awaited') return;\n\t\tif (this.consumed) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\tthis.consumed === how\n\t\t\t\t\t\t? `This turn was already ${how}. Call ask() again to ask again; a turn is a live stream, not a replayable list.`\n\t\t\t\t\t\t: `This turn was already ${this.consumed} and cannot also be ${how}. Pick one: await it for the reply, or iterate it for the events. Call ask() again to ask again.`,\n\t\t\t\tcode: 'turn_already_consumed',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\tthis.consumed = how;\n\t}\n\n\tprivate async body(): Promise<ReadableStream<Uint8Array>> {\n\t\tconst res = await this.response;\n\t\tif (!res.body) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: `The turn response had no body to stream. ${this.recoveryMessage}`.trim(),\n\t\t\t\tcode: 'bad_response',\n\t\t\t\tstatus: res.status,\n\t\t\t});\n\t\t}\n\t\treturn res.body;\n\t}\n\n\t/** Drain the stream and resolve to the final reply. */\n\tprivate async settle(): Promise<string> {\n\t\t// parseSSE withholds the final answer until wire done confirms completion.\n\t\t// EOF is an interruption even if some or all of the reply already arrived.\n\t\tfor await (const event of parseSSE(await this.body())) {\n\t\t\tif (event.type === 'done') return event.reply;\n\t\t\tif (event.type === 'error') {\n\t\t\t\t// Awaiting is the shape that throws. Iterating gets the error as an\n\t\t\t\t// event and the loop ends, because a for-await that throws mid-render\n\t\t\t\t// leaves the UI half-painted.\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `${event.message} ${this.recoveryMessage}`.trim(),\n\t\t\t\t\tcode: 'turn_failed',\n\t\t\t\t\tstatus: event.retryable ? 503 : 400,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthrow new AgentNavaError({ message: `The response stream ended before the turn finished. ${this.recoveryMessage}`.trim(), code: 'turn_failed', status: 400 });\n\t}\n\n\tprivate drain(): Promise<string> {\n\t\t// claim() runs HERE rather than inside settle(), because settle() runs only\n\t\t// once: a second await would skip the check entirely, and awaiting a turn\n\t\t// that was already iterated would quietly return an empty string instead of\n\t\t// saying what went wrong.\n\t\t//\n\t\t// REJECT, never throw. `then` must return a promise; a synchronous throw out\n\t\t// of it escapes the promise chain, so a caller writing\n\t\t// `turn.then(a, b).catch(c)` would get an exception past their own .catch.\n\t\ttry {\n\t\t\tthis.claim('awaited');\n\t\t} catch (err) {\n\t\t\treturn Promise.reject(err);\n\t\t}\n\t\t// Started once. `await` calls `then`, and so does Promise.all, and so does\n\t\t// anything that duck-types a thenable; all of them share one drain.\n\t\tthis.settled ??= this.settle();\n\t\treturn this.settled;\n\t}\n\n\tthen<A = string, B = never>(\n\t\tonOk?: ((value: string) => A | PromiseLike<A>) | null,\n\t\tonErr?: ((reason: unknown) => B | PromiseLike<B>) | null,\n\t): Promise<A | B> {\n\t\treturn this.drain().then(onOk, onErr);\n\t}\n\n\tcatch<B = never>(onErr?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<string | B> {\n\t\treturn this.drain().catch(onErr);\n\t}\n\n\tfinally(onDone?: (() => void) | null): Promise<string> {\n\t\treturn this.drain().finally(onDone);\n\t}\n\n\tasync *[Symbol.asyncIterator](): AsyncGenerator<TurnEvent> {\n\t\tthis.claim('iterated');\n\t\tfor await (const event of parseSSE(await this.body())) {\n\t\t\tyield event.type === 'error' && this.recoveryMessage\n\t\t\t\t? { ...event, message: `${event.message} ${this.recoveryMessage}` }\n\t\t\t\t: event;\n\t\t}\n\t}\n}\n\nexport function makeTurn(response: Promise<Response>, recoveryMessage = ''): Turn {\n\treturn new TurnImpl(response, recoveryMessage) as unknown as Turn;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"turn.js","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"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","sourcesContent":["// One turn: `Promise<string> & AsyncIterable<TurnEvent>`.\n//\n// A caller who wants the answer writes `await conversation.ask('...')`. A caller\n// who wants to render it as it arrives writes `for await (const e of\n// conversation.ask('...'))`. One method, two shapes, because making the caller\n// choose `ask` or `askStream` up front means changing the call site later just\n// to show a spinner.\n//\n// THE MESSAGE IS SENT WHEN ask() IS CALLED, not when the result is awaited or\n// iterated. A lazy promise would mean `const t = ask(...)` did nothing and\n// `await t` twice asked twice; sending eagerly makes `ask` the verb it reads as,\n// and calling it twice is then obviously two questions.\n//\n// Awaiting AND iterating the same turn throws rather than replaying. The events\n// are a live stream over a socket, not a stored list, so a second consumer would\n// silently receive nothing at all, which is far worse than an error.\n\nimport { AgentNavaError } from './errors.ts';\nimport type { Step, TurnEvent, Widget } from './types.ts';\n\n/** What `conversation.ask()` returns. */\nexport type Turn = Promise<string> & AsyncIterable<TurnEvent>;\n\n/** Parse an SSE body into the events the API sends.\n *\n * Frames are `data: {json}` separated by blank lines. Anything else on the wire\n * (comments, retry directives, a stray blank) is ignored rather than treated as\n * an error: a proxy is allowed to insert keep-alive comments and a stream that\n * died because of one would be maddening to debug.\n *\n * `turnId` is the turn the API named in its X-Turn-ID header. It becomes the\n * first event, `start`, yielded inside the same cleanup as the rest, so a caller\n * who stops right there still releases the connection. */\nexport async function* parseSSE(body: ReadableStream<Uint8Array>, turnId?: string | null): AsyncGenerator<TurnEvent> {\n\tconst reader = body.getReader();\n\tconst decoder = new TextDecoder();\n\tlet buffer = '';\n\tlet completed = false;\n\tlet assembled = '';\n\tlet finalReply: string | undefined;\n\tlet finalWidgets: Widget[] | undefined;\n\tlet terminal = false;\n\tconst steps = new Map<string, Step>();\n\tconst eventsForLine = (line: string): TurnEvent[] => {\n\t\tconst wire = parseLine(line);\n\t\tif (!wire) return [];\n\t\t// Text being final does not prove the turn completed. Persistence or the\n\t\t// observer can still fail before the runtime's terminal done marker.\n\t\tif (wire.type === 'message-end') {\n\t\t\tfinalReply = typeof wire.final === 'string' ? wire.final : '';\n\t\t\tconst widgets = Array.isArray(wire.widgets) ? wire.widgets.flatMap((w) => widgetOf(w) ?? []) : [];\n\t\t\tfinalWidgets = widgets.length > 0 ? widgets : undefined;\n\t\t\treturn [];\n\t\t}\n\t\tif (wire.type === 'done') {\n\t\t\tterminal = true;\n\t\t\treturn [{ type: 'done', reply: finalReply ?? assembled, ...(finalWidgets && { widgets: finalWidgets }) }];\n\t\t}\n\t\tconst event = translate(wire);\n\t\tif (!event) return [];\n\t\tif (event.type === 'text') assembled += event.text;\n\t\tif (event.type === 'error') terminal = true;\n\t\tconst progress = event.type === 'tool' ? trackStep(steps, wire) : undefined;\n\t\treturn progress ? [event, progress] : [event];\n\t};\n\ttry {\n\t\tif (turnId) yield { type: 'start', turnId };\n\t\twhile (true) {\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (done) {\n\t\t\t\tcompleted = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t// Split on newlines, keeping the last partial line in the buffer. A\n\t\t\t// frame can be split across two network reads at any byte.\n\t\t\tlet nl = buffer.indexOf('\\n');\n\t\t\twhile (nl !== -1) {\n\t\t\t\tconst line = buffer.slice(0, nl).replace(/\\r$/, '');\n\t\t\t\tbuffer = buffer.slice(nl + 1);\n\t\t\t\tfor (const event of eventsForLine(line)) yield event;\n\t\t\t\tif (terminal) return;\n\t\t\t\tnl = buffer.indexOf('\\n');\n\t\t\t}\n\t\t}\n\t\t// A final frame with no trailing newline still counts.\n\t\tfor (const event of eventsForLine(buffer.replace(/\\r$/, ''))) yield event;\n\t\tif (!terminal) yield { type: 'error', message: 'The response stream ended before the turn finished.', retryable: false };\n\t} catch (error) {\n\t\t// Explicit cancellation keeps its AbortError identity. A lost transport\n\t\t// is an interrupted observer, never evidence that asking again is safe.\n\t\tif (error instanceof Error && error.name === 'AbortError') throw error;\n\t\tyield { type: 'error', message: `The response stream was interrupted: ${error instanceof Error ? error.message : String(error)}`, retryable: false };\n\t} finally {\n\t\t// A final reply, a wire error, or a caller's early break can stop before\n\t\t// EOF. Releasing the lock alone leaves the HTTP body and abort listener\n\t\t// alive; cancel unread data before releasing ownership of the reader.\n\t\ttry {\n\t\t\t// Cleanup can be slow or never settle. Initiate it before releasing\n\t\t\t// ownership, but do not delay the reply/error or iterator return.\n\t\t\tif (!completed) void reader.cancel().catch(() => {});\n\t\t} catch {\n\t\t\t// Cleanup must not replace the reply or the original stream/wire error.\n\t\t} finally {\n\t\t\treader.releaseLock();\n\t\t}\n\t}\n}\n\n/** One frame as it arrives on the wire, before translation. */\ninterface WireEvent {\n\ttype: string;\n\tdelta?: string;\n\tfinal?: string;\n\ttoolId?: string;\n\tname?: string;\n\t/** What a person sees for the tool call (\"Searching the web: <query>\"). */\n\tlabel?: string;\n\tkind?: string;\n\t/** False for a call that is not a step a person should see. */\n\tstep?: boolean;\n\tok?: boolean;\n\tmessage?: string;\n\tretryable?: boolean;\n\tpath?: string;\n\t/** A widget drawn inside the reply (`widget`), or the final list (`message-end`). */\n\twidget?: unknown;\n\twidgets?: unknown[];\n}\n\n/** A widget off the wire, or undefined for anything that is not one. */\nfunction widgetOf(raw: unknown): Widget | undefined {\n\tconst w = raw as Partial<Widget> | null;\n\tif (!w || typeof w.surfaceId !== 'string' || !Array.isArray(w.components)) return undefined;\n\treturn {\n\t\tsurfaceId: w.surfaceId,\n\t\tat: typeof w.at === 'number' && w.at >= 0 ? w.at : 0,\n\t\tcomponents: w.components,\n\t\tclosed: w.closed !== false,\n\t};\n}\n\nconst STEP_KINDS = new Set<Step['kind']>(['read', 'write', 'edit', 'search', 'shell', 'web', 'data', 'task', 'plan', 'other']);\n\n/**\n * Update the turn's steps from one tool event, and return the whole list when\n * it changed.\n *\n * The steps are the ones the portal's Working card shows: one per tool call a\n * person should see, in the order they started, in the words the API put on\n * the call. The API's labels come from the same mapping the portal uses\n * (packages/backend-contract/src/tool-steps.ts), so a developer's own app and\n * the portal list the same steps in the same words.\n *\n * Built from tool events on every runtime. The runtime's own `progress`\n * snapshot, which only some runtimes send, is not used, so a turn never\n * reports two different lists.\n *\n * An API from before labels sends none. A tool's identifier is not something\n * to show a person, so such a turn has no steps rather than raw names.\n */\nfunction trackStep(steps: Map<string, Step>, wire: WireEvent): TurnEvent | undefined {\n\tif (wire.step === false || !wire.label || !wire.toolId) return undefined;\n\tconst id = wire.toolId;\n\tconst known = steps.get(id);\n\tif (wire.type === 'tool-start' && known) return undefined;\n\t// A kind this version does not know yet still gets a step, as 'other'.\n\tconst kind = STEP_KINDS.has(wire.kind as Step['kind']) ? (wire.kind as Step['kind']) : 'other';\n\tconst status: Step['status'] = wire.type === 'tool-start' ? 'active' : wire.ok === false ? 'error' : 'done';\n\tsteps.set(id, known ? { ...known, status } : { id, label: wire.label, kind, status });\n\t// Copies, so a caller who keeps one list and edits it cannot change the next.\n\treturn { type: 'progress', steps: [...steps.values()].map((step) => ({ ...step })) };\n}\n\n/**\n * The API's event vocabulary, as the SDK's.\n *\n * The wire says `message-delta` / `message-end` / `tool-start` / `tool-end`, and\n * design/sdk-design.md says `text` / `done` / `tool`. Both are right for their\n * own audience: the wire shape is what apps/web's chat renders and has rendered\n * for a long time, and the SDK shape is what reads well at a call site\n * (`if (e.type === 'text')`).\n *\n * So this translates, which is the same job the SDK already does for drive\n * entries and connection rows. It is NOT a rename waiting to happen: changing\n * the wire would break a working chat UI to save a switch statement.\n *\n * Returns undefined for a frame with nothing to report, including an event type\n * this version does not know. A new event must never break an old client.\n * Steps (`progress`) are built beside this, from the tool events (trackStep).\n */\nfunction translate(wire: WireEvent): TurnEvent | undefined {\n\tswitch (wire.type) {\n\t\tcase 'message-delta':\n\t\t\t// A FRAGMENT. Append, do not assemble.\n\t\t\treturn typeof wire.delta === 'string' && wire.delta.length > 0\n\t\t\t\t? { type: 'text', text: wire.delta }\n\t\t\t\t: undefined;\n\t\tcase 'tool-start':\n\t\t\treturn { type: 'tool', name: wire.name ?? 'tool', state: 'start', ...(wire.label && { label: wire.label }) };\n\t\tcase 'tool-end':\n\t\t\treturn {\n\t\t\t\ttype: 'tool',\n\t\t\t\tname: wire.name ?? 'tool',\n\t\t\t\tstate: 'end',\n\t\t\t\t...(wire.label && { label: wire.label }),\n\t\t\t\t...(wire.ok !== undefined && { ok: wire.ok }),\n\t\t\t};\n\t\tcase 'widget': {\n\t\t\tconst widget = widgetOf(wire.widget);\n\t\t\treturn widget ? { type: 'widget', widget } : undefined;\n\t\t}\n\t\tcase 'file':\n\t\t\t// A file the agent wrote in the conversation's drive. Its path is what\n\t\t\t// conversation.readFile() takes.\n\t\t\treturn typeof wire.path === 'string' && wire.path ? { type: 'file', path: wire.path } : undefined;\n\t\tcase 'error':\n\t\t\treturn {\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: wire.message ?? 'the turn failed',\n\t\t\t\tretryable: wire.retryable === true,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction parseLine(line: string): WireEvent | undefined {\n\tif (!line.startsWith('data:')) return undefined;\n\tconst payload = line.slice(5).trim();\n\tif (!payload || payload === '[DONE]') return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(payload) as WireEvent | null;\n\t\treturn parsed && typeof parsed.type === 'string' ? parsed : undefined;\n\t} catch {\n\t\t// A frame we cannot parse is dropped rather than thrown. A malformed frame\n\t\t// must not break a stream that is otherwise fine.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Build the dual-shaped result.\n *\n * `response` is already in flight when this is called: the caller passes the\n * running request, so the turn is sent whether or not anyone ever consumes it.\n *\n * A THENABLE, not a Promise subclass, and that was measured rather than assumed.\n * A subclass whose base promise never settles deadlocks, because `await` can\n * settle against the base's own state instead of the overridden `then`. A\n * subclass whose base resolves immediately fixes the deadlock and passes\n * `instanceof Promise`, but then anything reading the promise's INTERNAL state\n * sees \"resolved\" for a turn whose `then` rejects, which is a lie that tooling\n * believes. A thenable is honest: `await`, `Promise.all` and `.then/.catch/\n * .finally` are all defined in terms of `then` and all work, and anything that\n * demands a real Promise says so out loud instead of reporting a wrong answer.\n */\nclass TurnImpl implements AsyncIterable<TurnEvent> {\n\tprivate consumed?: 'awaited' | 'iterated';\n\tprivate settled?: Promise<string>;\n\n\tconstructor(private readonly response: Promise<Response>, private readonly recoveryMessage = '') {\n\t\t// ask() starts eagerly, while consuming its thenable may happen later.\n\t\t// Observe early rejection now; keep the ORIGINAL promise so a later\n\t\t// await/iteration still receives that same error.\n\t\tvoid response.catch(() => {});\n\t}\n\n\t/**\n\t * Record how this turn is being consumed, and refuse a second, different way.\n\t *\n\t * AWAITING TWICE IS ALLOWED, deliberately. A promise handing the same value to\n\t * two awaits is exactly what a promise is, and `drain()` memoises so the second\n\t * await costs nothing. Refusing it would break `Promise.all([turn, turn])` and\n\t * every library that attaches a `.catch()` and then awaits the same value.\n\t *\n\t * ITERATING TWICE IS NOT, and neither is mixing the two. The events are a live\n\t * stream over a socket, not a stored list: a second iterator would receive\n\t * nothing at all and report an empty turn, which is worse than an error.\n\t */\n\tprivate claim(how: 'awaited' | 'iterated'): void {\n\t\tif (how === 'awaited' && this.consumed === 'awaited') return;\n\t\tif (this.consumed) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\tthis.consumed === how\n\t\t\t\t\t\t? `This turn was already ${how}. Call ask() again to ask again; a turn is a live stream, not a replayable list.`\n\t\t\t\t\t\t: `This turn was already ${this.consumed} and cannot also be ${how}. Pick one: await it for the reply, or iterate it for the events. Call ask() again to ask again.`,\n\t\t\t\tcode: 'turn_already_consumed',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\tthis.consumed = how;\n\t}\n\n\tprivate async open(): Promise<{ body: ReadableStream<Uint8Array>; turnId: string | null }> {\n\t\tconst res = await this.response;\n\t\tif (!res.body) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: `The turn response had no body to stream. ${this.recoveryMessage}`.trim(),\n\t\t\t\tcode: 'bad_response',\n\t\t\t\tstatus: res.status,\n\t\t\t});\n\t\t}\n\t\treturn { body: res.body, turnId: res.headers.get('x-turn-id') };\n\t}\n\n\t/** Drain the stream and resolve to the final reply. */\n\tprivate async settle(): Promise<string> {\n\t\t// parseSSE withholds the final answer until wire done confirms completion.\n\t\t// EOF is an interruption even if some or all of the reply already arrived.\n\t\tfor await (const event of parseSSE((await this.open()).body)) {\n\t\t\tif (event.type === 'done') return event.reply;\n\t\t\tif (event.type === 'error') {\n\t\t\t\t// Awaiting is the shape that throws. Iterating gets the error as an\n\t\t\t\t// event and the loop ends, because a for-await that throws mid-render\n\t\t\t\t// leaves the UI half-painted.\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `${event.message} ${this.recoveryMessage}`.trim(),\n\t\t\t\t\tcode: 'turn_failed',\n\t\t\t\t\tstatus: event.retryable ? 503 : 400,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthrow new AgentNavaError({ message: `The response stream ended before the turn finished. ${this.recoveryMessage}`.trim(), code: 'turn_failed', status: 400 });\n\t}\n\n\tprivate drain(): Promise<string> {\n\t\t// claim() runs HERE rather than inside settle(), because settle() runs only\n\t\t// once: a second await would skip the check entirely, and awaiting a turn\n\t\t// that was already iterated would quietly return an empty string instead of\n\t\t// saying what went wrong.\n\t\t//\n\t\t// REJECT, never throw. `then` must return a promise; a synchronous throw out\n\t\t// of it escapes the promise chain, so a caller writing\n\t\t// `turn.then(a, b).catch(c)` would get an exception past their own .catch.\n\t\ttry {\n\t\t\tthis.claim('awaited');\n\t\t} catch (err) {\n\t\t\treturn Promise.reject(err);\n\t\t}\n\t\t// Started once. `await` calls `then`, and so does Promise.all, and so does\n\t\t// anything that duck-types a thenable; all of them share one drain.\n\t\tthis.settled ??= this.settle();\n\t\treturn this.settled;\n\t}\n\n\tthen<A = string, B = never>(\n\t\tonOk?: ((value: string) => A | PromiseLike<A>) | null,\n\t\tonErr?: ((reason: unknown) => B | PromiseLike<B>) | null,\n\t): Promise<A | B> {\n\t\treturn this.drain().then(onOk, onErr);\n\t}\n\n\tcatch<B = never>(onErr?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<string | B> {\n\t\treturn this.drain().catch(onErr);\n\t}\n\n\tfinally(onDone?: (() => void) | null): Promise<string> {\n\t\treturn this.drain().finally(onDone);\n\t}\n\n\tasync *[Symbol.asyncIterator](): AsyncGenerator<TurnEvent> {\n\t\tthis.claim('iterated');\n\t\tconst { body, turnId } = await this.open();\n\t\tfor await (const event of parseSSE(body, turnId)) {\n\t\t\tyield event.type === 'error' && this.recoveryMessage\n\t\t\t\t? { ...event, message: `${event.message} ${this.recoveryMessage}` }\n\t\t\t\t: event;\n\t\t}\n\t}\n}\n\nexport function makeTurn(response: Promise<Response>, recoveryMessage = ''): Turn {\n\treturn new TurnImpl(response, recoveryMessage) as unknown as Turn;\n}\n"]}
|
package/types.d.ts
CHANGED
|
@@ -2,11 +2,19 @@ import type { AppId } from './apps.generated.ts';
|
|
|
2
2
|
import type { Connection } from './connection.ts';
|
|
3
3
|
/** The quality of model an agent's turns run on, and what each turn costs.
|
|
4
4
|
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
5
|
+
* `standard` is the default: the fastest model the platform runs. `lite` is
|
|
6
|
+
* the lowest price. `premium` costs the same as `standard` and thinks harder,
|
|
7
|
+
* answering more slowly. `best` is the strongest model.
|
|
8
|
+
*
|
|
9
|
+
* Renamed in 0.49.0: the grade that was `fast` is now `standard`, and the one
|
|
10
|
+
* that was `standard` is now `lite`. The platform still accepts `fast` from
|
|
11
|
+
* older code and treats it as `standard`. */
|
|
12
|
+
export type Grade = (typeof GRADE_NAMES)[number];
|
|
13
|
+
/** Every grade, as a list a reply can be checked against. This SDK is
|
|
14
|
+
* published and cannot import the platform's private price list, so it keeps
|
|
15
|
+
* its own copy of the NAMES; a test in the platform's monorepo fails the
|
|
16
|
+
* moment the two lists differ. */
|
|
17
|
+
export declare const GRADE_NAMES: readonly ["lite", "standard", "premium", "best"];
|
|
10
18
|
export type Filesystem = 'drive' | 'none';
|
|
11
19
|
/** Where a SECRET is supplied.
|
|
12
20
|
*
|
|
@@ -264,17 +272,18 @@ export interface AgentData {
|
|
|
264
272
|
* call `get()` for it. */
|
|
265
273
|
readonly status?: AgentStatus;
|
|
266
274
|
}
|
|
267
|
-
/**
|
|
275
|
+
/** Who can chat with an agent's website chat. 'restricted' (the default):
|
|
276
|
+
* members of your workspace signed in to AgentNava, and visitors on the
|
|
277
|
+
* allowed websites. 'anyone': anyone with the link, on any website. */
|
|
278
|
+
export type WebsiteChatAccess = 'restricted' | 'anyone';
|
|
279
|
+
/** What `agent.websiteChat.get()` gives back. */
|
|
268
280
|
export interface WebsiteChat {
|
|
269
|
-
/**
|
|
281
|
+
/** Who can chat. */
|
|
282
|
+
readonly access: WebsiteChatAccess;
|
|
283
|
+
/** The hosted chat page, the share link. Share it, or open it in an iframe. */
|
|
270
284
|
readonly chatUrl: string;
|
|
271
|
-
/** The publishable token for the embed script. Safe in a public web page:
|
|
272
|
-
* it can only talk to this one agent. Your API key never goes there. */
|
|
273
|
-
readonly publicToken: string;
|
|
274
285
|
/** The id the embed script's `data-agent-id` takes. */
|
|
275
286
|
readonly embedAgentId: string;
|
|
276
|
-
/** The version the website chat serves from now on. */
|
|
277
|
-
readonly versionNumber: number;
|
|
278
287
|
}
|
|
279
288
|
/** Where an agent is live. See `AgentData.status`. */
|
|
280
289
|
export interface AgentStatus {
|
|
@@ -469,41 +478,88 @@ export interface Step {
|
|
|
469
478
|
kind: 'read' | 'write' | 'edit' | 'search' | 'shell' | 'web' | 'data' | 'task' | 'plan' | 'other';
|
|
470
479
|
status: 'active' | 'done' | 'error';
|
|
471
480
|
}
|
|
472
|
-
export type TurnEvent =
|
|
481
|
+
export type TurnEvent =
|
|
482
|
+
/** First, before anything else: the id the API gave this turn. */
|
|
483
|
+
{
|
|
473
484
|
type: 'start';
|
|
474
485
|
turnId: string;
|
|
475
486
|
}
|
|
476
487
|
/** Carries the WHOLE step list every time, not a change to it. Replace what
|
|
477
488
|
* you are showing rather than merging; that is what makes reconnecting
|
|
478
|
-
* safe.
|
|
489
|
+
* safe. Sent after each tool event that changes the list, with the steps
|
|
490
|
+
* and words the portal shows. */
|
|
479
491
|
| {
|
|
480
492
|
type: 'progress';
|
|
481
493
|
steps: Step[];
|
|
482
|
-
}
|
|
494
|
+
}
|
|
495
|
+
/** `name` is the tool's identifier, for code. `label` is what a person sees,
|
|
496
|
+
* the portal's words ("Searching the web: <query>"). */
|
|
497
|
+
| {
|
|
483
498
|
type: 'tool';
|
|
484
499
|
name: string;
|
|
485
500
|
state: 'start' | 'end';
|
|
501
|
+
label?: string;
|
|
486
502
|
ok?: boolean;
|
|
487
503
|
}
|
|
488
504
|
/** A fragment, not a line. Append; do not assemble. */
|
|
489
505
|
| {
|
|
490
506
|
type: 'text';
|
|
491
507
|
text: string;
|
|
492
|
-
}
|
|
508
|
+
}
|
|
509
|
+
/** A file the agent wrote in the conversation's drive. Pass `path` to
|
|
510
|
+
* conversation.readFile(). Sent once per file per turn. */
|
|
511
|
+
| {
|
|
493
512
|
type: 'file';
|
|
494
513
|
path: string;
|
|
495
|
-
}
|
|
514
|
+
}
|
|
515
|
+
/** A widget the agent is drawing inside its reply, sent whole each time it
|
|
516
|
+
* grows: replace the one with the same `surfaceId`. Only on a conversation
|
|
517
|
+
* started with `widgets: true`. */
|
|
518
|
+
| {
|
|
519
|
+
type: 'widget';
|
|
520
|
+
widget: Widget;
|
|
521
|
+
}
|
|
522
|
+
/** `widgets` is the reply's final widget list, present when it has any:
|
|
523
|
+
* what to keep, whatever arrived live. */
|
|
524
|
+
| {
|
|
496
525
|
type: 'done';
|
|
497
526
|
reply: string;
|
|
527
|
+
widgets?: Widget[];
|
|
498
528
|
} | {
|
|
499
529
|
type: 'error';
|
|
500
530
|
message: string;
|
|
501
531
|
retryable: boolean;
|
|
502
532
|
};
|
|
533
|
+
/**
|
|
534
|
+
* One part of a widget, in Google's A2UI v0.9 format: an id, which kind of
|
|
535
|
+
* part it is (`Column`, `Row`, `Card`, `Text`, `Stat`, `KeyValue`, `Table`,
|
|
536
|
+
* `Chart`, `Image`, `List`, `Note`, `Badge`, `Link`, `Button`, `Suggestions`,
|
|
537
|
+
* `Divider`), and that kind's props. A container lists its children by id.
|
|
538
|
+
*/
|
|
539
|
+
export interface WidgetPart {
|
|
540
|
+
id: string;
|
|
541
|
+
component: string;
|
|
542
|
+
[prop: string]: unknown;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* A card, table or chart the agent drew inside a reply. Draw it with any A2UI
|
|
546
|
+
* renderer: the parts are one A2UI surface, the one with id `root` at the top.
|
|
547
|
+
* `at` is where it sits in the reply's text, as a character count, so it goes
|
|
548
|
+
* between the right sentences.
|
|
549
|
+
*/
|
|
550
|
+
export interface Widget {
|
|
551
|
+
surfaceId: string;
|
|
552
|
+
at: number;
|
|
553
|
+
components: WidgetPart[];
|
|
554
|
+
/** Every part arrived. False only while it streams, or on a reply cut short. */
|
|
555
|
+
closed: boolean;
|
|
556
|
+
}
|
|
503
557
|
export interface Message {
|
|
504
558
|
role: 'user' | 'assistant';
|
|
505
559
|
content: string;
|
|
506
560
|
at: string;
|
|
561
|
+
/** The widgets in an assistant reply, when it has any. */
|
|
562
|
+
widgets?: Widget[];
|
|
507
563
|
}
|
|
508
564
|
/** An image goes to the model as an image. Anything else is written into the
|
|
509
565
|
* conversation's drive before the turn starts, so the agent can parse it,
|
|
@@ -605,6 +661,12 @@ export interface AgentFields {
|
|
|
605
661
|
*/
|
|
606
662
|
databases?: string[];
|
|
607
663
|
tools?: string[];
|
|
664
|
+
/**
|
|
665
|
+
* "Answer with widgets": on unless `false`. When on, the agent draws cards,
|
|
666
|
+
* tables and charts inside its answers, on a conversation started with
|
|
667
|
+
* `widgets: true`. Set `false` for an agent that should answer in text only.
|
|
668
|
+
*/
|
|
669
|
+
widgets?: boolean;
|
|
608
670
|
workflows?: Workflow[];
|
|
609
671
|
/**
|
|
610
672
|
* The apps this agent uses, each a NAMED connection that says whose account
|
|
@@ -667,10 +729,6 @@ export interface SpecInput extends Omit<AgentFields, 'workflows'> {
|
|
|
667
729
|
key: string;
|
|
668
730
|
workflows?: SpecFile[];
|
|
669
731
|
subagents?: SpecFile[];
|
|
670
|
-
widgets?: {
|
|
671
|
-
name: string;
|
|
672
|
-
[k: string]: unknown;
|
|
673
|
-
}[];
|
|
674
732
|
sandbox?: Filesystem;
|
|
675
733
|
role?: string;
|
|
676
734
|
/** Retired in favour of `tools: ['web_search']`, still read off an existing
|
package/types.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// The shapes this SDK hands back. Transcribed from design/sdk-design.md's own
|
|
2
2
|
// Types section so the reference and the code cannot drift.
|
|
3
|
-
|
|
3
|
+
/** Every grade, as a list a reply can be checked against. This SDK is
|
|
4
|
+
* published and cannot import the platform's private price list, so it keeps
|
|
5
|
+
* its own copy of the NAMES; a test in the platform's monorepo fails the
|
|
6
|
+
* moment the two lists differ. */
|
|
7
|
+
export const GRADE_NAMES = ['lite', 'standard', 'premium', 'best'];
|
|
4
8
|
//# sourceMappingURL=types.js.map
|