@economic/agents 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -225,10 +225,7 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
225
225
  private _getExchangedToken;
226
226
  /**
227
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.
228
+ * body. No-op when token exchange is not configured.
232
229
  */
233
230
  private _applyTokenExchange;
234
231
  /**
package/dist/index.mjs CHANGED
@@ -4,6 +4,37 @@ 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/util/agent.ts
8
+ function sendConnectionStatus(connection, status) {
9
+ connection.send(JSON.stringify({
10
+ type: "connection_status",
11
+ status
12
+ }));
13
+ }
14
+ function sendAgentType(connection, agentType) {
15
+ connection.send(JSON.stringify({
16
+ type: "agent_type",
17
+ agentType
18
+ }));
19
+ }
20
+ function sendSubAgentName(connection, subAgentName) {
21
+ connection.send(JSON.stringify({
22
+ type: "sub_agent_name",
23
+ subAgentName
24
+ }));
25
+ }
26
+ /**
27
+ * Close code for a getUserContext failure. Subclasses signal an intentional
28
+ * auth-style rejection by throwing an Error carrying a numeric `closeCode`
29
+ * in the WebSocket application range (4000-4999); anything else is treated
30
+ * as an internal error (1011).
31
+ */
32
+ function getConnectCloseCode(error) {
33
+ const closeCode = error?.closeCode;
34
+ if (typeof closeCode === "number" && closeCode >= 4e3 && closeCode <= 4999) return closeCode;
35
+ return 1011;
36
+ }
37
+ //#endregion
7
38
  //#region src/server/features/auth/tokenExchange.ts
8
39
  /**
9
40
  * OAuth 2.0 Token Exchange (RFC 8693) against an identity server.
@@ -94,6 +125,56 @@ function withExchangedToken(body, tokenHeader, accessToken) {
94
125
  }
95
126
  };
96
127
  }
128
+ /**
129
+ * Connect-time exchange. On failure, signals the client and closes the
130
+ * connection — `unauthorized`/4001 when the session token itself was rejected
131
+ * (only a re-login fixes that), `error`/1011 for identity-server or
132
+ * configuration faults — and returns undefined so the caller abandons the
133
+ * connect.
134
+ */
135
+ async function exchangeConnectToken(subjectToken, config, connection) {
136
+ let result;
137
+ try {
138
+ result = await exchangeToken(subjectToken, config);
139
+ } catch (error) {
140
+ sendConnectionStatus(connection, "error");
141
+ console.error(`[Agent] Token exchange error - ${error}`);
142
+ connection.close(1011, "Token exchange failed");
143
+ return;
144
+ }
145
+ if (!result.success) {
146
+ console.error(`[Agent] Token exchange failed - ${result.message}`);
147
+ if (result.subjectTokenRejected) {
148
+ sendConnectionStatus(connection, "unauthorized");
149
+ connection.close(4001, "Unauthorized");
150
+ } else {
151
+ sendConnectionStatus(connection, "error");
152
+ connection.close(1011, "Token exchange failed");
153
+ }
154
+ return;
155
+ }
156
+ return {
157
+ accessToken: result.accessToken,
158
+ expiresAt: result.expiresAt
159
+ };
160
+ }
161
+ /**
162
+ * Turn-time injection: returns a body copy with the held token under
163
+ * `tokens[config.tokenHeader]`. When the held token is missing or expired the
164
+ * turn cannot run — only a re-authenticating reconnect delivers a fresh
165
+ * subject token — so the current connection (if any) is closed to force one,
166
+ * and this throws to fail the turn.
167
+ */
168
+ function injectExchangedToken(body, config, token, connection) {
169
+ if (!token || token.expiresAt <= Date.now()) {
170
+ if (connection) {
171
+ sendConnectionStatus(connection, "unauthorized");
172
+ connection.close(4001, "Unauthorized");
173
+ }
174
+ throw new Error("Exchanged token missing or expired - the client must reconnect");
175
+ }
176
+ return withExchangedToken(body, config.tokenHeader ?? "X-EconomicToken", token.accessToken);
177
+ }
97
178
  //#endregion
98
179
  //#region src/server/util/tools.ts
99
180
  function tool(tool) {
@@ -183,26 +264,6 @@ async function touchAgentInstance(db, input) {
183
264
  WHERE agent_name = ? AND durable_object_name = ?`).bind(input.updatedAt, input.agentName, input.durableObjectName).run();
184
265
  }
185
266
  //#endregion
186
- //#region src/server/util/agent.ts
187
- function sendConnectionStatus(connection, status) {
188
- connection.send(JSON.stringify({
189
- type: "connection_status",
190
- status
191
- }));
192
- }
193
- function sendAgentType(connection, agentType) {
194
- connection.send(JSON.stringify({
195
- type: "agent_type",
196
- agentType
197
- }));
198
- }
199
- function sendSubAgentName(connection, subAgentName) {
200
- connection.send(JSON.stringify({
201
- type: "sub_agent_name",
202
- subAgentName
203
- }));
204
- }
205
- //#endregion
206
267
  //#region src/server/util/llm.ts
207
268
  /**
208
269
  * Convention: tools can include a `sources` array in their return value to surface
@@ -240,17 +301,6 @@ function buildSourcesTransform(additional) {
240
301
  //#endregion
241
302
  //#region src/server/agents/Agent.ts
242
303
  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
- }
254
304
  var Agent = class extends Think {
255
305
  agentType = "agent";
256
306
  clientIp;
@@ -314,31 +364,10 @@ var Agent = class extends Think {
314
364
  const getTokenExchangeConfig = this.constructor.getTokenExchangeConfig;
315
365
  const exchangeConfig = getTokenExchangeConfig?.(this.env);
316
366
  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;
367
+ const exchanged = await exchangeConnectToken(token, exchangeConfig, connection);
368
+ if (!exchanged) return;
369
+ await this._storeExchangedToken(exchanged);
370
+ effectiveToken = exchanged.accessToken;
342
371
  }
343
372
  if (effectiveToken && this.getUserContext) try {
344
373
  connection.setState({ userContext: await this.getUserContext(effectiveToken) });
@@ -587,25 +616,13 @@ var Agent = class extends Think {
587
616
  }
588
617
  /**
589
618
  * 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.
619
+ * body. No-op when token exchange is not configured.
594
620
  */
595
621
  async _applyTokenExchange(body) {
596
622
  const getTokenExchangeConfig = this.constructor.getTokenExchangeConfig;
597
623
  const exchangeConfig = getTokenExchangeConfig?.(this.env);
598
624
  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);
625
+ return injectExchangedToken(body, exchangeConfig, await this._getExchangedToken(), getCurrentAgent().connection);
609
626
  }
610
627
  /**
611
628
  * The user context for the session.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "license": "MIT",
6
6
  "files": [