@cubos/agent-sdk 0.0.1136563

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,446 @@
1
+ # @cubos/agent-sdk
2
+
3
+ Client for Cubos Agent. Two surfaces: **conversation**, for your end-user app, and **admin**, for your backend.
4
+
5
+ Zero dependencies. Uses only `fetch`, `ReadableStream` and `AbortController`, so the same code runs in the browser, Node ≥ 18, Bun, Deno and Cloudflare Workers.
6
+
7
+ ```sh
8
+ bun add @cubos/agent-sdk # or npm / pnpm / yarn
9
+ ```
10
+
11
+ ## The token: minted by your backend, never in the client
12
+
13
+ A Cubos Agent api_key is tenant-wide and **must never reach a browser**. Your backend trades it for a short-lived token scoped to one user:
14
+
15
+ ```ts
16
+ import { createAdminClient } from "@cubos/agent-sdk";
17
+
18
+ // On YOUR server.
19
+ const admin = createAdminClient({ baseUrl, apiKey: process.env.CUBOS_API_KEY! });
20
+
21
+ const { token, expires_at } = await admin.tenant("acme").userTokens.create({
22
+ user_id: session.userId, // from YOUR auth, never from the client
23
+ agent_slugs: ["support"],
24
+ ttl_seconds: 900, // optional; defaults to 3600
25
+ });
26
+ ```
27
+
28
+ The api_key needs the `user_tokens:write` grant. The resulting token reaches only the conversation endpoints, and only that user's own conversations — there is no way to widen it from the client.
29
+
30
+ `agent_slugs` lists the agents the token may open a conversation with. `ttl_seconds` defaults to 3600 and caps at 86400. **There is no revocation list**: prefer the shortest TTL your app can comfortably refresh. Blocking the user (`users.block`) invalidates their tokens on the next request.
31
+
32
+ ## Conversation
33
+
34
+ ```ts
35
+ import { createUserClient } from "@cubos/agent-sdk";
36
+
37
+ const client = createUserClient({
38
+ baseUrl: "https://agent.acme.com",
39
+ // Called per request — cache it yourself. On forceRefresh, return a freshly
40
+ // minted token (the SDK only asks for that after a 401).
41
+ getToken: async ({ forceRefresh }) => fetchTokenFromMyBackend({ forceRefresh }),
42
+ });
43
+
44
+ const conversation = await client.createConversation({ agentSlug: "support" });
45
+ await client.sendMessage(conversation.id, "My invoice was charged twice");
46
+
47
+ const subscription = client.subscribe(conversation.id, {
48
+ onMessage: (m) => console.log(m.role, m.content),
49
+ onActivity: (a) => setTyping(a.isProcessing || a.hasPendingTurn),
50
+ onTodos: (todos) => setPlan(todos),
51
+ });
52
+
53
+ subscription.close();
54
+ ```
55
+
56
+ If you already hold a token — a script, a test — pass it directly as `token: "…"` instead of writing a closure. A static token cannot be renewed, so it eventually expires for good; use `getToken` for anything real.
57
+
58
+ `subscribe` backfills the history on connect and resumes from the exact point after any drop, so what it delivers is the whole conversation — no need to pair it with `listMessages`, which exists for paging long history backwards.
59
+
60
+ ### Telling the agent what is on screen
61
+
62
+ ```ts
63
+ await client.setContext(conversationId, `screen: customers\nfilters: status=active`);
64
+ ```
65
+
66
+ Call it whenever the screen moves — it writes no event and starts no turn, so it
67
+ is safe on every navigation and even mid-turn. The server shows it to the agent
68
+ the next time a turn reads the conversation, and only when it changed, so
69
+ repeating an unchanged context costs nothing.
70
+
71
+ That is the raw route. `@cubos/agent-sdk-react`'s `useConversation({ context })`
72
+ goes one better and holds the value until a turn could read it — browsing eleven
73
+ screens before typing anything makes one request, not eleven.
74
+
75
+ The alternative most apps reach for is prefixing the context onto the user's
76
+ message. That spends it on every message, puts it in the transcript unless every
77
+ render path strips it out, and only works for whoever owns the composer.
78
+
79
+ `onActivity` rides that same stream, and the ordering is a guarantee you can build on: a frame saying the turn ended is never delivered before the reply that ended it. Turn the typing indicator off on it and the answer is already in hand. (It used to come from the conversation-metadata stream, a second connection with no ordering against the log, and the gap between them — milliseconds on a local socket, seconds through a proxy — showed a finished turn with nothing in it.)
80
+
81
+ For a long conversation that is the wrong trade: every reconnect re-delivers the whole log. Pass a cursor and the stream starts from there instead, leaving history to pagination:
82
+
83
+ ```ts
84
+ const page = await client.listMessagesPage(id, { limit: 50 });
85
+ setMessages(page.messages);
86
+
87
+ // Only what is newer than the page. Nothing is lost in between: the server
88
+ // catches up everything past the cursor before going live.
89
+ client.subscribe(id, { onMessage: append }, { since: page.latestChangeSeq ?? undefined });
90
+
91
+ // Scrolled to the top — the page before this one.
92
+ if (page.hasOlder) {
93
+ const older = await client.listMessagesPage(id, { before: page.oldestSeq, limit: 50 });
94
+ prepend(older.messages);
95
+ }
96
+ ```
97
+
98
+ `@cubos/agent-sdk-react` wires exactly this into `loadOlder` / `hasOlder`.
99
+
100
+ ### The log itself, when the conversation is not enough
101
+
102
+ The conversation surface hides the event log on purpose: a chat draws messages,
103
+ not `llm_call` rows. But hiding is not the same as withholding — the same
104
+ subscription carries both, and an app that needs the log gets it from the same
105
+ place:
106
+
107
+ ```ts
108
+ const page = await client.listEventsPage(id, { limit: 50 });
109
+
110
+ client.subscribe(id, {
111
+ onEvent: (e) => append(e), // every row, in stream order
112
+ onMessage: (m) => draw(m), // …and what it became, if you want both
113
+ }, { since: page.latestChangeSeq ?? undefined });
114
+ ```
115
+
116
+ One connection, one cursor, one catch-up. `onEvent` fires before the projections
117
+ see the frame, so an app taking both never has to reconcile them.
118
+
119
+ What differs is the promise, not the access. `Message | Todo | Activity` is
120
+ curated and stays put across internal refactors; `ConversationEvent` is the
121
+ server's own DTO and moves when the server does — the same trade the REST API
122
+ already offers, since these rows are what it returns. Reach for it to build an
123
+ operator console, an audit view or an activity tree; stay with `Message` to build
124
+ a chat.
125
+
126
+ `@cubos/agent-sdk-react` wires this into `useConversationEvents`.
127
+
128
+ ### Reopening is free
129
+
130
+ Conversations already loaded are kept in a bounded in-memory LRU, so opening one
131
+ a second time paints with no request:
132
+
133
+ ```ts
134
+ const start = await client.loadHistory(id); // start.fromCache on a repeat
135
+ client.subscribe(id, { onMessage: append, onCursor: (seq) => (cursor = seq) },
136
+ { since: start.latestChangeSeq ?? undefined });
137
+
138
+ // Hand the current state back whenever it moves; the next open resumes from here.
139
+ await client.saveHistory(id, { messages, oldestSeq, latestChangeSeq: cursor, hasOlder });
140
+ ```
141
+
142
+ **A hit is not stale.** The server's event log never rewrites a row's `content`,
143
+ `seq` or `type`, and every mutation that does happen — a tentative event being
144
+ consolidated or discarded, a delivery receipt — goes through a trigger that
145
+ issues a fresh `change_seq`. So subscribing with the cached cursor replays
146
+ exactly what changed while the app was away, inserts and updates alike.
147
+
148
+ Pass `cache: null` to switch it off, `cacheMessageLimit` to change how much is
149
+ kept per conversation (300 messages by default; older ones are dropped from the
150
+ cache, not the server, and come back through paging). To survive a reload,
151
+ implement `ConversationCache` over IndexedDB, AsyncStorage or SQLite:
152
+
153
+ ```ts
154
+ interface ConversationCache {
155
+ read(key: string): Promise<CachedConversation | null>;
156
+ write(key: string, entry: CachedConversation): Promise<void>;
157
+ clear(key?: string): Promise<void>;
158
+ }
159
+ ```
160
+
161
+ Keys arrive already scoped to tenant and user — a persistent store is shared by
162
+ every session on the device, and two users must not read each other's messages
163
+ out of it. Treat them as opaque. A store that rejects is treated as a miss, so a
164
+ corrupt cache degrades to the network rather than breaking the chat.
165
+
166
+ The tenant is resolved from the token via `GET /me` on the first call. Pass `tenant` in the options to skip that round-trip.
167
+
168
+ ### Pagination
169
+
170
+ When you want everything and would rather not manage a cursor:
171
+
172
+ ```ts
173
+ for await (const conversation of client.iterateConversations()) { … }
174
+
175
+ // The 20 most recent messages, newest first.
176
+ const recent = [];
177
+ for await (const message of client.iterateMessages(id)) {
178
+ recent.push(message);
179
+ if (recent.length === 20) break;
180
+ }
181
+ ```
182
+
183
+ Breaking out does not fetch the next page.
184
+
185
+ `pageSize` counts **events**, not messages: the log also holds the agent's tool calls and bookkeeping, so a page can hold fewer messages than you asked for. The iterator accounts for that; a page with no messages at all does not end the walk.
186
+
187
+ ### Response components
188
+
189
+ Your app can let the agent answer with **your** components — a price chart, a
190
+ date picker, an order card — instead of markdown alone. What the agent may use
191
+ comes from **component libraries**, which an operator authors over the admin API;
192
+ your app enables the ones the open screen can draw:
193
+
194
+ ```ts
195
+ const conversation = await client.createConversation({
196
+ agentSlug: "support",
197
+ componentLibraries: ["support-widgets"],
198
+ });
199
+ ```
200
+
201
+ `setComponentLibraries(id, slugs)` replaces the list at any time — send the
202
+ complete one; an empty list takes the agent back to plain markdown. A change
203
+ applies from the agent's next turn.
204
+
205
+ The split is a trust boundary. A component's summary, prop descriptions and
206
+ example are shown to the model **verbatim**, so writing them is writing prompt
207
+ content and needs an api_key; enabling a library is only wiring, which is why
208
+ this client — holding a short-lived end-user token — can do it and nothing more.
209
+
210
+ Both the enable call and its `list` counterpart answer with `tags`, every
211
+ component the enabled libraries resolve to, in the order the agent sees them
212
+ indexed. Check it against what you can actually render: a tag with no renderer is
213
+ a block the agent will happily write and nothing will draw.
214
+
215
+ Every agent message then arrives split into `blocks`, with props already parsed
216
+ and checked against the library's schema:
217
+
218
+ ```tsx
219
+ {message.blocks?.map((block, i) =>
220
+ block.type === "markdown" ? (
221
+ <Markdown key={i}>{block.text}</Markdown>
222
+ ) : (
223
+ <MyComponent key={i} tag={block.tag} {...block.props} />
224
+ ),
225
+ )}
226
+ ```
227
+
228
+ A component is **always self-closing** — there is no `<Tag>…</Tag>`. Whatever it
229
+ should display goes in a prop, and the markdown around it stays outside as its
230
+ own blocks:
231
+
232
+ ```jsonc
233
+ // leia **isto**
234
+ // <PriceChart symbol="PETR4" points={[1,2]} />
235
+ [
236
+ { "type": "markdown", "text": "leia **isto**" },
237
+ { "type": "component", "tag": "PriceChart", "props": { "symbol": "PETR4", "points": [1, 2] } }
238
+ ]
239
+ ```
240
+
241
+ Worth knowing:
242
+
243
+ - **`blocks` is always there on an agent message.** A reply with no component is
244
+ one `markdown` block, so you write one rendering path, not two.
245
+ - **The server enforces the contract.** A component no enabled library offers,
246
+ HTML, or props that miss the schema are refused before the message is written —
247
+ the agent gets the error and rewrites. Validation is the full JSON Schema, not
248
+ a shallow type check, and the refusal names the path that failed
249
+ (`points.0.v: "muito" is not of type "number"`), so the agent can fix it.
250
+ - **`content` is still the whole message** as the agent wrote it, if you would
251
+ rather render it yourself.
252
+ - **Blocks are derived when read, never stored.** Dropping a library does not
253
+ rewrite old messages, so keep a renderer for anything the transcript may still
254
+ contain.
255
+ - Libraries are enabled per conversation only where your app is the renderer.
256
+ Doing it on a channel-backed conversation (WhatsApp, Telegram) is refused — an
257
+ external channel declares its libraries on the channel instead.
258
+ - Interactivity is yours: the agent emits the block, your component handles the
259
+ click, and whatever it should mean goes back through `sendMessage`.
260
+ ### Live conversation list
261
+
262
+ ```ts
263
+ const page = await client.listConversations();
264
+ client.subscribeToConversations({ onConversation: (c) => upsertAndResort(c) });
265
+ ```
266
+
267
+ A conversation is re-emitted whenever its activity advances, which is what keeps
268
+ a chat list sorted without polling.
269
+
270
+ ### Images
271
+
272
+ ```ts
273
+ await client.sendImage(id, file, { caption: "is this the right charge?" });
274
+
275
+ // Up to 10 in one message, so the agent reasons over the set rather than one
276
+ // turn per picture. A label names an image for the model, which lets it answer
277
+ // about "the receipt" instead of "the second image".
278
+ await client.sendImages(id, [
279
+ { image: receipt, label: "receipt" },
280
+ { image: statement, label: "statement" },
281
+ ], { caption: "compare these" });
282
+ ```
283
+
284
+ png, jpeg, webp and gif, up to 10 MB each. The agent sees the picture natively when its model has vision, otherwise a description from the agent's fallback vision model — either way it costs a turn and is budgeted as one.
285
+
286
+ An image arrives as a `Message` whose `attachments` describe the media and whose `content` is the caption. **Sending without a caption is normal, so render `attachments` even when `content` is empty.** The bytes come back as a `Blob`, not a URL, because the token travels in a header — an `<img src>` pointing at the route would arrive unauthenticated:
287
+
288
+ ```ts
289
+ const blob = await client.fetchAttachment(conversationId, message.id, attachment.id);
290
+ img.src = URL.createObjectURL(blob); // revoke it when the element goes away
291
+ ```
292
+
293
+ When the fallback vision model describes a picture, that description stays out of
294
+ the transcript: it is the model talking to itself about something already on
295
+ screen, not a message the user sent.
296
+
297
+ ### Voice messages
298
+
299
+ ```ts
300
+ await client.sendAudio(conversation.id, blobFromMediaRecorder);
301
+ ```
302
+
303
+ The clip is transcribed by the agent's STT model before the turn runs. It arrives as a `Message` carrying one `audio` attachment, with `content` holding the transcription — empty until STT finishes, which is the window where a client shows the player with no text under it yet. Bytes for playback come from `fetchAttachment` too.
304
+
305
+ ### Client tools
306
+
307
+ Tools with no server-side implementation: the agent calls one, **its turn
308
+ suspends**, and your app answers.
309
+
310
+ ```ts
311
+ const session = await client.serveClientTools(id, {
312
+ tools: {
313
+ get_location: {
314
+ description: "Where the user is, as their device reports it.",
315
+ inputSchema: { type: "object", properties: {} },
316
+ readOnlyHint: true,
317
+ handler: async () => ({ city: "Fortaleza" }),
318
+ },
319
+ },
320
+ });
321
+ ```
322
+
323
+ That declares the set, watches the conversation for calls, leases each one so two
324
+ tabs don't run it twice, renews the lease while a slow handler works, and retries
325
+ the result POST — the answer is worth more than one attempt once the side effect
326
+ has happened.
327
+
328
+ It opens its own event stream to do the watching. **If you already subscribe to
329
+ the conversation**, don't pay for a second connection:
330
+
331
+ ```ts
332
+ const session = await client.serveClientTools(id, { tools, watch: false });
333
+ client.subscribe(id, {
334
+ onOpen: () => session.poke(), // catches up after a (re)connect
335
+ onClientToolCall: () => session.poke(), // and on every new call
336
+ onMessage: append,
337
+ });
338
+ ```
339
+
340
+ `setClientTools` declares without running anything — useful right before the
341
+ first message, since a tool the agent was never told about can't be called in the
342
+ turn that follows. `@cubos/agent-sdk-react` does all of this for you.
343
+
344
+ ## Conversation surface
345
+
346
+ | | |
347
+ |---|---|
348
+ | `me()` / `refreshIdentity()` | the token's user, tenant and agents |
349
+ | `listConversations()` / `iterateConversations()` | one page with a cursor, or all of them |
350
+ | `createConversation()` | opens a conversation with an agent |
351
+ | `getConversation()` / `renameConversation()` / `archiveConversation()` | |
352
+ | `listMessages()` / `iterateMessages()` | history |
353
+ | `listMessagesPage()` | history plus the cursors for paging back and for `subscribe({ since })` |
354
+ | `loadHistory()` / `saveHistory()` / `forgetHistory()` | the conversation cache |
355
+ | `sendMessage()` / `sendAudio()` | the user's message, typed or spoken |
356
+ | `sendImage()` / `sendImages()` | one image, or up to 10 in one message |
357
+ | `setClientTools()` / `serveClientTools()` | declare your functions, and run the calls |
358
+ | `setComponentLibraries()` / `listComponentLibraries()` | which component libraries the agent may use |
359
+ | `fetchAttachment()` | an attachment's bytes, as a `Blob` |
360
+ | `steer()` | injects an instruction mid-turn |
361
+ | `subscribe()` / `subscribeToConversations()` | live state |
362
+
363
+ All of them accept an `AbortSignal`.
364
+
365
+ The exposed types (`Conversation`, `Message`, `Attachment`, `Block`,
366
+ `EnabledComponents`, `Todo`, `Activity`) are a curated
367
+ surface, narrower than the server's DTOs: the event log has dozens of types that
368
+ exist for the operator dashboard, and pinning those here would make every
369
+ internal refactor a breaking change.
370
+
371
+ ## Admin
372
+
373
+ The whole API, authenticated with an api_key, namespaced by resource and scoped
374
+ per tenant:
375
+
376
+ ```ts
377
+ import { createAdminClient } from "@cubos/agent-sdk";
378
+
379
+ const admin = createAdminClient({ baseUrl: "https://agent.acme.com", apiKey });
380
+
381
+ await admin.tenant("acme").agents.list();
382
+ await admin.tenant("acme").users.block(userId);
383
+ await admin.tenants.list();
384
+ await admin.apiKeys.rotate(keyId);
385
+ ```
386
+
387
+ Types come from the server's OpenAPI document (`Schemas["Agent"]`, …),
388
+ regenerated on every route change — so the client tracks the server on its own.
389
+ `admin.raw` is the escape hatch for a route not yet wrapped.
390
+
391
+ **Server-side only.** An api_key is tenant-wide: it can read and write every
392
+ conversation of every user in the tenant. Use `createUserClient` in a browser.
393
+
394
+ ## Errors
395
+
396
+ Everything the SDK throws extends `AgentError`, so one `catch` covers the two
397
+ cases that matter:
398
+
399
+ ```ts
400
+ try {
401
+ await admin.tenant("acme").agents.get("support");
402
+ } catch (err) {
403
+ if (err instanceof AgentNetworkError) {
404
+ // Never reached the server: wrong URL, server down, CORS, or the timeout
405
+ // (`err.timedOut`). `err.cause` holds the original failure.
406
+ } else if (err instanceof AgentApiError) {
407
+ // The server answered, and refused.
408
+ if (err.isNotFound) …
409
+ if (err.isConflict) … // 409
410
+ if (err.isAuthError) … // 401/403
411
+ if (err.isRetryable) … // 429 or 5xx
412
+ }
413
+ }
414
+ ```
415
+
416
+ `AgentApiError` carries `status`, `requestId` and the verbatim `body`
417
+ (`err.json()` to parse it). `AgentConfigError` comes out of client construction
418
+ rather than a call — usually a `baseUrl` with no `http://`.
419
+
420
+ Every request has a 30s timeout; change it with `timeoutMs`, or `0` to disable.
421
+ Streams are exempt, since staying open is the point.
422
+
423
+ ### Rate limits
424
+
425
+ The server budgets end-user tokens per instance: roughly 20 agent turns and 300
426
+ other calls per minute, plus a cap on concurrent streams. A refusal is a 429 with
427
+ `Retry-After`, and **the SDK handles it** — it waits the advertised interval and
428
+ replays the request, twice by default. A 429 means refused, not half-applied, so
429
+ replaying a `POST` is safe.
430
+
431
+ It gives up and throws when the retries run out, when there is no `Retry-After`
432
+ to honour, or when the wait would exceed 20 seconds — blocking a caller for
433
+ minutes is worse than telling them now. `maxRetries: 0` opts out. An abort during
434
+ the wait cancels the retry rather than finishing it.
435
+
436
+ api_keys are exempt from all of this.
437
+
438
+ Transient stream failures do not end a subscription: the SDK reconnects with
439
+ exponential backoff and reports them through `onError`, for logging or a
440
+ "reconnecting" hint. A 4xx on a stream is final and closes it.
441
+
442
+ ## Also in the package
443
+
444
+ - **`@cubos/agent-sdk/sse`** — the Server-Sent Events reader on its own (reconnect, cursor replay, backoff), for consumers who talk to the API directly and want just that piece.
445
+ - **`@cubos/agent-sdk-react`** — the same conversation surface as React hooks, in a separate package. Hooks only: it renders nothing, so the chat UI stays yours.
446
+ - **`@cubos/agent-sdk-react-dom`** — the few components that are generic enough to share, starting with the agent's markdown. Web only, and it styles itself.
@@ -0,0 +1,191 @@
1
+ import type { Transport } from "../http.js";
2
+ import type { Schemas } from "../schemas.js";
3
+ export declare function agentsApi(t: Transport, tenantSlug: string): {
4
+ list: (signal?: AbortSignal) => Promise<{
5
+ ai_provider: import("../generated/schema.js").components["schemas"]["AiProvider"];
6
+ created_at: string;
7
+ description: string;
8
+ display_name: string;
9
+ feature_code_execution: boolean;
10
+ feature_guardrail: boolean;
11
+ feature_structured_memory: boolean;
12
+ feature_subagent: boolean;
13
+ feature_task_planning: boolean;
14
+ feature_user_notes: boolean;
15
+ feature_web_search: boolean;
16
+ feature_workspace: boolean;
17
+ id: string;
18
+ model: import("../generated/schema.js").components["schemas"]["AiProviderModel"];
19
+ slug: string;
20
+ updated_at: string;
21
+ }[]>;
22
+ get: (slug: string, signal?: AbortSignal) => Promise<{
23
+ agents_md: string;
24
+ ai_provider: import("../generated/schema.js").components["schemas"]["AiProvider"];
25
+ allowed_subagent_slugs: string[];
26
+ capabilities_md: string;
27
+ code_execution_timeout_seconds: number;
28
+ compaction_threshold_pct: number;
29
+ created_at: string;
30
+ description: string;
31
+ display_name: string;
32
+ embedding_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
33
+ embedding_model_dimensions: number | null;
34
+ error_fallback_message: string | null;
35
+ feature_code_execution: boolean;
36
+ feature_guardrail: boolean;
37
+ feature_proactive_memory: boolean;
38
+ feature_structured_memory: boolean;
39
+ feature_subagent: boolean;
40
+ feature_task_planning: boolean;
41
+ feature_user_notes: boolean;
42
+ feature_web_search: boolean;
43
+ feature_workspace: boolean;
44
+ guardrail_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
45
+ guardrail_prompt: string | null;
46
+ guardrail_reasoning_level: string | null;
47
+ id: string;
48
+ identity_md: string;
49
+ max_messages_per_turn: number;
50
+ max_turns: number;
51
+ model: import("../generated/schema.js").components["schemas"]["AiProviderModel"];
52
+ nap_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
53
+ nap_reasoning_level: string | null;
54
+ onboarding_md: string;
55
+ reasoning_level: string | null;
56
+ slug: string;
57
+ soul_md: string;
58
+ stt_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
59
+ updated_at: string;
60
+ vision_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
61
+ websearch_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
62
+ }>;
63
+ create: (input: Schemas["CreateAgentInput"]) => Promise<{
64
+ agents_md: string;
65
+ ai_provider: import("../generated/schema.js").components["schemas"]["AiProvider"];
66
+ allowed_subagent_slugs: string[];
67
+ capabilities_md: string;
68
+ code_execution_timeout_seconds: number;
69
+ compaction_threshold_pct: number;
70
+ created_at: string;
71
+ description: string;
72
+ display_name: string;
73
+ embedding_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
74
+ embedding_model_dimensions: number | null;
75
+ error_fallback_message: string | null;
76
+ feature_code_execution: boolean;
77
+ feature_guardrail: boolean;
78
+ feature_proactive_memory: boolean;
79
+ feature_structured_memory: boolean;
80
+ feature_subagent: boolean;
81
+ feature_task_planning: boolean;
82
+ feature_user_notes: boolean;
83
+ feature_web_search: boolean;
84
+ feature_workspace: boolean;
85
+ guardrail_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
86
+ guardrail_prompt: string | null;
87
+ guardrail_reasoning_level: string | null;
88
+ id: string;
89
+ identity_md: string;
90
+ max_messages_per_turn: number;
91
+ max_turns: number;
92
+ model: import("../generated/schema.js").components["schemas"]["AiProviderModel"];
93
+ nap_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
94
+ nap_reasoning_level: string | null;
95
+ onboarding_md: string;
96
+ reasoning_level: string | null;
97
+ slug: string;
98
+ soul_md: string;
99
+ stt_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
100
+ updated_at: string;
101
+ vision_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
102
+ websearch_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
103
+ }>;
104
+ update: (currentSlug: string, input: Schemas["UpdateAgentInput"]) => Promise<{
105
+ agents_md: string;
106
+ ai_provider: import("../generated/schema.js").components["schemas"]["AiProvider"];
107
+ allowed_subagent_slugs: string[];
108
+ capabilities_md: string;
109
+ code_execution_timeout_seconds: number;
110
+ compaction_threshold_pct: number;
111
+ created_at: string;
112
+ description: string;
113
+ display_name: string;
114
+ embedding_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
115
+ embedding_model_dimensions: number | null;
116
+ error_fallback_message: string | null;
117
+ feature_code_execution: boolean;
118
+ feature_guardrail: boolean;
119
+ feature_proactive_memory: boolean;
120
+ feature_structured_memory: boolean;
121
+ feature_subagent: boolean;
122
+ feature_task_planning: boolean;
123
+ feature_user_notes: boolean;
124
+ feature_web_search: boolean;
125
+ feature_workspace: boolean;
126
+ guardrail_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
127
+ guardrail_prompt: string | null;
128
+ guardrail_reasoning_level: string | null;
129
+ id: string;
130
+ identity_md: string;
131
+ max_messages_per_turn: number;
132
+ max_turns: number;
133
+ model: import("../generated/schema.js").components["schemas"]["AiProviderModel"];
134
+ nap_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
135
+ nap_reasoning_level: string | null;
136
+ onboarding_md: string;
137
+ reasoning_level: string | null;
138
+ slug: string;
139
+ soul_md: string;
140
+ stt_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
141
+ updated_at: string;
142
+ vision_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
143
+ websearch_model: null | import("../generated/schema.js").components["schemas"]["AiProviderModel"];
144
+ }>;
145
+ delete: (slug: string) => Promise<void>;
146
+ countTokens: (slug: string, input: Schemas["DraftAgentConfig"], signal?: AbortSignal) => Promise<{
147
+ breakdown: null | import("../generated/schema.js").components["schemas"]["Breakdown"];
148
+ context_window_tokens: number | null;
149
+ contributions: {
150
+ [key: string]: number;
151
+ };
152
+ error: string | null;
153
+ max_input_tokens: number | null;
154
+ skill_count: number;
155
+ tool_count: number;
156
+ total: number | null;
157
+ }>;
158
+ listMcps: (agentSlug: string, signal?: AbortSignal) => Promise<{
159
+ enabled: boolean;
160
+ enabled_tools: string[] | null;
161
+ mcp: import("../generated/schema.js").components["schemas"]["McpRef"];
162
+ role_configs: import("../generated/schema.js").components["schemas"]["AgentMcpRoleConfig"][];
163
+ }[]>;
164
+ addMcp: (agentSlug: string, mcpSlug: string) => Promise<void>;
165
+ removeMcp: (agentSlug: string, mcpSlug: string) => Promise<void>;
166
+ /** `null` restores "all of the MCP's tools"; a list narrows to those. */
167
+ updateMcpTools: (agentSlug: string, mcpSlug: string, enabledTools: string[] | null) => Promise<void>;
168
+ replaceMcpRoles: (agentSlug: string, mcpSlug: string, roleSlugs: string[]) => Promise<void>;
169
+ updateMcpRoleTools: (agentSlug: string, mcpSlug: string, roleSlug: string, enabledTools: string[] | null) => Promise<void>;
170
+ listSkills: (agentSlug: string, signal?: AbortSignal) => Promise<{
171
+ enabled: boolean;
172
+ role_slugs: string[];
173
+ skill: import("../generated/schema.js").components["schemas"]["SkillRef"];
174
+ }[]>;
175
+ addSkill: (agentSlug: string, skillSlug: string) => Promise<void>;
176
+ removeSkill: (agentSlug: string, skillSlug: string) => Promise<void>;
177
+ replaceSkillRoles: (agentSlug: string, skillSlug: string, roleSlugs: string[]) => Promise<void>;
178
+ listTaskTemplates: (agentSlug: string, signal?: AbortSignal) => Promise<{
179
+ enabled: boolean;
180
+ role_slugs: string[];
181
+ task_template: import("../generated/schema.js").components["schemas"]["TaskTemplateRef"];
182
+ }[]>;
183
+ addTaskTemplate: (agentSlug: string, templateSlug: string) => Promise<void>;
184
+ removeTaskTemplate: (agentSlug: string, templateSlug: string) => Promise<void>;
185
+ replaceTaskTemplateRoles: (agentSlug: string, templateSlug: string, roleSlugs: string[]) => Promise<void>;
186
+ listAutoCallTools: (agentSlug: string, signal?: AbortSignal) => Promise<{
187
+ available: import("../generated/schema.js").components["schemas"]["AutoCallableTool"][];
188
+ enabled_mcp_count: number;
189
+ }>;
190
+ putAutoCallTools: (agentSlug: string, tools: Schemas["AutoCallToolRef"][]) => Promise<void>;
191
+ };