@cerebro-labs/agentnava-sdk 0.26.0 → 0.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -23
- package/account.d.ts +154 -0
- package/account.js +219 -0
- package/account.js.map +1 -0
- package/agent.d.ts +229 -87
- package/agent.js +310 -125
- package/agent.js.map +1 -1
- package/apps.generated.d.ts +154 -0
- package/apps.generated.js +152 -0
- package/apps.generated.js.map +1 -0
- package/attach.d.ts +25 -26
- package/attach.js +53 -66
- package/attach.js.map +1 -1
- package/connection.d.ts +79 -82
- package/connection.js +67 -134
- package/connection.js.map +1 -1
- package/conversation.d.ts +55 -35
- package/conversation.js +70 -50
- package/conversation.js.map +1 -1
- package/database.d.ts +53 -0
- package/database.js +69 -0
- package/database.js.map +1 -0
- package/end-user.d.ts +58 -47
- package/end-user.js +62 -72
- package/end-user.js.map +1 -1
- package/errors.d.ts +2 -1
- package/errors.js +2 -1
- package/errors.js.map +1 -1
- package/http.d.ts +5 -0
- package/http.js +109 -8
- package/http.js.map +1 -1
- package/import-tools.d.ts +54 -33
- package/import-tools.js +526 -124
- package/import-tools.js.map +1 -1
- package/index.d.ts +5 -2
- package/index.js +9 -2
- package/index.js.map +1 -1
- package/load-agent-dir.js +28 -5
- package/load-agent-dir.js.map +1 -1
- package/package.json +5 -1
- package/secret.d.ts +1 -1
- package/secret.js +1 -1
- package/secret.js.map +1 -1
- package/templates-inline.js +1 -1
- package/templates-inline.js.map +1 -1
- package/turn.d.ts +1 -1
- package/turn.js +77 -29
- package/turn.js.map +1 -1
- package/types.d.ts +462 -83
- package/types.js.map +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
- package/workflows.d.ts +10 -0
- package/workflows.js +16 -0
- package/workflows.js.map +1 -1
- package/workspace.d.ts +80 -62
- package/workspace.js +92 -75
- package/workspace.js.map +1 -1
- package/connection-status.d.ts +0 -28
- package/connection-status.js +0 -61
- package/connection-status.js.map +0 -1
package/database.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A database your agents can query: registered once in the workspace, under a
|
|
3
|
+
* key, then named in each agent configuration that should reach it.
|
|
4
|
+
*
|
|
5
|
+
* The order matters and is the whole design. `ws.databases.create()` logs in
|
|
6
|
+
* before it saves anything, so a wrong password fails there and not in the
|
|
7
|
+
* middle of a conversation; an agent then names the key in its `databases`, and
|
|
8
|
+
* only an agent that names it can query it. The agent's prompt lists its
|
|
9
|
+
* databases by key, and its `sql_*` tools take that key.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const shop = await ws.databases.create({ key: 'shop', name: 'Shop DB', url: process.env.SHOP_DB_URL! });
|
|
13
|
+
* await shop.tables(); // [{ schema: 'public', name: 'orders', kind: 'table' }]
|
|
14
|
+
* await ws.agents.create({ name: 'Ops', instructions, databases: ['shop'] });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* The login is stored encrypted and never read back: this handle reports where
|
|
18
|
+
* the database is, never how to log in to it.
|
|
19
|
+
*/
|
|
20
|
+
export class Database {
|
|
21
|
+
/** Workspace-unique, and what an agent configuration stores. Not editable:
|
|
22
|
+
* renaming it would silently cut off every agent that names it. */
|
|
23
|
+
key;
|
|
24
|
+
#http;
|
|
25
|
+
snapshot;
|
|
26
|
+
constructor(http, key, snapshot) {
|
|
27
|
+
this.#http = http;
|
|
28
|
+
this.key = key;
|
|
29
|
+
if (snapshot)
|
|
30
|
+
this.snapshot = snapshot;
|
|
31
|
+
}
|
|
32
|
+
get base() {
|
|
33
|
+
return `/v1/databases/${encodeURIComponent(this.key)}`;
|
|
34
|
+
}
|
|
35
|
+
/** The database's current record: label, engine, host, port, name. */
|
|
36
|
+
async get() {
|
|
37
|
+
const res = await this.#http.get(this.base);
|
|
38
|
+
this.snapshot = res.database;
|
|
39
|
+
return res.database;
|
|
40
|
+
}
|
|
41
|
+
/** What was known when this handle was made, without a round trip.
|
|
42
|
+
* Undefined on a handle built from a bare key. */
|
|
43
|
+
get data() {
|
|
44
|
+
return this.snapshot;
|
|
45
|
+
}
|
|
46
|
+
/** The tables and views it holds, optionally within one schema. Logs in to
|
|
47
|
+
* ask, so this is also the quickest way to check the login still works. */
|
|
48
|
+
async tables(options = {}) {
|
|
49
|
+
const res = await this.#http.get(`${this.base}/tables`, options.schema ? { schema: options.schema } : undefined);
|
|
50
|
+
return res.tables ?? [];
|
|
51
|
+
}
|
|
52
|
+
/** One table's columns, with their types. */
|
|
53
|
+
async describe(table, options = {}) {
|
|
54
|
+
const res = await this.#http.get(`${this.base}/tables/${encodeURIComponent(table)}`, options.schema ? { schema: options.schema } : undefined);
|
|
55
|
+
return res.columns ?? [];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Remove the database from the workspace, and its stored login with it.
|
|
59
|
+
*
|
|
60
|
+
* REFUSED while any agent's configuration still names it, with an error
|
|
61
|
+
* naming those agents. That refusal is what makes naming a key safe: an agent
|
|
62
|
+
* published against `shop` would otherwise find it gone the next morning and
|
|
63
|
+
* report that it has no data.
|
|
64
|
+
*/
|
|
65
|
+
async delete() {
|
|
66
|
+
await this.#http.delete(this.base);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=database.js.map
|
package/database.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,QAAQ;IACpB;wEACoE;IAC3D,GAAG,CAAS;IAEZ,KAAK,CAAO;IACb,QAAQ,CAAgB;IAEhC,YAAY,IAAU,EAAE,GAAW,EAAE,QAAuB;QAC3D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED,IAAY,IAAI;QACf,OAAO,iBAAiB,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACxD,CAAC;IAED,sEAAsE;IACtE,KAAK,CAAC,GAAG;QACR,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6B,IAAI,CAAC,IAAI,CAAC,CAAC;QACxE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC7B,OAAO,GAAG,CAAC,QAAQ,CAAC;IACrB,CAAC;IAED;uDACmD;IACnD,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED;gFAC4E;IAC5E,KAAK,CAAC,MAAM,CAAC,UAA+B,EAAE;QAC7C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,GAAG,IAAI,CAAC,IAAI,SAAS,EACrB,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CACvD,CAAC;QACF,OAAO,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,6CAA6C;IAC7C,KAAK,CAAC,QAAQ,CAAC,KAAa,EAAE,UAA+B,EAAE;QAC9D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,GAAG,IAAI,CAAC,IAAI,WAAW,kBAAkB,CAAC,KAAK,CAAC,EAAE,EAClD,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CACvD,CAAC;QACF,OAAO,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;IAC1B,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,MAAM;QACX,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;CACD","sourcesContent":["import type { Http } from './http.ts';\nimport type { DatabaseColumn, DatabaseData, DatabaseTable } from './types.ts';\n\n/**\n * A database your agents can query: registered once in the workspace, under a\n * key, then named in each agent configuration that should reach it.\n *\n * The order matters and is the whole design. `ws.databases.create()` logs in\n * before it saves anything, so a wrong password fails there and not in the\n * middle of a conversation; an agent then names the key in its `databases`, and\n * only an agent that names it can query it. The agent's prompt lists its\n * databases by key, and its `sql_*` tools take that key.\n *\n * ```ts\n * const shop = await ws.databases.create({ key: 'shop', name: 'Shop DB', url: process.env.SHOP_DB_URL! });\n * await shop.tables(); // [{ schema: 'public', name: 'orders', kind: 'table' }]\n * await ws.agents.create({ name: 'Ops', instructions, databases: ['shop'] });\n * ```\n *\n * The login is stored encrypted and never read back: this handle reports where\n * the database is, never how to log in to it.\n */\nexport class Database {\n\t/** Workspace-unique, and what an agent configuration stores. Not editable:\n\t * renaming it would silently cut off every agent that names it. */\n\treadonly key: string;\n\n\treadonly #http: Http;\n\tprivate snapshot?: DatabaseData;\n\n\tconstructor(http: Http, key: string, snapshot?: DatabaseData) {\n\t\tthis.#http = http;\n\t\tthis.key = key;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/databases/${encodeURIComponent(this.key)}`;\n\t}\n\n\t/** The database's current record: label, engine, host, port, name. */\n\tasync get(): Promise<DatabaseData> {\n\t\tconst res = await this.#http.get<{ database: DatabaseData }>(this.base);\n\t\tthis.snapshot = res.database;\n\t\treturn res.database;\n\t}\n\n\t/** What was known when this handle was made, without a round trip.\n\t * Undefined on a handle built from a bare key. */\n\tget data(): DatabaseData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\t/** The tables and views it holds, optionally within one schema. Logs in to\n\t * ask, so this is also the quickest way to check the login still works. */\n\tasync tables(options: { schema?: string } = {}): Promise<DatabaseTable[]> {\n\t\tconst res = await this.#http.get<{ tables: DatabaseTable[] }>(\n\t\t\t`${this.base}/tables`,\n\t\t\toptions.schema ? { schema: options.schema } : undefined,\n\t\t);\n\t\treturn res.tables ?? [];\n\t}\n\n\t/** One table's columns, with their types. */\n\tasync describe(table: string, options: { schema?: string } = {}): Promise<DatabaseColumn[]> {\n\t\tconst res = await this.#http.get<{ columns: DatabaseColumn[] }>(\n\t\t\t`${this.base}/tables/${encodeURIComponent(table)}`,\n\t\t\toptions.schema ? { schema: options.schema } : undefined,\n\t\t);\n\t\treturn res.columns ?? [];\n\t}\n\n\t/**\n\t * Remove the database from the workspace, and its stored login with it.\n\t *\n\t * REFUSED while any agent's configuration still names it, with an error\n\t * naming those agents. That refusal is what makes naming a key safe: an agent\n\t * published against `shop` would otherwise find it gone the next morning and\n\t * report that it has no data.\n\t */\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n}\n"]}
|
package/end-user.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Account } from './account.ts';
|
|
2
2
|
import { Secret } from './secret.ts';
|
|
3
3
|
import { Conversation } from './conversation.ts';
|
|
4
4
|
import type { Http } from './http.ts';
|
|
5
|
+
import type { AppId } from './apps.generated.ts';
|
|
5
6
|
/**
|
|
6
7
|
* One of YOUR end users, as this platform sees them.
|
|
7
8
|
*
|
|
@@ -34,32 +35,29 @@ import type { Http } from './http.ts';
|
|
|
34
35
|
* do the other ten thousand. Every call you make is on behalf of one of them.
|
|
35
36
|
*
|
|
36
37
|
* Without this, the person is an argument: `endUserId` threaded through
|
|
37
|
-
* `
|
|
38
|
+
* `accounts.connect`, `secrets.create`, `conversations` and `start`, and
|
|
38
39
|
* forgotten in exactly one of them one day. With it, you say who once and the
|
|
39
40
|
* rest follows:
|
|
40
41
|
*
|
|
41
42
|
* ```ts
|
|
42
|
-
*
|
|
43
|
+
* // once, in the agent's setup: each person uses their own Gmail
|
|
44
|
+
* await agent.connections.add(Connection.endUserAccount(App.GMAIL));
|
|
43
45
|
*
|
|
44
|
-
* const
|
|
45
|
-
*
|
|
46
|
-
*
|
|
46
|
+
* const ananya = ws.endUser(row.userId); // no request
|
|
47
|
+
* const { url } = await ananya.accounts.connect({ app: App.GMAIL });
|
|
48
|
+
* showToUser(url); // she signs in ONCE
|
|
47
49
|
*
|
|
48
50
|
* const conversation = await agent.start({ endUserId: ananya.id });
|
|
49
|
-
* await conversation.attach(gmail); // EXPLICIT. Nothing is implied.
|
|
50
51
|
* await conversation.ask('what did I miss this morning?');
|
|
51
52
|
*
|
|
52
53
|
* await ananya.conversations(); // everything of hers, any agent
|
|
53
54
|
* ```
|
|
54
55
|
*
|
|
55
|
-
* NAMING AN END USER
|
|
56
|
-
* conversation so it can be listed back to them
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* label. It was wrong even when it picked correctly: reading the code told you
|
|
61
|
-
* nothing about what the agent could reach, and one mistyped id silently swapped
|
|
62
|
-
* whose mailbox a turn read.
|
|
56
|
+
* WHAT NAMING AN END USER DOES, exactly. `start({ endUserId })` labels the
|
|
57
|
+
* conversation so it can be listed back to them, and it decides whose account
|
|
58
|
+
* a connection to the end user's account uses: this person's, for that app,
|
|
59
|
+
* and nobody else's. The grant is the agent's setup, which you can read; it never
|
|
60
|
+
* falls back to the workspace's account or to anybody else's.
|
|
63
61
|
*
|
|
64
62
|
* THE ID IS YOURS. A row id from your database, opaque to us, and we never
|
|
65
63
|
* authenticate it. We are not an identity provider and do not want to be. It
|
|
@@ -72,38 +70,60 @@ export declare class EndUser {
|
|
|
72
70
|
#private;
|
|
73
71
|
readonly id: string;
|
|
74
72
|
constructor(http: Http, id: string);
|
|
73
|
+
private get base();
|
|
75
74
|
/**
|
|
76
|
-
* This person's own
|
|
75
|
+
* This person's own accounts: the "end user's account" that a connection
|
|
76
|
+
* built with `Connection.endUserAccount(app)` uses.
|
|
77
77
|
*
|
|
78
|
-
* Connected ONCE,
|
|
79
|
-
* stays theirs:
|
|
80
|
-
*
|
|
81
|
-
*
|
|
78
|
+
* Connected ONCE, on the app's own page. After that the account is theirs
|
|
79
|
+
* and stays theirs: every conversation of theirs uses it, with any agent
|
|
80
|
+
* whose setup asks for the end user's account of that app, and nobody is
|
|
81
|
+
* sent through a sign-in again per thread. Nothing is attached per
|
|
82
|
+
* conversation.
|
|
82
83
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* consults: a turn sees only what was attached to it.
|
|
84
|
+
* An agent never reaches these any other way: not through the workspace's
|
|
85
|
+
* own account of the same app, and not in another person's conversation.
|
|
86
86
|
*/
|
|
87
|
-
readonly
|
|
88
|
-
/** Begin connecting
|
|
89
|
-
*
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
readonly accounts: {
|
|
88
|
+
/** Begin connecting one of their accounts. Returns it, still `pending`,
|
|
89
|
+
* and the url to send them to. It is an ordinary `Account`:
|
|
90
|
+
* `account.waitUntilConnected()` tells you when they are done, and
|
|
91
|
+
* `ws.account(id)` reaches it later. `conversation.accountsNeeded()`
|
|
92
|
+
* hands you the same kind of link for whatever a conversation is still
|
|
93
|
+
* missing. */
|
|
94
|
+
connect: (input: {
|
|
95
|
+
app: AppId;
|
|
96
|
+
}) => Promise<{
|
|
97
|
+
account: Account;
|
|
98
|
+
url: string;
|
|
99
|
+
}>;
|
|
100
|
+
/** Theirs alone. The agent creators' accounts are not here:
|
|
101
|
+
* `ws.accounts.list()` is the view that shows those. */
|
|
102
|
+
list: () => Promise<Account[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Disconnect every account they connected, in one call: what
|
|
105
|
+
* off-boarding calls, so it is not a loop the caller has to remember to
|
|
106
|
+
* write. Every conversation of theirs stops reaching those apps from the
|
|
107
|
+
* next turn, and nothing is kept. Returns how many were disconnected.
|
|
108
|
+
*/
|
|
109
|
+
disconnectAll: () => Promise<{
|
|
110
|
+
disconnected: number;
|
|
111
|
+
}>;
|
|
96
112
|
};
|
|
97
113
|
/**
|
|
98
114
|
* Credentials that belong to THIS PERSON: their API key for a system you
|
|
99
115
|
* integrate with, their token for a service only they have an account on.
|
|
100
116
|
*
|
|
101
|
-
* THIS WAS ABSENT UNTIL THERE WAS A WAY TO USE IT. A
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
117
|
+
* THIS WAS ABSENT UNTIL THERE WAS A WAY TO USE IT. A secret reaches a turn
|
|
118
|
+
* only by being ATTACHED, and for a long time it could not be, because
|
|
119
|
+
* binding one took its VALUE, and you do not hold the value of a secret the
|
|
120
|
+
* platform stored for you. So a per-person secret would have been a surface
|
|
121
|
+
* that accepted writes, listed them back, and was read by nothing. It was
|
|
122
|
+
* left out rather than shipped looking like a feature.
|
|
123
|
+
*
|
|
124
|
+
* Secrets are still attached; accounts are not (0.44). A person's account is
|
|
125
|
+
* reached through the agent's setup (`Connection.endUserAccount`), so there is
|
|
126
|
+
* nothing to attach for it.
|
|
107
127
|
*
|
|
108
128
|
* `sessionBindings.secretIds` is what closed it, and attaching is still an
|
|
109
129
|
* explicit line:
|
|
@@ -147,13 +167,4 @@ export declare class EndUser {
|
|
|
147
167
|
limit?: number;
|
|
148
168
|
before?: string;
|
|
149
169
|
}): Promise<Conversation[]>;
|
|
150
|
-
/**
|
|
151
|
-
* Cut this person off.
|
|
152
|
-
*
|
|
153
|
-
* Revokes every account they connected, so every conversation of theirs
|
|
154
|
-
* stops acting as them from the next turn. What off-boarding calls, and the
|
|
155
|
-
* reason it is one call rather than a loop the caller has to remember to
|
|
156
|
-
* write.
|
|
157
|
-
*/
|
|
158
|
-
revokeConnections(): Promise<void>;
|
|
159
170
|
}
|
package/end-user.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// One of YOUR end users: a person your application serves, who never signs in here.
|
|
2
|
-
import {
|
|
3
|
-
import { toConnectionStatus } from "./connection-status.js";
|
|
2
|
+
import { Account, accountIn } from "./account.js";
|
|
4
3
|
import { Secret } from "./secret.js";
|
|
5
4
|
import { Conversation } from "./conversation.js";
|
|
6
5
|
/**
|
|
@@ -35,32 +34,29 @@ import { Conversation } from "./conversation.js";
|
|
|
35
34
|
* do the other ten thousand. Every call you make is on behalf of one of them.
|
|
36
35
|
*
|
|
37
36
|
* Without this, the person is an argument: `endUserId` threaded through
|
|
38
|
-
* `
|
|
37
|
+
* `accounts.connect`, `secrets.create`, `conversations` and `start`, and
|
|
39
38
|
* forgotten in exactly one of them one day. With it, you say who once and the
|
|
40
39
|
* rest follows:
|
|
41
40
|
*
|
|
42
41
|
* ```ts
|
|
43
|
-
*
|
|
42
|
+
* // once, in the agent's setup: each person uses their own Gmail
|
|
43
|
+
* await agent.connections.add(Connection.endUserAccount(App.GMAIL));
|
|
44
44
|
*
|
|
45
|
-
* const
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* const ananya = ws.endUser(row.userId); // no request
|
|
46
|
+
* const { url } = await ananya.accounts.connect({ app: App.GMAIL });
|
|
47
|
+
* showToUser(url); // she signs in ONCE
|
|
48
48
|
*
|
|
49
49
|
* const conversation = await agent.start({ endUserId: ananya.id });
|
|
50
|
-
* await conversation.attach(gmail); // EXPLICIT. Nothing is implied.
|
|
51
50
|
* await conversation.ask('what did I miss this morning?');
|
|
52
51
|
*
|
|
53
52
|
* await ananya.conversations(); // everything of hers, any agent
|
|
54
53
|
* ```
|
|
55
54
|
*
|
|
56
|
-
* NAMING AN END USER
|
|
57
|
-
* conversation so it can be listed back to them
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* label. It was wrong even when it picked correctly: reading the code told you
|
|
62
|
-
* nothing about what the agent could reach, and one mistyped id silently swapped
|
|
63
|
-
* whose mailbox a turn read.
|
|
55
|
+
* WHAT NAMING AN END USER DOES, exactly. `start({ endUserId })` labels the
|
|
56
|
+
* conversation so it can be listed back to them, and it decides whose account
|
|
57
|
+
* a connection to the end user's account uses: this person's, for that app,
|
|
58
|
+
* and nobody else's. The grant is the agent's setup, which you can read; it never
|
|
59
|
+
* falls back to the workspace's account or to anybody else's.
|
|
64
60
|
*
|
|
65
61
|
* THE ID IS YOURS. A row id from your database, opaque to us, and we never
|
|
66
62
|
* authenticate it. We are not an identity provider and do not want to be. It
|
|
@@ -76,61 +72,68 @@ export class EndUser {
|
|
|
76
72
|
this.#http = http;
|
|
77
73
|
this.id = id;
|
|
78
74
|
}
|
|
75
|
+
get base() {
|
|
76
|
+
return `/v1/end-users/${encodeURIComponent(this.id)}/accounts`;
|
|
77
|
+
}
|
|
79
78
|
/**
|
|
80
|
-
* This person's own
|
|
79
|
+
* This person's own accounts: the "end user's account" that a connection
|
|
80
|
+
* built with `Connection.endUserAccount(app)` uses.
|
|
81
81
|
*
|
|
82
|
-
* Connected ONCE,
|
|
83
|
-
* stays theirs:
|
|
84
|
-
*
|
|
85
|
-
*
|
|
82
|
+
* Connected ONCE, on the app's own page. After that the account is theirs
|
|
83
|
+
* and stays theirs: every conversation of theirs uses it, with any agent
|
|
84
|
+
* whose setup asks for the end user's account of that app, and nobody is
|
|
85
|
+
* sent through a sign-in again per thread. Nothing is attached per
|
|
86
|
+
* conversation.
|
|
86
87
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* consults: a turn sees only what was attached to it.
|
|
88
|
+
* An agent never reaches these any other way: not through the workspace's
|
|
89
|
+
* own account of the same app, and not in another person's conversation.
|
|
90
90
|
*/
|
|
91
|
-
|
|
92
|
-
/** Begin connecting
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
//
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
required: false,
|
|
106
|
-
endUserId: this.id,
|
|
107
|
-
}, res.authUrl);
|
|
91
|
+
accounts = {
|
|
92
|
+
/** Begin connecting one of their accounts. Returns it, still `pending`,
|
|
93
|
+
* and the url to send them to. It is an ordinary `Account`:
|
|
94
|
+
* `account.waitUntilConnected()` tells you when they are done, and
|
|
95
|
+
* `ws.account(id)` reaches it later. `conversation.accountsNeeded()`
|
|
96
|
+
* hands you the same kind of link for whatever a conversation is still
|
|
97
|
+
* missing. */
|
|
98
|
+
connect: async (input) => {
|
|
99
|
+
// FIELDS PICKED: the person comes from the path, never from the body.
|
|
100
|
+
const res = await this.#http.post(`${this.base}/connect`, {
|
|
101
|
+
app: input.app,
|
|
102
|
+
});
|
|
103
|
+
const account = accountIn(res, `${this.base}/connect`);
|
|
104
|
+
return { account: new Account(this.#http, account.id, account), url: res.url };
|
|
108
105
|
},
|
|
109
|
-
/** Theirs alone. The
|
|
110
|
-
* `ws.
|
|
106
|
+
/** Theirs alone. The agent creators' accounts are not here:
|
|
107
|
+
* `ws.accounts.list()` is the view that shows those. */
|
|
111
108
|
list: async () => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
109
|
+
const res = await this.#http.get(this.base);
|
|
110
|
+
return (res.accounts ?? []).map((a) => new Account(this.#http, a.id, a));
|
|
111
|
+
},
|
|
112
|
+
/**
|
|
113
|
+
* Disconnect every account they connected, in one call: what
|
|
114
|
+
* off-boarding calls, so it is not a loop the caller has to remember to
|
|
115
|
+
* write. Every conversation of theirs stops reaching those apps from the
|
|
116
|
+
* next turn, and nothing is kept. Returns how many were disconnected.
|
|
117
|
+
*/
|
|
118
|
+
disconnectAll: async () => {
|
|
119
|
+
const res = await this.#http.delete(this.base);
|
|
120
|
+
return { disconnected: res?.disconnected ?? 0 };
|
|
122
121
|
},
|
|
123
122
|
};
|
|
124
123
|
/**
|
|
125
124
|
* Credentials that belong to THIS PERSON: their API key for a system you
|
|
126
125
|
* integrate with, their token for a service only they have an account on.
|
|
127
126
|
*
|
|
128
|
-
* THIS WAS ABSENT UNTIL THERE WAS A WAY TO USE IT. A
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
127
|
+
* THIS WAS ABSENT UNTIL THERE WAS A WAY TO USE IT. A secret reaches a turn
|
|
128
|
+
* only by being ATTACHED, and for a long time it could not be, because
|
|
129
|
+
* binding one took its VALUE, and you do not hold the value of a secret the
|
|
130
|
+
* platform stored for you. So a per-person secret would have been a surface
|
|
131
|
+
* that accepted writes, listed them back, and was read by nothing. It was
|
|
132
|
+
* left out rather than shipped looking like a feature.
|
|
133
|
+
*
|
|
134
|
+
* Secrets are still attached; accounts are not (0.44). A person's account is
|
|
135
|
+
* reached through the agent's setup (`Connection.endUserAccount`), so there is
|
|
136
|
+
* nothing to attach for it.
|
|
134
137
|
*
|
|
135
138
|
* `sessionBindings.secretIds` is what closed it, and attaching is still an
|
|
136
139
|
* explicit line:
|
|
@@ -192,18 +195,5 @@ export class EndUser {
|
|
|
192
195
|
});
|
|
193
196
|
return (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));
|
|
194
197
|
}
|
|
195
|
-
/**
|
|
196
|
-
* Cut this person off.
|
|
197
|
-
*
|
|
198
|
-
* Revokes every account they connected, so every conversation of theirs
|
|
199
|
-
* stops acting as them from the next turn. What off-boarding calls, and the
|
|
200
|
-
* reason it is one call rather than a loop the caller has to remember to
|
|
201
|
-
* write.
|
|
202
|
-
*/
|
|
203
|
-
async revokeConnections() {
|
|
204
|
-
for (const connection of await this.connections.list()) {
|
|
205
|
-
await connection.revoke();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
198
|
}
|
|
209
199
|
//# sourceMappingURL=end-user.js.map
|
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 { Connection } from './connection.ts';\nimport { toConnectionStatus, type WireConnection } from './connection-status.ts';\nimport { Secret } from './secret.ts';\nimport { Conversation } from './conversation.ts';\nimport type { Http } from './http.ts';\nimport type { AgentConversationData, ConnectionStatus, 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 * `connections.create`, `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 * const ananya = ws.endUser(row.userId); // no request\n *\n * const gmail = await ananya.connections.create({ provider: 'gmail' });\n * showToUser(gmail.url);\n * await gmail.waitUntilConnected();\n *\n * const conversation = await agent.start({ endUserId: ananya.id });\n * await conversation.attach(gmail); // EXPLICIT. Nothing is implied.\n * await conversation.ask('what did I miss this morning?');\n *\n * await ananya.conversations(); // everything of hers, any agent\n * ```\n *\n * NAMING AN END USER GRANTS NOTHING. `start({ endUserId })` labels the\n * conversation so it can be listed back to them; it does not hand the agent\n * their accounts. The `attach` is the grant, and it is a line you can see.\n *\n * An earlier version resolved a person's credentials automatically from that\n * label. It was wrong even when it picked correctly: reading the code told you\n * nothing about what the agent could reach, and one mistyped id silently swapped\n * whose mailbox a turn read.\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\t/**\n\t * This person's own third-party accounts.\n\t *\n\t * Connected ONCE, in their browser. After that the account is theirs and\n\t * stays theirs: you attach it to whichever conversations should use it,\n\t * rather than sending them through a sign-in again per thread. That is the\n\t * saving. The attach is not.\n\t *\n\t * Listing here is the CALLER'S view, for showing someone what they have\n\t * connected and for revoking it when they leave. It is not what a turn\n\t * consults: a turn sees only what was attached to it.\n\t */\n\treadonly connections = {\n\t\t/** Begin connecting. Returns a `Connection` carrying the url to send\n\t\t * them to; `waitUntilConnected()` tells you when they are done. */\n\t\tcreate: async (input: { provider: string }): Promise<Connection> => {\n\t\t\tconst res = await this.#http.post<{ authUrl: string; connectionId: string }>(\n\t\t\t\t'/v1/connections/connect',\n\t\t\t\t{ provider: input.provider, endUserId: this.id },\n\t\t\t);\n\t\t\treturn new Connection(\n\t\t\t\tthis.#http,\n\t\t\t\tres.connectionId,\n\t\t\t\t// NOT `as never`. That cast was here to let `scope: 'user'` through,\n\t\t\t\t// a value outside ConnectionStatus's own type, and it disabled\n\t\t\t\t// checking on every other field at the same time.\n\t\t\t\t{\n\t\t\t\t\tid: res.connectionId,\n\t\t\t\t\tprovider: input.provider,\n\t\t\t\t\tconnected: false,\n\t\t\t\t\tdeclared: true,\n\t\t\t\t\trequired: false,\n\t\t\t\t\tendUserId: this.id,\n\t\t\t\t},\n\t\t\t\tres.authUrl,\n\t\t\t);\n\t\t},\n\n\t\t/** Theirs alone. The shared workspace and agent accounts are not here;\n\t\t * `ws.connections.list()` is the view that shows those. */\n\t\tlist: async (): Promise<Connection[]> => {\n\t\t\t// WireConnection, not ConnectionStatus. The endpoint sends the wire\n\t\t\t// shape, and typing it as the SDK shape is what hid the whole bug:\n\t\t\t// `connected` is never sent, so every account here reported\n\t\t\t// `connected === undefined` while type-checking as a boolean.\n\t\t\tconst res = await this.#http.get<{ connections: WireConnection[] }>('/v1/connections', {\n\t\t\t\tendUserId: this.id,\n\t\t\t});\n\t\t\treturn (res.connections ?? [])\n\t\t\t\t.filter((c) => c.id)\n\t\t\t\t.map((c) => new Connection(this.#http, c.id, toConnectionStatus(c)));\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 credential reaches a\n\t * turn only by being ATTACHED. A connection could be, by id; a secret could\n\t * not, because binding one took its VALUE, and you do not hold the value of a\n\t * secret the platform stored for you. So a per-person secret would have been\n\t * a surface that accepted writes, listed them back, and was read by nothing.\n\t * It was left out rather than shipped looking like a feature.\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\t/**\n\t * Cut this person off.\n\t *\n\t * Revokes every account they connected, so every conversation of theirs\n\t * stops acting as them from the next turn. What off-boarding calls, and the\n\t * reason it is one call rather than a loop the caller has to remember to\n\t * write.\n\t */\n\tasync revokeConnections(): Promise<void> {\n\t\tfor (const connection of await this.connections.list()) {\n\t\t\tawait connection.revoke();\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"end-user.js","sourceRoot":"","sources":["../src/end-user.ts"],"names":[],"mappings":"AAAA,oFAAoF;AAEpF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAKjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,MAAM,OAAO,OAAO;IACV,EAAE,CAAS;IACX,KAAK,CAAO;IAErB,YAAY,IAAU,EAAE,EAAU;QACjC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,CAAC;IAED,IAAY,IAAI;QACf,OAAO,iBAAiB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC;IAChE,CAAC;IAED;;;;;;;;;;;;OAYG;IACM,QAAQ,GAAG;QACnB;;;;;uBAKe;QACf,OAAO,EAAE,KAAK,EAAE,KAAqB,EAA8C,EAAE;YACpF,sEAAsE;YACtE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAyC,GAAG,IAAI,CAAC,IAAI,UAAU,EAAE;gBACjG,GAAG,EAAE,KAAK,CAAC,GAAG;aACd,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,UAAU,CAAC,CAAC;YACvD,OAAO,EAAE,OAAO,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;QAChF,CAAC;QAED;iEACyD;QACzD,IAAI,EAAE,KAAK,IAAwB,EAAE;YACpC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAA8B,IAAI,CAAC,IAAI,CAAC,CAAC;YACzE,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1E,CAAC;QAED;;;;;WAKG;QACH,aAAa,EAAE,KAAK,IAAuC,EAAE;YAC5D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAA4B,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1E,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,IAAI,CAAC,EAAE,CAAC;QACjD,CAAC;KACD,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACM,OAAO,GAAG;QAClB;yEACiE;QACjE,MAAM,EAAE,KAAK,EAAE,KAKd,EAAmB,EAAE;YACrB,oEAAoE;YACpE,gEAAgE;YAChE,sEAAsE;YACtE,kCAAkC;YAClC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAuC,aAAa,EAAE;gBACtF,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,IAAI,CAAC,EAAE;gBAClB,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;gBAC1E,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;aAC9D,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;YAC/B,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC9C,CAAC;QACD;sDAC8C;QAC9C,IAAI,EAAE,KAAK,IAAuB,EAAE;YACnC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,aAAa,EAAE;gBAC1E,WAAW,EAAE,IAAI,CAAC,EAAE;aACpB,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QACxE,CAAC;KACD,CAAC;IAEF;;;;;;;OAOG;IACH,KAAK,CAAC,aAAa,CAAC,UAA+C,EAAE;QACpE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6C,mBAAmB,EAAE;YACjG,WAAW,EAAE,IAAI,CAAC,EAAE;YACpB,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;YAC5D,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;SACjD,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;CACD","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"]}
|
package/errors.d.ts
CHANGED
|
@@ -18,7 +18,8 @@ export declare class AgentNavaError extends Error {
|
|
|
18
18
|
readonly status: number;
|
|
19
19
|
/** Whether trying the identical request again could succeed.
|
|
20
20
|
*
|
|
21
|
-
* True for 5xx and 429
|
|
21
|
+
* True for 5xx and 429 unless a turn may already be accepted or the server
|
|
22
|
+
* says retrying is unsafe. A 400 will fail the same way forever, and a
|
|
22
23
|
* 404 will not become found, so retrying either just turns one error into
|
|
23
24
|
* several. This is the only thing worth automating on. */
|
|
24
25
|
readonly retryable: boolean;
|
package/errors.js
CHANGED
|
@@ -18,7 +18,8 @@ export class AgentNavaError extends Error {
|
|
|
18
18
|
status;
|
|
19
19
|
/** Whether trying the identical request again could succeed.
|
|
20
20
|
*
|
|
21
|
-
* True for 5xx and 429
|
|
21
|
+
* True for 5xx and 429 unless a turn may already be accepted or the server
|
|
22
|
+
* says retrying is unsafe. A 400 will fail the same way forever, and a
|
|
22
23
|
* 404 will not become found, so retrying either just turns one error into
|
|
23
24
|
* several. This is the only thing worth automating on. */
|
|
24
25
|
retryable;
|
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
|
|
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"]}
|
package/http.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface ClientOptions {
|
|
|
10
10
|
}
|
|
11
11
|
export interface RequestOptions {
|
|
12
12
|
method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
13
|
+
/** Internal turn submission identity, minted once by Conversation.ask. */
|
|
14
|
+
idempotencyKey?: string;
|
|
13
15
|
/** Sent as a JSON body. Mutually exclusive with `bytes`. */
|
|
14
16
|
body?: unknown;
|
|
15
17
|
/** Sent as the raw body, for file writes. Mutually exclusive with `body`. */
|
|
@@ -27,6 +29,9 @@ export declare class Http {
|
|
|
27
29
|
request<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
28
30
|
get<T>(path: string, query?: RequestOptions['query']): Promise<T>;
|
|
29
31
|
post<T>(path: string, body?: unknown, query?: RequestOptions['query']): Promise<T>;
|
|
32
|
+
put<T>(path: string, body?: unknown): Promise<T>;
|
|
30
33
|
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
31
34
|
delete<T>(path: string, query?: RequestOptions['query']): Promise<T>;
|
|
32
35
|
}
|
|
36
|
+
/** Kept in errors because a dropped response may not have returned a turn ID. */
|
|
37
|
+
export declare function turnRecoveryMessage(path: string, idempotencyKey: string): string;
|