@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.
Files changed (62) hide show
  1. package/README.md +212 -23
  2. package/account.d.ts +154 -0
  3. package/account.js +219 -0
  4. package/account.js.map +1 -0
  5. package/agent.d.ts +229 -87
  6. package/agent.js +310 -125
  7. package/agent.js.map +1 -1
  8. package/apps.generated.d.ts +154 -0
  9. package/apps.generated.js +152 -0
  10. package/apps.generated.js.map +1 -0
  11. package/attach.d.ts +25 -26
  12. package/attach.js +53 -66
  13. package/attach.js.map +1 -1
  14. package/connection.d.ts +79 -82
  15. package/connection.js +67 -134
  16. package/connection.js.map +1 -1
  17. package/conversation.d.ts +55 -35
  18. package/conversation.js +70 -50
  19. package/conversation.js.map +1 -1
  20. package/database.d.ts +53 -0
  21. package/database.js +69 -0
  22. package/database.js.map +1 -0
  23. package/end-user.d.ts +58 -47
  24. package/end-user.js +62 -72
  25. package/end-user.js.map +1 -1
  26. package/errors.d.ts +2 -1
  27. package/errors.js +2 -1
  28. package/errors.js.map +1 -1
  29. package/http.d.ts +5 -0
  30. package/http.js +109 -8
  31. package/http.js.map +1 -1
  32. package/import-tools.d.ts +54 -33
  33. package/import-tools.js +526 -124
  34. package/import-tools.js.map +1 -1
  35. package/index.d.ts +5 -2
  36. package/index.js +9 -2
  37. package/index.js.map +1 -1
  38. package/load-agent-dir.js +28 -5
  39. package/load-agent-dir.js.map +1 -1
  40. package/package.json +5 -1
  41. package/secret.d.ts +1 -1
  42. package/secret.js +1 -1
  43. package/secret.js.map +1 -1
  44. package/templates-inline.js +1 -1
  45. package/templates-inline.js.map +1 -1
  46. package/turn.d.ts +1 -1
  47. package/turn.js +77 -29
  48. package/turn.js.map +1 -1
  49. package/types.d.ts +462 -83
  50. package/types.js.map +1 -1
  51. package/version.d.ts +1 -1
  52. package/version.js +1 -1
  53. package/version.js.map +1 -1
  54. package/workflows.d.ts +10 -0
  55. package/workflows.js +16 -0
  56. package/workflows.js.map +1 -1
  57. package/workspace.d.ts +80 -62
  58. package/workspace.js +92 -75
  59. package/workspace.js.map +1 -1
  60. package/connection-status.d.ts +0 -28
  61. package/connection-status.js +0 -61
  62. package/connection-status.js.map +0 -1
package/types.d.ts CHANGED
@@ -1,20 +1,24 @@
1
- export type Grade = 'standard' | 'premium';
1
+ import type { AppId } from './apps.generated.ts';
2
+ import type { Connection } from './connection.ts';
3
+ /** The quality of model an agent's turns run on, and what each turn costs.
4
+ *
5
+ * `fast` is the one that surprises people: it buys LATENCY rather than
6
+ * capability, runs a smaller model on silicon built for time-to-first-token,
7
+ * and costs MORE than `premium` while being less capable. Pick it for voice
8
+ * and interactive agents, not to save money. `best` is the strongest model. */
9
+ export type Grade = 'standard' | 'premium' | 'fast' | 'best';
2
10
  export type Filesystem = 'drive' | 'none';
