@cerebro-labs/agentnava-sdk 0.75.1 → 0.79.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/end-user.d.ts CHANGED
@@ -4,7 +4,13 @@ import { Conversation } from './conversation.ts';
4
4
  import type { Http } from './http.ts';
5
5
  import type { AppId } from './apps.generated.ts';
6
6
  /**
7
- * One of YOUR end users, as this platform sees them.
7
+ * One of YOUR end users, of ONE agent, as this platform sees them.
8
+ *
9
+ * EACH AGENT IS ITS OWN APP, WITH ITS OWN END USERS. The same id on two agents is
10
+ * two different people, with separate accounts, secrets and conversations. A
11
+ * person using two of your agents signs in to each. There is no workspace-wide
12
+ * end user, and no way to erase one, because the same id on another agent may be
13
+ * someone else.
8
14
  *
9
15
  * WHY THE NAME IS `EndUser` AND NOT `User`. There are two populations of people
10
16
  * in this product, and calling both of them "user" was a bug waiting to be
@@ -13,10 +19,11 @@ import type { AppId } from './apps.generated.ts';
13
19
  * | | workspace member | END USER |
14
20
  * |---|---|---|
15
21
  * | who | your colleague, on a seat | the person your application serves |
22
+ * | belongs to | the workspace | one agent |
16
23
  * | signs in to this platform | yes | NEVER |
17
24
  * | who issues the id | we do | YOUR database |
18
25
  * | do we authenticate it | yes | no, deliberately |
19
- * | in this SDK | `ws.members()` | `ws.endUser(id)` |
26
+ * | in this SDK | `ws.members()` | `agent.endUser(id)` |
20
27
  *
21
28
  * They were both spelled `userId`, so `ws.members()` handed you an id that
22
29
  * `agent.start()` would accept without complaint and label a conversation with
@@ -43,21 +50,22 @@ import type { AppId } from './apps.generated.ts';
43
50
  * // once, in the agent's setup: each person uses their own Gmail
44
51
  * await agent.connections.add(Connection.endUserAccount(App.GMAIL));
45
52
  *
46
- * const ananya = ws.endUser(row.userId); // no request
53
+ * const ananya = agent.endUser(row.userId); // no request
47
54
  * const { url } = await ananya.accounts.connect({ app: App.GMAIL });
48
- * showToUser(url); // she signs in ONCE
55
+ * showToUser(url); // she signs in ONCE, for this agent
49
56
  *
50
57
  * const conversation = await agent.start({ endUserId: ananya.id });
51
58
  * await conversation.ask('what did I miss this morning?');
52
59
  *
53
- * await ananya.conversations(); // everything of hers, any agent
60
+ * await ananya.conversations(); // everything of hers, with this agent
61
+ * await ananya.erase(); // she left: all of it, on this agent
54
62
  * ```
55
63
  *
56
64
  * WHAT NAMING AN END USER DOES, exactly. `start({ endUserId })` labels the
57
65
  * 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.
66
+ * a connection to the end user's account uses: this person's, on this agent, for
67
+ * that app, and nobody else's. The grant is the agent's setup, which you can read;
68
+ * it never falls back to the workspace's account, or to anybody else's.
61
69
  *
62
70
  * THE ID IS YOURS. A row id from your database, opaque to us, and we never
63
71
  * authenticate it. We are not an identity provider and do not want to be. It
@@ -69,20 +77,23 @@ import type { AppId } from './apps.generated.ts';
69
77
  export declare class EndUser {
70
78
  #private;
71
79
  readonly id: string;
72
- constructor(http: Http, id: string);
80
+ /** The agent this person belongs to, as you addressed it. */
81
+ readonly agentId: string;
82
+ constructor(http: Http, agentId: string, id: string);
73
83
  private get base();
74
84
  /**
75
- * This person's own accounts: the "end user's account" that a connection
76
- * built with `Connection.endUserAccount(app)` uses.
85
+ * This person's own accounts on this agent: the "end user's account" that a
86
+ * connection built with `Connection.endUserAccount(app)` uses.
77
87
  *
78
88
  * 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.
89
+ * on this agent and stays theirs: every conversation of theirs with this agent
90
+ * uses it, and nobody is sent through a sign-in again per thread. Nothing is
91
+ * attached per conversation.
83
92
  *
84
93
  * 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.
94
+ * own account of the same app, not in another person's conversation, and not
95
+ * on another agent. The same id on another agent is another person, who signs
96
+ * in there.
86
97
  */
