@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/attach.js
CHANGED
|
@@ -1,49 +1,27 @@
|
|
|
1
|
-
// Turning the
|
|
1
|
+
// Turning the secrets a caller is holding into what the wire speaks.
|
|
2
2
|
//
|
|
3
3
|
// ITS OWN MODULE because `agent.ts` already imports `conversation.ts`, so a
|
|
4
4
|
// helper living in either one and used by both is a cycle. Shared so that
|
|
5
5
|
// `agent.start`, `agent.attach` and `conversation.attach` cannot drift: all
|
|
6
6
|
// three had to agree on this shape, and when they did not, the disagreement was
|
|
7
7
|
// invisible until a turn failed somewhere else.
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
8
|
+
//
|
|
9
|
+
// SECRETS ONLY, since 0.44. Accounts used to be attached here too, to an agent
|
|
10
|
+
// or to one conversation, and a turn then had to work out which of several
|
|
11
|
+
// attached and owned accounts to use. An agent's setup now names the account
|
|
12
|
+
// each connection uses (`agent.connections.add`), so there is nothing left to
|
|
13
|
+
// attach, and an account handed to any of these is refused by name rather than
|
|
14
|
+
// dropped.
|
|
15
|
+
import { AgentNavaError } from "./errors.js";
|
|
16
|
+
// A VALUE import, not type-only, because the check below is an `instanceof`.
|
|
17
|
+
// secret.ts imports nothing but types, so this adds no cycle.
|
|
13
18
|
import { Secret } from "./secret.js";
|
|
14
19
|
/**
|
|
15
|
-
*
|
|
16
|
-
* wire speaks.
|
|
20
|
+
* Distinguish `attach({ secrets })` from `attach(secret)`.
|
|
17
21
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* only
|
|
21
|
-
*/
|
|
22
|
-
export async function providersOf(what) {
|
|
23
|
-
if (!what)
|
|
24
|
-
return undefined;
|
|
25
|
-
const list = Array.isArray(what) ? what : [what];
|
|
26
|
-
if (list.length === 0)
|
|
27
|
-
return undefined;
|
|
28
|
-
const out = {};
|
|
29
|
-
for (const connection of list) {
|
|
30
|
-
const provider = connection.provider ?? (await connection.get()).provider;
|
|
31
|
-
// Two different accounts for one provider in a single call is a caller
|
|
32
|
-
// mistake. A silent last-one-wins would surface much later as "the agent
|
|
33
|
-
// read the wrong mailbox", which is expensive to trace back to here.
|
|
34
|
-
if (out[provider] && out[provider] !== connection.id) {
|
|
35
|
-
throw new Error(`two different ${provider} accounts were attached in one call: ${out[provider]} and ${connection.id}`);
|
|
36
|
-
}
|
|
37
|
-
out[provider] = connection.id;
|
|
38
|
-
}
|
|
39
|
-
return out;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Distinguish `attach({ secrets })` from `attach(connection)`.
|
|
43
|
-
*
|
|
44
|
-
* Structural rather than `instanceof`, so a Connection that crossed a module
|
|
45
|
-
* boundary or was rebuilt from JSON is still recognised. A Connection always
|
|
46
|
-
* has an `id`; a Bindings object has only `secrets` and `connections`.
|
|
22
|
+
* Structural rather than `instanceof`, so a plain object built anywhere is
|
|
23
|
+
* still recognised. A Secret always has an `id`; a Bindings object does not.
|
|
24
|
+
* `connections` is recognised too, only so it can be refused by name below.
|
|
47
25
|
*/
|
|
48
26
|
export function isBindings(what) {
|
|
49
27
|
if (Array.isArray(what) || what === null || typeof what !== 'object')
|
|
@@ -51,6 +29,43 @@ export function isBindings(what) {
|
|
|
51
29
|
const o = what;
|
|
52
30
|
return !('id' in o) && ('secrets' in o || 'connections' in o);
|
|
53
31
|
}
|
|
32
|
+
/** The words every refusal below uses, so an upgrader reads one explanation. */
|
|
33
|
+
const ACCOUNTS_MOVED = 'Accounts are not attached any more (0.44): name the account in the agent\'s setup with ' +
|
|
34
|
+
'agent.connections.add(Connection.creatorAccount(app, account)), or use ' +
|
|
35
|
+
'Connection.endUserAccount(app) so each person signs in with their own.';
|
|
36
|
+
/** Refuse `{ connections }`. A JavaScript caller still holding 0.43 code would
|
|
37
|
+
* otherwise have it dropped in silence, and the agent would answer without the
|
|
38
|
+
* account it was plainly given. */
|
|
39
|
+
export function refuseConnections(bindings) {
|
|
40
|
+
if ('connections' in bindings) {
|
|
41
|
+
throw new AgentNavaError({ message: ACCOUNTS_MOVED, code: 'not_a_secret', status: 400 });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check that everything handed to `attach` is a Secret, and hand back the list.
|
|
46
|
+
*
|
|
47
|
+
* BY `instanceof`, not by duck-typing: a Secret addressed with `ws.secret(id)`
|
|
48
|
+
* has no `name` until it reads one, so "has a name" answers wrongly for exactly
|
|
49
|
+
* the handles that need it most.
|
|
50
|
+
*
|
|
51
|
+
* Refused rather than ignored. Silently dropping an unrecognised attachment is
|
|
52
|
+
* how a conversation starts without the credential its caller plainly
|
|
53
|
+
* attached, and the failure then appears one layer down as a tool that cannot
|
|
54
|
+
* authenticate.
|
|
55
|
+
*/
|
|
56
|
+
export function secretsIn(what) {
|
|
57
|
+
const list = what === undefined ? [] : Array.isArray(what) ? what : [what];
|
|
58
|
+
for (const item of list) {
|
|
59
|
+
if (!(item instanceof Secret)) {
|
|
60
|
+
throw new AgentNavaError({
|
|
61
|
+
message: `attach takes a Secret. ${ACCOUNTS_MOVED}`,
|
|
62
|
+
code: 'not_a_secret',
|
|
63
|
+
status: 400,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return list;
|
|
68
|
+
}
|
|
54
69
|
/**
|
|
55
70
|
* Turn stored secrets into the `{ DECLARED_NAME: secretId }` map the wire
|
|
56
71
|
* speaks.
|
|
@@ -76,8 +91,7 @@ export async function secretIdsOf(what) {
|
|
|
76
91
|
const name = secret.name ?? (await secret.get()).name;
|
|
77
92
|
// Two different secrets under one name in a single call is a caller
|
|
78
93
|
// mistake, and last-one-wins would surface much later as a tool failing
|
|
79
|
-
// on a credential that looks present.
|
|
80
|
-
// provider, for the same reason.
|
|
94
|
+
// on a credential that looks present.
|
|
81
95
|
if (out[name] && out[name] !== secret.id) {
|
|
82
96
|
throw new Error(`two different secrets were attached as ${name}: ${out[name]} and ${secret.id}`);
|
|
83
97
|
}
|
|
@@ -85,31 +99,4 @@ export async function secretIdsOf(what) {
|
|
|
85
99
|
}
|
|
86
100
|
return out;
|
|
87
101
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Split a mixed list into the accounts and the credentials.
|
|
90
|
-
*
|
|
91
|
-
* BY `instanceof`, not by duck-typing. A `Connection` addressed with
|
|
92
|
-
* `ws.connection(id)` has no `provider` until it reads one, and a `Secret` from
|
|
93
|
-
* `ws.secret(id)` has no `name`, so "has a provider" and "has a name" both
|
|
94
|
-
* answer wrongly for exactly the handles that need splitting most.
|
|
95
|
-
*/
|
|
96
|
-
export function splitAttachments(what) {
|
|
97
|
-
const list = what === undefined ? [] : Array.isArray(what) ? what : [what];
|
|
98
|
-
const connections = [];
|
|
99
|
-
const secrets = [];
|
|
100
|
-
for (const item of list) {
|
|
101
|
-
if (item instanceof Secret)
|
|
102
|
-
secrets.push(item);
|
|
103
|
-
else if (item instanceof ConnectionClass)
|
|
104
|
-
connections.push(item);
|
|
105
|
-
else {
|
|
106
|
-
// Refused rather than ignored. Silently dropping an unrecognised
|
|
107
|
-
// attachment is how a conversation starts without the credential its
|
|
108
|
-
// caller plainly attached, and the failure then appears one layer
|
|
109
|
-
// down as a tool that cannot authenticate.
|
|
110
|
-
throw new Error('attach takes a Connection or a Secret');
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return { connections, secrets };
|
|
114
|
-
}
|
|
115
102
|
//# sourceMappingURL=attach.js.map
|
package/attach.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attach.js","sourceRoot":"","sources":["../src/attach.ts"],"names":[],"mappings":"AAAA,
|
|
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"]}
|
package/connection.d.ts
CHANGED
|
@@ -1,89 +1,86 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type {
|
|
1
|
+
import { type Account } from './account.ts';
|
|
2
|
+
import type { AppId } from './apps.generated.ts';
|
|
3
|
+
/** A connection that uses the AGENT CREATOR'S ACCOUNT: one account in this
|
|
4
|
+
* workspace, signed in once, used by every conversation. `account` is the
|
|
5
|
+
* `Account` you hold or its id; an `Account` goes over the wire as its id. */
|
|
6
|
+
export interface CreatorAccountConnection {
|
|
7
|
+
name?: string;
|
|
8
|
+
app: AppId;
|
|
9
|
+
account: Account | string;
|
|
10
|
+
endUser?: never;
|
|
11
|
+
}
|
|
12
|
+
/** A connection that uses the END USER'S ACCOUNT: each person talking to the
|
|
13
|
+
* agent signs in with their own, once, and it is remembered for them. */
|
|
14
|
+
export interface EndUserAccountConnection {
|
|
15
|
+
name?: string;
|
|
16
|
+
app: AppId;
|
|
17
|
+
endUser: true;
|
|
18
|
+
account?: never;
|
|
19
|
+
}
|
|
3
20
|
/**
|
|
4
|
-
*
|
|
21
|
+
* One connection, as you hand it to `ws.agents.create()`, `agent.update()` or
|
|
22
|
+
* `agent.connections.add()`: an app, and whose account.
|
|
5
23
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
24
|
+
* Build it with `Connection.creatorAccount()` or `Connection.endUserAccount()`,
|
|
25
|
+
* which ask the same question the portal's Add a connection screen asks. Or
|
|
26
|
+
* write the object yourself: `{ app, account }` or `{ app, endUser: true }`.
|
|
27
|
+
* Exactly one of the two, and the type says so, so a connection with both, or
|
|
28
|
+
* with neither, does not compile.
|
|
29
|
+
*/
|
|
30
|
+
export type Connection = CreatorAccountConnection | EndUserAccountConnection;
|
|
31
|
+
/**
|
|
32
|
+
* Build the entries of an agent's setup.
|
|
12
33
|
*
|
|
13
34
|
* ```ts
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* await
|
|
17
|
-
*
|
|
35
|
+
* import { App, Connection } from '@cerebro-labs/agentnava-sdk';
|
|
36
|
+
*
|
|
37
|
+
* await ws.agents.create({
|
|
38
|
+
* name: 'Inbox helper',
|
|
39
|
+
* instructions: '...',
|
|
40
|
+
* connections: [
|
|
41
|
+
* Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }),
|
|
42
|
+
* Connection.creatorAccount(App.GMAIL, personal, { name: 'personal-gmail' }),
|
|
43
|
+
* Connection.endUserAccount(App.GOOGLE_CALENDAR),
|
|
44
|
+
* ],
|
|
45
|
+
* });
|
|
18
46
|
* ```
|
|
19
47
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
48
|
+
* WHY BUILDERS. Whose account a connection uses is the one decision that
|
|
49
|
+
* matters, and it used to be spelled `scope: 'fixed'` or `scope:
|
|
50
|
+
* 'conversation'`, words that answered a different question. These two names
|
|
51
|
+
* are the question itself, as the screen asks it: the agent creator's account,
|
|
52
|
+
* or the end user's account.
|
|
53
|
+
*
|
|
54
|
+
* NOT THE 0.43 `Connection`. That was a handle on a signed-in account and is
|
|
55
|
+
* now `Account`. This name builds an entry for an agent's setup; it holds
|
|
56
|
+
* nothing and makes no request.
|
|
57
|
+
*
|
|
58
|
+
* `name` tells two connections of one app apart, such as two Gmail accounts.
|
|
59
|
+
* Lowercase letters, digits and dashes, up to 64 characters, unique on the
|
|
60
|
+
* agent. Left out, it is the app's id.
|
|
22
61
|
*/
|
|
23
|
-
export declare
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* person
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
* DERIVED HERE, from which id the row carries, rather than read off a field.
|
|
50
|
-
* This getter used to return `snapshot.scope`, which is a different question
|
|
51
|
-
* entirely: `scope` is what an agent DECLARED it needs ('fixed' or
|
|
52
|
-
* 'conversation'), and the server never sent an ownership field at all. So
|
|
53
|
-
* it answered `undefined` for every connection you looked up, and for one
|
|
54
|
-
* you had just created it answered `'user'`, a value outside its own
|
|
55
|
-
* declared type that only compiled through an `as never` cast.
|
|
56
|
-
*
|
|
57
|
-
* Most specific first: a conversation's own account, then an end user's,
|
|
58
|
-
* then an agent's, then the workspace's.
|
|
59
|
-
*/
|
|
60
|
-
get owner(): CredentialOwner | undefined;
|
|
61
|
-
/** One of YOUR end users, when this account is theirs. Never a workspace
|
|
62
|
-
* member: the two are separate id namespaces. */
|
|
63
|
-
get endUserId(): string | null | undefined;
|
|
64
|
-
/** The last-known state, from whatever call produced this object. */
|
|
65
|
-
get data(): ConnectionStatus | undefined;
|
|
66
|
-
/** Read it back. */
|
|
67
|
-
get(): Promise<ConnectionStatus>;
|
|
68
|
-
/**
|
|
69
|
-
* Wait until the person has finished signing in.
|
|
70
|
-
*
|
|
71
|
-
* The companion to `create()`. That hands you a url; this tells you when
|
|
72
|
-
* they are done, so you do not write a polling loop or guess which field to
|
|
73
|
-
* believe.
|
|
74
|
-
*
|
|
75
|
-
* Polls rather than subscribing: there is a human in the middle, so a few
|
|
76
|
-
* seconds costs nothing and it works from any runtime without a socket.
|
|
77
|
-
*/
|
|
78
|
-
waitUntilConnected(options?: {
|
|
79
|
-
timeoutMs?: number;
|
|
80
|
-
intervalMs?: number;
|
|
81
|
-
}): Promise<Connection>;
|
|
82
|
-
/**
|
|
83
|
-
* Detach the account and drop its tokens.
|
|
84
|
-
*
|
|
85
|
-
* This is what off-boarding calls. Every conversation using it stops acting
|
|
86
|
-
* as that person from the next turn; nothing is retained.
|
|
87
|
-
*/
|
|
88
|
-
revoke(): Promise<void>;
|
|
89
|
-
}
|
|
62
|
+
export declare const Connection: {
|
|
63
|
+
/** A connection that uses the agent creator's account: the `Account` you
|
|
64
|
+
* hold, or its id. Produces `{ name?, app, account: '<id>' }`. */
|
|
65
|
+
creatorAccount(app: AppId, account: Account | string, options?: {
|
|
66
|
+
name?: string;
|
|
67
|
+
}): CreatorAccountConnection & {
|
|
68
|
+
account: string;
|
|
69
|
+
};
|
|
70
|
+
/** A connection that uses the end user's account: whichever account of
|
|
71
|
+
* `app` the person each conversation is for has connected. Produces
|
|
72
|
+
* `{ name?, app, endUser: true }`. */
|
|
73
|
+
endUserAccount(app: AppId, options?: {
|
|
74
|
+
name?: string;
|
|
75
|
+
}): EndUserAccountConnection;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* One connection as `POST /v1/agents/:id/connections` takes it: the fields it
|
|
79
|
+
* declares, picked by name, and an `Account` by its id.
|
|
80
|
+
*
|
|
81
|
+
* Picked, never forwarded: a JavaScript caller holding 0.43 code could pass
|
|
82
|
+
* `provider` or `scope`, and those must not reach the route as if they meant
|
|
83
|
+
* something. Neither `account` nor `endUser` is invented here: whatever was
|
|
84
|
+
* given goes, and the API names the problem when it is not exactly one.
|
|
85
|
+
*/
|
|
86
|
+
export declare function connectionForWire(connection: Connection): Record<string, unknown>;
|
package/connection.js
CHANGED
|
@@ -1,143 +1,76 @@
|
|
|
1
|
-
// A connection:
|
|
1
|
+
// A connection: one entry in an agent's setup. An app, and whose account.
|
|
2
|
+
import { accountIdOf } from "./account.js";
|
|
3
|
+
import { AgentNavaError } from "./errors.js";
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* A CLASS, like `Agent`, `Conversation`, `Project` and `KnowledgeBase`. It was
|
|
6
|
-
* briefly a bare `ConnectionStatus` with an id you carried around as a string,
|
|
7
|
-
* which made it the only thing in this SDK you could not hold. Holding it is
|
|
8
|
-
* the point: you create one, wait for the person to finish signing in, attach
|
|
9
|
-
* it where it is needed, and revoke it when they leave, and each of those is a
|
|
10
|
-
* method on the thing rather than a call with an id threaded through it.
|
|
5
|
+
* Build the entries of an agent's setup.
|
|
11
6
|
*
|
|
12
7
|
* ```ts
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* await
|
|
16
|
-
*
|
|
8
|
+
* import { App, Connection } from '@cerebro-labs/agentnava-sdk';
|
|
9
|
+
*
|
|
10
|
+
* await ws.agents.create({
|
|
11
|
+
* name: 'Inbox helper',
|
|
12
|
+
* instructions: '...',
|
|
13
|
+
* connections: [
|
|
14
|
+
* Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }),
|
|
15
|
+
* Connection.creatorAccount(App.GMAIL, personal, { name: 'personal-gmail' }),
|
|
16
|
+
* Connection.endUserAccount(App.GOOGLE_CALENDAR),
|
|
17
|
+
* ],
|
|
18
|
+
* });
|
|
17
19
|
* ```
|
|
18
20
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
+
* WHY BUILDERS. Whose account a connection uses is the one decision that
|
|
22
|
+
* matters, and it used to be spelled `scope: 'fixed'` or `scope:
|
|
23
|
+
* 'conversation'`, words that answered a different question. These two names
|
|
24
|
+
* are the question itself, as the screen asks it: the agent creator's account,
|
|
25
|
+
* or the end user's account.
|
|
26
|
+
*
|
|
27
|
+
* NOT THE 0.43 `Connection`. That was a handle on a signed-in account and is
|
|
28
|
+
* now `Account`. This name builds an entry for an agent's setup; it holds
|
|
29
|
+
* nothing and makes no request.
|
|
30
|
+
*
|
|
31
|
+
* `name` tells two connections of one app apart, such as two Gmail accounts.
|
|
32
|
+
* Lowercase letters, digits and dashes, up to 64 characters, unique on the
|
|
33
|
+
* agent. Left out, it is the app's id.
|
|
21
34
|
*/
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.url = url;
|
|
36
|
-
}
|
|
37
|
-
/** Which third party this is: `'gmail'`, `'hubspot'`. Named for what the
|
|
38
|
-
* person connecting recognises, never for whoever we buy it from. */
|
|
39
|
-
get provider() {
|
|
40
|
-
return this.snapshot?.provider;
|
|
41
|
-
}
|
|
42
|
-
/** True once the account is attached and usable.
|
|
43
|
-
*
|
|
44
|
-
* Reads the last snapshot rather than the network, the same rule
|
|
45
|
-
* `conversation.ready` follows. Undefined means this handle has not read
|
|
46
|
-
* yet, NOT that it is disconnected. */
|
|
47
|
-
get connected() {
|
|
48
|
-
const s = this.snapshot;
|
|
49
|
-
if (!s)
|
|
50
|
-
return undefined;
|
|
51
|
-
// Belt as well as braces. Every list path now normalises before
|
|
52
|
-
// constructing a Connection, but this object is also built directly from
|
|
53
|
-
// server payloads elsewhere, and a pass-through here meant a row that
|
|
54
|
-
// plainly said `status: 'connected'` reported `connected === undefined`.
|
|
55
|
-
return s.connected ?? s.status === 'connected';
|
|
56
|
-
}
|
|
57
|
-
/** The raw status string, for the cases where the boolean is too coarse:
|
|
58
|
-
* 'pending' and 'error' are both "not connected" and mean opposite things.
|
|
59
|
-
* This used to be reachable only through `.data`, the whole-snapshot escape
|
|
60
|
-
* hatch, which is a poor thing to make a caller reach for. */
|
|
61
|
-
get status() {
|
|
62
|
-
return this.snapshot?.status;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Whose account it is: the workspace's, one agent's, one of YOUR END USERS',
|
|
66
|
-
* or one conversation's.
|
|
67
|
-
*
|
|
68
|
-
* DERIVED HERE, from which id the row carries, rather than read off a field.
|
|
69
|
-
* This getter used to return `snapshot.scope`, which is a different question
|
|
70
|
-
* entirely: `scope` is what an agent DECLARED it needs ('fixed' or
|
|
71
|
-
* 'conversation'), and the server never sent an ownership field at all. So
|
|
72
|
-
* it answered `undefined` for every connection you looked up, and for one
|
|
73
|
-
* you had just created it answered `'user'`, a value outside its own
|
|
74
|
-
* declared type that only compiled through an `as never` cast.
|
|
75
|
-
*
|
|
76
|
-
* Most specific first: a conversation's own account, then an end user's,
|
|
77
|
-
* then an agent's, then the workspace's.
|
|
78
|
-
*/
|
|
79
|
-
get owner() {
|
|
80
|
-
const s = this.snapshot;
|
|
81
|
-
if (!s)
|
|
82
|
-
return undefined;
|
|
83
|
-
if (s.conversationId)
|
|
84
|
-
return 'conversation';
|
|
85
|
-
if (s.endUserId)
|
|
86
|
-
return 'endUser';
|
|
87
|
-
if (s.agentId)
|
|
88
|
-
return 'agent';
|
|
89
|
-
return 'workspace';
|
|
90
|
-
}
|
|
91
|
-
/** One of YOUR end users, when this account is theirs. Never a workspace
|
|
92
|
-
* member: the two are separate id namespaces. */
|
|
93
|
-
get endUserId() {
|
|
94
|
-
return this.snapshot?.endUserId;
|
|
95
|
-
}
|
|
96
|
-
/** The last-known state, from whatever call produced this object. */
|
|
97
|
-
get data() {
|
|
98
|
-
return this.snapshot;
|
|
99
|
-
}
|
|
100
|
-
/** Read it back. */
|
|
101
|
-
async get() {
|
|
102
|
-
const res = await this.#http.get('/v1/connections');
|
|
103
|
-
const mine = (res.connections ?? []).find((c) => c.id === this.id);
|
|
104
|
-
if (mine)
|
|
105
|
-
this.snapshot = mine;
|
|
106
|
-
return mine ?? { id: this.id, provider: '', declared: false, required: false, connected: false };
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Wait until the person has finished signing in.
|
|
110
|
-
*
|
|
111
|
-
* The companion to `create()`. That hands you a url; this tells you when
|
|
112
|
-
* they are done, so you do not write a polling loop or guess which field to
|
|
113
|
-
* believe.
|
|
114
|
-
*
|
|
115
|
-
* Polls rather than subscribing: there is a human in the middle, so a few
|
|
116
|
-
* seconds costs nothing and it works from any runtime without a socket.
|
|
117
|
-
*/
|
|
118
|
-
async waitUntilConnected(options = {}) {
|
|
119
|
-
const timeoutMs = options.timeoutMs ?? 120_000;
|
|
120
|
-
const intervalMs = options.intervalMs ?? 2_000;
|
|
121
|
-
const deadline = Date.now() + timeoutMs;
|
|
122
|
-
// Checked before the first sleep: an already-connected account should
|
|
123
|
-
// cost one request, not one request plus an interval.
|
|
124
|
-
let state = await this.get();
|
|
125
|
-
while (!state.connected) {
|
|
126
|
-
if (Date.now() >= deadline)
|
|
127
|
-
return this;
|
|
128
|
-
await new Promise((r) => setTimeout(r, Math.min(intervalMs, Math.max(0, deadline - Date.now()))));
|
|
129
|
-
state = await this.get();
|
|
35
|
+
export const Connection = {
|
|
36
|
+
/** A connection that uses the agent creator's account: the `Account` you
|
|
37
|
+
* hold, or its id. Produces `{ name?, app, account: '<id>' }`. */
|
|
38
|
+
creatorAccount(app, account, options = {}) {
|
|
39
|
+
const id = accountIdOf(account);
|
|
40
|
+
// Thrown here, naming the mistake, rather than sent as a connection with
|
|
41
|
+
// no account for the API to refuse in other words.
|
|
42
|
+
if (typeof id !== 'string' || !id) {
|
|
43
|
+
throw new AgentNavaError({
|
|
44
|
+
message: `Connection.creatorAccount(${JSON.stringify(app)}, ...) needs the Account, or its id.`,
|
|
45
|
+
code: 'account_required',
|
|
46
|
+
status: 400,
|
|
47
|
+
});
|
|
130
48
|
}
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
49
|
+
return { ...(options.name !== undefined && { name: options.name }), app, account: id };
|
|
50
|
+
},
|
|
51
|
+
/** A connection that uses the end user's account: whichever account of
|
|
52
|
+
* `app` the person each conversation is for has connected. Produces
|
|
53
|
+
* `{ name?, app, endUser: true }`. */
|
|
54
|
+
endUserAccount(app, options = {}) {
|
|
55
|
+
return { ...(options.name !== undefined && { name: options.name }), app, endUser: true };
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* One connection as `POST /v1/agents/:id/connections` takes it: the fields it
|
|
60
|
+
* declares, picked by name, and an `Account` by its id.
|
|
61
|
+
*
|
|
62
|
+
* Picked, never forwarded: a JavaScript caller holding 0.43 code could pass
|
|
63
|
+
* `provider` or `scope`, and those must not reach the route as if they meant
|
|
64
|
+
* something. Neither `account` nor `endUser` is invented here: whatever was
|
|
65
|
+
* given goes, and the API names the problem when it is not exactly one.
|
|
66
|
+
*/
|
|
67
|
+
export function connectionForWire(connection) {
|
|
68
|
+
const c = connection;
|
|
69
|
+
return {
|
|
70
|
+
...(c.name !== undefined && { name: c.name }),
|
|
71
|
+
app: c.app,
|
|
72
|
+
...(c.account !== undefined && { account: accountIdOf(c.account) }),
|
|
73
|
+
...(c.endUser !== undefined && { endUser: c.endUser }),
|
|
74
|
+
};
|
|
142
75
|
}
|
|
143
76
|
//# sourceMappingURL=connection.js.map
|