3
- /** Where a credential is supplied.
11
+ /** Where a SECRET is supplied.
4
12
  *
5
13
  * 'fixed' is set once on the agent and used by every conversation. 'conversation'
6
14
  * is asked for when a conversation starts, so each user brings their own. The
7
- * two DO NOT substitute for each other: an account connected on the agent does
8
- * not satisfy a declaration scoped 'conversation'. */
9
- export type Scope = 'fixed' | 'conversation';
10
- /** WHOSE credential a stored account or value is, which is a different question
11
- * from `Scope` above. `Scope` is what an agent DECLARES it needs; this is who
12
- * a row that exists actually belongs to.
15
+ * two DO NOT substitute for each other: a value set on the agent does not
16
+ * satisfy a declaration scoped 'conversation'.
13
17
  *
14
- * 'endUser' is one of YOUR end users: the person your application serves,
15
- * never a member of this workspace. The two are separate populations with
16
- * separate id namespaces: see `EndUser`. */
17
- export type CredentialOwner = 'workspace' | 'agent' | 'endUser' | 'conversation';
18
+ * Connections stopped using this in 0.44. A connection says WHOSE ACCOUNT it
19
+ * uses instead: the agent creator's (`account`) or each end user's own
20
+ * (`endUser: true`). See `AgentConnection`. */
21
+ export type Scope = 'fixed' | 'conversation';
18
22
  export interface WorkspaceInfo {
19
23
  id: string;
20
24
  name: string;
@@ -31,37 +35,160 @@ export interface Workflow {
31
35
  }
32
36
  export type Trigger = {
33
37
  kind: 'chat';
34
- } | {
38
+ } | ({
35
39
  kind: 'schedule';
40
+ enabled?: boolean;
36
41
  cron: string;
37
42
  timezone?: string;
38
43
  message: string;
39
- } | {
44
+ } & TriggerTarget) | {
40
45
  kind: 'interval';
41
46
  seconds: number;
42
47
  maxRuns?: number;
43
48
  message: string;
44
- } | {
49
+ } | ({
45
50
  kind: 'webhook';
46
51
  message?: string;
52
+ /**
53
+ * Where your system POSTs, set by the platform. Read it from the agent
54
+ * that `setTriggers()` returns: `agent.data?.triggers`. Anything you
55
+ * send here is ignored, so passing back a list you read is safe and
56
+ * keeps the same URL. One webhook per agent; `rotateWebhookUrl()`
57
+ * replaces the URL.
58
+ */
47
59
  url?: string;
48
- };
60
+ /**
61
+ * Check WHO sent each delivery, beyond knowing the URL. See
62
+ * WebhookVerify for the schemes and an example. Leave it out to keep
63
+ * what the webhook has. The secret is stored encrypted and never read
64
+ * back: a read says `secretSet: true` instead.
65
+ */
66
+ verify?: WebhookVerify;
67
+ } & TriggerTarget);
49
68
  /**
50
- * What an agent NEEDS, not what it has.
69
+ * How a webhook checks its sender. Platforms that send webhooks sign every
70
+ * delivery with a secret they show you when you add the URL there; give it
71
+ * here, and a delivery whose signature does not match is refused (401) and
72
+ * never runs the agent.
51
73
  *
52
- * Named `Connection` until the actual account became an object you hold. Two
53
- * different things cannot share one name: this one says "this agent requires
54
- * Gmail, supplied per conversation", and says nothing about whether anybody has
55
- * connected. `Connection` is the account itself, with tokens behind it.
74
+ * ```ts
75
+ * { kind: 'webhook', verify: { kind: 'stripe', secret: 'whsec_...' } }
76
+ * { kind: 'webhook', verify: { kind: 'header', header: 'X-Webhook-Secret', secret: '...' } }
77
+ * ```
56
78
  *
57
- * The distinction is the one `ConnectionStatus.declared` versus `.connected`
58
- * already draws, now in the type names too.
79
+ * The schemes: `stripe`, `github`, `shopify`, `slack` and
80
+ * `standard` (the Standard Webhooks spec) check that platform's signature;
81
+ * `header` checks a header your sender adds (Zapier, Make, n8n, your own code
82
+ * can all add one); `none` relies on the URL alone.
59
83
  */
