@economic/agents 2.4.0 → 2.6.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/README.md CHANGED
@@ -514,6 +514,39 @@ protected async getUserContext(jwtToken: string) {
514
514
  }
515
515
  ```
516
516
 
517
+ If `getUserContext` throws, the socket closes with `1011` — or, when the thrown `Error` carries a numeric `closeCode` in the `4000`–`4999` range, with that code and an `"unauthorized"` status frame:
518
+
519
+ ```typescript
520
+ const error = new Error("No Eva access");
521
+ (error as Error & { closeCode: number }).closeCode = 4003;
522
+ throw error;
523
+ ```
524
+
525
+ ### Token exchange
526
+
527
+ For agents whose clients connect with a _down-scoped_ token — one only valid for the client ↔ agent hop — override the static `getTokenExchangeConfig(env)` to exchange it (RFC 8693) at the identity server for a token issued to the agent's own OAuth client. Requires `getJwtAuthConfig`.
528
+
529
+ ```typescript
530
+ export class SupportAgent extends Agent {
531
+ static getTokenExchangeConfig(env: Cloudflare.Env) {
532
+ return {
533
+ tokenUrl: `${env.IDENTITY_ENDPOINT}/connect/token`,
534
+ clientId: env.OAUTH_CLIENT_ID,
535
+ clientSecret: env.OAUTH_CLIENT_SECRET, // string or Secrets Store binding
536
+ scopes: "api.read api.write", // always sent explicitly
537
+ tokenHeader: "X-EconomicToken", // default
538
+ };
539
+ }
540
+ }
541
+ ```
542
+
543
+ When configured:
544
+
545
+ - The verified connect token is exchanged on every connect; the exchanged token is what `getUserContext` receives and what tools see as `ctx.tokens[tokenHeader]` each turn. **Client-sent request-body tokens for that key are ignored** — tool credentials always derive from the verified connection identity, so clients should stop sending them.
546
+ - Renewal is connection-based. The client token rides in the WebSocket protocols, so presenting a renewed token always means a reconnect, which re-verifies and re-exchanges. Keep the `authToken` passed to the hooks current (refresh it before expiry) and the socket rotates silently — the `unauthorized` close (`4001`) then only happens when the session is genuinely dead (logout, revocation).
547
+ - The exchanged token expires on the identity server's clock (with a 30s margin). A turn arriving after expiry closes the connection `4001` to force a reconnect; a client that refreshes its token on time never hits this.
548
+ - Exchange failures at connect: `invalid_grant` (dead session token) → status `"unauthorized"` + close `4001`; anything else (identity server down, misconfigured client) → status `"error"` + close `1011`. Partial configuration is the app's concern — throw from `getTokenExchangeConfig` if you consider it a deployment fault, or return `undefined` to disable the exchange.
549
+
517
550
  ## Observability
518
551
 
519
552
  Emitted per turn from the `ai` SDK's OpenTelemetry spans, no setup beyond bindings:
package/dist/index.d.mts CHANGED
@@ -26,6 +26,61 @@ type SqlFn = InstanceType<typeof Agent$1>["sql"];
26
26
  type AgentConnectionStatus = "connecting" | "connected" | "disconnected" | "unauthorized" | "error";
27
27
  type AgentConnectionType = "agent" | "chat" | "assistant";
28
28
  //#endregion
29
+ //#region src/server/features/auth/tokenExchange.d.ts
30
+ interface TokenExchangeConfig {
31
+ /** Identity server token endpoint, e.g. `${identityOrigin}/connect/token`. */
32
+ tokenUrl: string;
33
+ /** OAuth client the agent authenticates as. */
34
+ clientId: string;
35
+ /**
36
+ * Plain secret, or a binding resolved with `get()` (e.g. a Cloudflare
37
+ * Secrets Store binding). Resolved on every exchange so rotation is
38
+ * picked up without a redeploy.
39
+ */
40
+ clientSecret: string | {
41
+ get(): Promise<string>;
42
+ };
43
+ /**
44
+ * Space-separated scopes for the exchanged token. Always sent explicitly:
45
+ * omitting `scope` would make the identity server issue every scope the
46
+ * client is allowed, the opposite of least privilege.
47
+ */
48
+ scopes: string;
49
+ /**
50
+ * Key under the request-context `tokens` record that tools read the token
51
+ * from. Defaults to {@link DEFAULT_TOKEN_HEADER}.
52
+ */
53
+ tokenHeader?: string;
54
+ }
55
+ interface ExchangedToken {
56
+ accessToken: string;
57
+ /** Unix ms; already includes {@link EXPIRY_MARGIN_MS}. */
58
+ expiresAt: number;
59
+ }
60
+ type TokenExchangeResult = ({
61
+ success: true;
62
+ } & ExchangedToken) | {
63
+ success: false;
64
+ status: number;
65
+ errorCode: string;
66
+ message: string;
67
+ /**
68
+ * True when the identity server rejected the subject token itself
69
+ * (RFC 6749 `invalid_grant`: expired or revoked). Re-authenticating is
70
+ * the only fix — other failures (`invalid_client`, `invalid_scope`,
71
+ * 5xx) are deployment or identity-server faults the user cannot fix by
72
+ * logging in again.
73
+ */
74
+ subjectTokenRejected: boolean;
75
+ };
76
+ /**
77
+ * Exchange `subjectToken` for a token issued to the agent's own OAuth client.
78
+ *
79
+ * Expected identity-server rejections return a failure result; unexpected
80
+ * failures (network error, timeout, malformed success response) throw.
81
+ */
82
+ declare function exchangeToken(subjectToken: string, config: TokenExchangeConfig): Promise<TokenExchangeResult>;
83
+ //#endregion
29
84
  //#region src/server/util/tools.d.ts
30
85
  type ToolSet = Record<string, Tool>;
31
86
  type Tool<Context extends Record<string, unknown> = Record<string, unknown>, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any> = Tool$1<INPUT, OUTPUT> & {
@@ -141,6 +196,41 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
141
196
  * @returns JWT auth config or undefined to skip authentication
142
197
  */
143
198
  static getJwtAuthConfig?(env: Cloudflare.Env): JwtAuthConfig<Record<string, unknown>> | undefined;
199
+ /**
200
+ * Override to exchange the verified connect token (RFC 8693) for a token
201
+ * issued to this agent's own OAuth client. Requires {@link getJwtAuthConfig}.
202
+ *
203
+ * When configured, the exchanged token is passed to {@link getUserContext}
204
+ * and injected into the tool context every turn under
205
+ * `tokens[config.tokenHeader]` — client-sent request-body tokens for that
206
+ * key are ignored. Renewal is connection-based: a client presenting a new
207
+ * token reconnects, and the connect flow re-verifies and re-exchanges.
208
+ *
209
+ * Do not add side effects to this method - return a single config from it,
210
+ * or undefined to disable the exchange.
211
+ *
212
+ * @param env - The worker environment variables
213
+ * @returns Token exchange config or undefined to disable
214
+ */
215
+ static getTokenExchangeConfig?(env: Cloudflare.Env): TokenExchangeConfig | undefined;
216
+ /**
217
+ * The exchanged token for this agent, refreshed on every authenticated
218
+ * connect. Kept in memory for the hot path and mirrored to DO storage so
219
+ * turns that run after hibernation (or without a connection at all) still
220
+ * have it. Short-lived by construction — at most the identity server's
221
+ * token lifetime.
222
+ */
223
+ private _exchangedToken?;
224
+ private _storeExchangedToken;
225
+ private _getExchangedToken;
226
+ /**
227
+ * Swaps the client-sent token for the exchanged one in a copy of the turn
228
+ * body. No-op when token exchange is not configured. When the exchanged
229
+ * token is missing or expired the turn cannot run: only a reconnect can
230
+ * deliver a fresh subject token, so the current connection (if any) is
231
+ * closed to force one, and the turn fails.
232
+ */
233
+ private _applyTokenExchange;
144
234
  /**
145
235
  * The user context for the session.
146
236
  * Define getUserContext to set a user context.
@@ -286,6 +376,7 @@ type Chat = {
286
376
  durable_object_name: string;
287
377
  title?: string;
288
378
  summary?: string;
379
+ title_set_by_user: number;
289
380
  created_at: number;
290
381
  updated_at: number;
291
382
  positive_feedback_count: number;
@@ -302,6 +393,7 @@ declare abstract class Assistant<State = unknown> extends Agent$1<Cloudflare.Env
302
393
  onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
303
394
  createChat(): Promise<string>;
304
395
  deleteChat(id: string): Promise<void>;
396
+ renameChat(id: string, title: string): Promise<void>;
305
397
  getChats(): Promise<Chat[]>;
306
398
  getChatAgentClassName(): Promise<string>;
307
399
  recordChatTurn(durableObjectName: string, messages: UIMessage[]): Promise<void>;
@@ -363,4 +455,4 @@ declare function skill<Context extends Record<string, unknown> = Record<string,
363
455
  //#region src/server/index.d.ts
364
456
  declare function getCurrentToolContext(): any;
365
457
  //#endregion
366
- export { Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, Assistant, ChatAgent, type FacetStub, type LegacyChatStub, type LegacyMessageFeedback, type MigrationDeps, type Skill, type SkillSource, type Tool, type ToolContext, type ToolSet, type TurnSnapshot, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
458
+ export { Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, Assistant, ChatAgent, type ExchangedToken, type FacetStub, type LegacyChatStub, type LegacyMessageFeedback, type MigrationDeps, type Skill, type SkillSource, type TokenExchangeConfig, type TokenExchangeResult, type Tool, type ToolContext, type ToolSet, type TurnSnapshot, exchangeToken, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
package/dist/index.mjs CHANGED
@@ -4,6 +4,97 @@ import { Think, skills } from "@cloudflare/think";
4
4
  import { Output, convertToModelMessages, generateText, jsonSchema, pruneMessages, tool as tool$1 } from "ai";
5
5
  import { nanoid } from "nanoid";
6
6
  import { createCompactFunction } from "agents/experimental/memory/utils";
7
+ //#region src/server/features/auth/tokenExchange.ts
8
+ /**
9
+ * OAuth 2.0 Token Exchange (RFC 8693) against an identity server.
10
+ *
11
+ * The client connects with a token down-scoped for the client ↔ agent hop only.
12
+ * When an agent defines `static getTokenExchangeConfig`, the verified connect
13
+ * token is exchanged — authenticating as the agent's own OAuth client — for a
14
+ * token carrying the scopes the agent needs against backends. Backends then see
15
+ * the agent's `client_id` and can attribute calls to it, and the browser never
16
+ * holds a backend-capable credential.
17
+ *
18
+ * Expected failures (the identity server rejecting the request) return a result
19
+ * object; unexpected failures (network errors, malformed responses) throw. Same
20
+ * contract as `verifyJwt` in ./index.ts.
21
+ */
22
+ const TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
23
+ const ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
24
+ const TOKEN_EXCHANGE_TIMEOUT_MS = 3e4;
25
+ /** Exchanged tokens are treated as expired this long before they actually are. */
26
+ const EXPIRY_MARGIN_MS = 3e4;
27
+ /**
28
+ * Exchange `subjectToken` for a token issued to the agent's own OAuth client.
29
+ *
30
+ * Expected identity-server rejections return a failure result; unexpected
31
+ * failures (network error, timeout, malformed success response) throw.
32
+ */
33
+ async function exchangeToken(subjectToken, config) {
34
+ const clientSecret = typeof config.clientSecret === "string" ? config.clientSecret : await config.clientSecret.get();
35
+ const body = new URLSearchParams({
36
+ client_id: config.clientId,
37
+ client_secret: clientSecret,
38
+ grant_type: TOKEN_EXCHANGE_GRANT_TYPE,
39
+ subject_token: subjectToken,
40
+ subject_token_type: ACCESS_TOKEN_TYPE,
41
+ scope: config.scopes
42
+ });
43
+ const response = await fetch(config.tokenUrl, {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
46
+ body: body.toString(),
47
+ signal: AbortSignal.timeout(TOKEN_EXCHANGE_TIMEOUT_MS)
48
+ });
49
+ if (!response.ok) {
50
+ let errorCode = "unknown_error";
51
+ let description;
52
+ try {
53
+ const json = await response.json();
54
+ errorCode = json.error ?? errorCode;
55
+ description = json.error_description;
56
+ } catch {}
57
+ return {
58
+ success: false,
59
+ status: response.status,
60
+ errorCode,
61
+ message: `Token exchange failed with status ${response.status} (${errorCode})${description ? `: ${description}` : ""}`,
62
+ subjectTokenRejected: response.status === 400 && errorCode === "invalid_grant"
63
+ };
64
+ }
65
+ let json;
66
+ try {
67
+ json = await response.json();
68
+ } catch {
69
+ throw new Error("Token exchange response body was not valid JSON");
70
+ }
71
+ if (!json.access_token) throw new Error("Token exchange response contained no access_token");
72
+ if (typeof json.expires_in !== "number") throw new Error("Token exchange response contained no expires_in");
73
+ return {
74
+ success: true,
75
+ accessToken: json.access_token,
76
+ expiresAt: Date.now() + Math.max(0, json.expires_in * 1e3 - EXPIRY_MARGIN_MS)
77
+ };
78
+ }
79
+ /**
80
+ * Returns a copy of the request body with the exchanged token injected under
81
+ * `tokens[tokenHeader]`, overriding any client-sent value for that key.
82
+ *
83
+ * Never mutates `body`: think persists the original request body
84
+ * (`_lastBody`) and replays it on continuation turns, so a mutation would
85
+ * persist the exchanged token and replay it as a subject token later.
86
+ */
87
+ function withExchangedToken(body, tokenHeader, accessToken) {
88
+ const tokens = body?.tokens ?? {};
89
+ return {
90
+ ...body,
91
+ tokens: {
92
+ ...tokens,
93
+ [tokenHeader]: accessToken
94
+ }
95
+ };
96
+ }
97
+ //#endregion
7
98
  //#region src/server/util/tools.ts
8
99
  function tool(tool) {
9
100
  return tool$1(tool);
@@ -148,6 +239,18 @@ function buildSourcesTransform(additional) {
148
239
  }
149
240
  //#endregion
150
241
  //#region src/server/agents/Agent.ts
242
+ const EXCHANGED_TOKEN_STORAGE_KEY = "economic:exchangedToken";
243
+ /**
244
+ * Close code for a getUserContext failure. Subclasses signal an intentional
245
+ * auth-style rejection by throwing an Error carrying a numeric `closeCode`
246
+ * in the WebSocket application range (4000-4999); anything else is treated
247
+ * as an internal error (1011).
248
+ */
249
+ function getConnectCloseCode(error) {
250
+ const closeCode = error?.closeCode;
251
+ if (typeof closeCode === "number" && closeCode >= 4e3 && closeCode <= 4999) return closeCode;
252
+ return 1011;
253
+ }
151
254
  var Agent = class extends Think {
152
255
  agentType = "agent";
153
256
  clientIp;
@@ -207,7 +310,45 @@ var Agent = class extends Think {
207
310
  return;
208
311
  }
209
312
  const token = extractTokenFromConnectRequest(ctx.request);
210
- if (token && this.getUserContext) connection.setState({ userContext: await this.getUserContext(token) });
313
+ let effectiveToken = token;
314
+ const getTokenExchangeConfig = this.constructor.getTokenExchangeConfig;
315
+ const exchangeConfig = getTokenExchangeConfig?.(this.env);
316
+ if (token && exchangeConfig) {
317
+ let result;
318
+ try {
319
+ result = await exchangeToken(token, exchangeConfig);
320
+ } catch (error) {
321
+ sendConnectionStatus(connection, "error");
322
+ console.error(`[Agent] Token exchange error - ${error}`);
323
+ connection.close(1011, "Token exchange failed");
324
+ return;
325
+ }
326
+ if (!result.success) {
327
+ console.error(`[Agent] Token exchange failed - ${result.message}`);
328
+ if (result.subjectTokenRejected) {
329
+ sendConnectionStatus(connection, "unauthorized");
330
+ connection.close(4001, "Unauthorized");
331
+ } else {
332
+ sendConnectionStatus(connection, "error");
333
+ connection.close(1011, "Token exchange failed");
334
+ }
335
+ return;
336
+ }
337
+ await this._storeExchangedToken({
338
+ accessToken: result.accessToken,
339
+ expiresAt: result.expiresAt
340
+ });
341
+ effectiveToken = result.accessToken;
342
+ }
343
+ if (effectiveToken && this.getUserContext) try {
344
+ connection.setState({ userContext: await this.getUserContext(effectiveToken) });
345
+ } catch (error) {
346
+ const closeCode = getConnectCloseCode(error);
347
+ sendConnectionStatus(connection, closeCode === 1011 ? "error" : "unauthorized");
348
+ console.error(`[Agent] getUserContext error - ${error}`);
349
+ connection.close(closeCode, "Could not establish user context");
350
+ return;
351
+ }
211
352
  }
212
353
  }
213
354
  sendConnectionStatus(connection, "connected");
@@ -231,7 +372,8 @@ var Agent = class extends Think {
231
372
  */
232
373
  async beforeTurn(ctx) {
233
374
  this.registerInstanceActivity();
234
- this._toolContextForCurrentTurn = this.getToolContextForCurrentTurn(ctx.body);
375
+ const body = await this._applyTokenExchange(ctx.body);
376
+ this._toolContextForCurrentTurn = this.getToolContextForCurrentTurn(body);
235
377
  this._toolsForCurrentTurn = ctx.tools;
236
378
  const activeSkillName = this._getLastActivatedSkillName(ctx.messages);
237
379
  const { tools, activeTools } = await this._getAuthorizedTools(ctx.tools, activeSkillName);
@@ -424,6 +566,48 @@ var Agent = class extends Think {
424
566
  }
425
567
  }
426
568
  /**
569
+ * The exchanged token for this agent, refreshed on every authenticated
570
+ * connect. Kept in memory for the hot path and mirrored to DO storage so
571
+ * turns that run after hibernation (or without a connection at all) still
572
+ * have it. Short-lived by construction — at most the identity server's
573
+ * token lifetime.
574
+ */
575
+ _exchangedToken;
576
+ async _storeExchangedToken(token) {
577
+ this._exchangedToken = token;
578
+ await this.ctx.storage.put(EXCHANGED_TOKEN_STORAGE_KEY, token);
579
+ }
580
+ async _getExchangedToken() {
581
+ if (!this._exchangedToken) this._exchangedToken = await this.ctx.storage.get(EXCHANGED_TOKEN_STORAGE_KEY);
582
+ if (this._exchangedToken && this._exchangedToken.expiresAt <= Date.now()) {
583
+ this._exchangedToken = void 0;
584
+ await this.ctx.storage.delete(EXCHANGED_TOKEN_STORAGE_KEY);
585
+ }
586
+ return this._exchangedToken;
587
+ }
588
+ /**
589
+ * Swaps the client-sent token for the exchanged one in a copy of the turn
590
+ * body. No-op when token exchange is not configured. When the exchanged
591
+ * token is missing or expired the turn cannot run: only a reconnect can
592
+ * deliver a fresh subject token, so the current connection (if any) is
593
+ * closed to force one, and the turn fails.
594
+ */
595
+ async _applyTokenExchange(body) {
596
+ const getTokenExchangeConfig = this.constructor.getTokenExchangeConfig;
597
+ const exchangeConfig = getTokenExchangeConfig?.(this.env);
598
+ if (!exchangeConfig) return body;
599
+ const token = await this._getExchangedToken();
600
+ if (!token) {
601
+ const { connection } = getCurrentAgent();
602
+ if (connection) {
603
+ sendConnectionStatus(connection, "unauthorized");
604
+ connection.close(4001, "Unauthorized");
605
+ }
606
+ throw new Error("Exchanged token missing or expired - the client must reconnect");
607
+ }
608
+ return withExchangedToken(body, exchangeConfig.tokenHeader ?? "X-EconomicToken", token.accessToken);
609
+ }
610
+ /**
427
611
  * The user context for the session.
428
612
  * Define getUserContext to set a user context.
429
613
  */
@@ -504,6 +688,7 @@ function ensureChatsTableExists(sql) {
504
688
  durable_object_name TEXT NOT NULL,
505
689
  title TEXT,
506
690
  summary TEXT,
691
+ title_set_by_user INTEGER NOT NULL DEFAULT 0,
507
692
  created_at INTEGER NOT NULL,
508
693
  updated_at INTEGER NOT NULL,
509
694
  positive_feedback_count INTEGER NOT NULL DEFAULT 0,
@@ -511,6 +696,7 @@ function ensureChatsTableExists(sql) {
511
696
  PRIMARY KEY (durable_object_name)
512
697
  )`;
