@cerebro-labs/agentnava-sdk 0.51.0 → 0.74.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.
Files changed (53) hide show
  1. package/README.md +3 -2
  2. package/agent.d.ts +67 -9
  3. package/agent.js +78 -9
  4. package/agent.js.map +1 -1
  5. package/attach.d.ts +3 -0
  6. package/attach.js.map +1 -1
  7. package/connection.d.ts +10 -0
  8. package/connection.js +13 -2
  9. package/connection.js.map +1 -1
  10. package/conversation.d.ts +37 -1
  11. package/conversation.js +28 -2
  12. package/conversation.js.map +1 -1
  13. package/end-user.d.ts +10 -0
  14. package/end-user.js +7 -0
  15. package/end-user.js.map +1 -1
  16. package/errors.d.ts +20 -0
  17. package/errors.js +24 -0
  18. package/errors.js.map +1 -1
  19. package/http.d.ts +4 -1
  20. package/http.js +21 -9
  21. package/http.js.map +1 -1
  22. package/import-tools.d.ts +5 -0
  23. package/import-tools.js +47 -6
  24. package/import-tools.js.map +1 -1
  25. package/index.d.ts +4 -3
  26. package/index.js +2 -1
  27. package/index.js.map +1 -1
  28. package/knowledge-base.d.ts +18 -18
  29. package/knowledge-base.js +28 -0
  30. package/knowledge-base.js.map +1 -1
  31. package/package.json +1 -1
  32. package/partner-app.d.ts +70 -0
  33. package/partner-app.js +125 -0
  34. package/partner-app.js.map +1 -0
  35. package/project.d.ts +5 -1
  36. package/project.js +3 -3
  37. package/project.js.map +1 -1
  38. package/react.css +32 -1
  39. package/react.js +16 -4
  40. package/react.js.map +1 -1
  41. package/secret.d.ts +6 -0
  42. package/secret.js +15 -0
  43. package/secret.js.map +1 -1
  44. package/turn.js +21 -0
  45. package/turn.js.map +1 -1
  46. package/types.d.ts +262 -3
  47. package/types.js.map +1 -1
  48. package/version.d.ts +1 -1
  49. package/version.js +1 -1
  50. package/version.js.map +1 -1
  51. package/workspace.d.ts +35 -1
  52. package/workspace.js +35 -0
  53. package/workspace.js.map +1 -1
package/attach.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"attach.js","sourceRoot":"","sources":["../src/attach.ts"],"names":[],"mappings":"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","sourcesContent":["// Turning the secrets a caller is holding into what the wire speaks.\n//\n// ITS OWN MODULE because `agent.ts` already imports `conversation.ts`, so a\n// helper living in either one and used by both is a cycle. Shared so that\n// `agent.start`, `agent.attach` and `conversation.attach` cannot drift: all\n// three had to agree on this shape, and when they did not, the disagreement was\n// invisible until a turn failed somewhere else.\n//\n// SECRETS ONLY, since 0.44. Accounts used to be attached here too, to an agent\n// or to one conversation, and a turn then had to work out which of several\n// attached and owned accounts to use. An agent's setup now names the account\n// each connection uses (`agent.connections.add`), so there is nothing left to\n// attach, and an account handed to any of these is refused by name rather than\n// dropped.\n\nimport { AgentNavaError } from './errors.ts';\n// A VALUE import, not type-only, because the check below is an `instanceof`.\n// secret.ts imports nothing but types, so this adds no cycle.\nimport { Secret } from './secret.ts';\nimport type { Bindings } from './types.ts';\n\n/** What the launch and the conversation's bindings route take: secret VALUES\n * by declared name, and stored secrets by id. */\nexport interface SessionBindings {\n\tsecrets?: Record<string, string>;\n\tsecretIds?: Record<string, string>;\n}\n\n/**\n * Distinguish `attach({ secrets })` from `attach(secret)`.\n *\n * Structural rather than `instanceof`, so a plain object built anywhere is\n * still recognised. A Secret always has an `id`; a Bindings object does not.\n * `connections` is recognised too, only so it can be refused by name below.\n */\nexport function isBindings(what: unknown): what is Bindings {\n\tif (Array.isArray(what) || what === null || typeof what !== 'object') return false;\n\tconst o = what as Record<string, unknown>;\n\treturn !('id' in o) && ('secrets' in o || 'connections' in o);\n}\n\n/** The words every refusal below uses, so an upgrader reads one explanation. */\nconst ACCOUNTS_MOVED =\n\t'Accounts are not attached any more (0.44): name the account in the agent\\'s setup with ' +\n\t'agent.connections.add(Connection.creatorAccount(app, account)), or use ' +\n\t'Connection.endUserAccount(app) so each person signs in with their own.';\n\n/** Refuse `{ connections }`. A JavaScript caller still holding 0.43 code would\n * otherwise have it dropped in silence, and the agent would answer without the\n * account it was plainly given. */\nexport function refuseConnections(bindings: Bindings): void {\n\tif ('connections' in (bindings as Record<string, unknown>)) {\n\t\tthrow new AgentNavaError({ message: ACCOUNTS_MOVED, code: 'not_a_secret', status: 400 });\n\t}\n}\n\n/**\n * Check that everything handed to `attach` is a Secret, and hand back the list.\n *\n * BY `instanceof`, not by duck-typing: a Secret addressed with `ws.secret(id)`\n * has no `name` until it reads one, so \"has a name\" answers wrongly for exactly\n * the handles that need it most.\n *\n * Refused rather than ignored. Silently dropping an unrecognised attachment is\n * how a conversation starts without the credential its caller plainly\n * attached, and the failure then appears one layer down as a tool that cannot\n * authenticate.\n */\nexport function secretsIn(what: Secret | Secret[] | undefined): Secret[] {\n\tconst list = what === undefined ? [] : Array.isArray(what) ? what : [what];\n\tfor (const item of list) {\n\t\tif (!(item instanceof Secret)) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: `attach takes a Secret. ${ACCOUNTS_MOVED}`,\n\t\t\t\tcode: 'not_a_secret',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t}\n\treturn list;\n}\n\n/**\n * Turn stored secrets into the `{ DECLARED_NAME: secretId }` map the wire\n * speaks.\n *\n * THE POINT OF SENDING AN ID RATHER THAN A VALUE. A secret stored against one\n * of your end users has no value you hold: the platform encrypted it when you\n * created it and never hands it back. Sending the id lets the launch resolve it\n * server-side, so attaching a credential does not require your process to have\n * read it.\n *\n * The NAME comes from the secret, because that is the name the agent declared\n * and the only thing it will look under. A handle you created carries it; one\n * you addressed by id has to read it, and that read is the only request here.\n */\nexport async function secretIdsOf(\n\twhat: Secret | Secret[] | undefined,\n): Promise<Record<string, string> | undefined> {\n\tif (!what) return undefined;\n\tconst list = Array.isArray(what) ? what : [what];\n\tif (list.length === 0) return undefined;\n\tconst out: Record<string, string> = {};\n\tfor (const secret of list) {\n\t\tconst name = secret.name ?? (await secret.get()).name;\n\t\t// Two different secrets under one name in a single call is a caller\n\t\t// mistake, and last-one-wins would surface much later as a tool failing\n\t\t// on a credential that looks present.\n\t\tif (out[name] && out[name] !== secret.id) {\n\t\t\tthrow new Error(\n\t\t\t\t`two different secrets were attached as ${name}: ${out[name]} and ${secret.id}`,\n\t\t\t);\n\t\t}\n\t\tout[name] = secret.id;\n\t}\n\treturn out;\n}\n"]}
1
+ {"version":3,"file":"attach.js","sourceRoot":"","sources":["../src/attach.ts"],"names":[],"mappings":"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","sourcesContent":["// Turning the secrets a caller is holding into what the wire speaks.\n//\n// ITS OWN MODULE because `agent.ts` already imports `conversation.ts`, so a\n// helper living in either one and used by both is a cycle. Shared so that\n// `agent.start`, `agent.attach` and `conversation.attach` cannot drift: all\n// three had to agree on this shape, and when they did not, the disagreement was\n// invisible until a turn failed somewhere else.\n//\n// SECRETS ONLY, since 0.44. Accounts used to be attached here too, to an agent\n// or to one conversation, and a turn then had to work out which of several\n// attached and owned accounts to use. An agent's setup now names the account\n// each connection uses (`agent.connections.add`), so there is nothing left to\n// attach, and an account handed to any of these is refused by name rather than\n// dropped.\n\nimport { AgentNavaError } from './errors.ts';\n// A VALUE import, not type-only, because the check below is an `instanceof`.\n// secret.ts imports nothing but types, so this adds no cycle.\nimport { Secret } from './secret.ts';\nimport type { Bindings } from './types.ts';\n\n/** What the launch and the conversation's bindings route take: secret VALUES\n * by declared name, and stored secrets by id. */\nexport interface SessionBindings {\n\tsecrets?: Record<string, string>;\n\tsecretIds?: Record<string, string>;\n\t/** How long the values in `secrets` stay usable, in seconds (the API's\n\t * default is one hour, its maximum one day). */\n\texpiresInSeconds?: number;\n}\n\n/**\n * Distinguish `attach({ secrets })` from `attach(secret)`.\n *\n * Structural rather than `instanceof`, so a plain object built anywhere is\n * still recognised. A Secret always has an `id`; a Bindings object does not.\n * `connections` is recognised too, only so it can be refused by name below.\n */\nexport function isBindings(what: unknown): what is Bindings {\n\tif (Array.isArray(what) || what === null || typeof what !== 'object') return false;\n\tconst o = what as Record<string, unknown>;\n\treturn !('id' in o) && ('secrets' in o || 'connections' in o);\n}\n\n/** The words every refusal below uses, so an upgrader reads one explanation. */\nconst ACCOUNTS_MOVED =\n\t'Accounts are not attached any more (0.44): name the account in the agent\\'s setup with ' +\n\t'agent.connections.add(Connection.creatorAccount(app, account)), or use ' +\n\t'Connection.endUserAccount(app) so each person signs in with their own.';\n\n/** Refuse `{ connections }`. A JavaScript caller still holding 0.43 code would\n * otherwise have it dropped in silence, and the agent would answer without the\n * account it was plainly given. */\nexport function refuseConnections(bindings: Bindings): void {\n\tif ('connections' in (bindings as Record<string, unknown>)) {\n\t\tthrow new AgentNavaError({ message: ACCOUNTS_MOVED, code: 'not_a_secret', status: 400 });\n\t}\n}\n\n/**\n * Check that everything handed to `attach` is a Secret, and hand back the list.\n *\n * BY `instanceof`, not by duck-typing: a Secret addressed with `ws.secret(id)`\n * has no `name` until it reads one, so \"has a name\" answers wrongly for exactly\n * the handles that need it most.\n *\n * Refused rather than ignored. Silently dropping an unrecognised attachment is\n * how a conversation starts without the credential its caller plainly\n * attached, and the failure then appears one layer down as a tool that cannot\n * authenticate.\n */\nexport function secretsIn(what: Secret | Secret[] | undefined): Secret[] {\n\tconst list = what === undefined ? [] : Array.isArray(what) ? what : [what];\n\tfor (const item of list) {\n\t\tif (!(item instanceof Secret)) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: `attach takes a Secret. ${ACCOUNTS_MOVED}`,\n\t\t\t\tcode: 'not_a_secret',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t}\n\treturn list;\n}\n\n/**\n * Turn stored secrets into the `{ DECLARED_NAME: secretId }` map the wire\n * speaks.\n *\n * THE POINT OF SENDING AN ID RATHER THAN A VALUE. A secret stored against one\n * of your end users has no value you hold: the platform encrypted it when you\n * created it and never hands it back. Sending the id lets the launch resolve it\n * server-side, so attaching a credential does not require your process to have\n * read it.\n *\n * The NAME comes from the secret, because that is the name the agent declared\n * and the only thing it will look under. A handle you created carries it; one\n * you addressed by id has to read it, and that read is the only request here.\n */\nexport async function secretIdsOf(\n\twhat: Secret | Secret[] | undefined,\n): Promise<Record<string, string> | undefined> {\n\tif (!what) return undefined;\n\tconst list = Array.isArray(what) ? what : [what];\n\tif (list.length === 0) return undefined;\n\tconst out: Record<string, string> = {};\n\tfor (const secret of list) {\n\t\tconst name = secret.name ?? (await secret.get()).name;\n\t\t// Two different secrets under one name in a single call is a caller\n\t\t// mistake, and last-one-wins would surface much later as a tool failing\n\t\t// on a credential that looks present.\n\t\tif (out[name] && out[name] !== secret.id) {\n\t\t\tthrow new Error(\n\t\t\t\t`two different secrets were attached as ${name}: ${out[name]} and ${secret.id}`,\n\t\t\t);\n\t\t}\n\t\tout[name] = secret.id;\n\t}\n\treturn out;\n}\n"]}
package/connection.d.ts CHANGED
@@ -8,6 +8,12 @@ export interface CreatorAccountConnection {
8
8
  app: AppId;
9
9
  account: Account | string;
10
10
  endUser?: never;
11
+ /** Which of this app's own tools the agent may use, by id
12
+ * (`'GMAIL_SEND_EMAIL'`). Absent: the app's default from the catalog (all
13
+ * of them for most apps; none for Shopify). An empty list: none, so the
14
+ * connection only signs in the agent's HTTP tools. Every connection of one
15
+ * app on an agent has the same list. */
16
+ tools?: string[];
11
17
  }