60
- export interface ConnectionRequirement {
61
- provider: string;
62
- scope: Scope;
63
- required?: boolean;
84
+ export type WebhookVerify = {
85
+ kind: 'none';
86
+ } | {
87
+ kind: 'stripe' | 'github' | 'shopify' | 'slack' | 'standard';
88
+ secret?: string;
89
+ secretSet?: boolean;
90
+ } | {
91
+ kind: 'header';
92
+ header: string;
93
+ secret?: string;
94
+ secretSet?: boolean;
95
+ };
96
+ /** One delivery a webhook received, newest first from `agent.webhookDeliveries()`. */
97
+ export interface WebhookDelivery {
98
+ id: string;
99
+ receivedAt: string;
100
+ /** `pending`: checked and starting. `repeat`: the sender retried a delivery
101
+ * already accepted; the agent did not run again. */
102
+ outcome: 'pending' | 'accepted' | 'refused' | 'repeat' | 'test';
103
+ /** Why it was refused. */
104
+ reason?: string;
105
+ /** The sender's own id for it, when it gave one. */
106
+ deliveryId?: string;
107
+ /** The conversation it ran in. */
108
+ conversationId?: string;
64
109
  }
110
+ /**
111
+ * WHERE a schedule or webhook runs. Name a conversation, or an end user, or
112
+ * neither; never both.
113
+ *
114
+ * ```ts
115
+ * const conversation = await agent.start({ endUserId: 'ops-app' });
116
+ * await agent.setTriggers([
117
+ * { kind: 'chat' },
118
+ * // Every firing lands in THIS conversation: its history, its drive.
119
+ * { kind: 'schedule', cron: '0 15 * * *', message: 'REFRESH', conversationId: conversation.id },
120
+ * ]);
121
+ * ```
122
+ */
123
+ export type TriggerTarget = {
124
+ /**
125
+ * The conversation every firing CONTINUES: it sees that conversation's
126
+ * history and its drive, and its end user comes with it. Each firing
127
+ * also moves the conversation to the agent's latest version, so a fix
128
+ * you publish reaches it.
129
+ *
130
+ * This is the ONLY way a trigger continues one of your conversations.
131
+ * One end user can have many, so naming the end user cannot say which.
132
+ *
133
+ * Must be a conversation of this agent, started for one of your end
134
+ * users (the id `agent.start()` returned). Anything else is refused
135
+ * when you set the trigger. If the conversation is deleted later, the
136
+ * firings fail and say so; none is opened in its place.
137
+ */
138
+ conversationId: string;
139
+ userId?: undefined;
140
+ } | {
141
+ conversationId?: undefined;
142
+ /**
143
+ * The end user each firing is for, when it names no conversation. Each
144
+ * firing STARTS A NEW CONVERSATION for them, with an empty drive, and it
145
+ * is listed by `agent.conversations({ endUser })`. To keep adding to
146
+ * one conversation instead, name it with `conversationId`.
147
+ *
148
+ * Your id, as in `agent.start({ endUserId })`; the platform's prefixes
149
+ * (`member:`, `visitor:`, `job:`) are refused.
150
+ *
151
+ * Name neither and the trigger keeps one conversation of its own that
152
+ * every firing continues.
153
+ */
154
+ userId?: string;
155
+ };
156
+ /**
157
+ * One connection in an agent's setup, as it reads back: a NAME, an APP, and
158
+ * WHOSE ACCOUNT. Exactly one of these two:
159
+ *
160
+ * | the connection says | whose account | the person signs in |
161
+ * |---|---|---|
162
+ * | `account: '<id>'` | the agent creator's: one account in this workspace, from `ws.accounts.connect()` | once, and every conversation uses it |
163
+ * | `endUser: true` | the end user's: each person talking to the agent | once each, remembered for them |
164
+ *
165
+ * A turn uses EXACTLY the account the setup names, and never another one.
166
+ * There is no fallback to some other account of the same app: an account that
167
+ * is not signed in leaves the connection unavailable, and the agent says so.
168
+ *
169
+ * `name` tells two connections of one app apart, such as `work-gmail` and
170
+ * `personal-gmail`. 1 to 64 characters, lowercase letters, digits and dashes,
171
+ * no dash at either end, unique on the agent. Left out when sent, it is the
172
+ * app's id.
173
+ *
174
+ * Part of the agent's CONFIGURATION, so changing a connection publishes a
175
+ * version. The account itself is not versioned: signing in again reaches every
176
+ * agent that names it. To send one, build it with `Connection.creatorAccount()`
177
+ * or `Connection.endUserAccount()`.
178
+ */
179
+ export type AgentConnection = {
180
+ name: string;
181
+ /** The app's id: `App.GMAIL` is `'gmail'`. */
182
+ app: AppId;
183
+ /** An account id in this workspace whose app is `app`. */
184
+ account: string;
185
+ endUser?: never;
186
+ } | {
187
+ name: string;
188
+ app: AppId;
189
+ endUser: true;
190
+ account?: never;
191
+ };
65
192
  /** What an agent NEEDS, not the stored value. `Secret` is the value. */
66
193
  export interface SecretRequirement {
67
194
  name: string;
@@ -83,10 +210,15 @@ export interface McpServer {
83
210
  timeoutMs?: number;
84
211
  }
85
212
  export interface HttpToolParam {
86
- type: 'string' | 'number' | 'boolean';
213
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array';
87
214
  description?: string;
88
215
  required?: boolean;
89
- choices?: (string | number)[];
216
+ choices?: (string | number | boolean | null)[];
217
+ nullable?: boolean;
218
+ integer?: boolean;
219
+ properties?: Record<string, HttpToolParam>;
220
+ items?: HttpToolParam;
221
+ additionalProperties?: boolean;
90
222
  }
91
223
  export interface HttpTool {
92
224
  name: string;
@@ -108,7 +240,8 @@ export interface AgentData {
108
240
  filesystem: Filesystem;
109
241
  tools: string[];
110
242
  workflows: Workflow[];
111
- connections: ConnectionRequirement[];
243
+ /** The apps it uses, each saying whose account. See `AgentConnection`. */
244
+ connections: AgentConnection[];
112
245
  secrets: SecretRequirement[];
113
246
  parameters: Parameter[];
114
247
  triggers: Trigger[];
@@ -118,59 +251,163 @@ export interface AgentData {
118
251
  * when none are attached, so a caller never has to distinguish "none" from
119
252
  * "an older server that did not send the field". */
120
253
  knowledgeBases: string[];
254
+ /** Workspace databases this agent may query, by key. Always present, like
255
+ * `knowledgeBases`. */
256
+ databases: string[];
121
257
  version: number;
122
258
  createdAt: string;
123
259
  updatedAt: string;
260
+ /** Where it is live, as the portal shows it. The API works it out by the
261
+ * same rule it runs the agent by, so `channels.api === 'on'` is exactly
262
+ * "a turn will be taken". Read-only: nothing sets it. Sent by `agent.get()`
263
+ * and `ws.agents.list()`; absent from what create and update return, so
264
+ * call `get()` for it. */
265
+ readonly status?: AgentStatus;
266
+ }
267
+ /** What `agent.websiteChat.turnOn()` gives back: what to put on a website. */
268
+ export interface WebsiteChat {
269
+ /** The hosted chat page. Share it, or open it in an iframe. */
270
+ readonly chatUrl: string;
271
+ /** The publishable token for the embed script. Safe in a public web page:
272
+ * it can only talk to this one agent. Your API key never goes there. */
273
+ readonly publicToken: string;
274
+ /** The id the embed script's `data-agent-id` takes. */
275
+ readonly embedAgentId: string;
276
+ /** The version the website chat serves from now on. */
277
+ readonly versionNumber: number;
278
+ }
279
+ /** Where an agent is live. See `AgentData.status`. */
280
+ export interface AgentStatus {
281
+ /** True when any channel is on. */
282
+ readonly live: boolean;
283
+ /** Paused by its owner: nothing runs it until it is resumed. */
284
+ readonly paused: boolean;
285
+ /** Archived: nothing runs it again. */
286
+ readonly archived: boolean;
287
+ readonly channels: {
288
+ /** 'on' when the API takes new turns for it. */
289
+ readonly api: 'on' | 'off';
290
+ /** 'on' when its website chat page answers visitors. 'unavailable' when it
291
+ * needs what each user brings (`eachUserBrings`), which a visitor cannot. */
292
+ readonly websiteChat: 'on' | 'off' | 'unavailable';
293
+ };
294
+ /** Required accounts and secrets of its own that are not set up yet, on the
295
+ * version a new conversation runs. It still runs: the tools that use them
296
+ * fail until they are set up. Empty when nothing is missing. */
297
+ readonly needs: readonly {
298
+ kind: 'connection' | 'secret';
299
+ name: string;
300
+ }[];
301
+ /** The accounts and secrets each user supplies for themselves. */
302
+ readonly eachUserBrings: readonly {
303
+ kind: 'connection' | 'secret';
304
+ name: string;
305
+ }[];
124
306
  }
125
- /** Whether a provider is actually usable right now, as opposed to declared.
307
+ /**
308
+ * One of an agent's connections as it stands right now: the account it names,
309
+ * and whether that account works. What `agent.connections.list()` returns.
310
+ *
311
+ * DECLARED IS NOT CONNECTED, and this is the read that tells them apart. A
312
+ * connection can name an account nobody has finished signing in to, or one that
313
+ * was disconnected since; that is the state a setup checklist has to show, and
314
+ * without this the only way to find it is to start a conversation and watch
315
+ * the agent fail.
126
316
  *
127
- * `declared` and `connected` are separate on purpose: an agent can declare
128
- * HubSpot and have nobody connected, which is the state worth showing on a
129
- * setup checklist. */
317
+ * Reshaped in 0.44: it used to be keyed by `provider`, with `declared`,
318
+ * `required`, `scope` and `suppliedBy`. A connection is now keyed by its
319
+ * `name`, and it says whose account it uses instead of where one might come
320
+ * from.
321
+ */
130
322
  export interface ConnectionStatus {
131
- /** The connection row's id, which `disconnect()` revokes by. */
132
- id?: string;
133
- provider: string;
134
- /** 'connected' once the account is attached; anything else means it is not
135
- * usable yet. `connected` below is the boolean form of this. */
136
- status?: string;
137
- /** The conversation this account belongs to, or null for the agent's shared
138
- * account. `scope` and `suppliedBy` are both derived from it. */
139
- conversationId?: string | null;
140
- /** The agent this account belongs to. */
141
- agentId?: string | null;
142
- /** One of YOUR end users, when the account is theirs. Their id, from your
143
- * database, never a workspace member's. Null on a shared account. */
144
- endUserId?: string | null;
145
- /** Whether the AGENT DECLARES it needs this provider, and whether it is
146
- * mandatory.
147
- *
148
- * OPTIONAL because only one of the two endpoints can answer. `agent
149
- * .connections()` reads the MERGED view (`/v1/agents/<id>/connections`),
150
- * where the server has joined the agent's declarations against the live
151
- * rows, and both are real there. `ws.connections.list()` and
152
- * `endUser(id).connections.list()` read raw rows (`/v1/connections`), which
153
- * know nothing about any agent's configuration, so both are absent.
154
- *
155
- * They used to be declared as required booleans, which meant the raw-row
156
- * paths had to invent them. They were filled with the constants `true` and
157
- * `false`, so every row claimed to be declared and none claimed to be
158
- * required. Absent is the honest answer, and undefined reads as "this
159
- * endpoint cannot tell you" rather than as "no". */
160
- declared?: boolean;
161
- required?: boolean;
162
- /** What the agent DECLARED it needs. Whose account this actually is, is
163
- * `owner`, and the two are not the same question. */
164
- scope?: Scope;
165
- /** Whose account this is. Derived from which of the three id fields above
166
- * is set, so it is correct even on a server that does not send it. */
167
- owner?: CredentialOwner;
168
- connected: boolean;
169
- /** Which of the three scopes supplied the account. 'endUser' is one of YOUR
170
- * users, whose account outlives any single conversation. */
171
- suppliedBy?: 'conversation' | 'agent' | 'endUser' | 'workspace';
172
- accountLabel?: string;
173
- error?: string;
323
+ name: string;
324
+ app: AppId;
325
+ /** The agent creator's account the connection names, read fresh. Absent on
326
+ * an end user's connection, and on one whose account is gone. */
327
+ account?: AccountData;
328
+ /** True when each person talking to the agent signs in with their own. */
329
+ endUser?: true;
330
+ /** Whether the connection works now, as the API reports it. For an agent
331
+ * creator's account it follows that account's status. */
332
+ status: string;
333
+ /** Only in the agent's unpublished draft, so no conversation uses it yet. */
334
+ inDraft: boolean;
335
+ /** Removed in the agent's unpublished draft, and still used until the draft
336
+ * is published. */
337
+ removedInDraft: boolean;
338
+ }
339
+ /** Where one account's sign-in stands.
340
+ *
341
+ * 'pending' made, and nobody has finished signing in yet
342
+ * 'connected' signed in and working
343
+ * 'needs_sign_in' it worked, and the app now wants the person to sign in again
344
+ * 'error' the app refused it for another reason
345
+ */
346
+ export type AccountStatus = 'pending' | 'connected' | 'needs_sign_in' | 'error';
347
+ /** An agent whose setup names an account, and the connection that names it.
348
+ * Counted from the agent's latest published version and its draft, never
349
+ * from older versions. */
350
+ export interface UsedBy {
351
+ /** The agent's id: the same value as `Agent.id`, so compare this one. */
352
+ id: string;
353
+ /** AgentNava's internal handle for the same agent (`agt_...`), not `Agent.id`. */
354
+ agentId: string;
355
+ agentName: string;
356
+ connectionName: string;
357
+ /** Named in the agent's unpublished draft. */
358
+ inDraft: boolean;
359
+ /** Named in the agent's latest published version. */
360
+ published: boolean;
361
+ }
362
+ /**
363
+ * One saved sign-in to one app.
364
+ *
365
+ * TWO OWNERS, and the difference is who signs in:
366
+ *
367
+ * | `owner` | whose | a connection uses it as |
368
+ * |---|---|---|
369
+ * | `'workspace'` | the agent creator's, signed in once, usable by any agent | `Connection.creatorAccount(app, account)` |
370
+ * | `'end-user'` | one of YOUR end users', remembered for that person | `Connection.endUserAccount(app)`, in that person's conversations |
371
+ *
372
+ * Both owners are read and changed through the same routes and the same
373
+ * `Account` handle; `owner` says which this is.
374
+ *
375
+ * Nobody handles a token: the person signs in on the app's own page, and this
376
+ * record says who they signed in as, never how.
377
+ */
378
+ export interface AccountData {
379
+ id: string;
380
+ /** The app's id: `App.GMAIL` is `'gmail'`. */
381
+ app: AppId;
382
+ /** Who it is signed in as: the email or username the app reports. Null until
383
+ * somebody has finished signing in. */
384
+ label: string | null;
385
+ /** Your own name for it, such as `'Support inbox'`. Null when none was given. */
386
+ nickname: string | null;
387
+ status: AccountStatus;
388
+ /** True while a reconnect waits for the person to sign in again. The
389
+ * account keeps working meanwhile, so `status` can still say
390
+ * `'connected'`; `waitUntilConnected()` waits for this to clear. */
391
+ reconnecting: boolean;
392
+ owner: 'workspace' | 'end-user';
393
+ /** The end user it belongs to, when `owner` is `'end-user'`. An id from YOUR
394
+ * database, never a workspace member's. */
395
+ endUserId?: string;
396
+ connectedAt: string | null;
397
+ lastCheckedAt: string | null;
398
+ /** The agents whose setup names this account. Empty for an end user's
399
+ * account, which a setup never names by id. */
400
+ usedBy: UsedBy[];
401
+ }
402
+ /** An end user's account a conversation still needs, with the link that
403
+ * person signs in at. From `conversation.accountsNeeded()`. */
404
+ export interface AccountNeeded {
405
+ /** The connection's name on the agent. */
406
+ name: string;
407
+ app: AppId;
408
+ /** Send the person here. They sign in once, and every later conversation
409
+ * of theirs has it. */
410
+ url: string;
174
411
  }
175
412
  /** Something a conversation is still missing before it can work. */
176
413
  export interface Requirement {
@@ -181,6 +418,9 @@ export interface Requirement {
181
418
  export interface AgentConversationData {
182
419
  id: string;
183
420
  agentId: string;
421
+ /** The number of the agent version this conversation runs, as `agent.version`
422
+ * numbers them. Fixed at start, except that a schedule or webhook naming the
423
+ * conversation moves it to the latest version at each firing. */
184
424
  version: number;
185
425
  startedAt: string;
186
426
  title?: string;
@@ -351,9 +591,44 @@ export interface AgentFields {
351
591
  * that was valid stays valid.
352
592
  */
353
593
  knowledgeBases?: string[];
594
+ /**
595
+ * Workspace databases this agent may query, by key.
596
+ *
597
+ * Register the database first with `ws.databases.create()`, which logs in
598
+ * and gives it the key; then name the key here. Naming it is the grant: a
599
+ * database answers only the agents whose configuration names it, and the
600
+ * agent's prompt lists them so it knows what to query.
601
+ *
602
+ * Part of the CONFIGURATION, so changing it publishes a version. A key that
603
+ * names no database is refused when the configuration is saved, and
604
+ * removing a database is refused while any agent names it.
605
+ */
606
+ databases?: string[];
354
607
  tools?: string[];
355
608
  workflows?: Workflow[];
356
- connections?: ConnectionRequirement[];
609
+ /**
610
+ * The apps this agent uses, each a NAMED connection that says whose account
611
+ * it uses: the agent creator's account, or each end user's own. See
612
+ * `Connection` and `AgentConnection`.
613
+ *
614
+ * ```ts
615
+ * connections: [
616
+ * Connection.creatorAccount(App.GMAIL, work, { name: 'work-gmail' }),
617
+ * Connection.creatorAccount(App.GMAIL, personal, { name: 'personal-gmail' }),
618
+ * Connection.endUserAccount(App.GOOGLE_CALENDAR),
619
+ * ]
620
+ * ```
621
+ *
622
+ * The builders make plain objects, `{ app, account: '<id>' }` and
623
+ * `{ app, endUser: true }`, and those objects are accepted as they are.
624
+ * `App.GMAIL` is the string `'gmail'`, so a plain app id works too.
625
+ *
626
+ * Part of the CONFIGURATION, like `databases`, so changing it publishes a
627
+ * version. An unknown account id, an account of a different app, a repeated
628
+ * name or a name of the wrong shape is refused when the configuration is
629
+ * saved, naming the problem.
630
+ */
631
+ connections?: Connection[];
357
632
  secrets?: SecretRequirement[];
358
633
  parameters?: Parameter[];
359
634
  triggers?: Trigger[];
@@ -367,13 +642,15 @@ export interface ProjectFields {
367
642
  grade?: Grade;
368
643
  agents?: string[];
369
644
  }
370
- /** Values supplied when a conversation starts: the credentials its agent
371
- * declared at 'conversation' scope, plus any parameters. */
645
+ /** Secret VALUES supplied to an agent (`agent.attach`) or to one conversation
646
+ * (`conversation.attach`): the secrets its agent declared.
647
+ *
648
+ * Accounts are no longer supplied this way (0.44). An agent's setup names the
649
+ * account each connection uses, so there is nothing left to attach. */
372
650
  export interface Bindings {
373
651
  secrets?: Record<string, string>;
374
- connections?: Record<string, string>;
375
652
  }
376
- /** One markdown file read off disk: a workflow, a subagent, an imported skill.
653
+ /** One markdown file read off disk: a workflow or a subagent.
377
654
  *
378
655
  * `when` is OPTIONAL here and required at publish. A file on disk has no `when`
379
656
  * until someone writes one, and refusing to read it would be the wrong place to
@@ -389,7 +666,6 @@ export interface SpecFile {
389
666
  export interface SpecInput extends Omit<AgentFields, 'workflows'> {
390
667
  key: string;
391
668
  workflows?: SpecFile[];
392
- skills?: SpecFile[];
393
669
  subagents?: SpecFile[];
394
670
  widgets?: {
395
671
  name: string;
@@ -483,3 +759,106 @@ export interface SecretMeta {
483
759
  updatedAt?: string;
484
760
  lastUsedAt?: string | null;
485
761
  }
762
+ /** A SQL engine AgentNava can query. */
763
+ export type DatabaseEngine = 'postgres' | 'mysql' | 'sqlserver';
764
+ /** How the connection to a database is encrypted. `verify` (the default)
765
+ * encrypts and always checks the server's certificate: against public and
766
+ * cloud CAs and the host name, or, for a database with its own home-made CA,
767
+ * against the CA AgentNava trusted the first time it connected. `disable` is
768
+ * for a server with no SSL at all. */
769
+ export type DatabaseTls = 'verify' | 'disable';
770
+ /**
771
+ * How to log in to a database: a connection string, OR the same login as
772
+ * separate fields. Never both; a request with both is refused.
773
+ *
774
+ * Read-only unless `writable: true`, which no SQL the agent sends can override.
775
+ */
776
+ export type DatabaseLogin = {
777
+ /** `postgres://`, `postgresql://` or `mysql://`, with the user (and the
778
+ * password, when there is one) in it. An `sslmode` in the string sets `tls`. */
779
+ url: string;
780
+ engine?: DatabaseEngine;
781
+ /** Overrides the string's `sslmode`. */
782
+ tls?: DatabaseTls;
783
+ writable?: boolean;
784
+ } | {
785
+ engine: DatabaseEngine;
786
+ host: string;
787
+ port: number;
788
+ database: string;
789
+ username: string;
790
+ /** Leave it out for a database that takes a user name alone. */
791
+ password?: string;
792
+ tls?: DatabaseTls;
793
+ writable?: boolean;
794
+ };
795
+ /** What `ws.databases.create()` takes: the key agents will name it by, an
796
+ * optional label, and a login. */
797
+ export type DatabaseFields = DatabaseLogin & {
798
+ /** Lowercase letters, digits and dashes, like `shop` or `sales-db`. What an
799
+ * agent configuration stores, so it is not editable afterwards. */
800
+ key: string;
801
+ /** A label for people. Defaults to the key. */
802
+ name?: string;
803
+ };
804
+ /** A registered database as AgentNava reports it: where it is, never how to
805
+ * log in. Neither the password nor the connection string is ever returned. */
806
+ export interface DatabaseData {
807
+ key: string;
808
+ /** The underlying connection's id. */
809
+ id: string;
810
+ name: string;
811
+ engine: DatabaseEngine | null;
812
+ host: string | null;
813
+ port: number | null;
814
+ database: string | null;
815
+ tls: DatabaseTls | null;
816
+ writable: boolean;
817
+ status: string;
818
+ createdAt: string;
819
+ /** The agents whose configuration names this database. */
820
+ usedBy: DatabaseUsedBy[];
821
+ }
822
+ /** An agent whose configuration names a database: in its latest published
823
+ * version, in its draft, or in both. */
824
+ export interface DatabaseUsedBy {
825
+ /** The agent's id: the same value as `Agent.id`, so compare this one. */
826
+ id: string;
827
+ /** AgentNava's internal handle for the same agent (`agt_...`), not `Agent.id`. */
828
+ agentId: string;
829
+ /** The agent's display name. */
830
+ name: string;
831
+ /** Named in the agent's latest published version, so its conversations can
832
+ * query the database now. */
833
+ published: boolean;
834
+ /** Named in the configuration being edited: the draft when there is one,
835
+ * else the published version itself. */
836
+ draft: boolean;
837
+ }
838
+ /** One table or view in a database. */
839
+ export interface DatabaseTable {
840
+ schema: string;
841
+ name: string;
842
+ kind: 'table' | 'view';
843
+ }
844
+ /** One column of a table. */
845
+ export interface DatabaseColumn {
846
+ name: string;
847
+ dataType: string;
848
+ nullable: boolean;
849
+ isPrimaryKey: boolean;
850
+ }
851
+ /** What `ws.databases.test()` reports. A refused login is an answer, not an
852
+ * exception, because it is the answer the call exists to get. */
853
+ export type DatabaseTestResult = {
854
+ ok: true;
855
+ engine: DatabaseEngine;
856
+ tableCount: number;
857
+ tables: DatabaseTable[];
858
+ } | {
859
+ ok: false;
860
+ error: {
861
+ code: string;
862
+ message: string;
863
+ };
864
+ };