513
698
  ensureChatsFeedbackColumnsExist(sql);
699
+ ensureChatsTitleSetByUserColumnExists(sql);
514
700
  ensureChatMessageFeedbackTableExists(sql);
515
701
  } catch (error) {
516
702
  console.error("[Agent] Failed to create chats table", error);
@@ -524,6 +710,11 @@ function ensureChatsFeedbackColumnsExist(sql) {
524
710
  sql`ALTER TABLE chats ADD COLUMN negative_feedback_count INTEGER NOT NULL DEFAULT 0`;
525
711
  } catch {}
526
712
  }
713
+ function ensureChatsTitleSetByUserColumnExists(sql) {
714
+ try {
715
+ sql`ALTER TABLE chats ADD COLUMN title_set_by_user INTEGER NOT NULL DEFAULT 0`;
716
+ } catch {}
717
+ }
527
718
  function ensureChatMessageFeedbackTableExists(sql) {
528
719
  sql`CREATE TABLE IF NOT EXISTS chat_message_feedback (
529
720
  chat_id TEXT NOT NULL,
@@ -552,6 +743,19 @@ function registerChatWithMetadata(sql, durableObjectName, metadata) {
552
743
  VALUES (${durableObjectName}, ${title ?? null}, ${summary ?? null}, ${createdAt}, ${updatedAt})
553
744
  ON CONFLICT (durable_object_name) DO NOTHING`;
554
745
  }