87
98
  readonly accounts: {
88
99
  /** Begin connecting one of their accounts. Returns it, still `pending`,
@@ -97,14 +108,15 @@ export declare class EndUser {
97
108
  account: Account;
98
109
  url: string;
99
110
  }>;
100
- /** Theirs alone. The agent creators' accounts are not here:
111
+ /** Theirs on this agent alone. The agent creators' accounts are not here:
101
112
  * `ws.accounts.list()` is the view that shows those. */
102
113
  list: () => Promise<Account[]>;
103
114
  /**
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.
115
+ * Disconnect every account they connected on this agent, in one call, so it
116
+ * is not a loop the caller has to remember to write. Every conversation of
117
+ * theirs with this agent stops reaching those apps from the next turn, and
118
+ * nothing is kept. Returns how many were disconnected. To remove everything
119
+ * about them, use `erase()`.
108
120
  */
109
121
  disconnectAll: () => Promise<{
110
122
  disconnected: number;
@@ -129,7 +141,7 @@ export declare class EndUser {
129
141
  * explicit line:
130
142
  *
131
143
  * ```ts
132
- * const ananya = ws.endUser(row.userId);
144
+ * const ananya = agent.endUser(row.userId);
133
145
  * const key = await ananya.secrets.create({ name: 'ZENDESK_TOKEN', value: token });
134
146
  *
135
147
  * const conversation = await agent.start({ endUserId: ananya.id });
@@ -139,8 +151,9 @@ export declare class EndUser {
139
151
  * TWO THINGS THE PLATFORM ENFORCES, neither of which you can turn off:
140
152
  * the agent must DECLARE that secret name at session scope, so attaching by
141
153
  * id is not a way around the allowlist; and a secret belonging to one end
142
- * user can only be attached to a conversation launched for that same person,
143
- * so one mistyped id cannot hand Ananya's key to somebody else's turn.
154
+ * user can only be attached to a conversation launched for that same person
155
+ * with this same agent, so one mistyped id cannot hand Ananya's key to somebody
156
+ * else's turn.
144
157
  * Both are checked again at every tool call, not only when you attach.
145
158
  *
146
159
  * THE CONVERSATION FOLLOWS THE SECRET. It keeps a pointer, never a copy, so
@@ -155,26 +168,42 @@ export declare class EndUser {
155
168
  name: string;
156
169
  value: string;
157
170
  description?: string;
158
- agentId?: string;
159
171
  /** The only hosts a tool may send it to, checked at every call of a
160
172
  * conversation it is attached to. Left out, it goes only to the host
161
173
  * the agent's tool writes out in full. */
162
174
  allowedHosts?: string[];
163
175
  }) => Promise<Secret>;
164
- /** This person's secrets, values excluded. The workspace's own are not in
165
- * here: `ws.secrets.list()` answers that. */
176
+ /** This person's secrets on this agent, values excluded. The workspace's own
177
+ * are not in here: `ws.secrets.list()` answers that. */
166
178
  list: () => Promise<Secret[]>;
167
179
  };
168
180
  /**
169
- * Everything this person has said, ACROSS EVERY AGENT, newest first.
170
- *
171
- * `agent.conversations({ endUser })` answers the narrower question, one agent
172
- * at a time. This is the one an application renders as "your chats", and it
173
- * is the only listing in this SDK with a real cursor: pass `before` from the
174
- * previous page to keep going, rather than being capped at 200.
181
+ * Everything this person has said to this agent, newest first. Pass `before`
182
+ * from the previous page to keep going, rather than being capped at 200.
175
183
  */
176
184
  conversations(options?: {
177
185
  limit?: number;
178
186
  before?: string;
179
187
  }): Promise<Conversation[]>;
188
+ /**
189
+ * Delete everything about them on this agent, in one call: their accounts
190
+ * (signed out at the app first), their secrets, their conversations, and the
191
+ * wakeups those conversations set. What off-boarding and an erasure request call.
192
+ *
193
+ * This agent only. The same id on another agent may be someone else, so there
194
+ * is no workspace-wide erase: if one person uses two of your agents, erase them
195
+ * from each. A schedule you configured on the agent to name them stays, because
196
+ * it is your setup. A project's conversations belong to the project and are
197
+ * deleted by id.
198
+ *
199
+ * Safe to call again. An account that cannot be signed out fails the call with
200
+ * the rest still in place, so a credential is never orphaned. A turn still running
201
+ * when this lands can write after it, and a second call clears what it left.
202
+ * Returns how many of each went.
203
+ */
204
+ erase(): Promise<{
205
+ accounts: number;
206
+ secrets: number;
207
+ conversations: number;
208
+ }>;
180
209
  }
package/end-user.js CHANGED
@@ -3,7 +3,13 @@ import { Account, accountIn } from "./account.js";
3
3
  import { Secret } from "./secret.js";
4
4
  import { Conversation } from "./conversation.js";
5
5
  /**
6
- * One of YOUR end users, as this platform sees them.
6
+ * One of YOUR end users, of ONE agent, as this platform sees them.
7
+ *
8
+ * EACH AGENT IS ITS OWN APP, WITH ITS OWN END USERS. The same id on two agents is
9
+ * two different people, with separate accounts, secrets and conversations. A
10
+ * person using two of your agents signs in to each. There is no workspace-wide
11
+ * end user, and no way to erase one, because the same id on another agent may be
12
+ * someone else.
7
13
  *
8
14
  * WHY THE NAME IS `EndUser` AND NOT `User`. There are two populations of people
9
15
  * in this product, and calling both of them "user" was a bug waiting to be
@@ -12,10 +18,11 @@ import { Conversation } from "./conversation.js";
12
18
  * | | workspace member | END USER |
13
19
  * |---|---|---|
14
20
  * | who | your colleague, on a seat | the person your application serves |
21
+ * | belongs to | the workspace | one agent |
15
22
  * | signs in to this platform | yes | NEVER |
16
23
  * | who issues the id | we do | YOUR database |
17
24
  * | do we authenticate it | yes | no, deliberately |
18
- * | in this SDK | `ws.members()` | `ws.endUser(id)` |
25
+ * | in this SDK | `ws.members()` | `agent.endUser(id)` |
19
26
  *
20
27
  * They were both spelled `userId`, so `ws.members()` handed you an id that
21
28
  * `agent.start()` would accept without complaint and label a conversation with
@@ -42,21 +49,22 @@ import { Conversation } from "./conversation.js";
42
49
  * // once, in the agent's setup: each person uses their own Gmail
43
50
  * await agent.connections.add(Connection.endUserAccount(App.GMAIL));
44
51
  *
45
- * const ananya = ws.endUser(row.userId); // no request
52
+ * const ananya = agent.endUser(row.userId); // no request
46
53
  * const { url } = await ananya.accounts.connect({ app: App.GMAIL });
47
- * showToUser(url); // she signs in ONCE
54
+ * showToUser(url); // she signs in ONCE, for this agent
48
55
  *
49
56
  * const conversation = await agent.start({ endUserId: ananya.id });
50
57
  * await conversation.ask('what did I miss this morning?');
51
58
  *
52
- * await ananya.conversations(); // everything of hers, any agent
59
+ * await ananya.conversations(); // everything of hers, with this agent
60
+ * await ananya.erase(); // she left: all of it, on this agent
53
61
  * ```
54
62
  *
55
63
  * WHAT NAMING AN END USER DOES, exactly. `start({ endUserId })` labels the
56
64
  * 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.
65
+ * a connection to the end user's account uses: this person's, on this agent, for
66
+ * that app, and nobody else's. The grant is the agent's setup, which you can read;
67
+ * it never falls back to the workspace's account, or to anybody else's.
60
68
  *
61
69
  * THE ID IS YOURS. A row id from your database, opaque to us, and we never
62
70
  * authenticate it. We are not an identity provider and do not want to be. It
@@ -67,26 +75,30 @@ import { Conversation } from "./conversation.js";
67
75
  */
68
76
  export class EndUser {
69
77
  id;
78
+ /** The agent this person belongs to, as you addressed it. */
79
+ agentId;
70
80
  #http;
71
- constructor(http, id) {
81
+ constructor(http, agentId, id) {
72
82
  this.#http = http;
83
+ this.agentId = agentId;
73
84
  this.id = id;
74
85
  }
75
86
  get base() {
76
- return `/v1/end-users/${encodeURIComponent(this.id)}/accounts`;
87
+ return `/v1/agents/${encodeURIComponent(this.agentId)}/end-users/${encodeURIComponent(this.id)}`;
77
88
  }
78
89
  /**
79
- * This person's own accounts: the "end user's account" that a connection
80
- * built with `Connection.endUserAccount(app)` uses.
90
+ * This person's own accounts on this agent: the "end user's account" that a
91
+ * connection built with `Connection.endUserAccount(app)` uses.
81
92
  *
82
93
  * 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.
94
+ * on this agent and stays theirs: every conversation of theirs with this agent
95
+ * uses it, and nobody is sent through a sign-in again per thread. Nothing is
96
+ * attached per conversation.
87
97
  *
88
98
  * 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.
99
+ * own account of the same app, not in another person's conversation, and not
100
+ * on another agent. The same id on another agent is another person, who signs
101
+ * in there.
90
102
  */
91
103
  accounts = {
92
104
  /** Begin connecting one of their accounts. Returns it, still `pending`,
@@ -96,27 +108,28 @@ export class EndUser {
96
108
  * hands you the same kind of link for whatever a conversation is still
97
109
  * missing. */
98
110
  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`, {
111
+ // FIELDS PICKED: the agent and the person come from the path, never from the body.
112
+ const res = await this.#http.post(`${this.base}/accounts/connect`, {
101
113
  app: input.app,
102
114
  });
103
- const account = accountIn(res, `${this.base}/connect`);
115
+ const account = accountIn(res, `${this.base}/accounts/connect`);
104
116
  return { account: new Account(this.#http, account.id, account), url: res.url };
105
117
  },
106
- /** Theirs alone. The agent creators' accounts are not here:
118
+ /** Theirs on this agent alone. The agent creators' accounts are not here:
107
119
  * `ws.accounts.list()` is the view that shows those. */
108
120
  list: async () => {
109
- const res = await this.#http.get(this.base);
121
+ const res = await this.#http.get(`${this.base}/accounts`);
110
122
  return (res.accounts ?? []).map((a) => new Account(this.#http, a.id, a));
111
123
  },
112
124
  /**
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.
125
+ * Disconnect every account they connected on this agent, in one call, so it
126
+ * is not a loop the caller has to remember to write. Every conversation of
127
+ * theirs with this agent stops reaching those apps from the next turn, and
128
+ * nothing is kept. Returns how many were disconnected. To remove everything
129
+ * about them, use `erase()`.
117
130
  */
118
131
  disconnectAll: async () => {
119
- const res = await this.#http.delete(this.base);
132
+ const res = await this.#http.delete(`${this.base}/accounts`);
120
133
  return { disconnected: res?.disconnected ?? 0 };
121
134
  },
122
135
  };
@@ -139,7 +152,7 @@ export class EndUser {
139
152
  * explicit line:
140
153
  *
141
154
  * ```ts
142
- * const ananya = ws.endUser(row.userId);
155
+ * const ananya = agent.endUser(row.userId);
143
156
  * const key = await ananya.secrets.create({ name: 'ZENDESK_TOKEN', value: token });
144
157
  *
145
158
  * const conversation = await agent.start({ endUserId: ananya.id });
@@ -149,8 +162,9 @@ export class EndUser {
149
162
  * TWO THINGS THE PLATFORM ENFORCES, neither of which you can turn off:
150
163
  * the agent must DECLARE that secret name at session scope, so attaching by
151
164
  * id is not a way around the allowlist; and a secret belonging to one end
152
- * user can only be attached to a conversation launched for that same person,
153
- * so one mistyped id cannot hand Ananya's key to somebody else's turn.
165
+ * user can only be attached to a conversation launched for that same person
166
+ * with this same agent, so one mistyped id cannot hand Ananya's key to somebody
167
+ * else's turn.
154
168
  * Both are checked again at every tool call, not only when you attach.
155
169
  *
156
170
  * THE CONVERSATION FOLLOWS THE SECRET. It keeps a pointer, never a copy, so
@@ -170,37 +184,63 @@ export class EndUser {
170
184
  name: input.name,
171
185
  value: input.value,
172
186
  endUserId: this.id,
187
+ agentId: this.agentId,
173
188
  ...(input.description !== undefined && { description: input.description }),
174
- ...(input.agentId !== undefined && { agentId: input.agentId }),
175
189
  ...(input.allowedHosts !== undefined && { allowedHosts: input.allowedHosts }),
176
190
  });
177
191
  const meta = res.secret ?? res;
178
192
  return new Secret(this.#http, meta.id, meta);
179
193
  },
180
- /** This person's secrets, values excluded. The workspace's own are not in
181
- * here: `ws.secrets.list()` answers that. */
194
+ /** This person's secrets on this agent, values excluded. The workspace's own
195
+ * are not in here: `ws.secrets.list()` answers that. */
182
196
  list: async () => {
183
197
  const res = await this.#http.get('/v1/secrets', {
184
198
  end_user_id: this.id,
199
+ agentId: this.agentId,
185
200
  });
186
201
  return (res.secrets ?? []).map((m) => new Secret(this.#http, m.id, m));
187
202
  },
188
203
  };
189
204
  /**
190
- * Everything this person has said, ACROSS EVERY AGENT, newest first.
191
- *
192
- * `agent.conversations({ endUser })` answers the narrower question, one agent
193
- * at a time. This is the one an application renders as "your chats", and it
194
- * is the only listing in this SDK with a real cursor: pass `before` from the
195
- * previous page to keep going, rather than being capped at 200.
205
+ * Everything this person has said to this agent, newest first. Pass `before`
206
+ * from the previous page to keep going, rather than being capped at 200.
196
207
  */
197
208
  async conversations(options = {}) {
198
209
  const res = await this.#http.get('/v1/conversations', {
210
+ agentId: this.agentId,
199
211
  end_user_id: this.id,
200
212
  ...(options.limit !== undefined && { limit: options.limit }),
201
213
  ...(options.before && { before: options.before }),
202
214
  });
203
215
  return (res.conversations ?? []).map((c) => new Conversation(this.#http, c.id, c));
204
216
  }
217
+ /**
218
+ * Delete everything about them on this agent, in one call: their accounts
219
+ * (signed out at the app first), their secrets, their conversations, and the
220
+ * wakeups those conversations set. What off-boarding and an erasure request call.
221
+ *
222
+ * This agent only. The same id on another agent may be someone else, so there
223
+ * is no workspace-wide erase: if one person uses two of your agents, erase them
224
+ * from each. A schedule you configured on the agent to name them stays, because
225
+ * it is your setup. A project's conversations belong to the project and are
226
+ * deleted by id.
227
+ *
228
+ * Safe to call again. An account that cannot be signed out fails the call with
229
+ * the rest still in place, so a credential is never orphaned. A turn still running
230
+ * when this lands can write after it, and a second call clears what it left.
231
+ * Returns how many of each went.
232
+ */
233
+ async erase() {
234
+ const total = { accounts: 0, secrets: 0, conversations: 0 };
235
+ let more = true;
236
+ while (more) {
237
+ const res = await this.#http.delete(this.base);
238
+ total.accounts += res?.accounts ?? 0;
239
+ total.secrets += res?.secrets ?? 0;
240
+ total.conversations += res?.conversations ?? 0;
241
+ more = res?.more === true;
242
+ }
243
+ return total;
244
+ }
205
245
  }
206
246
  //# 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 { 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"]}
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, of ONE agent, as this platform sees them.\n *\n * EACH AGENT IS ITS OWN APP, WITH ITS OWN END USERS. The same id on two agents is\n * two different people, with separate accounts, secrets and conversations. A\n * person using two of your agents signs in to each. There is no workspace-wide\n * end user, and no way to erase one, because the same id on another agent may be\n * someone else.\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 * | belongs to | the workspace | one agent |\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()` | `agent.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 = agent.endUser(row.userId); // no request\n * const { url } = await ananya.accounts.connect({ app: App.GMAIL });\n * showToUser(url); // she signs in ONCE, for this agent\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, with this agent\n * await ananya.erase(); // she left: all of it, on this 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, on this agent, for\n * that app, and nobody else's. The grant is the agent's setup, which you can read;\n * it never 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\t/** The agent this person belongs to, as you addressed it. */\n\treadonly agentId: string;\n\treadonly #http: Http;\n\n\tconstructor(http: Http, agentId: string, id: string) {\n\t\tthis.#http = http;\n\t\tthis.agentId = agentId;\n\t\tthis.id = id;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/agents/${encodeURIComponent(this.agentId)}/end-users/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/**\n\t * This person's own accounts on this agent: the \"end user's account\" that a\n\t * connection 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 * on this agent and stays theirs: every conversation of theirs with this agent\n\t * uses it, and nobody is sent through a sign-in again per thread. Nothing is\n\t * attached per 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, not in another person's conversation, and not\n\t * on another agent. The same id on another agent is another person, who signs\n\t * in there.\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 agent and the person come from the path, never from the body.\n\t\t\tconst res = await this.#http.post<{ account?: AccountData; url: string }>(`${this.base}/accounts/connect`, {\n\t\t\t\tapp: input.app,\n\t\t\t});\n\t\t\tconst account = accountIn(res, `${this.base}/accounts/connect`);\n\t\t\treturn { account: new Account(this.#http, account.id, account), url: res.url };\n\t\t},\n\n\t\t/** Theirs on this agent 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}/accounts`);\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 on this agent, in one call, so it\n\t\t * is not a loop the caller has to remember to write. Every conversation of\n\t\t * theirs with this agent stops reaching those apps from the next turn, and\n\t\t * nothing is kept. Returns how many were disconnected. To remove everything\n\t\t * about them, use `erase()`.\n\t\t */\n\t\tdisconnectAll: async (): Promise<{ disconnected: number }> => {\n\t\t\tconst res = await this.#http.delete<{ disconnected?: number }>(`${this.base}/accounts`);\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 = agent.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 * with this same agent, so one mistyped id cannot hand Ananya's key to somebody\n\t * 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\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\tagentId: this.agentId,\n\t\t\t\t...(input.description !== undefined && { description: input.description }),\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 on this agent, values excluded. The workspace's own\n\t\t * are not in 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\tagentId: this.agentId,\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 to this agent, newest first. Pass `before`\n\t * from the 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\tagentId: this.agentId,\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 * Delete everything about them on this agent, in one call: their accounts\n\t * (signed out at the app first), their secrets, their conversations, and the\n\t * wakeups those conversations set. What off-boarding and an erasure request call.\n\t *\n\t * This agent only. The same id on another agent may be someone else, so there\n\t * is no workspace-wide erase: if one person uses two of your agents, erase them\n\t * from each. A schedule you configured on the agent to name them stays, because\n\t * it is your setup. A project's conversations belong to the project and are\n\t * deleted by id.\n\t *\n\t * Safe to call again. An account that cannot be signed out fails the call with\n\t * the rest still in place, so a credential is never orphaned. A turn still running\n\t * when this lands can write after it, and a second call clears what it left.\n\t * Returns how many of each went.\n\t */\n\tasync erase(): Promise<{ accounts: number; secrets: number; conversations: number }> {\n\t\tconst total = { accounts: 0, secrets: 0, conversations: 0 };\n\t\tlet more = true;\n\t\twhile (more) {\n\t\t\tconst res = await this.#http.delete<{ accounts?: number; secrets?: number; conversations?: number; more?: boolean }>(this.base);\n\t\t\ttotal.accounts += res?.accounts ?? 0;\n\t\t\ttotal.secrets += res?.secrets ?? 0;\n\t\t\ttotal.conversations += res?.conversations ?? 0;\n\t\t\tmore = res?.more === true;\n\t\t}\n\t\treturn total;\n\t}\n}\n"]}
package/index.d.ts CHANGED
@@ -17,5 +17,5 @@ export { loadAgentDir } from './load-agent-dir.ts';
17
17
  export { loadManagerTemplate } from './templates.ts';
18
18
  export type { Turn } from './turn.ts';
19
19
  export type { ClientOptions } from './http.ts';
20
- export type { AppTool, AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentDraft, AgentFields, AgentStatus, WebsiteChatAppearance, Attachment, Bindings, ConnectionStatus, Credits, Guardrails, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, Widget, WidgetPart, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
20
+ export type { AppTool, AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentDraft, AgentFields, AgentStatus, WebsiteChatAppearance, Attachment, Bindings, ConnectionStatus, Credits, Guardrails, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, SentAttachment, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, Widget, WidgetPart, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
21
21
  export { placeWidgets, type ReplyPiece } from './widgets.ts';
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAgE,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAA4D,MAAM,mBAAmB,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,2DAA2D;AAC3D,OAAO,EACN,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,GASZ,MAAM,mBAAmB,CAAC;AAC3B,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AA6DrD,OAAO,EAAE,YAAY,EAAmB,MAAM,cAAc,CAAC","sourcesContent":["// @cerebro-labs/agentnava-sdk\n//\n// Configure an agent, start a conversation, work with it.\n//\n// import { AgentNava } from '@cerebro-labs/agentnava-sdk';\n//\n// const ws = AgentNava.getWorkspace();\n// const agent = await ws.agents.create({\n// name: 'Refund checker',\n// instructions: 'Decide whether a late delivery qualifies for a refund.',\n// });\n// const conversation = await agent.start({ endUserId: 'u_ananya' });\n// console.log(await conversation.ask('Order 4471 was nine days late.'));\n//\n// The reference this implements is design/sdk-design.md.\n\nexport { AgentNava, Workspace } from './workspace.ts';\nexport { PartnerApp, type PartnerAppOptions, type ExchangedCode, type CreditGrant } from './partner-app.ts';\nexport { Agent, type StartOptions } from './agent.ts';\nexport { Account } from './account.ts';\n// Builds one entry of an agent's setup: an app, and whose account. NOT the\n// 0.43 `Connection`, which was a handle on a signed-in account and is `Account`.\nexport {\n\tConnection,\n\ttype CreatorAccountConnection,\n\ttype EndUserAccountConnection,\n} from './connection.ts';\n// Every app the platform can connect, as constants: `App.GMAIL === 'gmail'`.\n// Generated from the connections catalog; any plain string still works.\nexport { App, type AppId } from './apps.generated.ts';\nexport { Secret } from './secret.ts';\nexport { EndUser } from './end-user.ts';\nexport { Conversation, type AskOptions, type AttachOptions, type FileUrlOptions } from './conversation.ts';\nexport { KnowledgeBase } from './knowledge-base.ts';\nexport { Database } from './database.ts';\nexport { Project, type ProjectStartOptions } from './project.ts';\nexport { AgentNavaError, SecretHostNotAllowed } from './errors.ts';\nexport { loadWorkflows, parseWorkflow } from './workflows.ts';\n// Draft an agent's tools from whatever a company already has: an OpenAPI\n// document, a Postman collection, or a working curl command. Runs entirely on\n// your machine: nothing about the source is sent to AgentNava, which matters\n// because those files routinely contain a live credential.\nexport {\n\timportTools,\n\tfromOpenApi,\n\tfromOpenApiUrl,\n\tfromPostman,\n\tfromCurl,\n\ttoolNameFrom,\n\ttype ImportSource,\n\ttype ImportOptions,\n\ttype ImportedTools,\n\ttype ImportedSecret,\n\ttype ImportWarning,\n\ttype ImportWarningKind,\n\ttype OpenApiDocument,\n\ttype PostmanCollection,\n} from './import-tools.ts';\n// Reads a whole Claude-Code-shaped agent directory (AGENT.md + workflows/ +\n// mcp-servers.json ...) into the fields ws.agents.create() takes.\nexport { loadAgentDir } from './load-agent-dir.ts';\n// The platform's built-in project-manager configuration. apps/api imports this\n// through the './templates' subpath; re-exported here so the package has one\n// surface rather than two.\nexport { loadManagerTemplate } from './templates.ts';\nexport type { Turn } from './turn.ts';\nexport type { ClientOptions } from './http.ts';\nexport type {\n\tAppTool,\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentDraft,\n\tAgentFields,\n\tAgentStatus,\n\tWebsiteChatAppearance,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tGuardrails,\n\tDatabaseColumn,\n\tDatabaseData,\n\tDatabaseEngine,\n\tDatabaseFields,\n\tDatabaseLogin,\n\tDatabaseTable,\n\tDatabaseTestResult,\n\tDatabaseTls,\n\tDatabaseUsedBy,\n\tFileEntry,\n\tFilesystem,\n\tGrade,\n\tHttpTool,\n\tHttpToolParam,\n\tMcpServer,\n\tProjectAgent,\n\tMessage,\n\tParameter,\n\tProjectConversationData,\n\tProjectData,\n\tProjectFields,\n\tRequirement,\n\tScope,\n\tSeatPlan,\n\tSecretMeta,\n\tSecretRequirement,\n\tStep,\n\tTrigger,\n\tWebhookDelivery,\n\tWebhookVerify,\n\tTriggerTarget,\n\tTurnEvent,\n\tWidget,\n\tWidgetPart,\n\tUsedBy,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWorkspaceInfo,\n\tWorkspaceMember,\n} from './types.ts';\nexport { placeWidgets, type ReplyPiece } from './widgets.ts';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAgE,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAA4D,MAAM,mBAAmB,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,2DAA2D;AAC3D,OAAO,EACN,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,GASZ,MAAM,mBAAmB,CAAC;AAC3B,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AA8DrD,OAAO,EAAE,YAAY,EAAmB,MAAM,cAAc,CAAC","sourcesContent":["// @cerebro-labs/agentnava-sdk\n//\n// Configure an agent, start a conversation, work with it.\n//\n// import { AgentNava } from '@cerebro-labs/agentnava-sdk';\n//\n// const ws = AgentNava.getWorkspace();\n// const agent = await ws.agents.create({\n// name: 'Refund checker',\n// instructions: 'Decide whether a late delivery qualifies for a refund.',\n// });\n// const conversation = await agent.start({ endUserId: 'u_ananya' });\n// console.log(await conversation.ask('Order 4471 was nine days late.'));\n//\n// The reference this implements is design/sdk-design.md.\n\nexport { AgentNava, Workspace } from './workspace.ts';\nexport { PartnerApp, type PartnerAppOptions, type ExchangedCode, type CreditGrant } from './partner-app.ts';\nexport { Agent, type StartOptions } from './agent.ts';\nexport { Account } from './account.ts';\n// Builds one entry of an agent's setup: an app, and whose account. NOT the\n// 0.43 `Connection`, which was a handle on a signed-in account and is `Account`.\nexport {\n\tConnection,\n\ttype CreatorAccountConnection,\n\ttype EndUserAccountConnection,\n} from './connection.ts';\n// Every app the platform can connect, as constants: `App.GMAIL === 'gmail'`.\n// Generated from the connections catalog; any plain string still works.\nexport { App, type AppId } from './apps.generated.ts';\nexport { Secret } from './secret.ts';\nexport { EndUser } from './end-user.ts';\nexport { Conversation, type AskOptions, type AttachOptions, type FileUrlOptions } from './conversation.ts';\nexport { KnowledgeBase } from './knowledge-base.ts';\nexport { Database } from './database.ts';\nexport { Project, type ProjectStartOptions } from './project.ts';\nexport { AgentNavaError, SecretHostNotAllowed } from './errors.ts';\nexport { loadWorkflows, parseWorkflow } from './workflows.ts';\n// Draft an agent's tools from whatever a company already has: an OpenAPI\n// document, a Postman collection, or a working curl command. Runs entirely on\n// your machine: nothing about the source is sent to AgentNava, which matters\n// because those files routinely contain a live credential.\nexport {\n\timportTools,\n\tfromOpenApi,\n\tfromOpenApiUrl,\n\tfromPostman,\n\tfromCurl,\n\ttoolNameFrom,\n\ttype ImportSource,\n\ttype ImportOptions,\n\ttype ImportedTools,\n\ttype ImportedSecret,\n\ttype ImportWarning,\n\ttype ImportWarningKind,\n\ttype OpenApiDocument,\n\ttype PostmanCollection,\n} from './import-tools.ts';\n// Reads a whole Claude-Code-shaped agent directory (AGENT.md + workflows/ +\n// mcp-servers.json ...) into the fields ws.agents.create() takes.\nexport { loadAgentDir } from './load-agent-dir.ts';\n// The platform's built-in project-manager configuration. apps/api imports this\n// through the './templates' subpath; re-exported here so the package has one\n// surface rather than two.\nexport { loadManagerTemplate } from './templates.ts';\nexport type { Turn } from './turn.ts';\nexport type { ClientOptions } from './http.ts';\nexport type {\n\tAppTool,\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentDraft,\n\tAgentFields,\n\tAgentStatus,\n\tWebsiteChatAppearance,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tGuardrails,\n\tDatabaseColumn,\n\tDatabaseData,\n\tDatabaseEngine,\n\tDatabaseFields,\n\tDatabaseLogin,\n\tDatabaseTable,\n\tDatabaseTestResult,\n\tDatabaseTls,\n\tDatabaseUsedBy,\n\tFileEntry,\n\tFilesystem,\n\tGrade,\n\tHttpTool,\n\tHttpToolParam,\n\tMcpServer,\n\tProjectAgent,\n\tMessage,\n\tSentAttachment,\n\tParameter,\n\tProjectConversationData,\n\tProjectData,\n\tProjectFields,\n\tRequirement,\n\tScope,\n\tSeatPlan,\n\tSecretMeta,\n\tSecretRequirement,\n\tStep,\n\tTrigger,\n\tWebhookDelivery,\n\tWebhookVerify,\n\tTriggerTarget,\n\tTurnEvent,\n\tWidget,\n\tWidgetPart,\n\tUsedBy,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWorkspaceInfo,\n\tWorkspaceMember,\n} from './types.ts';\nexport { placeWidgets, type ReplyPiece } from './widgets.ts';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerebro-labs/agentnava-sdk",
3
- "version": "0.75.1",
3
+ "version": "0.79.0",
4
4
  "description": "The AgentNava SDK. Configure an agent, start a conversation, and work with it.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/react.d.ts CHANGED
@@ -34,7 +34,9 @@ export interface ReplyViewProps extends Omit<WidgetViewProps, 'widget' | 'classN
34
34
  renderText?: (text: string) => ReactNode;
35
35
  className?: string;
36
36
  }
37
- /** One widget, drawn from its root part. Draws nothing when there is no root yet. */
37
+ /** One widget, drawn from its root part. Draws nothing when there is no root
38
+ * yet, or nothing in it to draw: a container that lists none of its parts
39
+ * drew an empty frame (Complexity, 2026-11-02, a root Column with no children). */
38
40
  export declare function WidgetView(props: WidgetViewProps): import("react/jsx-runtime").JSX.Element | null;
39
41
  /** A whole reply: its words, with each widget drawn where it sits. */
40
42
  export declare function ReplyView({ text, widgets, renderText, className, ...widgetProps }: ReplyViewProps): import("react/jsx-runtime").JSX.Element;
package/react.js CHANGED
@@ -275,7 +275,23 @@ function draw(id, ctx) {
275
275
  const own = ctx.props.parts?.[part.component];
276
276
  return own ? own({ part, children: kids, onAsk: ctx.props.onAsk }) : defaultPart(part, kids, ctx);
277
277
  }
278
- /** One widget, drawn from its root part. Draws nothing when there is no root yet. */
278
+ /** Whether a part draws anything: a part that is not a container does; a
279
+ * container only through a child that does (a Card through its picture too).
280
+ * A part listed twice, or a loop, counts once. */
281
+ function drawsSomething(id, byId, seen = new Set()) {
282
+ const part = byId.get(id);
283
+ if (!part || seen.has(id))
284
+ return false;
285
+ seen.add(id);
286
+ if (!CONTAINERS.has(part.component))
287
+ return true;
288
+ if (part.component === 'Card' && part.image)
289
+ return true;
290
+ return list(part.children).some((k) => typeof k === 'string' && drawsSomething(k, byId, seen));
291
+ }
292
+ /** One widget, drawn from its root part. Draws nothing when there is no root
293
+ * yet, or nothing in it to draw: a container that lists none of its parts
294
+ * drew an empty frame (Complexity, 2026-11-02, a root Column with no children). */
279
295
  export function WidgetView(props) {
280
296
  const { widget, currency = 'USD', locale, allowUrl } = props;
281
297
  const byId = new Map();
@@ -283,7 +299,7 @@ export function WidgetView(props) {
283
299
  if (p && typeof p === 'object' && typeof p.id === 'string' && typeof p.component === 'string' && !byId.has(p.id))
284
300
  byId.set(p.id, p);
285
301
  }
286
- if (!byId.has('root'))
302
+ if (!drawsSomething('root', byId))
287
303
  return null;
288
304
  let moneyFormat;
289
305
  try {