12
18
  /** A connection that uses the END USER'S ACCOUNT: each person talking to the
13
19
  * agent signs in with their own, once, and it is remembered for them. */
@@ -16,6 +22,8 @@ export interface EndUserAccountConnection {
16
22
  app: AppId;
17
23
  endUser: true;
18
24
  account?: never;
25
+ /** As `CreatorAccountConnection.tools`. */
26
+ tools?: string[];
19
27
  }
20
28
  /**
21
29
  * One connection, as you hand it to `ws.agents.create()`, `agent.update()` or
@@ -64,6 +72,7 @@ export declare const Connection: {
64
72
  * hold, or its id. Produces `{ name?, app, account: '<id>' }`. */
65
73
  creatorAccount(app: AppId, account: Account | string, options?: {
66
74
  name?: string;
75
+ tools?: string[];
67
76
  }): CreatorAccountConnection & {
68
77
  account: string;
69
78
  };
@@ -72,6 +81,7 @@ export declare const Connection: {
72
81
  * `{ name?, app, endUser: true }`. */
73
82
  endUserAccount(app: AppId, options?: {
74
83
  name?: string;
84
+ tools?: string[];
75
85
  }): EndUserAccountConnection;
76
86
  };
77
87
  /**
package/connection.js CHANGED
@@ -46,13 +46,23 @@ export const Connection = {
46
46
  status: 400,
47
47
  });
48
48
  }
49
- return { ...(options.name !== undefined && { name: options.name }), app, account: id };
49
+ return {
50
+ ...(options.name !== undefined && { name: options.name }),
51
+ app,
52
+ account: id,
53
+ ...(options.tools !== undefined && { tools: [...options.tools] }),
54
+ };
50
55
  },
51
56
  /** A connection that uses the end user's account: whichever account of
52
57
  * `app` the person each conversation is for has connected. Produces
53
58
  * `{ name?, app, endUser: true }`. */
54
59
  endUserAccount(app, options = {}) {
55
- return { ...(options.name !== undefined && { name: options.name }), app, endUser: true };
60
+ return {
61
+ ...(options.name !== undefined && { name: options.name }),
62
+ app,
63
+ endUser: true,
64
+ ...(options.tools !== undefined && { tools: [...options.tools] }),
65
+ };
56
66
  },
57
67
  };