746
+ /**
747
+ * Sets a chat's title on the user's behalf. Marks the title as user-set so
748
+ * the AI summariser stops overwriting it (it keeps updating the summary).
749
+ */
750
+ function renameChat(sql, durableObjectName, title, dateTime = Date.now()) {
751
+ const trimmed = title.trim();
752
+ if (!trimmed) throw new Error("Chat title cannot be empty");
753
+ sql`UPDATE chats
754
+ SET title = ${trimmed},
755
+ title_set_by_user = 1,
756
+ updated_at = ${dateTime}
757
+ WHERE durable_object_name = ${durableObjectName}`;
758
+ }
555
759
  function deleteChat(sql, durableObjectName) {
556
760
  sql`DELETE FROM chats WHERE durable_object_name = ${durableObjectName}`;
557
761
  sql`DELETE FROM chat_message_feedback WHERE chat_id = ${durableObjectName}`;
@@ -623,7 +827,7 @@ async function summariseChatWithAI(sql, durableObjectName, messages, model) {
623
827
  return;
624
828
  }
625
829
  sql`UPDATE chats
626
- SET title = ${title},
830
+ SET title = CASE WHEN title_set_by_user = 1 THEN title ELSE ${title} END,
627
831
  summary = ${summary},
628
832
  updated_at = ${Date.now()}
629
833
  WHERE durable_object_name = ${durableObjectName}`;
@@ -870,6 +1074,11 @@ var Assistant = class extends Agent$1 {
870
1074
  2,
871
1075
  "deleteChat"
872
1076
  ],
1077
+ [
1078
+ callable(),
1079
+ 2,
1080
+ "renameChat"
1081
+ ],
873
1082
  [
874
1083
  callable(),
875
1084
  2,
@@ -933,6 +1142,9 @@ var Assistant = class extends Agent$1 {
933
1142
  await this.deleteSubAgent(this.agent, id);
934
1143
  deleteChat(this.sql.bind(this), id);
935
1144
  }
1145
+ async renameChat(id, title) {
1146
+ renameChat(this.sql.bind(this), id, title);
1147
+ }
936
1148
  async getChats() {
937
1149
  return getChats(this.sql.bind(this));
938
1150
  }
@@ -1350,4 +1562,4 @@ function getCurrentToolContext() {
1350
1562
  return getCurrentAgent().agent._lastBody;
1351
1563
  }
1352
1564
  //#endregion
1353
- export { Agent, Assistant, ChatAgent, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
1565
+ export { Agent, Assistant, ChatAgent, exchangeToken, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "license": "MIT",
6
6
  "files": [