@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/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
- export async function* parseSSE(body) {
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 eventForLine = (line) => {
37
+ const steps = new Map();
38
+ const eventsForLine = (line) => {
33
39
  const wire = parseLine(line);
34
40
  if (!wire)
35
- return undefined;
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
- return undefined;
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?.type === 'text')
55
+ if (!event)
56
+ return [];
57
+ if (event.type === 'text')
48
58
  assembled += event.text;
49
- if (event?.type === 'error')
59
+ if (event.type === 'error')
50
60
  terminal = true;
51
- return event;
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 = eventForLine(line);
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 last = eventForLine(buffer.replace(/\r$/, ''));
77
- if (last)
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 body() {
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.body())) {
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
- for await (const event of parseSSE(await this.body())) {
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
- * `fast` is the one that surprises people: it buys LATENCY rather than
6
- * capability, runs a smaller model on silicon built for time-to-first-token,
7
- * and costs MORE than `premium` while being less capable. Pick it for voice
8
- * and interactive agents, not to save money. `best` is the strongest model. */
9
- export type Grade = 'standard' | 'premium' | 'fast' | 'best';
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
- /** What `agent.websiteChat.turnOn()` gives back: what to put on a website. */
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
- /** The hosted chat page. Share it, or open it in an iframe. */
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
- export {};
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