58
68
  /**
@@ -71,6 +81,7 @@ export function connectionForWire(connection) {
71
81
  app: c.app,
72
82
  ...(c.account !== undefined && { account: accountIdOf(c.account) }),
73
83
  ...(c.endUser !== undefined && { endUser: c.endUser }),
84
+ ...(c.tools !== undefined && { tools: [...c.tools] }),
74
85
  };
75
86
  }
76
87
  //# sourceMappingURL=connection.js.map
package/connection.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,OAAO,EAAgB,WAAW,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAiC7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB;uEACmE;IACnE,cAAc,CAAC,GAAU,EAAE,OAAyB,EAAE,UAA6B,EAAE;QAGpF,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QAChC,yEAAyE;QACzE,mDAAmD;QACnD,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,cAAc,CAAC;gBACxB,OAAO,EAAE,6BAA6B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,sCAAsC;gBAC/F,IAAI,EAAE,kBAAkB;gBACxB,MAAM,EAAE,GAAG;aACX,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACxF,CAAC;IAED;;2CAEuC;IACvC,cAAc,CAAC,GAAU,EAAE,UAA6B,EAAE;QACzD,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC1F,CAAC;CACD,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAsB;IACvD,MAAM,CAAC,GAAG,UAA0F,CAAC;IACrG,OAAO;QACN,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACnE,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;KACtD,CAAC;AACH,CAAC","sourcesContent":["// A connection: one entry in an agent's setup. An app, and whose account.\n\nimport { type Account, accountIdOf } from './account.ts';\nimport type { AppId } from './apps.generated.ts';\nimport { AgentNavaError } from './errors.ts';\n\n/** A connection that uses the AGENT CREATOR'S ACCOUNT: one account in this\n * workspace, signed in once, used by every conversation. `account` is the\n * `Account` you hold or its id; an `Account` goes over the wire as its id. */\nexport interface CreatorAccountConnection {\n\tname?: string;\n\tapp: AppId;\n\taccount: Account | string;\n\tendUser?: never;\n}\n\n/** A connection that uses the END USER'S ACCOUNT: each person talking to the\n * agent signs in with their own, once, and it is remembered for them. */\nexport interface EndUserAccountConnection {\n\tname?: string;\n\tapp: AppId;\n\tendUser: true;\n\taccount?: never;\n}\n\n/**\n * One connection, as you hand it to `ws.agents.create()`, `agent.update()` or\n * `agent.connections.add()`: an app, and whose account.\n *\n * Build it with `Connection.creatorAccount()` or `Connection.endUserAccount()`,\n * which ask the same question the portal's Add a connection screen asks. Or\n * write the object yourself: `{ app, account }` or `{ app, endUser: true }`.\n * Exactly one of the two, and the type says so, so a connection with both, or\n * with neither, does not compile.\n */\nexport type Connection = CreatorAccountConnection | EndUserAccountConnection;\n\n/**\n * Build the entries of an agent's setup.\n *\n * ```ts\n * import { App, Connection } from '@cerebro-labs/agentnava-sdk';\n *\n * await ws.agents.create({\n * name: 'Inbox helper',\n * instructions: '...',\n * connections: [\n * Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }),\n * Connection.creatorAccount(App.GMAIL, personal, { name: 'personal-gmail' }),\n * Connection.endUserAccount(App.GOOGLE_CALENDAR),\n * ],\n * });\n * ```\n *\n * WHY BUILDERS. Whose account a connection uses is the one decision that\n * matters, and it used to be spelled `scope: 'fixed'` or `scope:\n * 'conversation'`, words that answered a different question. These two names\n * are the question itself, as the screen asks it: the agent creator's account,\n * or the end user's account.\n *\n * NOT THE 0.43 `Connection`. That was a handle on a signed-in account and is\n * now `Account`. This name builds an entry for an agent's setup; it holds\n * nothing and makes no request.\n *\n * `name` tells two connections of one app apart, such as two Gmail accounts.\n * Lowercase letters, digits and dashes, up to 64 characters, unique on the\n * agent. Left out, it is the app's id.\n */\nexport const Connection = {\n\t/** A connection that uses the agent creator's account: the `Account` you\n\t * hold, or its id. Produces `{ name?, app, account: '<id>' }`. */\n\tcreatorAccount(app: AppId, account: Account | string, options: { name?: string } = {}): CreatorAccountConnection & {\n\t\taccount: string;\n\t} {\n\t\tconst id = accountIdOf(account);\n\t\t// Thrown here, naming the mistake, rather than sent as a connection with\n\t\t// no account for the API to refuse in other words.\n\t\tif (typeof id !== 'string' || !id) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: `Connection.creatorAccount(${JSON.stringify(app)}, ...) needs the Account, or its id.`,\n\t\t\t\tcode: 'account_required',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\treturn { ...(options.name !== undefined && { name: options.name }), app, account: id };\n\t},\n\n\t/** A connection that uses the end user's account: whichever account of\n\t * `app` the person each conversation is for has connected. Produces\n\t * `{ name?, app, endUser: true }`. */\n\tendUserAccount(app: AppId, options: { name?: string } = {}): EndUserAccountConnection {\n\t\treturn { ...(options.name !== undefined && { name: options.name }), app, endUser: true };\n\t},\n};\n\n/**\n * One connection as `POST /v1/agents/:id/connections` takes it: the fields it\n * declares, picked by name, and an `Account` by its id.\n *\n * Picked, never forwarded: a JavaScript caller holding 0.43 code could pass\n * `provider` or `scope`, and those must not reach the route as if they meant\n * something. Neither `account` nor `endUser` is invented here: whatever was\n * given goes, and the API names the problem when it is not exactly one.\n */\nexport function connectionForWire(connection: Connection): Record<string, unknown> {\n\tconst c = connection as { name?: string; app: AppId; account?: Account | string; endUser?: boolean };\n\treturn {\n\t\t...(c.name !== undefined && { name: c.name }),\n\t\tapp: c.app,\n\t\t...(c.account !== undefined && { account: accountIdOf(c.account) }),\n\t\t...(c.endUser !== undefined && { endUser: c.endUser }),\n\t};\n}\n"]}
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,OAAO,EAAgB,WAAW,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAyC7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB;uEACmE;IACnE,cAAc,CAAC,GAAU,EAAE,OAAyB,EAAE,UAA+C,EAAE;QAGtG,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QAChC,yEAAyE;QACzE,mDAAmD;QACnD,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,cAAc,CAAC;gBACxB,OAAO,EAAE,6BAA6B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,sCAAsC;gBAC/F,IAAI,EAAE,kBAAkB;gBACxB,MAAM,EAAE,GAAG;aACX,CAAC,CAAC;QACJ,CAAC;QACD,OAAO;YACN,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;YACzD,GAAG;YACH,OAAO,EAAE,EAAE;YACX,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;SACjE,CAAC;IACH,CAAC;IAED;;2CAEuC;IACvC,cAAc,CAAC,GAAU,EAAE,UAA+C,EAAE;QAC3E,OAAO;YACN,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;YACzD,GAAG;YACH,OAAO,EAAE,IAAI;YACb,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;SACjE,CAAC;IACH,CAAC;CACD,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAsB;IACvD,MAAM,CAAC,GAAG,UAA4G,CAAC;IACvH,OAAO;QACN,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACnE,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QACtD,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;KACrD,CAAC;AACH,CAAC","sourcesContent":["// A connection: one entry in an agent's setup. An app, and whose account.\n\nimport { type Account, accountIdOf } from './account.ts';\nimport type { AppId } from './apps.generated.ts';\nimport { AgentNavaError } from './errors.ts';\n\n/** A connection that uses the AGENT CREATOR'S ACCOUNT: one account in this\n * workspace, signed in once, used by every conversation. `account` is the\n * `Account` you hold or its id; an `Account` goes over the wire as its id. */\nexport interface CreatorAccountConnection {\n\tname?: string;\n\tapp: AppId;\n\taccount: Account | string;\n\tendUser?: never;\n\t/** Which of this app's own tools the agent may use, by id\n\t * (`'GMAIL_SEND_EMAIL'`). Absent: the app's default from the catalog (all\n\t * of them for most apps; none for Shopify). An empty list: none, so the\n\t * connection only signs in the agent's HTTP tools. Every connection of one\n\t * app on an agent has the same list. */\n\ttools?: string[];\n}\n\n/** A connection that uses the END USER'S ACCOUNT: each person talking to the\n * agent signs in with their own, once, and it is remembered for them. */\nexport interface EndUserAccountConnection {\n\tname?: string;\n\tapp: AppId;\n\tendUser: true;\n\taccount?: never;\n\t/** As `CreatorAccountConnection.tools`. */\n\ttools?: string[];\n}\n\n/**\n * One connection, as you hand it to `ws.agents.create()`, `agent.update()` or\n * `agent.connections.add()`: an app, and whose account.\n *\n * Build it with `Connection.creatorAccount()` or `Connection.endUserAccount()`,\n * which ask the same question the portal's Add a connection screen asks. Or\n * write the object yourself: `{ app, account }` or `{ app, endUser: true }`.\n * Exactly one of the two, and the type says so, so a connection with both, or\n * with neither, does not compile.\n */\nexport type Connection = CreatorAccountConnection | EndUserAccountConnection;\n\n/**\n * Build the entries of an agent's setup.\n *\n * ```ts\n * import { App, Connection } from '@cerebro-labs/agentnava-sdk';\n *\n * await ws.agents.create({\n * name: 'Inbox helper',\n * instructions: '...',\n * connections: [\n * Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }),\n * Connection.creatorAccount(App.GMAIL, personal, { name: 'personal-gmail' }),\n * Connection.endUserAccount(App.GOOGLE_CALENDAR),\n * ],\n * });\n * ```\n *\n * WHY BUILDERS. Whose account a connection uses is the one decision that\n * matters, and it used to be spelled `scope: 'fixed'` or `scope:\n * 'conversation'`, words that answered a different question. These two names\n * are the question itself, as the screen asks it: the agent creator's account,\n * or the end user's account.\n *\n * NOT THE 0.43 `Connection`. That was a handle on a signed-in account and is\n * now `Account`. This name builds an entry for an agent's setup; it holds\n * nothing and makes no request.\n *\n * `name` tells two connections of one app apart, such as two Gmail accounts.\n * Lowercase letters, digits and dashes, up to 64 characters, unique on the\n * agent. Left out, it is the app's id.\n */\nexport const Connection = {\n\t/** A connection that uses the agent creator's account: the `Account` you\n\t * hold, or its id. Produces `{ name?, app, account: '<id>' }`. */\n\tcreatorAccount(app: AppId, account: Account | string, options: { name?: string; tools?: string[] } = {}): CreatorAccountConnection & {\n\t\taccount: string;\n\t} {\n\t\tconst id = accountIdOf(account);\n\t\t// Thrown here, naming the mistake, rather than sent as a connection with\n\t\t// no account for the API to refuse in other words.\n\t\tif (typeof id !== 'string' || !id) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: `Connection.creatorAccount(${JSON.stringify(app)}, ...) needs the Account, or its id.`,\n\t\t\t\tcode: 'account_required',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\t...(options.name !== undefined && { name: options.name }),\n\t\t\tapp,\n\t\t\taccount: id,\n\t\t\t...(options.tools !== undefined && { tools: [...options.tools] }),\n\t\t};\n\t},\n\n\t/** A connection that uses the end user's account: whichever account of\n\t * `app` the person each conversation is for has connected. Produces\n\t * `{ name?, app, endUser: true }`. */\n\tendUserAccount(app: AppId, options: { name?: string; tools?: string[] } = {}): EndUserAccountConnection {\n\t\treturn {\n\t\t\t...(options.name !== undefined && { name: options.name }),\n\t\t\tapp,\n\t\t\tendUser: true,\n\t\t\t...(options.tools !== undefined && { tools: [...options.tools] }),\n\t\t};\n\t},\n};\n\n/**\n * One connection as `POST /v1/agents/:id/connections` takes it: the fields it\n * declares, picked by name, and an `Account` by its id.\n *\n * Picked, never forwarded: a JavaScript caller holding 0.43 code could pass\n * `provider` or `scope`, and those must not reach the route as if they meant\n * something. Neither `account` nor `endUser` is invented here: whatever was\n * given goes, and the API names the problem when it is not exactly one.\n */\nexport function connectionForWire(connection: Connection): Record<string, unknown> {\n\tconst c = connection as { name?: string; app: AppId; account?: Account | string; endUser?: boolean; tools?: string[] };\n\treturn {\n\t\t...(c.name !== undefined && { name: c.name }),\n\t\tapp: c.app,\n\t\t...(c.account !== undefined && { account: accountIdOf(c.account) }),\n\t\t...(c.endUser !== undefined && { endUser: c.endUser }),\n\t\t...(c.tools !== undefined && { tools: [...c.tools] }),\n\t};\n}\n"]}
package/conversation.d.ts CHANGED
@@ -16,6 +16,30 @@ export interface AskOptions {
16
16
  * after a lost response; a new intentional turn needs a new key. Defaults to
17
17
  * a generated key. Use 1–128 letters, digits, dots, underscores, colons or hyphens. */
18
18
  idempotencyKey?: string;
19
+ /**
20
+ * Fresh secret VALUES for this conversation, sent with the message and in
21
+ * force before the agent reads it. The way to keep a short-lived token
22
+ * current: send it with every message.
23
+ *
24
+ * ```ts
25
+ * await conversation.ask('Any open tickets?', { secrets: { ZENDESK_TOKEN: token } });
26
+ * ```
27
+ *
28
+ * The same rules as `agent.start({ secrets })`: only names the agent
29
+ * declared at conversation scope, and each value expires after
30
+ * `secretsExpireIn` seconds (one hour by default). A value sent here
31
+ * replaces the one the conversation had under that name.
32
+ */
33
+ secrets?: Record<string, string>;
34
+ /** How long the values in `secrets` stay usable, in seconds. Default 3600,
35
+ * at most 86400. */
36
+ secretsExpireIn?: number;
37
+ }
38
+ export interface AttachOptions {
39
+ /** How long secret VALUES attached here stay usable, in seconds. Default
40
+ * 3600 (one hour), at most 86400 (one day). Ignored for a stored `Secret`,
41
+ * which never expires on the conversation: it follows its source. */
42
+ expiresIn?: number;
19
43
  }
20
44
  export interface FileUrlOptions {
21
45
  download?: boolean;
@@ -69,6 +93,12 @@ export declare class Conversation {
69
93
  * ```
70
94
  */
71
95
  get agentId(): string | undefined;
96
+ /** When it started, from the last snapshot: what `agent.conversations({ before })`
97
+ * takes to read the next page. */
98
+ get startedAt(): string | undefined;
99
+ /** Its name, from the last snapshot: the one a person gave it, or the one
100
+ * written from what was asked. Undefined until it has one. */
101
+ get title(): string | undefined;
72
102
  /** True when nothing REQUIRED is outstanding.
73
103
  *
74
104
  * Reads from the last snapshot rather than the network, so it is a property
@@ -99,10 +129,16 @@ export declare class Conversation {
99
129
  * ```ts
100
130
  * await conversation.attach(key); // a stored Secret, by id
101
131
  * await conversation.attach({ secrets: { KEY: value } }); // a secret VALUE
132
+ * await conversation.attach({ secrets: { KEY: value } }, { expiresIn: 900 });
102
133
  * ```
103
134
  *
104
135
  * The narrowest scope there is: it reaches this conversation only.
105
136
  *
137
+ * A STORED SECRET IS FOLLOWED, NOT COPIED. The conversation keeps a pointer:
138
+ * `secret.update(value)` reaches it on its next tool call, and deleting the
139
+ * secret stops it at once. A VALUE expires after `expiresIn` seconds (one
140
+ * hour by default); attach a fresh one, or send it with `ask`, to renew it.
141
+ *
106
142
  * SECRETS ONLY, since 0.44. An account is never attached to a conversation
107
143
  * any more: the agent's setup names whose account each connection uses, and
108
144
  * for the end user's account the turn uses the account of the person this
@@ -114,7 +150,7 @@ export declare class Conversation {
114
150
  * a start and a later attach there is a window in which the conversation
115
151
  * exists, can be asked something, and would answer without the secret.
116
152
  */
117
- attach(what: Secret | Secret[] | Bindings): Promise<AgentConversationData>;
153
+ attach(what: Secret | Secret[] | Bindings, options?: AttachOptions): Promise<AgentConversationData>;
118
154
  /**
119
155
  * The end user's accounts this conversation still needs, each with the link
120
156
  * that person signs in at.
package/conversation.js CHANGED
@@ -62,6 +62,16 @@ export class Conversation {
62
62
  get agentId() {
63
63
  return this.snapshot?.agentId;
64
64
  }
65
+ /** When it started, from the last snapshot: what `agent.conversations({ before })`
66
+ * takes to read the next page. */
67
+ get startedAt() {
68
+ return this.snapshot?.startedAt;
69
+ }
70
+ /** Its name, from the last snapshot: the one a person gave it, or the one
71
+ * written from what was asked. Undefined until it has one. */
72
+ get title() {
73
+ return this.snapshot?.title;
74
+ }
65
75
  /** True when nothing REQUIRED is outstanding.
66
76
  *
67
77
  * Reads from the last snapshot rather than the network, so it is a property
@@ -107,6 +117,13 @@ export class Conversation {
107
117
  content: text,
108
118
  ...(options.attachments?.length && { attachments: options.attachments }),
109
119
  ...(this.#widgets && { widgets: true }),
120
+ // The same envelope the bindings route reads.
121
+ ...(options.secrets && {
122
+ sessionBindings: {
123
+ secrets: options.secrets,
124
+ ...(options.secretsExpireIn !== undefined && { expiresInSeconds: options.secretsExpireIn }),
125
+ },
126
+ }),
110
127
  },
111
128
  ...(options.signal && { signal: options.signal }),
112
129
  });
@@ -149,10 +166,16 @@ export class Conversation {
149
166
  * ```ts
150
167
  * await conversation.attach(key); // a stored Secret, by id
151
168
  * await conversation.attach({ secrets: { KEY: value } }); // a secret VALUE
169
+ * await conversation.attach({ secrets: { KEY: value } }, { expiresIn: 900 });
152
170
  * ```
153
171
  *
154
172
  * The narrowest scope there is: it reaches this conversation only.
155
173
  *
174
+ * A STORED SECRET IS FOLLOWED, NOT COPIED. The conversation keeps a pointer:
175
+ * `secret.update(value)` reaches it on its next tool call, and deleting the
176
+ * secret stops it at once. A VALUE expires after `expiresIn` seconds (one
177
+ * hour by default); attach a fresh one, or send it with `ask`, to renew it.
178
+ *
156
179
  * SECRETS ONLY, since 0.44. An account is never attached to a conversation
157
180
  * any more: the agent's setup names whose account each connection uses, and
158
181
  * for the end user's account the turn uses the account of the person this
@@ -164,10 +187,13 @@ export class Conversation {
164
187
  * a start and a later attach there is a window in which the conversation
165
188
  * exists, can be asked something, and would answer without the secret.
166
189
  */
167
- async attach(what) {
190
+ async attach(what, options = {}) {
168
191
  if (isBindings(what)) {
169
192
  refuseConnections(what);
170
- return this.#applyBindings(what);
193
+ return this.#applyBindings({
194
+ ...(what.secrets && { secrets: what.secrets }),
195
+ ...(what.secrets && options.expiresIn !== undefined && { expiresInSeconds: options.expiresIn }),
196
+ });
171
197
  }
172
198
  const secretIds = await secretIdsOf(secretsIn(what));
173
199
  return this.#applyBindings({ ...(secretIds && { secretIds }) });
@@ -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\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"]}
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\t/**\n\t * Fresh secret VALUES for this conversation, sent with the message and in\n\t * force before the agent reads it. The way to keep a short-lived token\n\t * current: send it with every message.\n\t *\n\t * ```ts\n\t * await conversation.ask('Any open tickets?', { secrets: { ZENDESK_TOKEN: token } });\n\t * ```\n\t *\n\t * The same rules as `agent.start({ secrets })`: only names the agent\n\t * declared at conversation scope, and each value expires after\n\t * `secretsExpireIn` seconds (one hour by default). A value sent here\n\t * replaces the one the conversation had under that name.\n\t */\n\tsecrets?: Record<string, string>;\n\t/** How long the values in `secrets` stay usable, in seconds. Default 3600,\n\t * at most 86400. */\n\tsecretsExpireIn?: number;\n}\n\nexport interface AttachOptions {\n\t/** How long secret VALUES attached here stay usable, in seconds. Default\n\t * 3600 (one hour), at most 86400 (one day). Ignored for a stored `Secret`,\n\t * which never expires on the conversation: it follows its source. */\n\texpiresIn?: number;\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/** When it started, from the last snapshot: what `agent.conversations({ before })`\n\t * takes to read the next page. */\n\tget startedAt(): string | undefined {\n\t\treturn this.snapshot?.startedAt;\n\t}\n\n\t/** Its name, from the last snapshot: the one a person gave it, or the one\n\t * written from what was asked. Undefined until it has one. */\n\tget title(): string | undefined {\n\t\treturn this.snapshot?.title;\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\t// The same envelope the bindings route reads.\n\t\t\t\t...(options.secrets && {\n\t\t\t\t\tsessionBindings: {\n\t\t\t\t\t\tsecrets: options.secrets,\n\t\t\t\t\t\t...(options.secretsExpireIn !== undefined && { expiresInSeconds: options.secretsExpireIn }),\n\t\t\t\t\t},\n\t\t\t\t}),\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 * await conversation.attach({ secrets: { KEY: value } }, { expiresIn: 900 });\n\t * ```\n\t *\n\t * The narrowest scope there is: it reaches this conversation only.\n\t *\n\t * A STORED SECRET IS FOLLOWED, NOT COPIED. The conversation keeps a pointer:\n\t * `secret.update(value)` reaches it on its next tool call, and deleting the\n\t * secret stops it at once. A VALUE expires after `expiresIn` seconds (one\n\t * hour by default); attach a fresh one, or send it with `ask`, to renew it.\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, options: AttachOptions = {}): Promise<AgentConversationData> {\n\t\tif (isBindings(what)) {\n\t\t\trefuseConnections(what);\n\t\t\treturn this.#applyBindings({\n\t\t\t\t...(what.secrets && { secrets: what.secrets }),\n\t\t\t\t...(what.secrets && options.expiresIn !== undefined && { expiresInSeconds: options.expiresIn }),\n\t\t\t});\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/end-user.d.ts CHANGED
@@ -141,6 +141,12 @@ export declare class EndUser {
141
141
  * id is not a way around the allowlist; and a secret belonging to one end
142
142
  * user can only be attached to a conversation launched for that same person,
143
143
  * so one mistyped id cannot hand Ananya's key to somebody else's turn.
144
+ * Both are checked again at every tool call, not only when you attach.
145
+ *
146
+ * THE CONVERSATION FOLLOWS THE SECRET. It keeps a pointer, never a copy, so
147
+ * when your app gets a new token for this person, `key.update(newToken)`
148
+ * reaches every conversation it is attached to on the next call, and
149
+ * `key.delete()` stops them all at once.
144
150
  */
145
151
  readonly secrets: {
146
152
  /** Store a credential for this person. The value is encrypted on arrival
@@ -150,6 +156,10 @@ export declare class EndUser {
150
156
  value: string;
151
157
  description?: string;
152
158
  agentId?: string;
159
+ /** The only hosts a tool may send it to, checked at every call of a
160
+ * conversation it is attached to. Left out, it goes only to the host
161
+ * the agent's tool writes out in full. */
162
+ allowedHosts?: string[];
153
163
  }) => Promise<Secret>;
154
164
  /** This person's secrets, values excluded. The workspace's own are not in
155
165
  * here: `ws.secrets.list()` answers that. */
package/end-user.js CHANGED
@@ -151,6 +151,12 @@ export class EndUser {
151
151
  * id is not a way around the allowlist; and a secret belonging to one end
152
152
  * user can only be attached to a conversation launched for that same person,
153
153
  * so one mistyped id cannot hand Ananya's key to somebody else's turn.
154
+ * Both are checked again at every tool call, not only when you attach.
155
+ *
156
+ * THE CONVERSATION FOLLOWS THE SECRET. It keeps a pointer, never a copy, so
157
+ * when your app gets a new token for this person, `key.update(newToken)`
158
+ * reaches every conversation it is attached to on the next call, and
159
+ * `key.delete()` stops them all at once.
154
160
  */
155
161
  secrets = {
156
162
  /** Store a credential for this person. The value is encrypted on arrival
@@ -166,6 +172,7 @@ export class EndUser {
166
172
  endUserId: this.id,
167
173
  ...(input.description !== undefined && { description: input.description }),
168
174
  ...(input.agentId !== undefined && { agentId: input.agentId }),
175
+ ...(input.allowedHosts !== undefined && { allowedHosts: input.allowedHosts }),
169
176
  });
170
177
  const meta = res.secret ?? res;
171
178
  return new Secret(this.#http, meta.id, meta);
package/end-user.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"end-user.js","sourceRoot":"","sources":["../src/end-user.ts"],"names":[],"mappings":"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","sourcesContent":["// One of YOUR end users: a person your application serves, who never signs in here.\n\nimport { Account, accountIn } from './account.ts';\nimport { Secret } from './secret.ts';\nimport { Conversation } from './conversation.ts';\nimport type { Http } from './http.ts';\nimport type { AppId } from './apps.generated.ts';\nimport type { AccountData, AgentConversationData, SecretMeta } from './types.ts';\n\n/**\n * One of YOUR end users, as this platform sees them.\n *\n * WHY THE NAME IS `EndUser` AND NOT `User`. There are two populations of people\n * in this product, and calling both of them \"user\" was a bug waiting to be\n * written:\n *\n * | | workspace member | END USER |\n * |---|---|---|\n * | who | your colleague, on a seat | the person your application serves |\n * | signs in to this platform | yes | NEVER |\n * | who issues the id | we do | YOUR database |\n * | do we authenticate it | yes | no, deliberately |\n * | in this SDK | `ws.members()` | `ws.endUser(id)` |\n *\n * They were both spelled `userId`, so `ws.members()` handed you an id that\n * `agent.start()` would accept without complaint and label a conversation with\n * a teammate's internal platform id. It could never have errored, because an\n * end-user id is never checked against anything. The two names now make that\n * call impossible to write by accident.\n *\n * NOT `Customer`, which was tried and reverted. Two reasons. \"Customer\" already\n * means the party that BUYS this platform, in our own strategy documents and in\n * `subscriptions.stripe_customer_id`, so the word points the other way. And it\n * is wrong for the common case: an enterprise deploying an internal workflow\n * agent serves its own EMPLOYEES, who are not anybody's customers.\n *\n * WHY THIS OBJECT EXISTS. You are building an application for many people.\n * Ananya has her own Gmail, her own conversations and her own history, and so\n * do the other ten thousand. Every call you make is on behalf of one of them.\n *\n * Without this, the person is an argument: `endUserId` threaded through\n * `accounts.connect`, `secrets.create`, `conversations` and `start`, and\n * forgotten in exactly one of them one day. With it, you say who once and the\n * rest follows:\n *\n * ```ts\n * // once, in the agent's setup: each person uses their own Gmail\n * await agent.connections.add(Connection.endUserAccount(App.GMAIL));\n *\n * const ananya = ws.endUser(row.userId); // no request\n * const { url } = await ananya.accounts.connect({ app: App.GMAIL });\n * showToUser(url); // she signs in ONCE\n *\n * const conversation = await agent.start({ endUserId: ananya.id });\n * await conversation.ask('what did I miss this morning?');\n *\n * await ananya.conversations(); // everything of hers, any agent\n * ```\n *\n * WHAT NAMING AN END USER DOES, exactly. `start({ endUserId })` labels the\n * conversation so it can be listed back to them, and it decides whose account\n * a connection to the end user's account uses: this person's, for that app,\n * and nobody else's. The grant is the agent's setup, which you can read; it never\n * falls back to the workspace's account or to anybody else's.\n *\n * THE ID IS YOURS. A row id from your database, opaque to us, and we never\n * authenticate it. We are not an identity provider and do not want to be. It\n * also means passing the wrong one hands that person somebody else's mailbox,\n * which is a sharper consequence than the same mistake has for a chat list.\n *\n * Holding one makes NO request.\n */\nexport class EndUser {\n\treadonly id: string;\n\treadonly #http: Http;\n\n\tconstructor(http: Http, id: string) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/end-users/${encodeURIComponent(this.id)}/accounts`;\n\t}\n\n\t/**\n\t * This person's own accounts: the \"end user's account\" that a connection\n\t * built with `Connection.endUserAccount(app)` uses.\n\t *\n\t * Connected ONCE, on the app's own page. After that the account is theirs\n\t * and stays theirs: every conversation of theirs uses it, with any agent\n\t * whose setup asks for the end user's account of that app, and nobody is\n\t * sent through a sign-in again per thread. Nothing is attached per\n\t * conversation.\n\t *\n\t * An agent never reaches these any other way: not through the workspace's\n\t * own account of the same app, and not in another person's conversation.\n\t */\n\treadonly accounts = {\n\t\t/** Begin connecting one of their accounts. Returns it, still `pending`,\n\t\t * and the url to send them to. It is an ordinary `Account`:\n\t\t * `account.waitUntilConnected()` tells you when they are done, and\n\t\t * `ws.account(id)` reaches it later. `conversation.accountsNeeded()`\n\t\t * hands you the same kind of link for whatever a conversation is still\n\t\t * missing. */\n\t\tconnect: async (input: { app: AppId }): Promise<{ account: Account; url: string }> => {\n\t\t\t// FIELDS PICKED: the person comes from the path, never from the body.\n\t\t\tconst res = await this.#http.post<{ account?: AccountData; url: string }>(`${this.base}/connect`, {\n\t\t\t\tapp: input.app,\n\t\t\t});\n\t\t\tconst account = accountIn(res, `${this.base}/connect`);\n\t\t\treturn { account: new Account(this.#http, account.id, account), url: res.url };\n\t\t},\n\n\t\t/** Theirs alone. The agent creators' accounts are not here:\n\t\t * `ws.accounts.list()` is the view that shows those. */\n\t\tlist: async (): Promise<Account[]> => {\n\t\t\tconst res = await this.#http.get<{ accounts: AccountData[] }>(this.base);\n\t\t\treturn (res.accounts ?? []).map((a) => new Account(this.#http, a.id, a));\n\t\t},\n\n\t\t/**\n\t\t * Disconnect every account they connected, in one call: what\n\t\t * off-boarding calls, so it is not a loop the caller has to remember to\n\t\t * write. Every conversation of theirs stops reaching those apps from the\n\t\t * next turn, and nothing is kept. Returns how many were disconnected.\n\t\t */\n\t\tdisconnectAll: async (): Promise<{ disconnected: number }> => {\n\t\t\tconst res = await this.#http.delete<{ disconnected?: number }>(this.base);\n\t\t\treturn { disconnected: res?.disconnected ?? 0 };\n\t\t},\n\t};\n\n\t/**\n\t * Credentials that belong to THIS PERSON: their API key for a system you\n\t * integrate with, their token for a service only they have an account on.\n\t *\n\t * THIS WAS ABSENT UNTIL THERE WAS A WAY TO USE IT. A secret reaches a turn\n\t * only by being ATTACHED, and for a long time it could not be, because\n\t * binding one took its VALUE, and you do not hold the value of a secret the\n\t * platform stored for you. So a per-person secret would have been a surface\n\t * that accepted writes, listed them back, and was read by nothing. It was\n\t * left out rather than shipped looking like a feature.\n\t *\n\t * Secrets are still attached; accounts are not (0.44). A person's account is\n\t * reached through the agent's setup (`Connection.endUserAccount`), so there is\n\t * nothing to attach for it.\n\t *\n\t * `sessionBindings.secretIds` is what closed it, and attaching is still an\n\t * explicit line:\n\t *\n\t * ```ts\n\t * const ananya = ws.endUser(row.userId);\n\t * const key = await ananya.secrets.create({ name: 'ZENDESK_TOKEN', value: token });\n\t *\n\t * const conversation = await agent.start({ endUserId: ananya.id });\n\t * await conversation.attach(key); // EXPLICIT. Nothing is implied.\n\t * ```\n\t *\n\t * TWO THINGS THE PLATFORM ENFORCES, neither of which you can turn off:\n\t * the agent must DECLARE that secret name at session scope, so attaching by\n\t * id is not a way around the allowlist; and a secret belonging to one end\n\t * user can only be attached to a conversation launched for that same person,\n\t * so one mistyped id cannot hand Ananya's key to somebody else's turn.\n\t */\n\treadonly secrets = {\n\t\t/** Store a credential for this person. The value is encrypted on arrival\n\t\t * and never handed back, which is why attaching uses the id. */\n\t\tcreate: async (input: {\n\t\t\tname: string;\n\t\t\tvalue: string;\n\t\t\tdescription?: string;\n\t\t\tagentId?: string;\n\t\t}): Promise<Secret> => {\n\t\t\t// FIELDS PICKED, never the input object forwarded. Removing a field\n\t\t\t// from a type does not stop a JavaScript caller sending it, and\n\t\t\t// forwarding wholesale is how `endUserId` kept reaching the API after\n\t\t\t// it had supposedly been removed.\n\t\t\tconst res = await this.#http.post<{ secret?: SecretMeta } & SecretMeta>('/v1/secrets', {\n\t\t\t\tname: input.name,\n\t\t\t\tvalue: input.value,\n\t\t\t\tendUserId: this.id,\n\t\t\t\t...(input.description !== undefined && { description: input.description }),\n\t\t\t\t...(input.agentId !== undefined && { agentId: input.agentId }),\n\t\t\t});\n\t\t\tconst meta = res.secret ?? res;\n\t\t\treturn new Secret(this.#http, meta.id, meta);\n\t\t},\n\t\t/** This person's secrets, values excluded. The workspace's own are not in\n\t\t * here: `ws.secrets.list()` answers that. */\n\t\tlist: async (): Promise<Secret[]> => {\n\t\t\tconst res = await this.#http.get<{ secrets: SecretMeta[] }>('/v1/secrets', {\n\t\t\t\tend_user_id: this.id,\n\t\t\t});\n\t\t\treturn (res.secrets ?? []).map((m) => new Secret(this.#http, m.id, m));\n\t\t},\n\t};\n\n\t/**\n\t * Everything this person has said, ACROSS EVERY AGENT, newest first.\n\t *\n\t * `agent.conversations({ endUser })` answers the narrower question, one agent\n\t * at a time. This is the one an application renders as \"your chats\", and it\n\t * is the only listing in this SDK with a real cursor: pass `before` from the\n\t * previous page to keep going, rather than being capped at 200.\n\t */\n\tasync conversations(options: { limit?: number; before?: string } = {}): Promise<Conversation[]> {\n\t\tconst res = await this.#http.get<{ conversations: AgentConversationData[] }>('/v1/conversations', {\n\t\t\tend_user_id: this.id,\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t...(options.before && { before: options.before }),\n\t\t});\n\t\treturn (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));\n\t}\n}\n"]}
1
+ {"version":3,"file":"end-user.js","sourceRoot":"","sources":["../src/end-user.ts"],"names":[],"mappings":"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","sourcesContent":["// One of YOUR end users: a person your application serves, who never signs in here.\n\nimport { Account, accountIn } from './account.ts';\nimport { Secret } from './secret.ts';\nimport { Conversation } from './conversation.ts';\nimport type { Http } from './http.ts';\nimport type { AppId } from './apps.generated.ts';\nimport type { AccountData, AgentConversationData, SecretMeta } from './types.ts';\n\n/**\n * One of YOUR end users, as this platform sees them.\n *\n * WHY THE NAME IS `EndUser` AND NOT `User`. There are two populations of people\n * in this product, and calling both of them \"user\" was a bug waiting to be\n * written:\n *\n * | | workspace member | END USER |\n * |---|---|---|\n * | who | your colleague, on a seat | the person your application serves |\n * | signs in to this platform | yes | NEVER |\n * | who issues the id | we do | YOUR database |\n * | do we authenticate it | yes | no, deliberately |\n * | in this SDK | `ws.members()` | `ws.endUser(id)` |\n *\n * They were both spelled `userId`, so `ws.members()` handed you an id that\n * `agent.start()` would accept without complaint and label a conversation with\n * a teammate's internal platform id. It could never have errored, because an\n * end-user id is never checked against anything. The two names now make that\n * call impossible to write by accident.\n *\n * NOT `Customer`, which was tried and reverted. Two reasons. \"Customer\" already\n * means the party that BUYS this platform, in our own strategy documents and in\n * `subscriptions.stripe_customer_id`, so the word points the other way. And it\n * is wrong for the common case: an enterprise deploying an internal workflow\n * agent serves its own EMPLOYEES, who are not anybody's customers.\n *\n * WHY THIS OBJECT EXISTS. You are building an application for many people.\n * Ananya has her own Gmail, her own conversations and her own history, and so\n * do the other ten thousand. Every call you make is on behalf of one of them.\n *\n * Without this, the person is an argument: `endUserId` threaded through\n * `accounts.connect`, `secrets.create`, `conversations` and `start`, and\n * forgotten in exactly one of them one day. With it, you say who once and the\n * rest follows:\n *\n * ```ts\n * // once, in the agent's setup: each person uses their own Gmail\n * await agent.connections.add(Connection.endUserAccount(App.GMAIL));\n *\n * const ananya = ws.endUser(row.userId); // no request\n * const { url } = await ananya.accounts.connect({ app: App.GMAIL });\n * showToUser(url); // she signs in ONCE\n *\n * const conversation = await agent.start({ endUserId: ananya.id });\n * await conversation.ask('what did I miss this morning?');\n *\n * await ananya.conversations(); // everything of hers, any agent\n * ```\n *\n * WHAT NAMING AN END USER DOES, exactly. `start({ endUserId })` labels the\n * conversation so it can be listed back to them, and it decides whose account\n * a connection to the end user's account uses: this person's, for that app,\n * and nobody else's. The grant is the agent's setup, which you can read; it never\n * falls back to the workspace's account or to anybody else's.\n *\n * THE ID IS YOURS. A row id from your database, opaque to us, and we never\n * authenticate it. We are not an identity provider and do not want to be. It\n * also means passing the wrong one hands that person somebody else's mailbox,\n * which is a sharper consequence than the same mistake has for a chat list.\n *\n * Holding one makes NO request.\n */\nexport class EndUser {\n\treadonly id: string;\n\treadonly #http: Http;\n\n\tconstructor(http: Http, id: string) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/end-users/${encodeURIComponent(this.id)}/accounts`;\n\t}\n\n\t/**\n\t * This person's own accounts: the \"end user's account\" that a connection\n\t * built with `Connection.endUserAccount(app)` uses.\n\t *\n\t * Connected ONCE, on the app's own page. After that the account is theirs\n\t * and stays theirs: every conversation of theirs uses it, with any agent\n\t * whose setup asks for the end user's account of that app, and nobody is\n\t * sent through a sign-in again per thread. Nothing is attached per\n\t * conversation.\n\t *\n\t * An agent never reaches these any other way: not through the workspace's\n\t * own account of the same app, and not in another person's conversation.\n\t */\n\treadonly accounts = {\n\t\t/** Begin connecting one of their accounts. Returns it, still `pending`,\n\t\t * and the url to send them to. It is an ordinary `Account`:\n\t\t * `account.waitUntilConnected()` tells you when they are done, and\n\t\t * `ws.account(id)` reaches it later. `conversation.accountsNeeded()`\n\t\t * hands you the same kind of link for whatever a conversation is still\n\t\t * missing. */\n\t\tconnect: async (input: { app: AppId }): Promise<{ account: Account; url: string }> => {\n\t\t\t// FIELDS PICKED: the person comes from the path, never from the body.\n\t\t\tconst res = await this.#http.post<{ account?: AccountData; url: string }>(`${this.base}/connect`, {\n\t\t\t\tapp: input.app,\n\t\t\t});\n\t\t\tconst account = accountIn(res, `${this.base}/connect`);\n\t\t\treturn { account: new Account(this.#http, account.id, account), url: res.url };\n\t\t},\n\n\t\t/** Theirs alone. The agent creators' accounts are not here:\n\t\t * `ws.accounts.list()` is the view that shows those. */\n\t\tlist: async (): Promise<Account[]> => {\n\t\t\tconst res = await this.#http.get<{ accounts: AccountData[] }>(this.base);\n\t\t\treturn (res.accounts ?? []).map((a) => new Account(this.#http, a.id, a));\n\t\t},\n\n\t\t/**\n\t\t * Disconnect every account they connected, in one call: what\n\t\t * off-boarding calls, so it is not a loop the caller has to remember to\n\t\t * write. Every conversation of theirs stops reaching those apps from the\n\t\t * next turn, and nothing is kept. Returns how many were disconnected.\n\t\t */\n\t\tdisconnectAll: async (): Promise<{ disconnected: number }> => {\n\t\t\tconst res = await this.#http.delete<{ disconnected?: number }>(this.base);\n\t\t\treturn { disconnected: res?.disconnected ?? 0 };\n\t\t},\n\t};\n\n\t/**\n\t * Credentials that belong to THIS PERSON: their API key for a system you\n\t * integrate with, their token for a service only they have an account on.\n\t *\n\t * THIS WAS ABSENT UNTIL THERE WAS A WAY TO USE IT. A secret reaches a turn\n\t * only by being ATTACHED, and for a long time it could not be, because\n\t * binding one took its VALUE, and you do not hold the value of a secret the\n\t * platform stored for you. So a per-person secret would have been a surface\n\t * that accepted writes, listed them back, and was read by nothing. It was\n\t * left out rather than shipped looking like a feature.\n\t *\n\t * Secrets are still attached; accounts are not (0.44). A person's account is\n\t * reached through the agent's setup (`Connection.endUserAccount`), so there is\n\t * nothing to attach for it.\n\t *\n\t * `sessionBindings.secretIds` is what closed it, and attaching is still an\n\t * explicit line:\n\t *\n\t * ```ts\n\t * const ananya = ws.endUser(row.userId);\n\t * const key = await ananya.secrets.create({ name: 'ZENDESK_TOKEN', value: token });\n\t *\n\t * const conversation = await agent.start({ endUserId: ananya.id });\n\t * await conversation.attach(key); // EXPLICIT. Nothing is implied.\n\t * ```\n\t *\n\t * TWO THINGS THE PLATFORM ENFORCES, neither of which you can turn off:\n\t * the agent must DECLARE that secret name at session scope, so attaching by\n\t * id is not a way around the allowlist; and a secret belonging to one end\n\t * user can only be attached to a conversation launched for that same person,\n\t * so one mistyped id cannot hand Ananya's key to somebody else's turn.\n\t * Both are checked again at every tool call, not only when you attach.\n\t *\n\t * THE CONVERSATION FOLLOWS THE SECRET. It keeps a pointer, never a copy, so\n\t * when your app gets a new token for this person, `key.update(newToken)`\n\t * reaches every conversation it is attached to on the next call, and\n\t * `key.delete()` stops them all at once.\n\t */\n\treadonly secrets = {\n\t\t/** Store a credential for this person. The value is encrypted on arrival\n\t\t * and never handed back, which is why attaching uses the id. */\n\t\tcreate: async (input: {\n\t\t\tname: string;\n\t\t\tvalue: string;\n\t\t\tdescription?: string;\n\t\t\tagentId?: string;\n\t\t\t/** The only hosts a tool may send it to, checked at every call of a\n\t\t\t * conversation it is attached to. Left out, it goes only to the host\n\t\t\t * the agent's tool writes out in full. */\n\t\t\tallowedHosts?: string[];\n\t\t}): Promise<Secret> => {\n\t\t\t// FIELDS PICKED, never the input object forwarded. Removing a field\n\t\t\t// from a type does not stop a JavaScript caller sending it, and\n\t\t\t// forwarding wholesale is how `endUserId` kept reaching the API after\n\t\t\t// it had supposedly been removed.\n\t\t\tconst res = await this.#http.post<{ secret?: SecretMeta } & SecretMeta>('/v1/secrets', {\n\t\t\t\tname: input.name,\n\t\t\t\tvalue: input.value,\n\t\t\t\tendUserId: this.id,\n\t\t\t\t...(input.description !== undefined && { description: input.description }),\n\t\t\t\t...(input.agentId !== undefined && { agentId: input.agentId }),\n\t\t\t\t...(input.allowedHosts !== undefined && { allowedHosts: input.allowedHosts }),\n\t\t\t});\n\t\t\tconst meta = res.secret ?? res;\n\t\t\treturn new Secret(this.#http, meta.id, meta);\n\t\t},\n\t\t/** This person's secrets, values excluded. The workspace's own are not in\n\t\t * here: `ws.secrets.list()` answers that. */\n\t\tlist: async (): Promise<Secret[]> => {\n\t\t\tconst res = await this.#http.get<{ secrets: SecretMeta[] }>('/v1/secrets', {\n\t\t\t\tend_user_id: this.id,\n\t\t\t});\n\t\t\treturn (res.secrets ?? []).map((m) => new Secret(this.#http, m.id, m));\n\t\t},\n\t};\n\n\t/**\n\t * Everything this person has said, ACROSS EVERY AGENT, newest first.\n\t *\n\t * `agent.conversations({ endUser })` answers the narrower question, one agent\n\t * at a time. This is the one an application renders as \"your chats\", and it\n\t * is the only listing in this SDK with a real cursor: pass `before` from the\n\t * previous page to keep going, rather than being capped at 200.\n\t */\n\tasync conversations(options: { limit?: number; before?: string } = {}): Promise<Conversation[]> {\n\t\tconst res = await this.#http.get<{ conversations: AgentConversationData[] }>('/v1/conversations', {\n\t\t\tend_user_id: this.id,\n\t\t\t...(options.limit !== undefined && { limit: options.limit }),\n\t\t\t...(options.before && { before: options.before }),\n\t\t});\n\t\treturn (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));\n\t}\n}\n"]}
package/errors.d.ts CHANGED
@@ -36,6 +36,26 @@ export declare class AgentNavaError extends Error {
36
36
  requestId?: string;
37
37
  });
38
38
  }
39
+ /** A save refused because a tool would send a key to a host the key is not
40
+ * allowed to go to (code `secret_host_not_allowed`). Each key goes only to
41
+ * the hosts in its list: use a separate key for `host`, or add the host on
42
+ * purpose with `secret.setAllowedHosts([...])`.
43
+ *
44
+ * Still an AgentNavaError, branchable on `code` like every other failure; a
45
+ * subclass only because it carries the fields a caller needs to fix it. */
46
+ export declare class SecretHostNotAllowed extends AgentNavaError {
47
+ /** The key's name, as `{{secrets.NAME}}` names it. */
48
+ readonly secret?: string;
49
+ /** The host the save would have sent it to. */
50
+ readonly host?: string;
51
+ /** The hosts it may go to. */
52
+ readonly allowedHosts: string[];
53
+ constructor(init: ConstructorParameters<typeof AgentNavaError>[0] & {
54
+ secret?: string;
55
+ host?: string;
56
+ allowedHosts?: string[];
57
+ });
58
+ }
39
59
  /** A failure with no HTTP response behind it: DNS, a dropped socket, a timeout.
40
60
  *
41
61
  * Reported as status 0 rather than invented as a 5xx, because a request that
package/errors.js CHANGED
@@ -40,6 +40,30 @@ export class AgentNavaError extends Error {
40
40
  this.requestId = init.requestId;
41
41
  }
42
42
  }
43
+ /** A save refused because a tool would send a key to a host the key is not
44
+ * allowed to go to (code `secret_host_not_allowed`). Each key goes only to
45
+ * the hosts in its list: use a separate key for `host`, or add the host on
46
+ * purpose with `secret.setAllowedHosts([...])`.
47
+ *
48
+ * Still an AgentNavaError, branchable on `code` like every other failure; a
49
+ * subclass only because it carries the fields a caller needs to fix it. */
50
+ export class SecretHostNotAllowed extends AgentNavaError {
51
+ /** The key's name, as `{{secrets.NAME}}` names it. */
52
+ secret;
53
+ /** The host the save would have sent it to. */
54
+ host;
55
+ /** The hosts it may go to. */
56
+ allowedHosts;
57
+ constructor(init) {
58
+ super(init);
59
+ this.name = 'SecretHostNotAllowed';
60
+ if (init.secret !== undefined)
61
+ this.secret = init.secret;
62
+ if (init.host !== undefined)
63
+ this.host = init.host;
64
+ this.allowedHosts = init.allowedHosts ?? [];
65
+ }
66
+ }
43
67
  /** A failure with no HTTP response behind it: DNS, a dropped socket, a timeout.
44
68
  *
45
69
  * Reported as status 0 rather than invented as a 5xx, because a request that
package/errors.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxC;;;8EAG0E;IACjE,IAAI,CAAS;IAEtB;;uDAEmD;IAC1C,MAAM,CAAS;IAExB;;;;;+DAK2D;IAClD,SAAS,CAAU;IAE5B,8CAA8C;IACrC,UAAU,CAAU;IAE7B;qDACiD;IACxC,SAAS,CAAU;IAE5B,YAAY,IAMX;QACA,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC;QAC3D,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACrE,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IACnE,CAAC;CACD;AAED;;;;mFAImF;AACnF,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,KAAe;IAC5D,MAAM,GAAG,GAAG,IAAI,cAAc,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9E,gEAAgE;IAChE,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3E,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,OAAO,GAAG,CAAC;AACZ,CAAC","sourcesContent":["/**\n * Every failure from this SDK is an AgentNavaError.\n *\n * One error type, not a hierarchy. A caller who wants to branch reads `code`,\n * and a caller who wants to retry reads `retryable`; neither is helped by\n * `instanceof NotFoundError`, and both are hurt by having to know which of nine\n * classes a given call can throw.\n */\nexport class AgentNavaError extends Error {\n\t/** Stable machine-readable reason, e.g. 'agent_not_found'.\n\t *\n\t * BRANCH ON THIS, never on `message`. Messages are written for a person\n\t * reading a log and will be reworded; codes are part of the contract. */\n\treadonly code: string;\n\n\t/** The HTTP status. 404 covers both \"no such thing\" and \"not yours\", so a\n\t * caller probing ids cannot tell another customer's agent from one that\n\t * never existed. There is deliberately no 403. */\n\treadonly status: number;\n\n\t/** Whether trying the identical request again could succeed.\n\t *\n\t * True for 5xx and 429 unless a turn may already be accepted or the server\n\t * says retrying is unsafe. A 400 will fail the same way forever, and a\n\t * 404 will not become found, so retrying either just turns one error into\n\t * several. This is the only thing worth automating on. */\n\treadonly retryable: boolean;\n\n\t/** Seconds to wait, on a 429 that said so. */\n\treadonly retryAfter?: number;\n\n\t/** The request id from the response, when there was one. Quote it to us and\n\t * we can find the exact request in our logs. */\n\treadonly requestId?: string;\n\n\tconstructor(init: {\n\t\tmessage: string;\n\t\tcode: string;\n\t\tstatus: number;\n\t\tretryAfter?: number;\n\t\trequestId?: string;\n\t}) {\n\t\tsuper(init.message);\n\t\tthis.name = 'AgentNavaError';\n\t\tthis.code = init.code;\n\t\tthis.status = init.status;\n\t\tthis.retryable = init.status >= 500 || init.status === 429;\n\t\tif (init.retryAfter !== undefined) this.retryAfter = init.retryAfter;\n\t\tif (init.requestId !== undefined) this.requestId = init.requestId;\n\t}\n}\n\n/** A failure with no HTTP response behind it: DNS, a dropped socket, a timeout.\n *\n * Reported as status 0 rather than invented as a 5xx, because a request that\n * never arrived is a different thing from one the server failed. It is\n * retryable for the same reason a 5xx is: nothing about the request was wrong. */\nexport function networkError(message: string, cause?: unknown): AgentNavaError {\n\tconst err = new AgentNavaError({ message, code: 'network_error', status: 0 });\n\t// status 0 is not >= 500, so retryable has to be asserted here.\n\tObject.defineProperty(err, 'retryable', { value: true, enumerable: true });\n\tif (cause !== undefined) Object.defineProperty(err, 'cause', { value: cause });\n\treturn err;\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxC;;;8EAG0E;IACjE,IAAI,CAAS;IAEtB;;uDAEmD;IAC1C,MAAM,CAAS;IAExB;;;;;+DAK2D;IAClD,SAAS,CAAU;IAE5B,8CAA8C;IACrC,UAAU,CAAU;IAE7B;qDACiD;IACxC,SAAS,CAAU;IAE5B,YAAY,IAMX;QACA,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC;QAC3D,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACrE,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IACnE,CAAC;CACD;AAED;;;;;;4EAM4E;AAC5E,MAAM,OAAO,oBAAqB,SAAQ,cAAc;IACvD,sDAAsD;IAC7C,MAAM,CAAU;IACzB,+CAA+C;IACtC,IAAI,CAAU;IACvB,8BAA8B;IACrB,YAAY,CAAW;IAEhC,YAAY,IAAmH;QAC9H,KAAK,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QACzD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACnD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;IAC7C,CAAC;CACD;AAED;;;;mFAImF;AACnF,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,KAAe;IAC5D,MAAM,GAAG,GAAG,IAAI,cAAc,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9E,gEAAgE;IAChE,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3E,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,OAAO,GAAG,CAAC;AACZ,CAAC","sourcesContent":["/**\n * Every failure from this SDK is an AgentNavaError.\n *\n * One error type, not a hierarchy. A caller who wants to branch reads `code`,\n * and a caller who wants to retry reads `retryable`; neither is helped by\n * `instanceof NotFoundError`, and both are hurt by having to know which of nine\n * classes a given call can throw.\n */\nexport class AgentNavaError extends Error {\n\t/** Stable machine-readable reason, e.g. 'agent_not_found'.\n\t *\n\t * BRANCH ON THIS, never on `message`. Messages are written for a person\n\t * reading a log and will be reworded; codes are part of the contract. */\n\treadonly code: string;\n\n\t/** The HTTP status. 404 covers both \"no such thing\" and \"not yours\", so a\n\t * caller probing ids cannot tell another customer's agent from one that\n\t * never existed. There is deliberately no 403. */\n\treadonly status: number;\n\n\t/** Whether trying the identical request again could succeed.\n\t *\n\t * True for 5xx and 429 unless a turn may already be accepted or the server\n\t * says retrying is unsafe. A 400 will fail the same way forever, and a\n\t * 404 will not become found, so retrying either just turns one error into\n\t * several. This is the only thing worth automating on. */\n\treadonly retryable: boolean;\n\n\t/** Seconds to wait, on a 429 that said so. */\n\treadonly retryAfter?: number;\n\n\t/** The request id from the response, when there was one. Quote it to us and\n\t * we can find the exact request in our logs. */\n\treadonly requestId?: string;\n\n\tconstructor(init: {\n\t\tmessage: string;\n\t\tcode: string;\n\t\tstatus: number;\n\t\tretryAfter?: number;\n\t\trequestId?: string;\n\t}) {\n\t\tsuper(init.message);\n\t\tthis.name = 'AgentNavaError';\n\t\tthis.code = init.code;\n\t\tthis.status = init.status;\n\t\tthis.retryable = init.status >= 500 || init.status === 429;\n\t\tif (init.retryAfter !== undefined) this.retryAfter = init.retryAfter;\n\t\tif (init.requestId !== undefined) this.requestId = init.requestId;\n\t}\n}\n\n/** A save refused because a tool would send a key to a host the key is not\n * allowed to go to (code `secret_host_not_allowed`). Each key goes only to\n * the hosts in its list: use a separate key for `host`, or add the host on\n * purpose with `secret.setAllowedHosts([...])`.\n *\n * Still an AgentNavaError, branchable on `code` like every other failure; a\n * subclass only because it carries the fields a caller needs to fix it. */\nexport class SecretHostNotAllowed extends AgentNavaError {\n\t/** The key's name, as `{{secrets.NAME}}` names it. */\n\treadonly secret?: string;\n\t/** The host the save would have sent it to. */\n\treadonly host?: string;\n\t/** The hosts it may go to. */\n\treadonly allowedHosts: string[];\n\n\tconstructor(init: ConstructorParameters<typeof AgentNavaError>[0] & { secret?: string; host?: string; allowedHosts?: string[] }) {\n\t\tsuper(init);\n\t\tthis.name = 'SecretHostNotAllowed';\n\t\tif (init.secret !== undefined) this.secret = init.secret;\n\t\tif (init.host !== undefined) this.host = init.host;\n\t\tthis.allowedHosts = init.allowedHosts ?? [];\n\t}\n}\n\n/** A failure with no HTTP response behind it: DNS, a dropped socket, a timeout.\n *\n * Reported as status 0 rather than invented as a 5xx, because a request that\n * never arrived is a different thing from one the server failed. It is\n * retryable for the same reason a 5xx is: nothing about the request was wrong. */\nexport function networkError(message: string, cause?: unknown): AgentNavaError {\n\tconst err = new AgentNavaError({ message, code: 'network_error', status: 0 });\n\t// status 0 is not >= 500, so retryable has to be asserted here.\n\tObject.defineProperty(err, 'retryable', { value: true, enumerable: true });\n\tif (cause !== undefined) Object.defineProperty(err, 'cause', { value: cause });\n\treturn err;\n}\n"]}