@economic/agents 2.5.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 +33 -0
- package/dist/index.d.mts +91 -1
- package/dist/index.mjs +187 -3
- package/package.json +1 -1
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.
|
|
@@ -365,4 +455,4 @@ declare function skill<Context extends Record<string, unknown> = Record<string,
|
|
|
365
455
|
//#region src/server/index.d.ts
|
|
366
456
|
declare function getCurrentToolContext(): any;
|
|
367
457
|
//#endregion
|
|
368
|
-
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
|
-
|
|
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
|
-
|
|
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
|
*/
|
|
@@ -1378,4 +1562,4 @@ function getCurrentToolContext() {
|
|
|
1378
1562
|
return getCurrentAgent().agent._lastBody;
|
|
1379
1563
|
}
|
|
1380
1564
|
//#endregion
|
|
1381
|
-
export { Agent, Assistant, ChatAgent, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
|
|
1565
|
+
export { Agent, Assistant, ChatAgent, exchangeToken, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
|