@cerebro-labs/agentnava-sdk 0.47.1 → 0.51.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 +20 -0
- package/agent.d.ts +15 -1
- package/agent.js +6 -3
- 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 +2 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/load-agent-dir.js +2 -21
- package/load-agent-dir.js.map +1 -1
- package/package.json +15 -2
- package/project.js +10 -3
- package/project.js.map +1 -1
- package/react.css +409 -0
- package/react.d.ts +40 -0
- package/react.js +196 -0
- package/react.js.map +1 -0
- package/turn.d.ts +6 -2
- package/turn.js +85 -19
- package/turn.js.map +1 -1
- package/types.d.ts +66 -14
- package/types.js +1 -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/widgets.d.ts +15 -0
- package/widgets.js +29 -0
- package/widgets.js.map +1 -0
package/README.md
CHANGED
|
@@ -173,11 +173,18 @@ const reply = await conversation.ask('...'); // just the answer
|
|
|
173
173
|
|
|
174
174
|
```ts
|
|
175
175
|
for await (const e of conversation.ask('...')) { // as it happens
|
|
176
|
+
if (e.type === 'progress') showSteps(e.steps); // the whole list, each time
|
|
176
177
|
if (e.type === 'text') append(e.text);
|
|
178
|
+
if (e.type === 'file') show(await conversation.readFile(e.path));
|
|
177
179
|
if (e.type === 'done') save(e.reply);
|
|
178
180
|
}
|
|
179
181
|
```
|
|
180
182
|
|
|
183
|
+
`progress` lists the steps the AgentNava app shows while the agent works, in
|
|
184
|
+
the same words ("Searching the web: steel tariffs"), each `active`, `done` or
|
|
185
|
+
`error`. Replace what you show with each one. `file` names a file the agent
|
|
186
|
+
wrote, once per file.
|
|
187
|
+
|
|
181
188
|
**The message is sent when you call `ask`**, not when you await or iterate it.
|
|
182
189
|
Calling `ask` twice generates two submission keys and asks twice. If you need
|
|
183
190
|
to identify an explicit retry after a lost response, save a key before sending:
|
|
@@ -400,6 +407,19 @@ explicit patent grant. Section 6 grants no rights to the names: "AgentNava"
|
|
|
400
407
|
and "Cerebro Labs" remain trademarks of Cerebro Labs, Inc.
|
|
401
408
|
|
|
402
409
|
|
|
410
|
+
### Widgets in replies (0.50)
|
|
411
|
+
|
|
412
|
+
`agent.start({ endUserId, widgets: true })` says your screen can draw cards, tables and charts. Each turn then streams `widget` events (a whole widget each time, replace it by `surfaceId`), `done` carries the final `widgets`, and `messages()` returns them on each reply. Continue one with `agent.conversation(id, { widgets: true })`. Widgets use Google's open A2UI format with AgentNava's own set of parts (`Widget`, `WidgetPart`): draw each with your own components, starting from its part with id `root`, or use the ready-made React views below. `placeWidgets(text, widgets)` splits a reply into words and widgets in reading order, for any framework.
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
import { ReplyView } from '@cerebro-labs/agentnava-sdk/react'
|
|
416
|
+
import '@cerebro-labs/agentnava-sdk/react/styles.css'
|
|
417
|
+
|
|
418
|
+
<ReplyView text={reply} widgets={widgets} onAsk={send} />
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
React is an optional peer: only `@cerebro-labs/agentnava-sdk/react` imports it. Every kind of part has a default look, themed with `--an-w-*` CSS variables; `parts={{ Stat: MyStat }}` swaps any kind for your own component, `renderText` draws the words, `currency` and `locale` format numbers, and `allowUrl` narrows which links and pictures open (https:// only by default). An agent's own `widgets: false` turns them off for it. The agent field's old meaning, a list of widget kinds nothing read, is gone, and so is the `widgets.json` file `loadAgentDir` used to load.
|
|
422
|
+
|
|
403
423
|
### Where an agent is live (0.41)
|
|
404
424
|
|
|
405
425
|
`agent.get()` and `ws.agents.list()` carry a read-only `status`: where the agent is live, by the rule the API runs it by, and in `needs` any required account or secret not set up yet (it still runs; the tools that use them fail until they are).
|
package/agent.d.ts
CHANGED
|
@@ -95,6 +95,15 @@ export interface StartOptions {
|
|
|
95
95
|
* conversation with a colleague's internal id and looked fine.
|
|
96
96
|
*/
|
|
97
97
|
endUserId: string;
|
|
98
|
+
/**
|
|
99
|
+
* This conversation's screen can draw widgets: cards, tables and charts the
|
|
100
|
+
* agent writes inside its replies, in Google's A2UI format. Off unless set.
|
|
101
|
+
* When on, each turn's stream carries `widget` events and `done.widgets`,
|
|
102
|
+
* and `messages()` returns each reply's `widgets`. The agent draws only if
|
|
103
|
+
* its own "Answer with widgets" setting is on (it is, unless turned off).
|
|
104
|
+
* The SDK ships no screens: draw them with any A2UI renderer.
|
|
105
|
+
*/
|
|
106
|
+
widgets?: boolean;
|
|
98
107
|
}
|
|
99
108
|
/**
|
|
100
109
|
* An agent you can configure and start conversations from.
|
|
@@ -390,6 +399,9 @@ export declare class Agent {
|
|
|
390
399
|
/**
|
|
391
400
|
* Address one of this agent's conversations by id. No request.
|
|
392
401
|
*
|
|
402
|
+
* Pass `{ widgets: true }` when the screen continuing it can draw widgets,
|
|
403
|
+
* as on `start`: the choice belongs to the screen, so it is made per handle.
|
|
404
|
+
*
|
|
393
405
|
* Here rather than on the workspace because a conversation belongs to an
|
|
394
406
|
* agent. You already hold the agent: it is a constant in your own code, not
|
|
395
407
|
* a per-row value, so naming it costs nothing and the ownership is visible.
|
|
@@ -398,7 +410,9 @@ export declare class Agent {
|
|
|
398
410
|
* await ws.agent(AGENT).conversation(row.conversationId).ask('carry on');
|
|
399
411
|
* ```
|
|
400
412
|
*/
|
|
401
|
-
conversation(conversationId: string
|
|
413
|
+
conversation(conversationId: string, options?: {
|
|
414
|
+
widgets?: boolean;
|
|
415
|
+
}): Conversation;
|
|
402
416
|
/**
|
|
403
417
|
* This agent's most recent conversations, newest first.
|
|
404
418
|
*
|
package/agent.js
CHANGED
|
@@ -503,11 +503,14 @@ export class Agent {
|
|
|
503
503
|
// continue a conversation, hold its id: `agent.conversation(id)`.
|
|
504
504
|
fresh: true,
|
|
505
505
|
});
|
|
506
|
-
return new Conversation(this.#http, data.id, data);
|
|
506
|
+
return new Conversation(this.#http, data.id, data, { widgets: options.widgets === true });
|
|
507
507
|
}
|
|
508
508
|
/**
|
|
509
509
|
* Address one of this agent's conversations by id. No request.
|
|
510
510
|
*
|
|
511
|
+
* Pass `{ widgets: true }` when the screen continuing it can draw widgets,
|
|
512
|
+
* as on `start`: the choice belongs to the screen, so it is made per handle.
|
|
513
|
+
*
|
|
511
514
|
* Here rather than on the workspace because a conversation belongs to an
|
|
512
515
|
* agent. You already hold the agent: it is a constant in your own code, not
|
|
513
516
|
* a per-row value, so naming it costs nothing and the ownership is visible.
|
|
@@ -516,8 +519,8 @@ export class Agent {
|
|
|
516
519
|
* await ws.agent(AGENT).conversation(row.conversationId).ask('carry on');
|
|
517
520
|
* ```
|
|
518
521
|
*/
|
|
519
|
-
conversation(conversationId) {
|
|
520
|
-
return new Conversation(this.#http, conversationId);
|
|
522
|
+
conversation(conversationId, options = {}) {
|
|
523
|
+
return new Conversation(this.#http, conversationId, undefined, { widgets: options.widgets === true });
|
|
521
524
|
}
|
|
522
525
|
/**
|
|
523
526
|
* This agent's most recent conversations, newest first.
|
package/agent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["// An agent: a configuration you start conversations from.\n\nimport { type Account, accountIdOf } from './account.ts';\nimport { isBindings, refuseConnections, secretIdsOf, secretsIn } from './attach.ts';\nimport { Conversation } from './conversation.ts';\nimport { AgentNavaError } from './errors.ts';\nimport type { Http } from './http.ts';\nimport type { Secret } from './secret.ts';\nimport { type Connection, connectionForWire } from './connection.ts';\nimport type {\n\tAgentConversationData,\n\tAgentData,\n\tAgentFields,\n\tBindings,\n\tConnectionStatus,\n\tFileEntry,\n\tTrigger,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWebhookDelivery,\n} from './types.ts';\n\nexport interface StartOptions {\n\t/**\n\t * Stored secrets to attach AS THE CONVERSATION STARTS.\n\t *\n\t * A `Secret` from `ws.endUser(id).secrets.create(...)` attaches by id, which\n\t * is the only way a stored per-person credential can reach a turn: you never\n\t * hold its value.\n\t *\n\t * ```ts\n\t * const key = await ws.endUser(row.userId).secrets.create({ name: 'ZENDESK_TOKEN', value });\n\t * const conversation = await agent.start({ endUserId: row.userId, attach: key });\n\t * ```\n\t *\n\t * Doing it here rather than with a second `conversation.attach()` call\n\t * closes a real window: between `start` and the attach, the conversation\n\t * exists and can be asked something, and it would answer without it.\n\t *\n\t * ACCOUNTS ARE NOT ATTACHED HERE any more (0.44), and one handed in is\n\t * refused. The agent's setup names the account each connection uses; for a\n\t * connection to the end user's account (`Connection.endUserAccount`), the\n\t * turn uses the account of the person this conversation is for, found by\n\t * `endUserId`. Ask\n\t * `conversation.accountsNeeded()` for the ones they have not connected yet.\n\t */\n\tattach?: Secret | Secret[];\n\n\t/**\n\t * Secret VALUES for this conversation only.\n\t *\n\t * Not `attach`, because a secret has no id you can hand over: binding one\n\t * takes the value itself. That asymmetry is in the product, not in this\n\t * naming, and hiding it behind the same word would not remove it.\n\t */\n\tsecrets?: Record<string, string>;\n\n\t/** Pin a specific version. Defaults to the latest at the moment of starting.\n\t *\n\t * A conversation pins its version for life: publishing afterwards does not\n\t * move a conversation that is already open. The one exception is a schedule\n\t * or webhook that names the conversation (`conversationId`): each firing\n\t * moves it to the latest version, so a fix you publish reaches it. */\n\tversion?: number;\n\tparameters?: Record<string, string | number>;\n\n\t/**\n\t * WHO this conversation is for: one of YOUR users, not one of ours.\n\t *\n\t * REQUIRED, and never null. Every conversation belongs to someone. The id\n\t * is yours to choose: a user's row id, an anonymous visitor's random id, or\n\t * a name for a job such as `'nightly-report'`. Only a non-empty string is\n\t * accepted; anything else throws before a request is sent. (Decided\n\t * 2026-09-26, AGE-161: a conversation with no owner cannot be listed back to\n\t * the person it was for, and the accounts connected inside it could not be\n\t * revoked with that person.)\n\t *\n\t * It LABELS the conversation, and it decides one thing more: a connection\n\t * to the end user's account (`Connection.endUserAccount`) uses THIS\n\t * person's account for that app.\n\t * It does not decide which conversation you get, because `start` always\n\t * starts a new one. Later you can ask for everything belonging to that\n\t * person:\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: row.userId });\n\t * const nightly = await agent.start({ endUserId: 'nightly-report' }); // a job has an id too\n\t * const theirs = await agent.conversations({ endUser: row.userId });\n\t * ```\n\t *\n\t * To CONTINUE a conversation, keep its id and address it directly. That is\n\t * the only way to continue one, on purpose:\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('and the rest?');\n\t * ```\n\t *\n\t * The id is YOURS and opaque to us: a row id from your database, not an\n\t * email and not something we authenticate. We never verify it, so it is\n\t * exactly as trustworthy as whatever holds your API key.\n\t *\n\t * The platform's prefixes are refused with a 400 (`end_user_reserved`):\n\t * `member:`, `visitor:` and `job:`, which name a member in the web app, an\n\t * embed visitor and work nobody is typing, and the older `webhook:`,\n\t * `builder:` and `librarian:`. Case and spacing do not matter to the check\n\t * (`MEMBER:x` is refused too). An id you chose must never be mistaken for\n\t * one of ours.\n\t *\n\t * NOT A WORKSPACE MEMBER'S ID. `ws.members()` lists the colleagues who share\n\t * this account, and their `userId` is a platform id we issued. This field is\n\t * a different namespace entirely. Both were once spelled `userId`, so\n\t * passing a member id here was a call that could not fail: there is nothing\n\t * for an end-user id to be checked against, so it just labelled the\n\t * conversation with a colleague's internal id and looked fine.\n\t */\n\tendUserId: string;\n}\n\n/**\n * An agent you can configure and start conversations from.\n *\n * Holding one makes NO request: `ws.agent(id)` is free and the first call on it\n * is the first round trip.\n *\n * EVERY METHOD THAT CHANGES THE AGENT PUBLISHES A VERSION. There is no separate\n * save. What you do not send is inherited from the current version, which is the\n * only reason changing one field is safe: a resend that rebuilt the whole\n * configuration would drop whatever the caller forgot, and that bug has shipped\n * here four separate times.\n */\nexport class Agent {\n\treadonly id: string;\n\treadonly #http: Http;\n\tprivate snapshot?: AgentData;\n\n\tconstructor(http: Http, id: string, snapshot?: AgentData) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/agents/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/** The last-known configuration, from whatever call produced this object.\n\t * Undefined on an agent you addressed by id and have not read. */\n\tget data(): AgentData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\tasync get(options: { version?: number } = {}): Promise<AgentData> {\n\t\tconst data = await this.#http.get<AgentData>(\n\t\t\tthis.base,\n\t\t\toptions.version !== undefined ? { version: options.version } : undefined,\n\t\t);\n\t\t// Only cache the CURRENT configuration. Caching a historical version would\n\t\t// make `agent.data` mean different things depending on the last call.\n\t\tif (options.version === undefined) this.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/** Every version, newest first. */\n\tasync versions(): Promise<AgentData[]> {\n\t\tconst res = await this.#http.get<{ versions: AgentData[] }>(`${this.base}/versions`);\n\t\treturn res.versions ?? [];\n\t}\n\n\t/** Publish a new version with these fields changed and everything else kept.\n\t * `connections`, when sent, replaces the whole list; an `Account` in it goes\n\t * over the wire as its id (`Account.toJSON`). */\n\tasync update(fields: AgentFields): Promise<Agent> {\n\t\tconst data = await this.#http.post<AgentData>(`${this.base}/versions`, fields);\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t// --------------------------------------------------------------- workflows\n\n\t/**\n\t * Add a workflow, or replace the one with the same name.\n\t *\n\t * Reads the current list and sends it back with this one in place, because\n\t * the API replaces the whole list: a workflow set is a list, and there is no\n\t * \"merge one item\" that does not have to decide what a duplicate name means.\n\t * Deciding it here, once, is better than every caller deciding it.\n\t */\n\tasync setWorkflow(workflow: Workflow): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\tconst next = current.filter((w) => w.name !== workflow.name).concat(workflow);\n\t\treturn this.update({ workflows: next });\n\t}\n\n\tasync removeWorkflow(name: string): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\treturn this.update({ workflows: current.filter((w) => w.name !== name) });\n\t}\n\n\t/** Replace the trigger list. Sending `[]` removes every trigger, including\n\t * `chat`, which is what leaves an agent that only runs on a schedule. */\n\tasync setTriggers(triggers: Trigger[]): Promise<Agent> {\n\t\treturn this.update({ triggers });\n\t}\n\n\t/**\n\t * Replace the webhook's URL and return the new one.\n\t *\n\t * The old URL stops working the moment this returns, so update the system\n\t * that calls it. Use it when a URL leaked. The webhook's `message` and who it\n\t * runs for are kept. Publishes a version. Throws `no_webhook` (409) when the\n\t * agent has no webhook trigger.\n\t */\n\tasync rotateWebhookUrl(): Promise<string> {\n\t\tconst data = await this.#http.post<AgentData>(`${this.base}/webhook/rotate`, {});\n\t\tthis.snapshot = data;\n\t\tconst webhook = data.triggers.find((t) => t.kind === 'webhook');\n\t\tif (webhook?.kind !== 'webhook' || !webhook.url) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: 'The webhook was rotated but no URL came back. Read it with agent.get().',\n\t\t\t\tcode: 'webhook_url_missing',\n\t\t\t\tstatus: 502,\n\t\t\t});\n\t\t}\n\t\treturn webhook.url;\n\t}\n\n\t/** What the webhook received, newest first: accepted, refused (and why), a\n\t * sender's retry recognised as a repeat, or a test. Up to `limit` (200). */\n\tasync webhookDeliveries(options: { limit?: number } = {}): Promise<WebhookDelivery[]> {\n\t\tconst res = await this.#http.get<{ deliveries: WebhookDelivery[] }>(\n\t\t\t`${this.base}/webhook/deliveries`,\n\t\t\toptions.limit !== undefined ? { limit: options.limit } : undefined,\n\t\t);\n\t\treturn res.deliveries ?? [];\n\t}\n\n\t/**\n\t * Send the webhook a sample event and run the agent on it, the way a real\n\t * delivery would (the signature check aside). A real turn, charged like\n\t * one. Returns the conversation it ran in.\n\t */\n\tasync sendTestWebhook(): Promise<{ conversationId?: string }> {\n\t\tconst res = await this.#http.post<{ conversationId?: string }>(`${this.base}/webhook/test`, {});\n\t\treturn res.conversationId ? { conversationId: res.conversationId } : {};\n\t}\n\n\t// ------------------------------------------------------------ website chat\n\n\t/**\n\t * The agent's website chat: its chat page on AgentNava (the share link),\n\t * and the embed script that puts it on your website. Every agent has one\n\t * from the moment it is created, however it was made (here, through the\n\t * API, or in the portal), except an agent that needs each user's own\n\t * account or secret, which a visitor cannot bring.\n\t *\n\t * Who can chat is one setting, `access`:\n\t *\n\t * 'restricted' the default. The share link works for members of your\n\t * workspace who are signed in to AgentNava, so your team can\n\t * pass it around to test. The chat shows on another website\n\t * only when that website is on `allowedWebsites`.\n\t * 'anyone' anyone with the link can chat, and any website can show\n\t * the chat. Each visitor's turns are charged to your\n\t * workspace.\n\t *\n\t * You never need it to use the agent from code: the agent answers your API\n\t * key, and the portal's Test, as soon as it exists. Publishing a new version\n\t * moves the website chat to it, as it moves new conversations.\n\t */\n\treadonly websiteChat = {\n\t\t/** Who can chat, the share link, and the id the embed script's\n\t\t * `data-agent-id` takes. */\n\t\tget: async (): Promise<WebsiteChat> => toWebsiteChat(\n\t\t\tawait this.#http.get<WebsiteChatWire>(`${this.base}/website-chat-access`),\n\t\t),\n\t\t/** Who can chat: 'restricted' (your workspace's signed-in members, and\n\t\t * the allowed websites) or 'anyone' (anyone with the link). */\n\t\taccess: {\n\t\t\t/** The setting as it stands. */\n\t\t\tget: async (): Promise<WebsiteChatAccess> =>\n\t\t\t\t(await this.#http.get<WebsiteChatWire>(`${this.base}/website-chat-access`)).access,\n\t\t\t/** Change it. Returns the setting as stored. Anything but 'restricted'\n\t\t\t * or 'anyone' is refused (400). */\n\t\t\tset: async (access: WebsiteChatAccess): Promise<WebsiteChatAccess> =>\n\t\t\t\t(await this.#http.put<WebsiteChatWire>(`${this.base}/website-chat-access`, { access })).access,\n\t\t},\n\t\t/** The websites a Restricted agent's chat may be shown on through the\n\t\t * embed script, where visitors chat without signing in. A domain covers\n\t\t * its subdomains; an empty list means no other website. While access is\n\t\t * 'anyone' the list is kept and not needed. */\n\t\tallowedWebsites: {\n\t\t\t/** The list as it stands. */\n\t\t\tget: async (): Promise<string[]> =>\n\t\t\t\t(await this.#http.get<{ websites: string[] }>(`${this.base}/allowed-websites`)).websites,\n\t\t\t/** Replace the list. Each entry is normalised (\"https://acme.com/page\"\n\t\t\t * becomes \"acme.com\"; \"localhost\" is allowed for testing), and one that\n\t\t\t * is not a website is refused (400). Returns the list as stored. */\n\t\t\tset: async (websites: string[]): Promise<string[]> =>\n\t\t\t\t(await this.#http.put<{ websites: string[] }>(`${this.base}/allowed-websites`, { websites })).websites,\n\t\t},\n\t};\n\n\t// --------------------------------------------------------------- knowledge\n\n\t#readKnowledgeResponse(path: string): Promise<Response> {\n\t\treturn this.#http.send(`${this.base}/knowledge/files/content`, { query: { path } });\n\t}\n\n\t/**\n\t * The agent's knowledge: files every conversation can read.\n\t *\n\t * BY REFERENCE, not by copy. A conversation reads the knowledge as it is now,\n\t * so replacing a file changes what open conversations see on their next turn.\n\t * That is the opposite of how a version works, and it is deliberate: a price\n\t * list that could not be corrected without republishing the agent would be\n\t * useless.\n\t */\n\treadonly knowledge = {\n\t\tput: async (path: string, bytes: Uint8Array | ArrayBuffer, contentType?: string): Promise<FileEntry> =>\n\t\t\tthis.#http.request<FileEntry>(`${this.base}/knowledge/files`, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbytes,\n\t\t\t\tquery: { path },\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t}),\n\t\t/** Read an agent-owned knowledge file as bytes, without starting a turn. */\n\t\tget: async (path: string): Promise<Uint8Array> => {\n\t\t\tconst res = await this.#readKnowledgeResponse(path);\n\t\t\treturn new Uint8Array(await res.arrayBuffer());\n\t\t},\n\t\t/** Read UTF-8 text. JSON files remain text rather than being parsed. */\n\t\ttext: async (path: string): Promise<string> => {\n\t\t\tconst res = await this.#readKnowledgeResponse(path);\n\t\t\treturn res.text();\n\t\t},\n\t\tlist: async (path = '/'): Promise<FileEntry[]> => {\n\t\t\t// The route returns EVERY knowledge file for the agent; it takes no\n\t\t\t// directory parameter. Filtering here rather than sending one the server\n\t\t\t// ignores, because a call that looks filtered and is not is worse than\n\t\t\t// no filter at all. Knowledge sets are small, so one read is right.\n\t\t\tconst res = await this.#http.get<{ files: (FileEntry & { path: string })[] }>(\n\t\t\t\t`${this.base}/knowledge/files`,\n\t\t\t);\n\t\t\tconst prefix = path === '/' ? '/' : `${path.replace(/\\/+$/, '')}/`;\n\t\t\treturn (res.files ?? []).filter((f) => f.path?.startsWith(prefix));\n\t\t},\n\t\tremove: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/knowledge/files`, { path });\n\t\t},\n\t};\n\n\t// -------------------------------------------------------- knowledge bases\n\n\t/**\n\t * The WORKSPACE knowledge bases this agent reads.\n\t *\n\t * Different from `knowledge` above, and the distinction is the whole point.\n\t * `agent.knowledge` is files this agent owns; nobody else has them. A\n\t * knowledge base is shared: one price list, attached to nine agents, corrected\n\t * in one place.\n\t *\n\t * Each attached base is mounted read-only at `/knowledge/<key>/` when a turn\n\t * starts, so the agent reads it with `ls`, `grep` and `cat`.\n\t *\n\t * ATTACHING PUBLISHES A VERSION. Which bases an agent reads is part of its\n\t * configuration, so the change is diffable and reversible like any other\n\t * configuration change. The CONTENTS of a base are not versioned: correcting\n\t * a file changes what every attached agent sees on its next turn.\n\t */\n\treadonly knowledgeBases = {\n\t\t/**\n\t\t * Attach one base by key.\n\t\t *\n\t\t * One call, not read-modify-write, deliberately. Two attaches racing\n\t\t * through a read of the current list and a write of the whole list would\n\t\t * lose one of them silently, and losing an attachment looks exactly like\n\t\t * an agent that cannot find its knowledge. The server appends.\n\t\t *\n\t\t * Attaching a key that names no base is refused, naming the key. Attaching\n\t\t * one that is already attached is a no-op, not an error.\n\t\t */\n\t\tattach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/knowledge-bases`, { key });\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Detach one base. The base itself and its files are untouched: this only\n\t\t * stops THIS agent from reading it. Detaching one that is not attached is\n\t\t * a no-op. */\n\t\tdetach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(\n\t\t\t\t`${this.base}/knowledge-bases/${encodeURIComponent(key)}`,\n\t\t\t);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** The keys this agent currently reads, in configuration order. */\n\t\tlist: async (): Promise<string[]> => {\n\t\t\tconst current = this.snapshot ?? (await this.get());\n\t\t\treturn current.knowledgeBases ?? [];\n\t\t},\n\t};\n\n\t// -------------------------------------------------------------- databases\n\n\t/**\n\t * The WORKSPACE databases this agent may query, by key.\n\t *\n\t * Register a database first with `ws.databases.create()`, which gives it its\n\t * key; attaching names that key in this agent's configuration. Naming it is\n\t * the grant: the database answers this agent's `sql_*` calls and no agent\n\t * that does not name it, and the agent's prompt lists it by key.\n\t *\n\t * ATTACHING PUBLISHES A VERSION, like a knowledge base: which databases an\n\t * agent reaches is part of its configuration, so the change is diffable and\n\t * reversible. Detaching takes effect on the agent's next query, including in\n\t * a conversation already under way.\n\t */\n\treadonly databases = {\n\t\t/** Attach one database by key. The server appends, so two attaches cannot\n\t\t * lose each other. A key that names no database is refused; one already\n\t\t * attached is a no-op. */\n\t\tattach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/databases`, { key });\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Stop this agent querying one database. The database stays registered\n\t\t * for every other agent that names it. */\n\t\tdetach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(`${this.base}/databases/${encodeURIComponent(key)}`);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** The keys this agent may query, in configuration order. */\n\t\tlist: async (): Promise<string[]> => {\n\t\t\tconst current = this.snapshot ?? (await this.get());\n\t\t\treturn current.databases ?? [];\n\t\t},\n\t};\n\n\t// ------------------------------------------------------------ connections\n\n\t/**\n\t * The apps this agent uses: each a NAMED connection that says WHOSE ACCOUNT\n\t * it uses, built the way the portal's Add a connection screen asks.\n\t *\n\t * | built with | whose account | the person signs in |\n\t * |---|---|---|\n\t * | `Connection.creatorAccount(app, account)` | the agent creator's, from `ws.accounts.connect()` | once, and every conversation uses it |\n\t * | `Connection.endUserAccount(app)` | the end user's: each person talking to the agent | once each, remembered for them |\n\t *\n\t * ```ts\n\t * const { account: work } = await ws.accounts.connect({ app: App.GMAIL, nickname: 'Work' });\n\t * await agent.connections.add(Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }));\n\t * await agent.connections.add(Connection.endUserAccount(App.GOOGLE_CALENDAR));\n\t * ```\n\t *\n\t * A turn uses EXACTLY the account the setup names, and never another one.\n\t * There is no fallback to whichever account of that app happens to exist:\n\t * that fallback is what this replaced, and it meant reading an agent's code\n\t * told you nothing about whose mailbox it read.\n\t *\n\t * EVERY CHANGE PUBLISHES A VERSION, like `databases`: which accounts an\n\t * agent reaches is part of its configuration, so the change is diffable and\n\t * reversible. A conversation uses the connections of the version it runs.\n\t * The ACCOUNTS are not versioned: signing one in again reaches every agent\n\t * that names it, from the next turn.\n\t *\n\t * Removing a connection never touches the account. Disconnecting an account\n\t * is the other operation, on the account: `account.disconnect()`.\n\t */\n\treadonly connections = {\n\t\t/**\n\t\t * Add one connection. One call that publishes, not read-modify-write, so\n\t\t * two adds racing cannot lose each other.\n\t\t *\n\t\t * Takes what `Connection.creatorAccount()` or `Connection.endUserAccount()`\n\t\t * builds, or the same plain object. Refused, naming the problem, when the\n\t\t * name is taken on this agent (`connection_name_taken`), when the account\n\t\t * is of a different app (`account_app_mismatch`), or when no account has\n\t\t * that id (`account_not_found`).\n\t\t */\n\t\tadd: async (connection: Connection): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/connections`, connectionForWire(connection));\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/**\n\t\t * Point one connection at another account: `{ account }` for the agent\n\t\t * creator's (the `Account` you hold, or its id), or `{ endUser: true }`\n\t\t * for each end user's own. The name and the app stay; an account of a\n\t\t * different app is refused (`account_app_mismatch`).\n\t\t *\n\t\t * READ-MODIFY-WRITE, unlike `add` and `remove`, because there is no\n\t\t * route that changes one connection in place: this reads the current\n\t\t * version and publishes it again with the one connection changed. Two\n\t\t * changes racing can therefore lose one, so make them one after the\n\t\t * other. A name the agent does not have throws\n\t\t * (`connection_not_found`) before anything is sent.\n\t\t */\n\t\tchange: async (\n\t\t\tname: string,\n\t\t\tchange: { account: Account | string; endUser?: never } | { endUser: true; account?: never },\n\t\t): Promise<Agent> => {\n\t\t\t// Read FRESH, never from the snapshot. A stale list sent back whole\n\t\t\t// would silently undo whatever changed since it was read.\n\t\t\tconst current = (await this.get()).connections ?? [];\n\t\t\tif (!current.some((c) => c.name === name)) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `This agent has no connection named \"${name}\". It has: ${\n\t\t\t\t\t\tcurrent.map((c) => c.name).join(', ') || 'none'\n\t\t\t\t\t}.`,\n\t\t\t\t\tcode: 'connection_not_found',\n\t\t\t\t\tstatus: 404,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst whose =\n\t\t\t\tchange.endUser === true ? { endUser: true as const } : { account: accountIdOf(change.account as Account | string) };\n\t\t\t// Every other connection goes back exactly as it was read: this changes\n\t\t\t// one, and must not reinterpret the rest.\n\t\t\tconst connections = current.map((c) => (c.name === name ? { name: c.name, app: c.app, ...whose } : c));\n\t\t\tconst data = await this.#http.post<AgentData>(`${this.base}/versions`, { connections });\n\t\t\tthis.snapshot = data;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Remove one connection by name. Publishes a version. NEVER touches the\n\t\t * account: it stays connected for every other agent that names it, and\n\t\t * for this one if you add it back. */\n\t\tremove: async (name: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(`${this.base}/connections/${encodeURIComponent(name)}`);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/**\n\t\t * Each connection with the account it uses and whether that account works\n\t\t * now: DECLARED IS NOT CONNECTED. An agent can name an account nobody has\n\t\t * finished signing in to, or one that was disconnected since, and that is\n\t\t * the state a setup checklist has to show.\n\t\t *\n\t\t * Includes what an unpublished draft adds (`inDraft`) and removes\n\t\t * (`removedInDraft`), so it matches the portal.\n\t\t */\n\t\tlist: async (): Promise<ConnectionStatus[]> => {\n\t\t\tconst res = await this.#http.get<{ connections: ConnectionStatus[] }>(`${this.base}/connections`);\n\t\t\treturn res.connections ?? [];\n\t\t},\n\t};\n\n\t// ------------------------------------------------------ secrets, agent tier\n\n\t/**\n\t * Write secret VALUES to this agent's own scope: readable by this agent and\n\t * nothing else in the workspace, overriding a workspace secret of the same\n\t * name. Writing one twice replaces the value rather than failing, because\n\t * attaching means \"this is the credential now\".\n\t *\n\t * This used to POST the whole object to `/v1/agents/:id/bindings`, which\n\t * answers only to `{kind, decl}` and records a DECLARATION, never a value. So\n\t * the call 400'd, and had it not, it would have stored the fact that a secret\n\t * is wanted rather than the secret.\n\t */\n\tasync #applyBindings(bindings: Bindings): Promise<Agent> {\n\t\tfor (const [name, value] of Object.entries(bindings.secrets ?? {})) {\n\t\t\tconst mine = (await this.#http.get<{ secrets: { id: string; name: string; agentId?: string | null }[] }>(\n\t\t\t\t'/v1/secrets',\n\t\t\t\t{ agentId: this.id },\n\t\t\t)).secrets ?? [];\n\t\t\tconst existing = mine.find((s) => s.name === name && s.agentId);\n\t\t\tif (existing) await this.#http.patch(`/v1/secrets/${encodeURIComponent(existing.id)}`, { value });\n\t\t\telse await this.#http.post('/v1/secrets', { name, value, agentId: this.id });\n\t\t}\n\t\tconst data = await this.get();\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\t/**\n\t * Give this agent secret values, so every conversation with it can use them.\n\t *\n\t * ```ts\n\t * await agent.attach({ secrets: { APOLLO_API_KEY: value } });\n\t * ```\n\t *\n\t * SECRETS ONLY, since 0.44. Accounts used to be attached here, and a turn\n\t * then had to guess which of the accounts attached and owned around it to\n\t * use. The agent's setup now names them: `agent.connections.add()`. One\n\t * handed in here is refused by name (`not_a_secret`) rather than dropped.\n\t *\n\t * A stored `Secret` is not attached to an agent either. It never was: one\n\t * was accepted here and nothing was done with it. Store one for the agent\n\t * with `ws.secrets.create({ name, value, agentId })`, or attach one of your\n\t * end user's to their conversation with `conversation.attach(secret)`.\n\t */\n\tasync attach(bindings: Bindings): Promise<Agent> {\n\t\tif (!isBindings(bindings)) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\t'agent.attach takes { secrets: { NAME: value } }. Accounts go in the agent\\'s setup ' +\n\t\t\t\t\t'(agent.connections.add), and a stored secret attaches to a conversation ' +\n\t\t\t\t\t'(conversation.attach).',\n\t\t\t\tcode: 'not_a_secret',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\trefuseConnections(bindings);\n\t\treturn this.#applyBindings(bindings);\n\t}\n\n\t// ----------------------------------------------------------- conversations\n\n\t/** Start a conversation. It pins the agent's version now and keeps it.\n\t *\n\t * Nothing about accounts is passed here: the version's connections name\n\t * them, and a connection to the end user's account uses the account of\n\t * the person named by `endUserId`. */\n\tasync start(options: StartOptions): Promise<Conversation> {\n\t\tconst endUserId = requireEndUserId(options?.endUserId);\n\t\t// Resolved BEFORE the launch: a Secret is named on the wire by the\n\t\t// declared NAME it fills, and a handle addressed by id may not know its\n\t\t// own yet. Anything that is not a Secret is refused here, by name.\n\t\tconst secretIds = await secretIdsOf(secretsIn(options.attach));\n\t\tconst bindings =\n\t\t\toptions.secrets || secretIds\n\t\t\t\t? {\n\t\t\t\t\t\t...(options.secrets && { secrets: options.secrets }),\n\t\t\t\t\t\t...(secretIds && { secretIds }),\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\t\tconst data = await this.#http.post<AgentConversationData>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.version !== undefined && { version: options.version }),\n\t\t\t...(options.parameters && { parameters: options.parameters }),\n\t\t\t// `sessionBindings`, NOT `secrets` at the top level.\n\t\t\t//\n\t\t\t// This sent them at the top level and the launch route reads\n\t\t\t// `body.sessionBindings`, so they were dropped in silence: the route's\n\t\t\t// own body type has no top-level `secrets` to read. A conversation\n\t\t\t// started with a credential began without it, and the first turn\n\t\t\t// failed somewhere else entirely.\n\t\t\t//\n\t\t\t// Third place this exact envelope mistake was found in one afternoon,\n\t\t\t// after `agent.bind` and `conversation.bind`. The shape of the bug is\n\t\t\t// always the same: the SDK agrees with itself, the route agrees with\n\t\t\t// itself, and nothing compares the two.\n\t\t\t...(bindings && { sessionBindings: bindings }),\n\t\t\t// Always present: requireEndUserId() above has already refused\n\t\t\t// anything that is not a non-empty string.\n\t\t\tuser: { id: endUserId },\n\t\t\t// ALWAYS a new conversation, even when a user is named.\n\t\t\t//\n\t\t\t// The route resumes that user's most recent conversation when it is\n\t\t\t// given an identity and not told otherwise. That default exists for\n\t\t\t// our own web app, where a page reload must not leave a trail of empty\n\t\t\t// conversations, and it is wrong for a method called `start`: a verb\n\t\t\t// that sometimes does not do the thing is worse than two verbs. It is\n\t\t\t// also ambiguous the moment a user has more than one conversation,\n\t\t\t// since it silently picks the newest.\n\t\t\t//\n\t\t\t// So the SDK does not rely on that default. `start` starts. To\n\t\t\t// continue a conversation, hold its id: `agent.conversation(id)`.\n\t\t\tfresh: true,\n\t\t});\n\t\treturn new Conversation(this.#http, data.id, data);\n\t}\n\n\t/**\n\t * Address one of this agent's conversations by id. No request.\n\t *\n\t * Here rather than on the workspace because a conversation belongs to an\n\t * agent. You already hold the agent: it is a constant in your own code, not\n\t * a per-row value, so naming it costs nothing and the ownership is visible.\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('carry on');\n\t * ```\n\t */\n\tconversation(conversationId: string): Conversation {\n\t\treturn new Conversation(this.#http, conversationId);\n\t}\n\n\t/**\n\t * This agent's most recent conversations, newest first.\n\t *\n\t * NOT ALL OF THEM, and the previous sentence here said \"every\", which was\n\t * wrong in the way that costs someone an afternoon. The server returns the\n\t * newest 50 unless you ask for more, and refuses to return more than 200 on\n\t * any single call (apps/api/src/state.ts listAgentSessions, and the Durable\n\t * Object twin beside it, clamp identically).\n\t *\n\t * There is no cursor on this route yet, so an agent with more than 200\n\t * conversations CANNOT be fully enumerated through this method. If you are\n\t * counting or exporting, do not build on it; that needs a paginated route we\n\t * have not added. `ws.endUser(id).conversations()` DOES have a cursor, if\n\t * the question is one person's history rather than one agent's.\n\t *\n\t * `endUser` narrows to one of YOUR end users, which is what you want for a\n\t * \"your conversations\" list. Leaving it out returns everyone's, so leave it\n\t * out only for an operator view.\n\t *\n\t * ```ts\n\t * const mine = await agent.conversations({ endUser: row.userId });\n\t * ```\n\t */\n\tasync conversations(options: { limit?: number; endUser?: string } = {}): Promise<Conversation[]> {\n\t\tconst res = await this.#http.get<{ conversations: AgentConversationData[] }>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t// One end user's history. Sending this used to be silently ignored on\n\t\t\t// this route, which answered with everyone's conversations, so an\n\t\t\t// application rendering \"your chats\" showed somebody else's.\n\t\t\t...(options.endUser !== undefined && { end_user_id: options.endUser }),\n\t\t});\n\t\treturn (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));\n\t}\n}\n\n/** GET and PUT /v1/agents/:id/website-chat-access, as sent. */\ninterface WebsiteChatWire {\n\tagentId: string;\n\taccess: WebsiteChatAccess;\n\tchatUrl: string;\n}\n\nfunction toWebsiteChat(wire: WebsiteChatWire): WebsiteChat {\n\treturn { access: wire.access, chatUrl: wire.chatUrl, embedAgentId: wire.agentId };\n}\n\n/** The end user a conversation belongs to: a non-empty string, or a thrown error before any request. */\nfunction requireEndUserId(value: unknown): string {\n\t// Checked at RUNTIME as well as in the type: a JavaScript caller, or a\n\t// TypeScript one reading a nullable column, can still pass null, a number or\n\t// ''. Each used to start an ownerless conversation that looked fine.\n\tif (typeof value === 'string' && value.trim()) return value.trim();\n\tthrow new AgentNavaError({\n\t\tmessage: 'agent.start() needs endUserId: a non-empty string naming who the conversation is for (your user id, a visitor id, or a job name).',\n\t\tcode: 'end_user_required',\n\t\tstatus: 400,\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,0DAA0D;;AAE1D,OAAO,EAAgB,WAAW,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAmB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAwHrE;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,KAAK;IACR,EAAE,CAAS;IACX,KAAK,CAAO;IACb,QAAQ,CAAa;IAE7B,YAAY,IAAU,EAAE,EAAU,EAAE,QAAoB;QACvD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED,IAAY,IAAI;QACf,OAAO,cAAc,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACpD,CAAC;IAED;uEACmE;IACnE,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,UAAgC,EAAE;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAChC,IAAI,CAAC,IAAI,EACT,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CACxE,CAAC;QACF,2EAA2E;QAC3E,sEAAsE;QACtE,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACxD,OAAO,IAAI,CAAC;IACb,CAAC;IAED,mCAAmC;IACnC,KAAK,CAAC,QAAQ;QACb,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,GAAG,IAAI,CAAC,IAAI,WAAW,CAAC,CAAC;QACrF,OAAO,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED;;sDAEkD;IAClD,KAAK,CAAC,MAAM,CAAC,MAAmB;QAC/B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,GAAG,IAAI,CAAC,IAAI,WAAW,EAAE,MAAM,CAAC,CAAC;QAC/E,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,EAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,MAAM;QACX,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,4EAA4E;IAE5E;;;;;;;OAOG;IACH,KAAK,CAAC,WAAW,CAAC,QAAkB;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC;QACzE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,IAAY;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC;QACzE,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED;8EAC0E;IAC1E,KAAK,CAAC,WAAW,CAAC,QAAmB;QACpC,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClC,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,gBAAgB;QACrB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,GAAG,IAAI,CAAC,IAAI,iBAAiB,EAAE,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;QAChE,IAAI,OAAO,EAAE,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;YACjD,MAAM,IAAI,cAAc,CAAC;gBACxB,OAAO,EAAE,yEAAyE;gBAClF,IAAI,EAAE,qBAAqB;gBAC3B,MAAM,EAAE,GAAG;aACX,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC,GAAG,CAAC;IACpB,CAAC;IAED;iFAC6E;IAC7E,KAAK,CAAC,iBAAiB,CAAC,UAA8B,EAAE;QACvD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,GAAG,IAAI,CAAC,IAAI,qBAAqB,EACjC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAClE,CAAC;QACF,OAAO,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe;QACpB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAA8B,GAAG,IAAI,CAAC,IAAI,eAAe,EAAE,EAAE,CAAC,CAAC;QAChG,OAAO,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,CAAC;IAED,4EAA4E;IAE5E;;;;;;;;;;;;;;;;;;;;OAoBG;IACM,WAAW,GAAG;QACtB;qCAC6B;QAC7B,GAAG,EAAE,KAAK,IAA0B,EAAE,CAAC,aAAa,CACnD,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,GAAG,IAAI,CAAC,IAAI,sBAAsB,CAAC,CACzE;QACD;wEACgE;QAChE,MAAM,EAAE;YACP,gCAAgC;YAChC,GAAG,EAAE,KAAK,IAAgC,EAAE,CAC3C,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,GAAG,IAAI,CAAC,IAAI,sBAAsB,CAAC,CAAC,CAAC,MAAM;YACnF;gDACoC;YACpC,GAAG,EAAE,KAAK,EAAE,MAAyB,EAA8B,EAAE,CACpE,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,GAAG,IAAI,CAAC,IAAI,sBAAsB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM;SAC/F;QACD;;;wDAGgD;QAChD,eAAe,EAAE;YAChB,6BAA6B;YAC7B,GAAG,EAAE,KAAK,IAAuB,EAAE,CAClC,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,GAAG,IAAI,CAAC,IAAI,mBAAmB,CAAC,CAAC,CAAC,QAAQ;YACzF;;iFAEqE;YACrE,GAAG,EAAE,KAAK,EAAE,QAAkB,EAAqB,EAAE,CACpD,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,GAAG,IAAI,CAAC,IAAI,mBAAmB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ;SACvG;KACD,CAAC;IAEF,4EAA4E;IAE5E,sBAAsB,CAAC,IAAY;QAClC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,0BAA0B,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IACrF,CAAC;IAED;;;;;;;;OAQG;IACM,SAAS,GAAG;QACpB,GAAG,EAAE,KAAK,EAAE,IAAY,EAAE,KAA+B,EAAE,WAAoB,EAAsB,EAAE,CACtG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAY,GAAG,IAAI,CAAC,IAAI,kBAAkB,EAAE;YAC7D,MAAM,EAAE,KAAK;YACb,KAAK;YACL,KAAK,EAAE,EAAE,IAAI,EAAE;YACf,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;SACnC,CAAC;QACH,4EAA4E;QAC5E,GAAG,EAAE,KAAK,EAAE,IAAY,EAAuB,EAAE;YAChD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC;YACpD,OAAO,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;QAChD,CAAC;QACD,wEAAwE;QACxE,IAAI,EAAE,KAAK,EAAE,IAAY,EAAmB,EAAE;YAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC;YACpD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC;QACD,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,GAAG,EAAwB,EAAE;YAChD,oEAAoE;YACpE,yEAAyE;YACzE,uEAAuE;YACvE,oEAAoE;YACpE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,GAAG,IAAI,CAAC,IAAI,kBAAkB,CAC9B,CAAC;YACF,MAAM,MAAM,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QACpE,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,IAAY,EAAiB,EAAE;YAC7C,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,kBAAkB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,CAAC;KACD,CAAC;IAEF,2EAA2E;IAE3E;;;;;;;;;;;;;;;OAeG;IACM,cAAc,GAAG;QACzB;;;;;;;;;;WAUG;QACH,MAAM,EAAE,KAAK,EAAE,GAAW,EAAkB,EAAE;YAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,GAAG,IAAI,CAAC,IAAI,kBAAkB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YACtF,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YACpB,OAAO,IAAI,CAAC;QACb,CAAC;QAED;;uBAEe;QACf,MAAM,EAAE,KAAK,EAAE,GAAW,EAAkB,EAAE;YAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAClC,GAAG,IAAI,CAAC,IAAI,oBAAoB,kBAAkB,CAAC,GAAG,CAAC,EAAE,CACzD,CAAC;YACF,IAAI,GAAG;gBAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YAC7B,OAAO,IAAI,CAAC;QACb,CAAC;QAED,mEAAmE;QACnE,IAAI,EAAE,KAAK,IAAuB,EAAE;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACpD,OAAO,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC;QACrC,CAAC;KACD,CAAC;IAEF,2EAA2E;IAE3E;;;;;;;;;;;;OAYG;IACM,SAAS,GAAG;QACpB;;mCAE2B;QAC3B,MAAM,EAAE,KAAK,EAAE,GAAW,EAAkB,EAAE;YAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,GAAG,IAAI,CAAC,IAAI,YAAY,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YAChF,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YACpB,OAAO,IAAI,CAAC;QACb,CAAC;QAED;mDAC2C;QAC3C,MAAM,EAAE,KAAK,EAAE,GAAW,EAAkB,EAAE;YAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAY,GAAG,IAAI,CAAC,IAAI,cAAc,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACpG,IAAI,GAAG;gBAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YAC7B,OAAO,IAAI,CAAC;QACb,CAAC;QAED,6DAA6D;QAC7D,IAAI,EAAE,KAAK,IAAuB,EAAE;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACpD,OAAO,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;QAChC,CAAC;KACD,CAAC;IAEF,2EAA2E;IAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACM,WAAW,GAAG;QACtB;;;;;;;;;WASG;QACH,GAAG,EAAE,KAAK,EAAE,UAAsB,EAAkB,EAAE;YACrD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,GAAG,IAAI,CAAC,IAAI,cAAc,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC;YACxG,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YACpB,OAAO,IAAI,CAAC;QACb,CAAC;QAED;;;;;;;;;;;;WAYG;QACH,MAAM,EAAE,KAAK,EACZ,IAAY,EACZ,MAA2F,EAC1E,EAAE;YACnB,oEAAoE;YACpE,0DAA0D;YAC1D,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC;YACrD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBAC3C,MAAM,IAAI,cAAc,CAAC;oBACxB,OAAO,EAAE,uCAAuC,IAAI,cACnD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAC1C,GAAG;oBACH,IAAI,EAAE,sBAAsB;oBAC5B,MAAM,EAAE,GAAG;iBACX,CAAC,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,GACV,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAa,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,OAA2B,CAAC,EAAE,CAAC;YACrH,wEAAwE;YACxE,0CAA0C;YAC1C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACvG,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,GAAG,IAAI,CAAC,IAAI,WAAW,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;YACxF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,OAAO,IAAI,CAAC;QACb,CAAC;QAED;;+CAEuC;QACvC,MAAM,EAAE,KAAK,EAAE,IAAY,EAAkB,EAAE;YAC9C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAY,GAAG,IAAI,CAAC,IAAI,gBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvG,IAAI,GAAG;gBAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;YAC7B,OAAO,IAAI,CAAC;QACb,CAAC;QAED;;;;;;;;WAQG;QACH,IAAI,EAAE,KAAK,IAAiC,EAAE;YAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsC,GAAG,IAAI,CAAC,IAAI,cAAc,CAAC,CAAC;YAClG,OAAO,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;QAC9B,CAAC;KACD,CAAC;IAEF,6EAA6E;IAE7E;;;;;;;;;;OAUG;IACH,KAAK,CAAC,cAAc,CAAC,QAAkB;QACtC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CACjC,aAAa,EACb,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CACpB,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;YACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;YAChE,IAAI,QAAQ;gBAAE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;;gBAC7F,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9E,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,EAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,MAAM,CAAC,QAAkB;QAC9B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,cAAc,CAAC;gBACxB,OAAO,EACN,qFAAqF;oBACrF,0EAA0E;oBAC1E,wBAAwB;gBACzB,IAAI,EAAE,cAAc;gBACpB,MAAM,EAAE,GAAG;aACX,CAAC,CAAC;QACJ,CAAC;QACD,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAC5B,OAAO,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED,4EAA4E;IAE5E;;;;2CAIuC;IACvC,KAAK,CAAC,KAAK,CAAC,OAAqB;QAChC,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QACvD,mEAAmE;QACnE,wEAAwE;QACxE,mEAAmE;QACnE,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,MAAM,QAAQ,GACb,OAAO,CAAC,OAAO,IAAI,SAAS;YAC3B,CAAC,CAAC;gBACA,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpD,GAAG,CAAC,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC;aAC/B;YACF,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,mBAAmB,EAAE;YAC9E,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClE,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;YAC7D,qDAAqD;YACrD,EAAE;YACF,6DAA6D;YAC7D,uEAAuE;YACvE,mEAAmE;YACnE,iEAAiE;YACjE,kCAAkC;YAClC,EAAE;YACF,sEAAsE;YACtE,sEAAsE;YACtE,qEAAqE;YACrE,wCAAwC;YACxC,GAAG,CAAC,QAAQ,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;YAC9C,+DAA+D;YAC/D,2CAA2C;YAC3C,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;YACvB,wDAAwD;YACxD,EAAE;YACF,oEAAoE;YACpE,oEAAoE;YACpE,uEAAuE;YACvE,qEAAqE;YACrE,sEAAsE;YACtE,mEAAmE;YACnE,sCAAsC;YACtC,EAAE;YACF,+DAA+D;YAC/D,kEAAkE;YAClE,KAAK,EAAE,IAAI;SACX,CAAC,CAAC;QACH,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,cAAsB,EAAE,UAAiC,EAAE;QACvE,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC,CAAC;IACvG,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,KAAK,CAAC,aAAa,CAAC,UAAgD,EAAE;QACrE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6C,mBAAmB,EAAE;YACjG,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;YAC5D,sEAAsE;YACtE,kEAAkE;YAClE,6DAA6D;YAC7D,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;SACtE,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;CACD;;AASD,SAAS,aAAa,CAAC,IAAqB;IAC3C,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;AACnF,CAAC;AAED,wGAAwG;AACxG,SAAS,gBAAgB,CAAC,KAAc;IACvC,uEAAuE;IACvE,6EAA6E;IAC7E,qEAAqE;IACrE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACnE,MAAM,IAAI,cAAc,CAAC;QACxB,OAAO,EAAE,mIAAmI;QAC5I,IAAI,EAAE,mBAAmB;QACzB,MAAM,EAAE,GAAG;KACX,CAAC,CAAC;AACJ,CAAC","sourcesContent":["// An agent: a configuration you start conversations from.\n\nimport { type Account, accountIdOf } from './account.ts';\nimport { isBindings, refuseConnections, secretIdsOf, secretsIn } from './attach.ts';\nimport { Conversation } from './conversation.ts';\nimport { AgentNavaError } from './errors.ts';\nimport type { Http } from './http.ts';\nimport type { Secret } from './secret.ts';\nimport { type Connection, connectionForWire } from './connection.ts';\nimport type {\n\tAgentConversationData,\n\tAgentData,\n\tAgentFields,\n\tBindings,\n\tConnectionStatus,\n\tFileEntry,\n\tTrigger,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWebhookDelivery,\n} from './types.ts';\n\nexport interface StartOptions {\n\t/**\n\t * Stored secrets to attach AS THE CONVERSATION STARTS.\n\t *\n\t * A `Secret` from `ws.endUser(id).secrets.create(...)` attaches by id, which\n\t * is the only way a stored per-person credential can reach a turn: you never\n\t * hold its value.\n\t *\n\t * ```ts\n\t * const key = await ws.endUser(row.userId).secrets.create({ name: 'ZENDESK_TOKEN', value });\n\t * const conversation = await agent.start({ endUserId: row.userId, attach: key });\n\t * ```\n\t *\n\t * Doing it here rather than with a second `conversation.attach()` call\n\t * closes a real window: between `start` and the attach, the conversation\n\t * exists and can be asked something, and it would answer without it.\n\t *\n\t * ACCOUNTS ARE NOT ATTACHED HERE any more (0.44), and one handed in is\n\t * refused. The agent's setup names the account each connection uses; for a\n\t * connection to the end user's account (`Connection.endUserAccount`), the\n\t * turn uses the account of the person this conversation is for, found by\n\t * `endUserId`. Ask\n\t * `conversation.accountsNeeded()` for the ones they have not connected yet.\n\t */\n\tattach?: Secret | Secret[];\n\n\t/**\n\t * Secret VALUES for this conversation only.\n\t *\n\t * Not `attach`, because a secret has no id you can hand over: binding one\n\t * takes the value itself. That asymmetry is in the product, not in this\n\t * naming, and hiding it behind the same word would not remove it.\n\t */\n\tsecrets?: Record<string, string>;\n\n\t/** Pin a specific version. Defaults to the latest at the moment of starting.\n\t *\n\t * A conversation pins its version for life: publishing afterwards does not\n\t * move a conversation that is already open. The one exception is a schedule\n\t * or webhook that names the conversation (`conversationId`): each firing\n\t * moves it to the latest version, so a fix you publish reaches it. */\n\tversion?: number;\n\tparameters?: Record<string, string | number>;\n\n\t/**\n\t * WHO this conversation is for: one of YOUR users, not one of ours.\n\t *\n\t * REQUIRED, and never null. Every conversation belongs to someone. The id\n\t * is yours to choose: a user's row id, an anonymous visitor's random id, or\n\t * a name for a job such as `'nightly-report'`. Only a non-empty string is\n\t * accepted; anything else throws before a request is sent. (Decided\n\t * 2026-09-26, AGE-161: a conversation with no owner cannot be listed back to\n\t * the person it was for, and the accounts connected inside it could not be\n\t * revoked with that person.)\n\t *\n\t * It LABELS the conversation, and it decides one thing more: a connection\n\t * to the end user's account (`Connection.endUserAccount`) uses THIS\n\t * person's account for that app.\n\t * It does not decide which conversation you get, because `start` always\n\t * starts a new one. Later you can ask for everything belonging to that\n\t * person:\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: row.userId });\n\t * const nightly = await agent.start({ endUserId: 'nightly-report' }); // a job has an id too\n\t * const theirs = await agent.conversations({ endUser: row.userId });\n\t * ```\n\t *\n\t * To CONTINUE a conversation, keep its id and address it directly. That is\n\t * the only way to continue one, on purpose:\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('and the rest?');\n\t * ```\n\t *\n\t * The id is YOURS and opaque to us: a row id from your database, not an\n\t * email and not something we authenticate. We never verify it, so it is\n\t * exactly as trustworthy as whatever holds your API key.\n\t *\n\t * The platform's prefixes are refused with a 400 (`end_user_reserved`):\n\t * `member:`, `visitor:` and `job:`, which name a member in the web app, an\n\t * embed visitor and work nobody is typing, and the older `webhook:`,\n\t * `builder:` and `librarian:`. Case and spacing do not matter to the check\n\t * (`MEMBER:x` is refused too). An id you chose must never be mistaken for\n\t * one of ours.\n\t *\n\t * NOT A WORKSPACE MEMBER'S ID. `ws.members()` lists the colleagues who share\n\t * this account, and their `userId` is a platform id we issued. This field is\n\t * a different namespace entirely. Both were once spelled `userId`, so\n\t * passing a member id here was a call that could not fail: there is nothing\n\t * for an end-user id to be checked against, so it just labelled the\n\t * conversation with a colleague's internal id and looked fine.\n\t */\n\tendUserId: string;\n\t/**\n\t * This conversation's screen can draw widgets: cards, tables and charts the\n\t * agent writes inside its replies, in Google's A2UI format. Off unless set.\n\t * When on, each turn's stream carries `widget` events and `done.widgets`,\n\t * and `messages()` returns each reply's `widgets`. The agent draws only if\n\t * its own \"Answer with widgets\" setting is on (it is, unless turned off).\n\t * The SDK ships no screens: draw them with any A2UI renderer.\n\t */\n\twidgets?: boolean;\n}\n\n/**\n * An agent you can configure and start conversations from.\n *\n * Holding one makes NO request: `ws.agent(id)` is free and the first call on it\n * is the first round trip.\n *\n * EVERY METHOD THAT CHANGES THE AGENT PUBLISHES A VERSION. There is no separate\n * save. What you do not send is inherited from the current version, which is the\n * only reason changing one field is safe: a resend that rebuilt the whole\n * configuration would drop whatever the caller forgot, and that bug has shipped\n * here four separate times.\n */\nexport class Agent {\n\treadonly id: string;\n\treadonly #http: Http;\n\tprivate snapshot?: AgentData;\n\n\tconstructor(http: Http, id: string, snapshot?: AgentData) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/agents/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/** The last-known configuration, from whatever call produced this object.\n\t * Undefined on an agent you addressed by id and have not read. */\n\tget data(): AgentData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\tasync get(options: { version?: number } = {}): Promise<AgentData> {\n\t\tconst data = await this.#http.get<AgentData>(\n\t\t\tthis.base,\n\t\t\toptions.version !== undefined ? { version: options.version } : undefined,\n\t\t);\n\t\t// Only cache the CURRENT configuration. Caching a historical version would\n\t\t// make `agent.data` mean different things depending on the last call.\n\t\tif (options.version === undefined) this.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/** Every version, newest first. */\n\tasync versions(): Promise<AgentData[]> {\n\t\tconst res = await this.#http.get<{ versions: AgentData[] }>(`${this.base}/versions`);\n\t\treturn res.versions ?? [];\n\t}\n\n\t/** Publish a new version with these fields changed and everything else kept.\n\t * `connections`, when sent, replaces the whole list; an `Account` in it goes\n\t * over the wire as its id (`Account.toJSON`). */\n\tasync update(fields: AgentFields): Promise<Agent> {\n\t\tconst data = await this.#http.post<AgentData>(`${this.base}/versions`, fields);\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t// --------------------------------------------------------------- workflows\n\n\t/**\n\t * Add a workflow, or replace the one with the same name.\n\t *\n\t * Reads the current list and sends it back with this one in place, because\n\t * the API replaces the whole list: a workflow set is a list, and there is no\n\t * \"merge one item\" that does not have to decide what a duplicate name means.\n\t * Deciding it here, once, is better than every caller deciding it.\n\t */\n\tasync setWorkflow(workflow: Workflow): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\tconst next = current.filter((w) => w.name !== workflow.name).concat(workflow);\n\t\treturn this.update({ workflows: next });\n\t}\n\n\tasync removeWorkflow(name: string): Promise<Agent> {\n\t\tconst current = this.snapshot?.workflows ?? (await this.get()).workflows;\n\t\treturn this.update({ workflows: current.filter((w) => w.name !== name) });\n\t}\n\n\t/** Replace the trigger list. Sending `[]` removes every trigger, including\n\t * `chat`, which is what leaves an agent that only runs on a schedule. */\n\tasync setTriggers(triggers: Trigger[]): Promise<Agent> {\n\t\treturn this.update({ triggers });\n\t}\n\n\t/**\n\t * Replace the webhook's URL and return the new one.\n\t *\n\t * The old URL stops working the moment this returns, so update the system\n\t * that calls it. Use it when a URL leaked. The webhook's `message` and who it\n\t * runs for are kept. Publishes a version. Throws `no_webhook` (409) when the\n\t * agent has no webhook trigger.\n\t */\n\tasync rotateWebhookUrl(): Promise<string> {\n\t\tconst data = await this.#http.post<AgentData>(`${this.base}/webhook/rotate`, {});\n\t\tthis.snapshot = data;\n\t\tconst webhook = data.triggers.find((t) => t.kind === 'webhook');\n\t\tif (webhook?.kind !== 'webhook' || !webhook.url) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: 'The webhook was rotated but no URL came back. Read it with agent.get().',\n\t\t\t\tcode: 'webhook_url_missing',\n\t\t\t\tstatus: 502,\n\t\t\t});\n\t\t}\n\t\treturn webhook.url;\n\t}\n\n\t/** What the webhook received, newest first: accepted, refused (and why), a\n\t * sender's retry recognised as a repeat, or a test. Up to `limit` (200). */\n\tasync webhookDeliveries(options: { limit?: number } = {}): Promise<WebhookDelivery[]> {\n\t\tconst res = await this.#http.get<{ deliveries: WebhookDelivery[] }>(\n\t\t\t`${this.base}/webhook/deliveries`,\n\t\t\toptions.limit !== undefined ? { limit: options.limit } : undefined,\n\t\t);\n\t\treturn res.deliveries ?? [];\n\t}\n\n\t/**\n\t * Send the webhook a sample event and run the agent on it, the way a real\n\t * delivery would (the signature check aside). A real turn, charged like\n\t * one. Returns the conversation it ran in.\n\t */\n\tasync sendTestWebhook(): Promise<{ conversationId?: string }> {\n\t\tconst res = await this.#http.post<{ conversationId?: string }>(`${this.base}/webhook/test`, {});\n\t\treturn res.conversationId ? { conversationId: res.conversationId } : {};\n\t}\n\n\t// ------------------------------------------------------------ website chat\n\n\t/**\n\t * The agent's website chat: its chat page on AgentNava (the share link),\n\t * and the embed script that puts it on your website. Every agent has one\n\t * from the moment it is created, however it was made (here, through the\n\t * API, or in the portal), except an agent that needs each user's own\n\t * account or secret, which a visitor cannot bring.\n\t *\n\t * Who can chat is one setting, `access`:\n\t *\n\t * 'restricted' the default. The share link works for members of your\n\t * workspace who are signed in to AgentNava, so your team can\n\t * pass it around to test. The chat shows on another website\n\t * only when that website is on `allowedWebsites`.\n\t * 'anyone' anyone with the link can chat, and any website can show\n\t * the chat. Each visitor's turns are charged to your\n\t * workspace.\n\t *\n\t * You never need it to use the agent from code: the agent answers your API\n\t * key, and the portal's Test, as soon as it exists. Publishing a new version\n\t * moves the website chat to it, as it moves new conversations.\n\t */\n\treadonly websiteChat = {\n\t\t/** Who can chat, the share link, and the id the embed script's\n\t\t * `data-agent-id` takes. */\n\t\tget: async (): Promise<WebsiteChat> => toWebsiteChat(\n\t\t\tawait this.#http.get<WebsiteChatWire>(`${this.base}/website-chat-access`),\n\t\t),\n\t\t/** Who can chat: 'restricted' (your workspace's signed-in members, and\n\t\t * the allowed websites) or 'anyone' (anyone with the link). */\n\t\taccess: {\n\t\t\t/** The setting as it stands. */\n\t\t\tget: async (): Promise<WebsiteChatAccess> =>\n\t\t\t\t(await this.#http.get<WebsiteChatWire>(`${this.base}/website-chat-access`)).access,\n\t\t\t/** Change it. Returns the setting as stored. Anything but 'restricted'\n\t\t\t * or 'anyone' is refused (400). */\n\t\t\tset: async (access: WebsiteChatAccess): Promise<WebsiteChatAccess> =>\n\t\t\t\t(await this.#http.put<WebsiteChatWire>(`${this.base}/website-chat-access`, { access })).access,\n\t\t},\n\t\t/** The websites a Restricted agent's chat may be shown on through the\n\t\t * embed script, where visitors chat without signing in. A domain covers\n\t\t * its subdomains; an empty list means no other website. While access is\n\t\t * 'anyone' the list is kept and not needed. */\n\t\tallowedWebsites: {\n\t\t\t/** The list as it stands. */\n\t\t\tget: async (): Promise<string[]> =>\n\t\t\t\t(await this.#http.get<{ websites: string[] }>(`${this.base}/allowed-websites`)).websites,\n\t\t\t/** Replace the list. Each entry is normalised (\"https://acme.com/page\"\n\t\t\t * becomes \"acme.com\"; \"localhost\" is allowed for testing), and one that\n\t\t\t * is not a website is refused (400). Returns the list as stored. */\n\t\t\tset: async (websites: string[]): Promise<string[]> =>\n\t\t\t\t(await this.#http.put<{ websites: string[] }>(`${this.base}/allowed-websites`, { websites })).websites,\n\t\t},\n\t};\n\n\t// --------------------------------------------------------------- knowledge\n\n\t#readKnowledgeResponse(path: string): Promise<Response> {\n\t\treturn this.#http.send(`${this.base}/knowledge/files/content`, { query: { path } });\n\t}\n\n\t/**\n\t * The agent's knowledge: files every conversation can read.\n\t *\n\t * BY REFERENCE, not by copy. A conversation reads the knowledge as it is now,\n\t * so replacing a file changes what open conversations see on their next turn.\n\t * That is the opposite of how a version works, and it is deliberate: a price\n\t * list that could not be corrected without republishing the agent would be\n\t * useless.\n\t */\n\treadonly knowledge = {\n\t\tput: async (path: string, bytes: Uint8Array | ArrayBuffer, contentType?: string): Promise<FileEntry> =>\n\t\t\tthis.#http.request<FileEntry>(`${this.base}/knowledge/files`, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbytes,\n\t\t\t\tquery: { path },\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t}),\n\t\t/** Read an agent-owned knowledge file as bytes, without starting a turn. */\n\t\tget: async (path: string): Promise<Uint8Array> => {\n\t\t\tconst res = await this.#readKnowledgeResponse(path);\n\t\t\treturn new Uint8Array(await res.arrayBuffer());\n\t\t},\n\t\t/** Read UTF-8 text. JSON files remain text rather than being parsed. */\n\t\ttext: async (path: string): Promise<string> => {\n\t\t\tconst res = await this.#readKnowledgeResponse(path);\n\t\t\treturn res.text();\n\t\t},\n\t\tlist: async (path = '/'): Promise<FileEntry[]> => {\n\t\t\t// The route returns EVERY knowledge file for the agent; it takes no\n\t\t\t// directory parameter. Filtering here rather than sending one the server\n\t\t\t// ignores, because a call that looks filtered and is not is worse than\n\t\t\t// no filter at all. Knowledge sets are small, so one read is right.\n\t\t\tconst res = await this.#http.get<{ files: (FileEntry & { path: string })[] }>(\n\t\t\t\t`${this.base}/knowledge/files`,\n\t\t\t);\n\t\t\tconst prefix = path === '/' ? '/' : `${path.replace(/\\/+$/, '')}/`;\n\t\t\treturn (res.files ?? []).filter((f) => f.path?.startsWith(prefix));\n\t\t},\n\t\tremove: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/knowledge/files`, { path });\n\t\t},\n\t};\n\n\t// -------------------------------------------------------- knowledge bases\n\n\t/**\n\t * The WORKSPACE knowledge bases this agent reads.\n\t *\n\t * Different from `knowledge` above, and the distinction is the whole point.\n\t * `agent.knowledge` is files this agent owns; nobody else has them. A\n\t * knowledge base is shared: one price list, attached to nine agents, corrected\n\t * in one place.\n\t *\n\t * Each attached base is mounted read-only at `/knowledge/<key>/` when a turn\n\t * starts, so the agent reads it with `ls`, `grep` and `cat`.\n\t *\n\t * ATTACHING PUBLISHES A VERSION. Which bases an agent reads is part of its\n\t * configuration, so the change is diffable and reversible like any other\n\t * configuration change. The CONTENTS of a base are not versioned: correcting\n\t * a file changes what every attached agent sees on its next turn.\n\t */\n\treadonly knowledgeBases = {\n\t\t/**\n\t\t * Attach one base by key.\n\t\t *\n\t\t * One call, not read-modify-write, deliberately. Two attaches racing\n\t\t * through a read of the current list and a write of the whole list would\n\t\t * lose one of them silently, and losing an attachment looks exactly like\n\t\t * an agent that cannot find its knowledge. The server appends.\n\t\t *\n\t\t * Attaching a key that names no base is refused, naming the key. Attaching\n\t\t * one that is already attached is a no-op, not an error.\n\t\t */\n\t\tattach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/knowledge-bases`, { key });\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Detach one base. The base itself and its files are untouched: this only\n\t\t * stops THIS agent from reading it. Detaching one that is not attached is\n\t\t * a no-op. */\n\t\tdetach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(\n\t\t\t\t`${this.base}/knowledge-bases/${encodeURIComponent(key)}`,\n\t\t\t);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** The keys this agent currently reads, in configuration order. */\n\t\tlist: async (): Promise<string[]> => {\n\t\t\tconst current = this.snapshot ?? (await this.get());\n\t\t\treturn current.knowledgeBases ?? [];\n\t\t},\n\t};\n\n\t// -------------------------------------------------------------- databases\n\n\t/**\n\t * The WORKSPACE databases this agent may query, by key.\n\t *\n\t * Register a database first with `ws.databases.create()`, which gives it its\n\t * key; attaching names that key in this agent's configuration. Naming it is\n\t * the grant: the database answers this agent's `sql_*` calls and no agent\n\t * that does not name it, and the agent's prompt lists it by key.\n\t *\n\t * ATTACHING PUBLISHES A VERSION, like a knowledge base: which databases an\n\t * agent reaches is part of its configuration, so the change is diffable and\n\t * reversible. Detaching takes effect on the agent's next query, including in\n\t * a conversation already under way.\n\t */\n\treadonly databases = {\n\t\t/** Attach one database by key. The server appends, so two attaches cannot\n\t\t * lose each other. A key that names no database is refused; one already\n\t\t * attached is a no-op. */\n\t\tattach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/databases`, { key });\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Stop this agent querying one database. The database stays registered\n\t\t * for every other agent that names it. */\n\t\tdetach: async (key: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(`${this.base}/databases/${encodeURIComponent(key)}`);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** The keys this agent may query, in configuration order. */\n\t\tlist: async (): Promise<string[]> => {\n\t\t\tconst current = this.snapshot ?? (await this.get());\n\t\t\treturn current.databases ?? [];\n\t\t},\n\t};\n\n\t// ------------------------------------------------------------ connections\n\n\t/**\n\t * The apps this agent uses: each a NAMED connection that says WHOSE ACCOUNT\n\t * it uses, built the way the portal's Add a connection screen asks.\n\t *\n\t * | built with | whose account | the person signs in |\n\t * |---|---|---|\n\t * | `Connection.creatorAccount(app, account)` | the agent creator's, from `ws.accounts.connect()` | once, and every conversation uses it |\n\t * | `Connection.endUserAccount(app)` | the end user's: each person talking to the agent | once each, remembered for them |\n\t *\n\t * ```ts\n\t * const { account: work } = await ws.accounts.connect({ app: App.GMAIL, nickname: 'Work' });\n\t * await agent.connections.add(Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }));\n\t * await agent.connections.add(Connection.endUserAccount(App.GOOGLE_CALENDAR));\n\t * ```\n\t *\n\t * A turn uses EXACTLY the account the setup names, and never another one.\n\t * There is no fallback to whichever account of that app happens to exist:\n\t * that fallback is what this replaced, and it meant reading an agent's code\n\t * told you nothing about whose mailbox it read.\n\t *\n\t * EVERY CHANGE PUBLISHES A VERSION, like `databases`: which accounts an\n\t * agent reaches is part of its configuration, so the change is diffable and\n\t * reversible. A conversation uses the connections of the version it runs.\n\t * The ACCOUNTS are not versioned: signing one in again reaches every agent\n\t * that names it, from the next turn.\n\t *\n\t * Removing a connection never touches the account. Disconnecting an account\n\t * is the other operation, on the account: `account.disconnect()`.\n\t */\n\treadonly connections = {\n\t\t/**\n\t\t * Add one connection. One call that publishes, not read-modify-write, so\n\t\t * two adds racing cannot lose each other.\n\t\t *\n\t\t * Takes what `Connection.creatorAccount()` or `Connection.endUserAccount()`\n\t\t * builds, or the same plain object. Refused, naming the problem, when the\n\t\t * name is taken on this agent (`connection_name_taken`), when the account\n\t\t * is of a different app (`account_app_mismatch`), or when no account has\n\t\t * that id (`account_not_found`).\n\t\t */\n\t\tadd: async (connection: Connection): Promise<Agent> => {\n\t\t\tconst res = await this.#http.post<AgentData>(`${this.base}/connections`, connectionForWire(connection));\n\t\t\tthis.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/**\n\t\t * Point one connection at another account: `{ account }` for the agent\n\t\t * creator's (the `Account` you hold, or its id), or `{ endUser: true }`\n\t\t * for each end user's own. The name and the app stay; an account of a\n\t\t * different app is refused (`account_app_mismatch`).\n\t\t *\n\t\t * READ-MODIFY-WRITE, unlike `add` and `remove`, because there is no\n\t\t * route that changes one connection in place: this reads the current\n\t\t * version and publishes it again with the one connection changed. Two\n\t\t * changes racing can therefore lose one, so make them one after the\n\t\t * other. A name the agent does not have throws\n\t\t * (`connection_not_found`) before anything is sent.\n\t\t */\n\t\tchange: async (\n\t\t\tname: string,\n\t\t\tchange: { account: Account | string; endUser?: never } | { endUser: true; account?: never },\n\t\t): Promise<Agent> => {\n\t\t\t// Read FRESH, never from the snapshot. A stale list sent back whole\n\t\t\t// would silently undo whatever changed since it was read.\n\t\t\tconst current = (await this.get()).connections ?? [];\n\t\t\tif (!current.some((c) => c.name === name)) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `This agent has no connection named \"${name}\". It has: ${\n\t\t\t\t\t\tcurrent.map((c) => c.name).join(', ') || 'none'\n\t\t\t\t\t}.`,\n\t\t\t\t\tcode: 'connection_not_found',\n\t\t\t\t\tstatus: 404,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst whose =\n\t\t\t\tchange.endUser === true ? { endUser: true as const } : { account: accountIdOf(change.account as Account | string) };\n\t\t\t// Every other connection goes back exactly as it was read: this changes\n\t\t\t// one, and must not reinterpret the rest.\n\t\t\tconst connections = current.map((c) => (c.name === name ? { name: c.name, app: c.app, ...whose } : c));\n\t\t\tconst data = await this.#http.post<AgentData>(`${this.base}/versions`, { connections });\n\t\t\tthis.snapshot = data;\n\t\t\treturn this;\n\t\t},\n\n\t\t/** Remove one connection by name. Publishes a version. NEVER touches the\n\t\t * account: it stays connected for every other agent that names it, and\n\t\t * for this one if you add it back. */\n\t\tremove: async (name: string): Promise<Agent> => {\n\t\t\tconst res = await this.#http.delete<AgentData>(`${this.base}/connections/${encodeURIComponent(name)}`);\n\t\t\tif (res) this.snapshot = res;\n\t\t\treturn this;\n\t\t},\n\n\t\t/**\n\t\t * Each connection with the account it uses and whether that account works\n\t\t * now: DECLARED IS NOT CONNECTED. An agent can name an account nobody has\n\t\t * finished signing in to, or one that was disconnected since, and that is\n\t\t * the state a setup checklist has to show.\n\t\t *\n\t\t * Includes what an unpublished draft adds (`inDraft`) and removes\n\t\t * (`removedInDraft`), so it matches the portal.\n\t\t */\n\t\tlist: async (): Promise<ConnectionStatus[]> => {\n\t\t\tconst res = await this.#http.get<{ connections: ConnectionStatus[] }>(`${this.base}/connections`);\n\t\t\treturn res.connections ?? [];\n\t\t},\n\t};\n\n\t// ------------------------------------------------------ secrets, agent tier\n\n\t/**\n\t * Write secret VALUES to this agent's own scope: readable by this agent and\n\t * nothing else in the workspace, overriding a workspace secret of the same\n\t * name. Writing one twice replaces the value rather than failing, because\n\t * attaching means \"this is the credential now\".\n\t *\n\t * This used to POST the whole object to `/v1/agents/:id/bindings`, which\n\t * answers only to `{kind, decl}` and records a DECLARATION, never a value. So\n\t * the call 400'd, and had it not, it would have stored the fact that a secret\n\t * is wanted rather than the secret.\n\t */\n\tasync #applyBindings(bindings: Bindings): Promise<Agent> {\n\t\tfor (const [name, value] of Object.entries(bindings.secrets ?? {})) {\n\t\t\tconst mine = (await this.#http.get<{ secrets: { id: string; name: string; agentId?: string | null }[] }>(\n\t\t\t\t'/v1/secrets',\n\t\t\t\t{ agentId: this.id },\n\t\t\t)).secrets ?? [];\n\t\t\tconst existing = mine.find((s) => s.name === name && s.agentId);\n\t\t\tif (existing) await this.#http.patch(`/v1/secrets/${encodeURIComponent(existing.id)}`, { value });\n\t\t\telse await this.#http.post('/v1/secrets', { name, value, agentId: this.id });\n\t\t}\n\t\tconst data = await this.get();\n\t\tthis.snapshot = data;\n\t\treturn new Agent(this.#http, this.id, data);\n\t}\n\n\t/**\n\t * Give this agent secret values, so every conversation with it can use them.\n\t *\n\t * ```ts\n\t * await agent.attach({ secrets: { APOLLO_API_KEY: value } });\n\t * ```\n\t *\n\t * SECRETS ONLY, since 0.44. Accounts used to be attached here, and a turn\n\t * then had to guess which of the accounts attached and owned around it to\n\t * use. The agent's setup now names them: `agent.connections.add()`. One\n\t * handed in here is refused by name (`not_a_secret`) rather than dropped.\n\t *\n\t * A stored `Secret` is not attached to an agent either. It never was: one\n\t * was accepted here and nothing was done with it. Store one for the agent\n\t * with `ws.secrets.create({ name, value, agentId })`, or attach one of your\n\t * end user's to their conversation with `conversation.attach(secret)`.\n\t */\n\tasync attach(bindings: Bindings): Promise<Agent> {\n\t\tif (!isBindings(bindings)) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\t'agent.attach takes { secrets: { NAME: value } }. Accounts go in the agent\\'s setup ' +\n\t\t\t\t\t'(agent.connections.add), and a stored secret attaches to a conversation ' +\n\t\t\t\t\t'(conversation.attach).',\n\t\t\t\tcode: 'not_a_secret',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\trefuseConnections(bindings);\n\t\treturn this.#applyBindings(bindings);\n\t}\n\n\t// ----------------------------------------------------------- conversations\n\n\t/** Start a conversation. It pins the agent's version now and keeps it.\n\t *\n\t * Nothing about accounts is passed here: the version's connections name\n\t * them, and a connection to the end user's account uses the account of\n\t * the person named by `endUserId`. */\n\tasync start(options: StartOptions): Promise<Conversation> {\n\t\tconst endUserId = requireEndUserId(options?.endUserId);\n\t\t// Resolved BEFORE the launch: a Secret is named on the wire by the\n\t\t// declared NAME it fills, and a handle addressed by id may not know its\n\t\t// own yet. Anything that is not a Secret is refused here, by name.\n\t\tconst secretIds = await secretIdsOf(secretsIn(options.attach));\n\t\tconst bindings =\n\t\t\toptions.secrets || secretIds\n\t\t\t\t? {\n\t\t\t\t\t\t...(options.secrets && { secrets: options.secrets }),\n\t\t\t\t\t\t...(secretIds && { secretIds }),\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\t\tconst data = await this.#http.post<AgentConversationData>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.version !== undefined && { version: options.version }),\n\t\t\t...(options.parameters && { parameters: options.parameters }),\n\t\t\t// `sessionBindings`, NOT `secrets` at the top level.\n\t\t\t//\n\t\t\t// This sent them at the top level and the launch route reads\n\t\t\t// `body.sessionBindings`, so they were dropped in silence: the route's\n\t\t\t// own body type has no top-level `secrets` to read. A conversation\n\t\t\t// started with a credential began without it, and the first turn\n\t\t\t// failed somewhere else entirely.\n\t\t\t//\n\t\t\t// Third place this exact envelope mistake was found in one afternoon,\n\t\t\t// after `agent.bind` and `conversation.bind`. The shape of the bug is\n\t\t\t// always the same: the SDK agrees with itself, the route agrees with\n\t\t\t// itself, and nothing compares the two.\n\t\t\t...(bindings && { sessionBindings: bindings }),\n\t\t\t// Always present: requireEndUserId() above has already refused\n\t\t\t// anything that is not a non-empty string.\n\t\t\tuser: { id: endUserId },\n\t\t\t// ALWAYS a new conversation, even when a user is named.\n\t\t\t//\n\t\t\t// The route resumes that user's most recent conversation when it is\n\t\t\t// given an identity and not told otherwise. That default exists for\n\t\t\t// our own web app, where a page reload must not leave a trail of empty\n\t\t\t// conversations, and it is wrong for a method called `start`: a verb\n\t\t\t// that sometimes does not do the thing is worse than two verbs. It is\n\t\t\t// also ambiguous the moment a user has more than one conversation,\n\t\t\t// since it silently picks the newest.\n\t\t\t//\n\t\t\t// So the SDK does not rely on that default. `start` starts. To\n\t\t\t// continue a conversation, hold its id: `agent.conversation(id)`.\n\t\t\tfresh: true,\n\t\t});\n\t\treturn new Conversation(this.#http, data.id, data, { widgets: options.widgets === true });\n\t}\n\n\t/**\n\t * Address one of this agent's conversations by id. No request.\n\t *\n\t * Pass `{ widgets: true }` when the screen continuing it can draw widgets,\n\t * as on `start`: the choice belongs to the screen, so it is made per handle.\n\t *\n\t * Here rather than on the workspace because a conversation belongs to an\n\t * agent. You already hold the agent: it is a constant in your own code, not\n\t * a per-row value, so naming it costs nothing and the ownership is visible.\n\t *\n\t * ```ts\n\t * await ws.agent(AGENT).conversation(row.conversationId).ask('carry on');\n\t * ```\n\t */\n\tconversation(conversationId: string, options: { widgets?: boolean } = {}): Conversation {\n\t\treturn new Conversation(this.#http, conversationId, undefined, { widgets: options.widgets === true });\n\t}\n\n\t/**\n\t * This agent's most recent conversations, newest first.\n\t *\n\t * NOT ALL OF THEM, and the previous sentence here said \"every\", which was\n\t * wrong in the way that costs someone an afternoon. The server returns the\n\t * newest 50 unless you ask for more, and refuses to return more than 200 on\n\t * any single call (apps/api/src/state.ts listAgentSessions, and the Durable\n\t * Object twin beside it, clamp identically).\n\t *\n\t * There is no cursor on this route yet, so an agent with more than 200\n\t * conversations CANNOT be fully enumerated through this method. If you are\n\t * counting or exporting, do not build on it; that needs a paginated route we\n\t * have not added. `ws.endUser(id).conversations()` DOES have a cursor, if\n\t * the question is one person's history rather than one agent's.\n\t *\n\t * `endUser` narrows to one of YOUR end users, which is what you want for a\n\t * \"your conversations\" list. Leaving it out returns everyone's, so leave it\n\t * out only for an operator view.\n\t *\n\t * ```ts\n\t * const mine = await agent.conversations({ endUser: row.userId });\n\t * ```\n\t */\n\tasync conversations(options: { limit?: number; endUser?: string } = {}): Promise<Conversation[]> {\n\t\tconst res = await this.#http.get<{ conversations: AgentConversationData[] }>('/v1/conversations', {\n\t\t\tagentId: this.id,\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t// One end user's history. Sending this used to be silently ignored on\n\t\t\t// this route, which answered with everyone's conversations, so an\n\t\t\t// application rendering \"your chats\" showed somebody else's.\n\t\t\t...(options.endUser !== undefined && { end_user_id: options.endUser }),\n\t\t});\n\t\treturn (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));\n\t}\n}\n\n/** GET and PUT /v1/agents/:id/website-chat-access, as sent. */\ninterface WebsiteChatWire {\n\tagentId: string;\n\taccess: WebsiteChatAccess;\n\tchatUrl: string;\n}\n\nfunction toWebsiteChat(wire: WebsiteChatWire): WebsiteChat {\n\treturn { access: wire.access, chatUrl: wire.chatUrl, embedAgentId: wire.agentId };\n}\n\n/** The end user a conversation belongs to: a non-empty string, or a thrown error before any request. */\nfunction requireEndUserId(value: unknown): string {\n\t// Checked at RUNTIME as well as in the type: a JavaScript caller, or a\n\t// TypeScript one reading a nullable column, can still pass null, a number or\n\t// ''. Each used to start an ownerless conversation that looked fine.\n\tif (typeof value === 'string' && value.trim()) return value.trim();\n\tthrow new AgentNavaError({\n\t\tmessage: 'agent.start() needs endUserId: a non-empty string naming who the conversation is for (your user id, a visitor id, or a job name).',\n\t\tcode: 'end_user_required',\n\t\tstatus: 400,\n\t});\n}\n"]}
|
package/conversation.d.ts
CHANGED
|
@@ -47,7 +47,9 @@ export declare class Conversation {
|
|
|
47
47
|
* came from `agent.start()`, absent when it came from `agent.conversation(id)`,
|
|
48
48
|
* which is exactly the difference between having made a request and not. */
|
|
49
49
|
private snapshot?;
|
|
50
|
-
constructor(http: Http, id: string, snapshot?: AgentConversationData
|
|
50
|
+
constructor(http: Http, id: string, snapshot?: AgentConversationData, options?: {
|
|
51
|
+
widgets?: boolean;
|
|
52
|
+
});
|
|
51
53
|
private get base();
|
|
52
54
|
/**
|
|
53
55
|
* The agent whose conversation this is.
|
package/conversation.js
CHANGED
|
@@ -29,11 +29,15 @@ export class Conversation {
|
|
|
29
29
|
* came from `agent.start()`, absent when it came from `agent.conversation(id)`,
|
|
30
30
|
* which is exactly the difference between having made a request and not. */
|
|
31
31
|
snapshot;
|
|
32
|
-
|
|
32
|
+
/** This handle's screen can draw widgets (`start` or `conversation(id)` with
|
|
33
|
+
* `widgets: true`). Sent with every turn, since the screen is what decides. */
|
|
34
|
+
#widgets;
|
|
35
|
+
constructor(http, id, snapshot, options = {}) {
|
|
33
36
|
this.#http = http;
|
|
34
37
|
this.id = id;
|
|
35
38
|
if (snapshot)
|
|
36
39
|
this.snapshot = snapshot;
|
|
40
|
+
this.#widgets = options.widgets === true;
|
|
37
41
|
}
|
|
38
42
|
get base() {
|
|
39
43
|
return `/v1/conversations/${encodeURIComponent(this.id)}`;
|
|
@@ -102,6 +106,7 @@ export class Conversation {
|
|
|
102
106
|
body: {
|
|
103
107
|
content: text,
|
|
104
108
|
...(options.attachments?.length && { attachments: options.attachments }),
|
|
109
|
+
...(this.#widgets && { widgets: true }),
|
|
105
110
|
},
|
|
106
111
|
...(options.signal && { signal: options.signal }),
|
|
107
112
|
});
|
package/conversation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation.js","sourceRoot":"","sources":["../src/conversation.ts"],"names":[],"mappings":"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","sourcesContent":["// A conversation: one continuous exchange with an agent, with its own history\n// and its own filesystem. Two conversations share nothing.\n\nimport { isBindings, refuseConnections, type SessionBindings, secretIdsOf, secretsIn } from './attach.ts';\nimport { AgentNavaError } from './errors.ts';\nimport { type Http, turnRecoveryMessage } from './http.ts';\nimport type { Secret } from './secret.ts';\nimport { makeTurn, type Turn } from './turn.ts';\nimport type {\n\tAccountNeeded,\n\tAgentConversationData,\n\tAttachment,\n\tBindings,\n\tFileEntry,\n\tMessage,\n\tRequirement,\n} from './types.ts';\n\nexport interface AskOptions {\n\tattachments?: Attachment[];\n\t/**\n\t * Stop receiving this turn's response. An already-aborted signal prevents\n\t * submission, so no turn is accepted or charged for this call. After the\n\t * server accepts a turn, aborting stops observation: accepted work can\n\t * continue and normal billing applies, with no automatic refund or server\n\t * cancellation. Read conversation.messages() before asking again.\n\t */\n\tsignal?: AbortSignal;\n\t/** Stable identity for one submission. Reuse it only for the same content\n\t * after a lost response; a new intentional turn needs a new key. Defaults to\n\t * a generated key. Use 1–128 letters, digits, dots, underscores, colons or hyphens. */\n\tidempotencyKey?: string;\n}\n\nexport interface FileUrlOptions {\n\tdownload?: boolean;\n\t/** Seconds the link stays valid. */\n\tttl?: number;\n}\n\n/**\n * A conversation you can work with.\n *\n * EVERY CONVERSATION BELONGS TO EXACTLY ONE AGENT, reachable as `agentId` once\n * this handle has read anything. A conversation is an agent doing a piece of\n * work; there is no such thing as a conversation with nobody.\n *\n * ADDRESSED FROM ITS PARENT: `agent.conversation(id)`, or\n * `project.conversation(id)` for one a project owns. It used to be\n * `ws.conversation(id)`, which made the parent look optional and is how this\n * SDK came to describe conversations as parentless in its own reference. The\n * objection to moving it was that a caller would then store two ids; in\n * practice the agent id is a constant in their own code, not a per-row value.\n *\n * Holding one makes NO request. The first call on it is the first round trip,\n * because fetching a conversation just to send it a message would be a round\n * trip for nothing.\n */\nexport class Conversation {\n\treadonly id: string;\n\treadonly #http: Http;\n\t/** Whatever the call that produced this object already knew. Present when it\n\t * came from `agent.start()`, absent when it came from `agent.conversation(id)`,\n\t * which is exactly the difference between having made a request and not. */\n\tprivate snapshot?: AgentConversationData;\n\n\tconstructor(http: Http, id: string, snapshot?: AgentConversationData) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/conversations/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/**\n\t * The agent whose conversation this is.\n\t *\n\t * EVERY CONVERSATION BELONGS TO EXACTLY ONE AGENT. That is the model, and it\n\t * is worth stating on the object because the SDK used to hold this value and\n\t * never show it: a method read it internally and nothing else could, which\n\t * made a conversation look parentless from outside.\n\t *\n\t * Undefined on a conversation you addressed by id and have not read yet,\n\t * for the same reason `ready` is: the handle has made no request. Call\n\t * `get()` to fill it in.\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: row.userId });\n\t * conversation.agentId === agent.id; // already known, no round trip\n\t * ```\n\t */\n\tget agentId(): string | undefined {\n\t\treturn this.snapshot?.agentId;\n\t}\n\n\t/** True when nothing REQUIRED is outstanding.\n\t *\n\t * Reads from the last snapshot rather than the network, so it is a property\n\t * and not a call. It is only meaningful on a conversation you just started\n\t * or just refreshed; call `get()` to bring it up to date. */\n\tget ready(): boolean | undefined {\n\t\treturn this.snapshot?.ready;\n\t}\n\n\t/** What the conversation is still missing. Same caveat as `ready`. */\n\tget requires(): Requirement[] {\n\t\treturn this.snapshot?.requires ?? [];\n\t}\n\n\t/** The number of the agent version it runs, from the last snapshot. Same\n\t * caveat as `ready`: a schedule naming this conversation moves it to the\n\t * latest version, and only `get()` sees that. */\n\tget version(): number | undefined {\n\t\treturn this.snapshot?.version;\n\t}\n\n\tasync get(): Promise<AgentConversationData> {\n\t\tconst data = await this.#http.get<AgentConversationData>(this.base);\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Ask, and get the reply.\n\t *\n\t * The message is sent HERE, not when you await or iterate the result, so\n\t * calling this twice with new keys asks twice. Await it for the answer or iterate it for\n\t * the events; doing both to the same turn throws rather than replaying.\n\t */\n\task(text: string, options: AskOptions = {}): Turn {\n\t\t// One key for this submission, retained in the transport request if a\n\t\t// fetch implementation retries it. A caller may retain their own key for\n\t\t// an explicit retry after losing the first response.\n\t\tconst idempotencyKey = options.idempotencyKey ?? newTurnKey();\n\t\tif (!/^[A-Za-z0-9._:-]{1,128}$/.test(idempotencyKey)) {\n\t\t\tthrow new AgentNavaError({ message: 'idempotencyKey must contain 1–128 letters, digits, dots, underscores, colons or hyphens.', code: 'invalid_idempotency_key', status: 400 });\n\t\t}\n\t\tconst response = this.#http.send(`${this.base}/messages`, {\n\t\t\tmethod: 'POST',\n\t\t\tidempotencyKey,\n\t\t\tbody: {\n\t\t\t\tcontent: text,\n\t\t\t\t...(options.attachments?.length && { attachments: options.attachments }),\n\t\t\t},\n\t\t\t...(options.signal && { signal: options.signal }),\n\t\t});\n\t\treturn makeTurn(response, turnRecoveryMessage(`${this.base}/messages`, idempotencyKey));\n\t}\n\n\tasync messages(): Promise<Message[]> {\n\t\tconst res = await this.#http.get<{ messages: Message[] }>(`${this.base}/messages`);\n\t\treturn res.messages ?? [];\n\t}\n\n\t/** End it, and its drive with it. */\n\tasync end(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t/** Supply the secrets this conversation's agent declared at 'conversation'\n\t * scope. Merges: sending one secret does not clear another.\n\t *\n\t * WRAPPED IN `sessionBindings`, and that envelope is load-bearing. This\n\t * posted the object at the top level, while `addSessionBindings` reads\n\t * `body.sessionBindings`. The handler therefore saw undefined, and undefined\n\t * is NOT an error there: a launch carrying no bindings is legitimate, so it\n\t * normalises to `{}`.\n\t *\n\t * So this returned 200 and bound nothing, and the failure appeared much\n\t * later as an agent that could not use a credential somebody had plainly\n\t * supplied.\n\t *\n\t * `agent.bind` had the same class of bug against its own route and its\n\t * comment records it. This is the second one, found by reading both\n\t * together. */\n\tasync #applyBindings(bindings: SessionBindings): Promise<AgentConversationData> {\n\t\tconst data = await this.#http.post<AgentConversationData>(`${this.base}/bindings`, {\n\t\t\tsessionBindings: bindings,\n\t\t});\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Give THIS conversation a secret, and nothing else.\n\t *\n\t * ```ts\n\t * await conversation.attach(key); // a stored Secret, by id\n\t * await conversation.attach({ secrets: { KEY: value } }); // a secret VALUE\n\t * ```\n\t *\n\t * The narrowest scope there is: it reaches this conversation only.\n\t *\n\t * SECRETS ONLY, since 0.44. An account is never attached to a conversation\n\t * any more: the agent's setup names whose account each connection uses, and\n\t * for the end user's account the turn uses the account of the person this\n\t * conversation is for. One handed in here is refused by name\n\t * (`not_a_secret`) rather than dropped. `accountsNeeded()` says which of\n\t * that person's accounts are still missing.\n\t *\n\t * PREFER `agent.start({ attach })` when you already know at launch. Between\n\t * a start and a later attach there is a window in which the conversation\n\t * exists, can be asked something, and would answer without the secret.\n\t */\n\tasync attach(what: Secret | Secret[] | Bindings): Promise<AgentConversationData> {\n\t\tif (isBindings(what)) {\n\t\t\trefuseConnections(what);\n\t\t\treturn this.#applyBindings(what);\n\t\t}\n\t\tconst secretIds = await secretIdsOf(secretsIn(what));\n\t\treturn this.#applyBindings({ ...(secretIds && { secretIds }) });\n\t}\n\n\t/**\n\t * The end user's accounts this conversation still needs, each with the link\n\t * that person signs in at.\n\t *\n\t * One for every connection to the end user's account (`endUser: true`)\n\t * whose app the person this conversation is for has not connected yet.\n\t * Empty when nothing is missing. They sign in ONCE: the account is remembered for\n\t * them, so their next conversation does not ask again.\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: user.id });\n\t * for (const { app, url } of await conversation.accountsNeeded()) {\n\t * showToUser(app, url); // popup, redirect, email, your choice\n\t * }\n\t * ```\n\t *\n\t * Ask again once they are back: an empty list means nothing is missing.\n\t *\n\t * A conversation is never refused for a missing account. It starts, and\n\t * the agent says what it could not reach; this is how your application\n\t * finds out first.\n\t */\n\tasync accountsNeeded(): Promise<AccountNeeded[]> {\n\t\tconst res = await this.#http.get<{ needed: AccountNeeded[] }>(`${this.base}/accounts-needed`);\n\t\treturn res.needed ?? [];\n\t}\n\n\t/**\n\t * Wait until this conversation has everything it needs: until `ready`.\n\t *\n\t * So you do not have to invent a polling loop or work out which field to\n\t * believe.\n\t *\n\t * ```ts\n\t * await conversation.waitUntilReady();\n\t * ```\n\t *\n\t * Returns as soon as nothing REQUIRED is outstanding, and returns the\n\t * conversation so you do not need a second call. An optional requirement\n\t * left unmet does not hold this up, for the same reason it does not make\n\t * `ready` false: the conversation genuinely works without it.\n\t *\n\t * ON TIMEOUT it throws naming what is still missing, because \"timed out\" on\n\t * its own sends you looking in the wrong place. The code is\n\t * `conversation_not_ready` and it is safe to call again: a person who has\n\t * not finished signing in yet may simply need longer.\n\t *\n\t * This polls rather than subscribing. For a step with a human in the middle,\n\t * a few seconds of latency costs nothing, and it keeps the method usable\n\t * from any runtime without a socket.\n\t */\n\tasync waitUntilReady(options: {\n\t\t/** Give up after this long. Default 2 minutes, which is generous for a\n\t\t * sign-in and short enough that a stuck flow does not hang a request. */\n\t\ttimeoutMs?: number;\n\t\t/** Gap between checks. Default 2 seconds. */\n\t\tintervalMs?: number;\n\t\t/** Cancel the wait, e.g. when the caller's own request is abandoned. */\n\t\tsignal?: AbortSignal;\n\t} = {}): Promise<AgentConversationData> {\n\t\tconst timeoutMs = options.timeoutMs ?? 120_000;\n\t\tconst intervalMs = options.intervalMs ?? 2_000;\n\t\tconst deadline = Date.now() + timeoutMs;\n\n\t\t// Checked BEFORE the first sleep. A conversation that is already ready\n\t\t// should cost one request, not one request plus an interval.\n\t\tlet data = await this.get();\n\t\twhile (!data.ready) {\n\t\t\tif (options.signal?.aborted) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: 'waitUntilReady was cancelled',\n\t\t\t\t\tcode: 'aborted',\n\t\t\t\t\tstatus: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\t// Name the outstanding REQUIRED items. The optional ones are not why\n\t\t\t\t// this failed and listing them would send someone after the wrong\n\t\t\t\t// thing.\n\t\t\t\tconst outstanding = data.requires\n\t\t\t\t\t.filter((r) => r.required)\n\t\t\t\t\t.map((r) => `${r.kind} ${r.name}`);\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: outstanding.length\n\t\t\t\t\t\t? `Conversation ${this.id} is still waiting on: ${outstanding.join(', ')}`\n\t\t\t\t\t\t: `Conversation ${this.id} did not become ready within ${timeoutMs}ms`,\n\t\t\t\t\tcode: 'conversation_not_ready',\n\t\t\t\t\tstatus: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\t// Never sleep past the deadline, so the timeout means what it says.\n\t\t\tawait sleep(Math.min(intervalMs, Math.max(0, deadline - Date.now())), options.signal);\n\t\t\tdata = await this.get();\n\t\t}\n\t\treturn data;\n\t}\n\n\t// ---------------------------------------------------------------- the drive\n\n\t/** List one directory. `path`, not `dir`: it is the same parameter every other\n\t * file route on this API takes, and one name for one idea beats a prettier\n\t * name used in one place. */\n\tasync files(path = '/'): Promise<FileEntry[]> {\n\t\tconst res = await this.#http.get<{ files: DriveEntry[] }>(`${this.base}/files`, { path });\n\t\treturn (res.files ?? []).map(toFileEntry);\n\t}\n\n\t/** Read a text file. */\n\tasync readFile(path: string): Promise<string> {\n\t\t// The route answers JSON `{path, content}`, not bare text. The envelope is\n\t\t// worth keeping on the wire (the canonical path comes back with it), so the\n\t\t// unwrapping happens here: a caller asked for the file, not for a wrapper.\n\t\tconst res = await this.#http.get<{ path: string; content: string }>(\n\t\t\t`${this.base}/files/content`,\n\t\t\t{ path },\n\t\t);\n\t\treturn res.content ?? '';\n\t}\n\n\tasync readFileBytes(path: string): Promise<Uint8Array> {\n\t\tconst res = await this.#http.send(`${this.base}/files/raw`, { query: { path } });\n\t\treturn new Uint8Array(await res.arrayBuffer());\n\t}\n\n\t/**\n\t * Put a file into the conversation's drive.\n\t *\n\t * This is how an application STAGES material: write the contract in, then ask\n\t * the question about it. Anywhere under the root is fine; `input/` is a\n\t * convention in the docs and nothing enforces it, because the agent and your\n\t * application share one namespace.\n\t */\n\tasync writeFile(\n\t\tpath: string,\n\t\tbytes: Uint8Array | ArrayBuffer,\n\t\tcontentType?: string,\n\t): Promise<FileEntry> {\n\t\treturn this.#http.request<FileEntry>(`${this.base}/files`, {\n\t\t\tmethod: 'PUT',\n\t\t\tbytes,\n\t\t\tquery: { path },\n\t\t\t...(contentType && { contentType }),\n\t\t});\n\t}\n\n\tasync removeFile(path: string): Promise<void> {\n\t\tawait this.#http.delete(`${this.base}/files`, { path });\n\t}\n\n\t/**\n\t * A link to one file that carries its own authorisation.\n\t *\n\t * A browser cannot hold an API key, so a Download button has no way to prove\n\t * it is allowed. This grants that one file for `ttl` seconds and nothing\n\t * else.\n\t */\n\tasync fileUrl(path: string, options: FileUrlOptions = {}): Promise<string> {\n\t\tconst res = await this.#http.post<{ url: string }>(`${this.base}/files/url`, {\n\t\t\tpath,\n\t\t\t...(options.download !== undefined && { download: options.download }),\n\t\t\t...(options.ttl !== undefined && { ttl: options.ttl }),\n\t\t});\n\t\treturn res.url;\n\t}\n}\n\nlet fallbackTurnSequence = 0;\nfunction newTurnKey(): string {\n\treturn globalThis.crypto?.randomUUID?.() ??\n\t\t`turn-${Date.now().toString(36)}-${++fallbackTurnSequence}-${Math.random().toString(36).slice(2)}`;\n}\n\n/** One entry as the Drive returns it. */\ninterface DriveEntry {\n\tname: string;\n\tpath: string;\n\tisDir: boolean;\n\tsize: number;\n\t/** Epoch milliseconds. */\n\tmtime: number;\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n\ttxt: 'text/plain',\n\tmd: 'text/markdown',\n\tjson: 'application/json',\n\tcsv: 'text/csv',\n\thtml: 'text/html',\n\tjs: 'text/javascript',\n\tts: 'text/typescript',\n\tpng: 'image/png',\n\tjpg: 'image/jpeg',\n\tjpeg: 'image/jpeg',\n\tgif: 'image/gif',\n\twebp: 'image/webp',\n\tsvg: 'image/svg+xml',\n\tpdf: 'application/pdf',\n};\n\n/**\n * A Drive entry as the SDK's FileEntry.\n *\n * The Drive speaks `isDir` and `mtime`; the SDK speaks `kind` and an ISO\n * `modifiedAt`. Translating at the edge is the SDK's job: the Drive's shape is\n * what apps/web's DrivePane reads, and changing it to suit this would break a\n * working screen to save a mapping function.\n *\n * `mimeType` is inferred from the extension, because the Drive does not store\n * one (see apps/backend-workers/src/sandbox/drive-reader.ts, which infers it the\n * same way on read).\n */\nfunction toFileEntry(entry: DriveEntry): FileEntry {\n\tconst ext = entry.path.slice(entry.path.lastIndexOf('.') + 1).toLowerCase();\n\treturn {\n\t\tpath: entry.path,\n\t\tkind: entry.isDir ? 'directory' : 'file',\n\t\tmimeType: entry.isDir ? 'inode/directory' : (CONTENT_TYPES[ext] ?? 'application/octet-stream'),\n\t\tsize: entry.size,\n\t\tmodifiedAt: new Date(entry.mtime).toISOString(),\n\t};\n}\n\n/** Wait, and stop waiting early if the caller aborts. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\tif (ms <= 0) return Promise.resolve();\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(done, ms);\n\t\tfunction done() {\n\t\t\tclearTimeout(timer);\n\t\t\tsignal?.removeEventListener('abort', done);\n\t\t\tresolve();\n\t\t}\n\t\tsignal?.addEventListener('abort', done, { once: true });\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"conversation.js","sourceRoot":"","sources":["../src/conversation.ts"],"names":[],"mappings":"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","sourcesContent":["// A conversation: one continuous exchange with an agent, with its own history\n// and its own filesystem. Two conversations share nothing.\n\nimport { isBindings, refuseConnections, type SessionBindings, secretIdsOf, secretsIn } from './attach.ts';\nimport { AgentNavaError } from './errors.ts';\nimport { type Http, turnRecoveryMessage } from './http.ts';\nimport type { Secret } from './secret.ts';\nimport { makeTurn, type Turn } from './turn.ts';\nimport type {\n\tAccountNeeded,\n\tAgentConversationData,\n\tAttachment,\n\tBindings,\n\tFileEntry,\n\tMessage,\n\tRequirement,\n} from './types.ts';\n\nexport interface AskOptions {\n\tattachments?: Attachment[];\n\t/**\n\t * Stop receiving this turn's response. An already-aborted signal prevents\n\t * submission, so no turn is accepted or charged for this call. After the\n\t * server accepts a turn, aborting stops observation: accepted work can\n\t * continue and normal billing applies, with no automatic refund or server\n\t * cancellation. Read conversation.messages() before asking again.\n\t */\n\tsignal?: AbortSignal;\n\t/** Stable identity for one submission. Reuse it only for the same content\n\t * after a lost response; a new intentional turn needs a new key. Defaults to\n\t * a generated key. Use 1–128 letters, digits, dots, underscores, colons or hyphens. */\n\tidempotencyKey?: string;\n}\n\nexport interface FileUrlOptions {\n\tdownload?: boolean;\n\t/** Seconds the link stays valid. */\n\tttl?: number;\n}\n\n/**\n * A conversation you can work with.\n *\n * EVERY CONVERSATION BELONGS TO EXACTLY ONE AGENT, reachable as `agentId` once\n * this handle has read anything. A conversation is an agent doing a piece of\n * work; there is no such thing as a conversation with nobody.\n *\n * ADDRESSED FROM ITS PARENT: `agent.conversation(id)`, or\n * `project.conversation(id)` for one a project owns. It used to be\n * `ws.conversation(id)`, which made the parent look optional and is how this\n * SDK came to describe conversations as parentless in its own reference. The\n * objection to moving it was that a caller would then store two ids; in\n * practice the agent id is a constant in their own code, not a per-row value.\n *\n * Holding one makes NO request. The first call on it is the first round trip,\n * because fetching a conversation just to send it a message would be a round\n * trip for nothing.\n */\nexport class Conversation {\n\treadonly id: string;\n\treadonly #http: Http;\n\t/** Whatever the call that produced this object already knew. Present when it\n\t * came from `agent.start()`, absent when it came from `agent.conversation(id)`,\n\t * which is exactly the difference between having made a request and not. */\n\tprivate snapshot?: AgentConversationData;\n\t/** This handle's screen can draw widgets (`start` or `conversation(id)` with\n\t * `widgets: true`). Sent with every turn, since the screen is what decides. */\n\treadonly #widgets: boolean;\n\n\tconstructor(http: Http, id: string, snapshot?: AgentConversationData, options: { widgets?: boolean } = {}) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t\tthis.#widgets = options.widgets === true;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/conversations/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/**\n\t * The agent whose conversation this is.\n\t *\n\t * EVERY CONVERSATION BELONGS TO EXACTLY ONE AGENT. That is the model, and it\n\t * is worth stating on the object because the SDK used to hold this value and\n\t * never show it: a method read it internally and nothing else could, which\n\t * made a conversation look parentless from outside.\n\t *\n\t * Undefined on a conversation you addressed by id and have not read yet,\n\t * for the same reason `ready` is: the handle has made no request. Call\n\t * `get()` to fill it in.\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: row.userId });\n\t * conversation.agentId === agent.id; // already known, no round trip\n\t * ```\n\t */\n\tget agentId(): string | undefined {\n\t\treturn this.snapshot?.agentId;\n\t}\n\n\t/** True when nothing REQUIRED is outstanding.\n\t *\n\t * Reads from the last snapshot rather than the network, so it is a property\n\t * and not a call. It is only meaningful on a conversation you just started\n\t * or just refreshed; call `get()` to bring it up to date. */\n\tget ready(): boolean | undefined {\n\t\treturn this.snapshot?.ready;\n\t}\n\n\t/** What the conversation is still missing. Same caveat as `ready`. */\n\tget requires(): Requirement[] {\n\t\treturn this.snapshot?.requires ?? [];\n\t}\n\n\t/** The number of the agent version it runs, from the last snapshot. Same\n\t * caveat as `ready`: a schedule naming this conversation moves it to the\n\t * latest version, and only `get()` sees that. */\n\tget version(): number | undefined {\n\t\treturn this.snapshot?.version;\n\t}\n\n\tasync get(): Promise<AgentConversationData> {\n\t\tconst data = await this.#http.get<AgentConversationData>(this.base);\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Ask, and get the reply.\n\t *\n\t * The message is sent HERE, not when you await or iterate the result, so\n\t * calling this twice with new keys asks twice. Await it for the answer or iterate it for\n\t * the events; doing both to the same turn throws rather than replaying.\n\t */\n\task(text: string, options: AskOptions = {}): Turn {\n\t\t// One key for this submission, retained in the transport request if a\n\t\t// fetch implementation retries it. A caller may retain their own key for\n\t\t// an explicit retry after losing the first response.\n\t\tconst idempotencyKey = options.idempotencyKey ?? newTurnKey();\n\t\tif (!/^[A-Za-z0-9._:-]{1,128}$/.test(idempotencyKey)) {\n\t\t\tthrow new AgentNavaError({ message: 'idempotencyKey must contain 1–128 letters, digits, dots, underscores, colons or hyphens.', code: 'invalid_idempotency_key', status: 400 });\n\t\t}\n\t\tconst response = this.#http.send(`${this.base}/messages`, {\n\t\t\tmethod: 'POST',\n\t\t\tidempotencyKey,\n\t\t\tbody: {\n\t\t\t\tcontent: text,\n\t\t\t\t...(options.attachments?.length && { attachments: options.attachments }),\n\t\t\t\t...(this.#widgets && { widgets: true }),\n\t\t\t},\n\t\t\t...(options.signal && { signal: options.signal }),\n\t\t});\n\t\treturn makeTurn(response, turnRecoveryMessage(`${this.base}/messages`, idempotencyKey));\n\t}\n\n\tasync messages(): Promise<Message[]> {\n\t\tconst res = await this.#http.get<{ messages: Message[] }>(`${this.base}/messages`);\n\t\treturn res.messages ?? [];\n\t}\n\n\t/** End it, and its drive with it. */\n\tasync end(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t/** Supply the secrets this conversation's agent declared at 'conversation'\n\t * scope. Merges: sending one secret does not clear another.\n\t *\n\t * WRAPPED IN `sessionBindings`, and that envelope is load-bearing. This\n\t * posted the object at the top level, while `addSessionBindings` reads\n\t * `body.sessionBindings`. The handler therefore saw undefined, and undefined\n\t * is NOT an error there: a launch carrying no bindings is legitimate, so it\n\t * normalises to `{}`.\n\t *\n\t * So this returned 200 and bound nothing, and the failure appeared much\n\t * later as an agent that could not use a credential somebody had plainly\n\t * supplied.\n\t *\n\t * `agent.bind` had the same class of bug against its own route and its\n\t * comment records it. This is the second one, found by reading both\n\t * together. */\n\tasync #applyBindings(bindings: SessionBindings): Promise<AgentConversationData> {\n\t\tconst data = await this.#http.post<AgentConversationData>(`${this.base}/bindings`, {\n\t\t\tsessionBindings: bindings,\n\t\t});\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Give THIS conversation a secret, and nothing else.\n\t *\n\t * ```ts\n\t * await conversation.attach(key); // a stored Secret, by id\n\t * await conversation.attach({ secrets: { KEY: value } }); // a secret VALUE\n\t * ```\n\t *\n\t * The narrowest scope there is: it reaches this conversation only.\n\t *\n\t * SECRETS ONLY, since 0.44. An account is never attached to a conversation\n\t * any more: the agent's setup names whose account each connection uses, and\n\t * for the end user's account the turn uses the account of the person this\n\t * conversation is for. One handed in here is refused by name\n\t * (`not_a_secret`) rather than dropped. `accountsNeeded()` says which of\n\t * that person's accounts are still missing.\n\t *\n\t * PREFER `agent.start({ attach })` when you already know at launch. Between\n\t * a start and a later attach there is a window in which the conversation\n\t * exists, can be asked something, and would answer without the secret.\n\t */\n\tasync attach(what: Secret | Secret[] | Bindings): Promise<AgentConversationData> {\n\t\tif (isBindings(what)) {\n\t\t\trefuseConnections(what);\n\t\t\treturn this.#applyBindings(what);\n\t\t}\n\t\tconst secretIds = await secretIdsOf(secretsIn(what));\n\t\treturn this.#applyBindings({ ...(secretIds && { secretIds }) });\n\t}\n\n\t/**\n\t * The end user's accounts this conversation still needs, each with the link\n\t * that person signs in at.\n\t *\n\t * One for every connection to the end user's account (`endUser: true`)\n\t * whose app the person this conversation is for has not connected yet.\n\t * Empty when nothing is missing. They sign in ONCE: the account is remembered for\n\t * them, so their next conversation does not ask again.\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: user.id });\n\t * for (const { app, url } of await conversation.accountsNeeded()) {\n\t * showToUser(app, url); // popup, redirect, email, your choice\n\t * }\n\t * ```\n\t *\n\t * Ask again once they are back: an empty list means nothing is missing.\n\t *\n\t * A conversation is never refused for a missing account. It starts, and\n\t * the agent says what it could not reach; this is how your application\n\t * finds out first.\n\t */\n\tasync accountsNeeded(): Promise<AccountNeeded[]> {\n\t\tconst res = await this.#http.get<{ needed: AccountNeeded[] }>(`${this.base}/accounts-needed`);\n\t\treturn res.needed ?? [];\n\t}\n\n\t/**\n\t * Wait until this conversation has everything it needs: until `ready`.\n\t *\n\t * So you do not have to invent a polling loop or work out which field to\n\t * believe.\n\t *\n\t * ```ts\n\t * await conversation.waitUntilReady();\n\t * ```\n\t *\n\t * Returns as soon as nothing REQUIRED is outstanding, and returns the\n\t * conversation so you do not need a second call. An optional requirement\n\t * left unmet does not hold this up, for the same reason it does not make\n\t * `ready` false: the conversation genuinely works without it.\n\t *\n\t * ON TIMEOUT it throws naming what is still missing, because \"timed out\" on\n\t * its own sends you looking in the wrong place. The code is\n\t * `conversation_not_ready` and it is safe to call again: a person who has\n\t * not finished signing in yet may simply need longer.\n\t *\n\t * This polls rather than subscribing. For a step with a human in the middle,\n\t * a few seconds of latency costs nothing, and it keeps the method usable\n\t * from any runtime without a socket.\n\t */\n\tasync waitUntilReady(options: {\n\t\t/** Give up after this long. Default 2 minutes, which is generous for a\n\t\t * sign-in and short enough that a stuck flow does not hang a request. */\n\t\ttimeoutMs?: number;\n\t\t/** Gap between checks. Default 2 seconds. */\n\t\tintervalMs?: number;\n\t\t/** Cancel the wait, e.g. when the caller's own request is abandoned. */\n\t\tsignal?: AbortSignal;\n\t} = {}): Promise<AgentConversationData> {\n\t\tconst timeoutMs = options.timeoutMs ?? 120_000;\n\t\tconst intervalMs = options.intervalMs ?? 2_000;\n\t\tconst deadline = Date.now() + timeoutMs;\n\n\t\t// Checked BEFORE the first sleep. A conversation that is already ready\n\t\t// should cost one request, not one request plus an interval.\n\t\tlet data = await this.get();\n\t\twhile (!data.ready) {\n\t\t\tif (options.signal?.aborted) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: 'waitUntilReady was cancelled',\n\t\t\t\t\tcode: 'aborted',\n\t\t\t\t\tstatus: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\t// Name the outstanding REQUIRED items. The optional ones are not why\n\t\t\t\t// this failed and listing them would send someone after the wrong\n\t\t\t\t// thing.\n\t\t\t\tconst outstanding = data.requires\n\t\t\t\t\t.filter((r) => r.required)\n\t\t\t\t\t.map((r) => `${r.kind} ${r.name}`);\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: outstanding.length\n\t\t\t\t\t\t? `Conversation ${this.id} is still waiting on: ${outstanding.join(', ')}`\n\t\t\t\t\t\t: `Conversation ${this.id} did not become ready within ${timeoutMs}ms`,\n\t\t\t\t\tcode: 'conversation_not_ready',\n\t\t\t\t\tstatus: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\t// Never sleep past the deadline, so the timeout means what it says.\n\t\t\tawait sleep(Math.min(intervalMs, Math.max(0, deadline - Date.now())), options.signal);\n\t\t\tdata = await this.get();\n\t\t}\n\t\treturn data;\n\t}\n\n\t// ---------------------------------------------------------------- the drive\n\n\t/** List one directory. `path`, not `dir`: it is the same parameter every other\n\t * file route on this API takes, and one name for one idea beats a prettier\n\t * name used in one place. */\n\tasync files(path = '/'): Promise<FileEntry[]> {\n\t\tconst res = await this.#http.get<{ files: DriveEntry[] }>(`${this.base}/files`, { path });\n\t\treturn (res.files ?? []).map(toFileEntry);\n\t}\n\n\t/** Read a text file. */\n\tasync readFile(path: string): Promise<string> {\n\t\t// The route answers JSON `{path, content}`, not bare text. The envelope is\n\t\t// worth keeping on the wire (the canonical path comes back with it), so the\n\t\t// unwrapping happens here: a caller asked for the file, not for a wrapper.\n\t\tconst res = await this.#http.get<{ path: string; content: string }>(\n\t\t\t`${this.base}/files/content`,\n\t\t\t{ path },\n\t\t);\n\t\treturn res.content ?? '';\n\t}\n\n\tasync readFileBytes(path: string): Promise<Uint8Array> {\n\t\tconst res = await this.#http.send(`${this.base}/files/raw`, { query: { path } });\n\t\treturn new Uint8Array(await res.arrayBuffer());\n\t}\n\n\t/**\n\t * Put a file into the conversation's drive.\n\t *\n\t * This is how an application STAGES material: write the contract in, then ask\n\t * the question about it. Anywhere under the root is fine; `input/` is a\n\t * convention in the docs and nothing enforces it, because the agent and your\n\t * application share one namespace.\n\t */\n\tasync writeFile(\n\t\tpath: string,\n\t\tbytes: Uint8Array | ArrayBuffer,\n\t\tcontentType?: string,\n\t): Promise<FileEntry> {\n\t\treturn this.#http.request<FileEntry>(`${this.base}/files`, {\n\t\t\tmethod: 'PUT',\n\t\t\tbytes,\n\t\t\tquery: { path },\n\t\t\t...(contentType && { contentType }),\n\t\t});\n\t}\n\n\tasync removeFile(path: string): Promise<void> {\n\t\tawait this.#http.delete(`${this.base}/files`, { path });\n\t}\n\n\t/**\n\t * A link to one file that carries its own authorisation.\n\t *\n\t * A browser cannot hold an API key, so a Download button has no way to prove\n\t * it is allowed. This grants that one file for `ttl` seconds and nothing\n\t * else.\n\t */\n\tasync fileUrl(path: string, options: FileUrlOptions = {}): Promise<string> {\n\t\tconst res = await this.#http.post<{ url: string }>(`${this.base}/files/url`, {\n\t\t\tpath,\n\t\t\t...(options.download !== undefined && { download: options.download }),\n\t\t\t...(options.ttl !== undefined && { ttl: options.ttl }),\n\t\t});\n\t\treturn res.url;\n\t}\n}\n\nlet fallbackTurnSequence = 0;\nfunction newTurnKey(): string {\n\treturn globalThis.crypto?.randomUUID?.() ??\n\t\t`turn-${Date.now().toString(36)}-${++fallbackTurnSequence}-${Math.random().toString(36).slice(2)}`;\n}\n\n/** One entry as the Drive returns it. */\ninterface DriveEntry {\n\tname: string;\n\tpath: string;\n\tisDir: boolean;\n\tsize: number;\n\t/** Epoch milliseconds. */\n\tmtime: number;\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n\ttxt: 'text/plain',\n\tmd: 'text/markdown',\n\tjson: 'application/json',\n\tcsv: 'text/csv',\n\thtml: 'text/html',\n\tjs: 'text/javascript',\n\tts: 'text/typescript',\n\tpng: 'image/png',\n\tjpg: 'image/jpeg',\n\tjpeg: 'image/jpeg',\n\tgif: 'image/gif',\n\twebp: 'image/webp',\n\tsvg: 'image/svg+xml',\n\tpdf: 'application/pdf',\n};\n\n/**\n * A Drive entry as the SDK's FileEntry.\n *\n * The Drive speaks `isDir` and `mtime`; the SDK speaks `kind` and an ISO\n * `modifiedAt`. Translating at the edge is the SDK's job: the Drive's shape is\n * what apps/web's DrivePane reads, and changing it to suit this would break a\n * working screen to save a mapping function.\n *\n * `mimeType` is inferred from the extension, because the Drive does not store\n * one (see apps/backend-workers/src/sandbox/drive-reader.ts, which infers it the\n * same way on read).\n */\nfunction toFileEntry(entry: DriveEntry): FileEntry {\n\tconst ext = entry.path.slice(entry.path.lastIndexOf('.') + 1).toLowerCase();\n\treturn {\n\t\tpath: entry.path,\n\t\tkind: entry.isDir ? 'directory' : 'file',\n\t\tmimeType: entry.isDir ? 'inode/directory' : (CONTENT_TYPES[ext] ?? 'application/octet-stream'),\n\t\tsize: entry.size,\n\t\tmodifiedAt: new Date(entry.mtime).toISOString(),\n\t};\n}\n\n/** Wait, and stop waiting early if the caller aborts. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\tif (ms <= 0) return Promise.resolve();\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(done, ms);\n\t\tfunction done() {\n\t\t\tclearTimeout(timer);\n\t\t\tsignal?.removeEventListener('abort', done);\n\t\t\tresolve();\n\t\t}\n\t\tsignal?.addEventListener('abort', done, { once: true });\n\t});\n}\n"]}
|
package/index.d.ts
CHANGED
|
@@ -16,4 +16,5 @@ export { loadAgentDir } from './load-agent-dir.ts';
|
|
|
16
16
|
export { loadManagerTemplate } from './templates.ts';
|
|
17
17
|
export type { Turn } from './turn.ts';
|
|
18
18
|
export type { ClientOptions } from './http.ts';
|
|
19
|
-
export type { AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentFields, AgentStatus, Attachment, Bindings, ConnectionStatus, Credits, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
|
|
19
|
+
export type { AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentFields, AgentStatus, Attachment, Bindings, ConnectionStatus, Credits, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, Widget, WidgetPart, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
|
|
20
|
+
export { placeWidgets, type ReplyPiece } from './widgets.ts';
|
package/index.js
CHANGED
|
@@ -42,4 +42,5 @@ export { loadAgentDir } from "./load-agent-dir.js";
|
|
|
42
42
|
// through the './templates' subpath; re-exported here so the package has one
|
|
43
43
|
// surface rather than two.
|
|
44
44
|
export { loadManagerTemplate } from "./templates.js";
|
|
45
|
+
export { placeWidgets } from "./widgets.js";
|
|
45
46
|
//# sourceMappingURL=index.js.map
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAwC,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,2DAA2D;AAC3D,OAAO,EACN,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,GASZ,MAAM,mBAAmB,CAAC;AAC3B,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["// @cerebro-labs/agentnava-sdk\n//\n// Configure an agent, start a conversation, work with it.\n//\n// import { AgentNava } from '@cerebro-labs/agentnava-sdk';\n//\n// const ws = AgentNava.getWorkspace();\n// const agent = await ws.agents.create({\n// name: 'Refund checker',\n// instructions: 'Decide whether a late delivery qualifies for a refund.',\n// });\n// const conversation = await agent.start({ endUserId: 'u_ananya' });\n// console.log(await conversation.ask('Order 4471 was nine days late.'));\n//\n// The reference this implements is design/sdk-design.md.\n\nexport { AgentNava, Workspace } from './workspace.ts';\nexport { Agent, type StartOptions } from './agent.ts';\nexport { Account } from './account.ts';\n// Builds one entry of an agent's setup: an app, and whose account. NOT the\n// 0.43 `Connection`, which was a handle on a signed-in account and is `Account`.\nexport {\n\tConnection,\n\ttype CreatorAccountConnection,\n\ttype EndUserAccountConnection,\n} from './connection.ts';\n// Every app the platform can connect, as constants: `App.GMAIL === 'gmail'`.\n// Generated from the connections catalog; any plain string still works.\nexport { App, type AppId } from './apps.generated.ts';\nexport { Secret } from './secret.ts';\nexport { EndUser } from './end-user.ts';\nexport { Conversation, type AskOptions, type FileUrlOptions } from './conversation.ts';\nexport { KnowledgeBase } from './knowledge-base.ts';\nexport { Database } from './database.ts';\nexport { Project, type ProjectStartOptions } from './project.ts';\nexport { AgentNavaError } from './errors.ts';\nexport { loadWorkflows, parseWorkflow } from './workflows.ts';\n// Draft an agent's tools from whatever a company already has: an OpenAPI\n// document, a Postman collection, or a working curl command. Runs entirely on\n// your machine: nothing about the source is sent to AgentNava, which matters\n// because those files routinely contain a live credential.\nexport {\n\timportTools,\n\tfromOpenApi,\n\tfromOpenApiUrl,\n\tfromPostman,\n\tfromCurl,\n\ttoolNameFrom,\n\ttype ImportSource,\n\ttype ImportOptions,\n\ttype ImportedTools,\n\ttype ImportedSecret,\n\ttype ImportWarning,\n\ttype ImportWarningKind,\n\ttype OpenApiDocument,\n\ttype PostmanCollection,\n} from './import-tools.ts';\n// Reads a whole Claude-Code-shaped agent directory (AGENT.md + workflows/ +\n// mcp-servers.json ...) into the fields ws.agents.create() takes.\nexport { loadAgentDir } from './load-agent-dir.ts';\n// The platform's built-in project-manager configuration. apps/api imports this\n// through the './templates' subpath; re-exported here so the package has one\n// surface rather than two.\nexport { loadManagerTemplate } from './templates.ts';\nexport type { Turn } from './turn.ts';\nexport type { ClientOptions } from './http.ts';\nexport type {\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentFields,\n\tAgentStatus,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tDatabaseColumn,\n\tDatabaseData,\n\tDatabaseEngine,\n\tDatabaseFields,\n\tDatabaseLogin,\n\tDatabaseTable,\n\tDatabaseTestResult,\n\tDatabaseTls,\n\tDatabaseUsedBy,\n\tFileEntry,\n\tFilesystem,\n\tGrade,\n\tHttpTool,\n\tHttpToolParam,\n\tMcpServer,\n\tProjectAgent,\n\tMessage,\n\tParameter,\n\tProjectConversationData,\n\tProjectData,\n\tProjectFields,\n\tRequirement,\n\tScope,\n\tSeatPlan,\n\tSecretMeta,\n\tSecretRequirement,\n\tStep,\n\tTrigger,\n\tWebhookDelivery,\n\tWebhookVerify,\n\tTriggerTarget,\n\tTurnEvent,\n\tUsedBy,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWorkspaceInfo,\n\tWorkspaceMember,\n} from './types.ts';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAwC,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,2DAA2D;AAC3D,OAAO,EACN,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,GASZ,MAAM,mBAAmB,CAAC;AAC3B,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAyDrD,OAAO,EAAE,YAAY,EAAmB,MAAM,cAAc,CAAC","sourcesContent":["// @cerebro-labs/agentnava-sdk\n//\n// Configure an agent, start a conversation, work with it.\n//\n// import { AgentNava } from '@cerebro-labs/agentnava-sdk';\n//\n// const ws = AgentNava.getWorkspace();\n// const agent = await ws.agents.create({\n// name: 'Refund checker',\n// instructions: 'Decide whether a late delivery qualifies for a refund.',\n// });\n// const conversation = await agent.start({ endUserId: 'u_ananya' });\n// console.log(await conversation.ask('Order 4471 was nine days late.'));\n//\n// The reference this implements is design/sdk-design.md.\n\nexport { AgentNava, Workspace } from './workspace.ts';\nexport { Agent, type StartOptions } from './agent.ts';\nexport { Account } from './account.ts';\n// Builds one entry of an agent's setup: an app, and whose account. NOT the\n// 0.43 `Connection`, which was a handle on a signed-in account and is `Account`.\nexport {\n\tConnection,\n\ttype CreatorAccountConnection,\n\ttype EndUserAccountConnection,\n} from './connection.ts';\n// Every app the platform can connect, as constants: `App.GMAIL === 'gmail'`.\n// Generated from the connections catalog; any plain string still works.\nexport { App, type AppId } from './apps.generated.ts';\nexport { Secret } from './secret.ts';\nexport { EndUser } from './end-user.ts';\nexport { Conversation, type AskOptions, type FileUrlOptions } from './conversation.ts';\nexport { KnowledgeBase } from './knowledge-base.ts';\nexport { Database } from './database.ts';\nexport { Project, type ProjectStartOptions } from './project.ts';\nexport { AgentNavaError } from './errors.ts';\nexport { loadWorkflows, parseWorkflow } from './workflows.ts';\n// Draft an agent's tools from whatever a company already has: an OpenAPI\n// document, a Postman collection, or a working curl command. Runs entirely on\n// your machine: nothing about the source is sent to AgentNava, which matters\n// because those files routinely contain a live credential.\nexport {\n\timportTools,\n\tfromOpenApi,\n\tfromOpenApiUrl,\n\tfromPostman,\n\tfromCurl,\n\ttoolNameFrom,\n\ttype ImportSource,\n\ttype ImportOptions,\n\ttype ImportedTools,\n\ttype ImportedSecret,\n\ttype ImportWarning,\n\ttype ImportWarningKind,\n\ttype OpenApiDocument,\n\ttype PostmanCollection,\n} from './import-tools.ts';\n// Reads a whole Claude-Code-shaped agent directory (AGENT.md + workflows/ +\n// mcp-servers.json ...) into the fields ws.agents.create() takes.\nexport { loadAgentDir } from './load-agent-dir.ts';\n// The platform's built-in project-manager configuration. apps/api imports this\n// through the './templates' subpath; re-exported here so the package has one\n// surface rather than two.\nexport { loadManagerTemplate } from './templates.ts';\nexport type { Turn } from './turn.ts';\nexport type { ClientOptions } from './http.ts';\nexport type {\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentFields,\n\tAgentStatus,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tDatabaseColumn,\n\tDatabaseData,\n\tDatabaseEngine,\n\tDatabaseFields,\n\tDatabaseLogin,\n\tDatabaseTable,\n\tDatabaseTestResult,\n\tDatabaseTls,\n\tDatabaseUsedBy,\n\tFileEntry,\n\tFilesystem,\n\tGrade,\n\tHttpTool,\n\tHttpToolParam,\n\tMcpServer,\n\tProjectAgent,\n\tMessage,\n\tParameter,\n\tProjectConversationData,\n\tProjectData,\n\tProjectFields,\n\tRequirement,\n\tScope,\n\tSeatPlan,\n\tSecretMeta,\n\tSecretRequirement,\n\tStep,\n\tTrigger,\n\tWebhookDelivery,\n\tWebhookVerify,\n\tTriggerTarget,\n\tTurnEvent,\n\tWidget,\n\tWidgetPart,\n\tUsedBy,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWorkspaceInfo,\n\tWorkspaceMember,\n} from './types.ts';\nexport { placeWidgets, type ReplyPiece } from './widgets.ts';\n"]}
|
package/load-agent-dir.js
CHANGED
|
@@ -66,8 +66,8 @@ export function loadAgentDir(path, opts) {
|
|
|
66
66
|
workflows.push(asWorkflow(skill));
|
|
67
67
|
}
|
|
68
68
|
// `commands/` is read only to WARN about it. A spec has no commands field any
|
|
69
|
-
// more: slash commands
|
|
70
|
-
//
|
|
69
|
+
// more: no runtime reads slash commands, and none ever had a way to author
|
|
70
|
+
// them in a UI.
|
|
71
71
|
//
|
|
72
72
|
// Deliberately not folded into workflows, which would be the tempting
|
|
73
73
|
// one-liner. A Claude Code slash command is invoked BY A PERSON; a workflow is
|
|
@@ -88,9 +88,6 @@ export function loadAgentDir(path, opts) {
|
|
|
88
88
|
const httpTools = loadHttpTools(join(path, 'http-tools.json'));
|
|
89
89
|
if (httpTools && httpTools.length > 0)
|
|
90
90
|
out.httpTools = httpTools;
|
|
91
|
-
const widgets = loadWidgets(join(path, 'widgets.json'));
|
|
92
|
-
if (widgets && widgets.length > 0)
|
|
93
|
-
out.widgets = widgets;
|
|
94
91
|
const mcpServers = loadMcpServers(join(path, 'mcp-servers.json'));
|
|
95
92
|
if (mcpServers && mcpServers.length > 0)
|
|
96
93
|
out.mcpServers = mcpServers;
|
|
@@ -149,22 +146,6 @@ function loadHttpTools(filePath) {
|
|
|
149
146
|
}
|
|
150
147
|
return parsed;
|
|
151
148
|
}
|
|
152
|
-
function loadWidgets(filePath) {
|
|
153
|
-
if (!existsSync(filePath) || !statSync(filePath).isFile())
|
|
154
|
-
return undefined;
|
|
155
|
-
const raw = readFileSync(filePath, 'utf8');
|
|
156
|
-
let parsed;
|
|
157
|
-
try {
|
|
158
|
-
parsed = JSON.parse(raw);
|
|
159
|
-
}
|
|
160
|
-
catch (e) {
|
|
161
|
-
throw new Error(`[loadAgentDir] widgets.json: invalid JSON (${e.message})`);
|
|
162
|
-
}
|
|
163
|
-
if (!Array.isArray(parsed)) {
|
|
164
|
-
throw new Error('[loadAgentDir] widgets.json: must be an array of widget defs');
|
|
165
|
-
}
|
|
166
|
-
return parsed;
|
|
167
|
-
}
|
|
168
149
|
function loadMcpServers(filePath) {
|
|
169
150
|
if (!existsSync(filePath) || !statSync(filePath).isFile())
|
|
170
151
|
return undefined;
|