@economic/agents 2.7.1 → 2.8.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
@@ -83,14 +83,13 @@ const { anthropic, gemini } = createAiGatewayVertexProviders({
83
83
  cloudflareAiGatewayId: env.AI_GATEWAY_ID,
84
84
  cloudflareApiToken: env.CLOUDFLARE_API_TOKEN,
85
85
  googleCloudProjectId: env.GOOGLE_CLOUD_PROJECT_ID,
86
- // Included in cf-aig-metadata on every request, including background calls.
87
- metadata: { agentId: "support" },
88
86
  });
89
87
  ```
90
88
 
91
- Set a stable `agentId` when multiple agents may use the same gateway. AI Gateway
92
- stores this custom metadata with each request, allowing cost queries to filter
93
- by agent without copying cost data into another store.
89
+ When these providers are used inside an SDK agent, the SDK automatically adds
90
+ the entry-point agent class as `agentId` in `cf-aig-metadata`. This includes
91
+ requests made by child chat/facet agents and allows a shared gateway's usage to
92
+ be grouped by agent without application configuration or duplicated analytics.
94
93
 
95
94
  ## Quick start: an agent
96
95
 
@@ -502,6 +501,7 @@ export class SupportAgent extends Agent {
502
501
  userGuid: payload.user_guid as string,
503
502
  agreementNumber: payload.agreement_number as number,
504
503
  }),
504
+ getActorId: (claims) => `${claims.agreementNumber}_${claims.userGuid}`,
505
505
  };
506
506
  }
507
507
 
@@ -509,6 +509,11 @@ export class SupportAgent extends Agent {
509
509
  }
510
510
  ```
511
511
 
512
+ `getActorId` derives the authenticated actor from verified claims. It must exactly match the
513
+ actor that owns the requested Durable Object: the Assistant name, the root parent of a chat
514
+ facet, or the prefix in a standalone `${actorId}:${chatId}` name. A valid token belonging to
515
+ another actor is rejected with `4003` before the connection is established.
516
+
512
517
  On failure the socket closes (`4001` unauthorized, `4003` forbidden) and status becomes `"unauthorized"`. The token is read from `Authorization: Bearer …`, then the `Sec-WebSocket-Protocol: bearer, <token>` header.
513
518
 
514
519
  ### User context
@@ -586,18 +591,18 @@ Imported from `@economic/agents` unless noted.
586
591
 
587
592
  ### Members
588
593
 
589
- | Member | On | Description |
590
- | ---------------------------------------------- | ----------- | ------------------------------------------- |
591
- | `getModel(ctx?)` | `Agent` | Required. Model for inference. |
592
- | `getSystemPrompt(ctx?)` | `Agent` | Required. System prompt. |
593
- | `getTools()` | `Agent` | Always-on tools (default `{}`). |
594
- | `getSkills()` | `Agent` | Local skills (default `[]`). |
595
- | `static getJwtAuthConfig(env)` | `Agent` | Optional JWT verification config. |
596
- | `getUserContext(jwtToken)` | `Agent` | Optional per-user context after auth. |
597
- | `submitMessageFeedback` / `getMessageFeedback` | `ChatAgent` | Callable message feedback. |
598
- | `agent` | `Assistant` | Required. Your `ChatAgent` subclass. |
599
- | `fastModel` | `Assistant` | Required. Cheap model for titles/summaries. |
600
- | `createChat` / `getChats` / `deleteChat` | `Assistant` | Callable chat management. |
594
+ | Member | On | Description |
595
+ | ---------------------------------------------- | ----------- | -------------------------------------------- |
596
+ | `getModel(ctx?)` | `Agent` | Required. Model for inference. |
597
+ | `getSystemPrompt(ctx?)` | `Agent` | Required. System prompt. |
598
+ | `getTools()` | `Agent` | Always-on tools (default `{}`). |
599
+ | `getSkills()` | `Agent` | Local skills (default `[]`). |
600
+ | `static getJwtAuthConfig(env)` | `Agent` | Optional JWT verification and actor binding. |
601
+ | `getUserContext(jwtToken)` | `Agent` | Optional per-user context after auth. |
602
+ | `submitMessageFeedback` / `getMessageFeedback` | `ChatAgent` | Callable message feedback. |
603
+ | `agent` | `Assistant` | Required. Your `ChatAgent` subclass. |
604
+ | `fastModel` | `Assistant` | Required. Cheap model for titles/summaries. |
605
+ | `createChat` / `getChats` / `deleteChat` | `Assistant` | Callable chat management. |
601
606
 
602
607
  ### Package root (`@economic/agents`)
603
608
 
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as JwtAuthConfig, t as routeAgentRequest } from "./route-agent-request-D2ll--nF.mjs";
1
+ import { n as JwtAuthConfig, t as routeAgentRequest } from "./route-agent-request-8C2T8Mu0.mjs";
2
2
  import { Agent as Agent$1, Connection, ConnectionContext, SubAgentClass } from "agents";
3
3
  import { ChatResponseResult, PrepareStepContext, Session, Think, ToolCallContext, ToolCallDecision, TurnConfig, TurnContext, skills } from "@cloudflare/think";
4
4
  import { JSONValue, LanguageModel, ModelMessage, Tool as Tool$1, UIMessage } from "ai";
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as verifyJwt, i as extractTokenFromConnectRequest, n as createAgentTracer, r as recordFeedbackAnalytics, t as routeAgentRequest } from "./route-agent-request-DBGSXu2O.mjs";
1
+ import { a as isJwtActorAuthorized, i as extractTokenFromConnectRequest, n as createAgentTracer, o as verifyJwt, r as recordFeedbackAnalytics, t as routeAgentRequest } from "./route-agent-request-CYclS_5Q.mjs";
2
2
  import { Agent as Agent$1, callable, getCurrentAgent } from "agents";
3
3
  import { Think, skills } from "@cloudflare/think";
4
4
  import { Output, convertToModelMessages, generateText, jsonSchema, pruneMessages, tool as tool$1 } from "ai";
@@ -359,6 +359,15 @@ var Agent = class extends Think {
359
359
  connection.close(result.status === 401 ? 4001 : 4003, result.message);
360
360
  return;
361
361
  }
362
+ if (!isJwtActorAuthorized(config, result.claims, {
363
+ durableObjectName: this.name,
364
+ parentPath: this.parentPath
365
+ })) {
366
+ sendConnectionStatus(connection, "unauthorized");
367
+ console.error("[Agent] JWT actor does not match the requested agent");
368
+ connection.close(4003, "Forbidden");
369
+ return;
370
+ }
362
371
  const token = extractTokenFromConnectRequest(ctx.request);
363
372
  let effectiveToken = token;
364
373
  const getTokenExchangeConfig = this.constructor.getTokenExchangeConfig;
@@ -1042,6 +1051,15 @@ var Assistant = class extends Agent$1 {
1042
1051
  connection.close(result.status === 401 ? 4001 : 4003, result.message);
1043
1052
  return;
1044
1053
  }
1054
+ if (!isJwtActorAuthorized(config, result.claims, {
1055
+ durableObjectName: this.name,
1056
+ parentPath: this.parentPath
1057
+ })) {
1058
+ sendConnectionStatus(connection, "unauthorized");
1059
+ console.error("[Assistant] JWT actor does not match the requested assistant");
1060
+ connection.close(4003, "Forbidden");
1061
+ return;
1062
+ }
1045
1063
  }
1046
1064
  }
1047
1065
  sendConnectionStatus(connection, "connected");
@@ -15,7 +15,7 @@ interface AnthropicVertexProviderOptions {
15
15
  googleCloudProjectId: string;
16
16
  location?: string;
17
17
  anthropicVersion?: string;
18
- /** Metadata included on every request sent through AI Gateway. */
18
+ /** Custom metadata included on every request. `agentId` is injected by the SDK. */
19
19
  metadata?: AiGatewayMetadata;
20
20
  }
21
21
  declare function createAnthropicProvider(options: AnthropicVertexProviderOptions): (modelId: AnthropicVertexModelId, metadata?: AiGatewayMetadata) => AnthropicMessagesLanguageModel;
@@ -28,7 +28,7 @@ interface GeminiProviderOptions {
28
28
  cloudflareApiToken: string;
29
29
  googleCloudProjectId: string;
30
30
  location?: string;
31
- /** Metadata included on every request sent through AI Gateway. */
31
+ /** Custom metadata included on every request. `agentId` is injected by the SDK. */
32
32
  metadata?: AiGatewayMetadata;
33
33
  }
34
34
  declare function createGeminiProvider(options: GeminiProviderOptions, metadata?: AiGatewayMetadata): GoogleGenerativeAIProvider;
@@ -1,13 +1,33 @@
1
+ import { getCurrentAgent } from "agents";
1
2
  import { AnthropicMessagesLanguageModel } from "@ai-sdk/anthropic/internal";
2
3
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
3
4
  //#region src/providers/metadata.ts
5
+ function getCurrentAgentId() {
6
+ const agent = getCurrentAgent().agent;
7
+ if (!agent) return void 0;
8
+ return agent.selfPath[0]?.className ?? agent.constructor.name;
9
+ }
4
10
  function createAiGatewayHeaders(cloudflareApiToken, defaultMetadata, requestMetadata) {
5
11
  const headers = { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` };
6
12
  const metadata = { ...defaultMetadata };
7
13
  for (const [key, value] of Object.entries(requestMetadata ?? {})) if (!(key in metadata)) metadata[key] = value;
14
+ const agentId = getCurrentAgentId();
15
+ if (agentId) metadata.agentId = agentId;
8
16
  if (Object.values(metadata).some((value) => value !== void 0)) headers["cf-aig-metadata"] = JSON.stringify(metadata);
9
17
  return headers;
10
18
  }
19
+ /** Adds AI Gateway authentication and SDK-owned agent metadata at request time. */
20
+ function createAiGatewayFetch(cloudflareApiToken, defaultMetadata, requestMetadata) {
21
+ return (input, init) => {
22
+ const headers = new Headers(input instanceof Request ? input.headers : void 0);
23
+ new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
24
+ for (const [key, value] of Object.entries(createAiGatewayHeaders(cloudflareApiToken, defaultMetadata, requestMetadata))) headers.set(key, value);
25
+ return globalThis.fetch(input, {
26
+ ...init,
27
+ headers
28
+ });
29
+ };
30
+ }
11
31
  //#endregion
12
32
  //#region src/providers/anthropic.ts
13
33
  function createAnthropicProvider(options) {
@@ -17,7 +37,8 @@ function createAnthropicProvider(options) {
17
37
  return new AnthropicMessagesLanguageModel(modelId, {
18
38
  provider: "vertex.anthropic.messages",
19
39
  baseURL: anthropicBaseURL,
20
- headers: createAiGatewayHeaders(cloudflareApiToken, defaultMetadata, metadata),
40
+ headers: {},
41
+ fetch: createAiGatewayFetch(cloudflareApiToken, defaultMetadata, metadata),
21
42
  buildRequestUrl: (baseURL, isStreaming) => `${baseURL}${modelId}:${isStreaming ? "streamRawPredict" : "rawPredict"}`,
22
43
  transformRequestBody: (args) => {
23
44
  const { model, ...rest } = args;
@@ -39,7 +60,7 @@ function createGeminiProvider(options, metadata) {
39
60
  return createGoogleGenerativeAI({
40
61
  apiKey: "unused",
41
62
  baseURL: `https://gateway.ai.cloudflare.com/v1/${cloudflareAccountId}/${cloudflareAiGatewayId}/google-vertex-ai/v1/projects/${googleCloudProjectId}/locations/${location}/publishers/google/models`,
42
- headers: createAiGatewayHeaders(cloudflareApiToken, defaultMetadata, metadata),
63
+ fetch: createAiGatewayFetch(cloudflareApiToken, defaultMetadata, metadata),
43
64
  name: "gateway.google.vertex-ai"
44
65
  });
45
66
  }
@@ -13,6 +13,8 @@ interface JwtAuthConfig<TClaims extends Record<string, unknown> = Record<string,
13
13
  * These will be stored and made available in the agent's tool context.
14
14
  */
15
15
  getClaims: (payload: Record<string, unknown>) => TClaims;
16
+ /** Derive the actor ID represented by the verified claims. */
17
+ getActorId: (claims: TClaims) => string;
16
18
  }
17
19
  //#endregion
18
20
  //#region src/server/route-agent-request.d.ts
@@ -3,6 +3,18 @@ import { createRemoteJWKSet, decodeJwt, errors, jwtVerify } from "jose";
3
3
  import { BasicTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
4
4
  //#region src/server/features/auth/index.ts
5
5
  const jwksByIssuer = /* @__PURE__ */ new Map();
6
+ /** Returns the actor portion of a top-level or parent-scoped agent name. */
7
+ function getTargetActorId(target) {
8
+ const rootName = target.parentPath?.[0]?.name ?? target.durableObjectName;
9
+ const separator = rootName.indexOf(":");
10
+ return separator === -1 ? rootName : rootName.slice(0, separator);
11
+ }
12
+ /** Checks that verified JWT claims belong to the addressed agent instance. */
13
+ function isJwtActorAuthorized(config, claims, target) {
14
+ if (typeof config.getActorId !== "function") return false;
15
+ const authenticatedActorId = config.getActorId(claims);
16
+ return typeof authenticatedActorId === "string" && authenticatedActorId.length > 0 && authenticatedActorId === getTargetActorId(target);
17
+ }
6
18
  function getJwksForIssuer(issuer) {
7
19
  const normalized = issuer.replace(/\/$/, "");
8
20
  let jwks = jwksByIssuer.get(normalized);
@@ -465,4 +477,4 @@ async function routeAgentRequest$1(request, env, options) {
465
477
  return response;
466
478
  }
467
479
  //#endregion
468
- export { verifyJwt as a, extractTokenFromConnectRequest as i, createAgentTracer as n, recordFeedbackAnalytics as r, routeAgentRequest$1 as t };
480
+ export { isJwtActorAuthorized as a, extractTokenFromConnectRequest as i, createAgentTracer as n, verifyJwt as o, recordFeedbackAnalytics as r, routeAgentRequest$1 as t };
package/dist/v1.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as JwtAuthConfig, t as routeAgentRequest } from "./route-agent-request-D2ll--nF.mjs";
1
+ import { n as JwtAuthConfig, t as routeAgentRequest } from "./route-agent-request-8C2T8Mu0.mjs";
2
2
  import { Agent as Agent$1, Connection, ConnectionContext } from "agents";
3
3
  import { LanguageModel, StreamTextOnFinishCallback, ToolSet, UIMessage, generateText, streamText } from "ai";
4
4
  import { AIChatAgent, ChatResponseResult, OnChatMessageOptions } from "@cloudflare/ai-chat";
package/dist/v1.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as verifyJwt, i as extractTokenFromConnectRequest, n as createAgentTracer, t as routeAgentRequest } from "./route-agent-request-DBGSXu2O.mjs";
1
+ import { a as isJwtActorAuthorized, i as extractTokenFromConnectRequest, n as createAgentTracer, o as verifyJwt, t as routeAgentRequest } from "./route-agent-request-CYclS_5Q.mjs";
2
2
  import { Agent as Agent$1, callable, getCurrentAgent } from "agents";
3
3
  import { Output, convertToModelMessages, generateText, jsonSchema, stepCountIs, streamText, tool } from "ai";
4
4
  import { AIChatAgent } from "@cloudflare/ai-chat";
@@ -312,6 +312,13 @@ var Agent = class extends Agent$1 {
312
312
  connection.close(result.status === 401 ? 4001 : 4003, result.message);
313
313
  return;
314
314
  }
315
+ if (!isJwtActorAuthorized(config, result.claims, {
316
+ durableObjectName: this.name,
317
+ parentPath: this.parentPath
318
+ })) {
319
+ connection.close(4003, "Forbidden");
320
+ return;
321
+ }
315
322
  const token = extractTokenFromConnectRequest(ctx.request);
316
323
  if (token) {
317
324
  const userContext = await this.getUserContext?.(token);
@@ -895,6 +902,17 @@ var ChatAgent = class extends AIChatAgent {
895
902
  });
896
903
  return;
897
904
  }
905
+ if (!isJwtActorAuthorized(config, result.claims, {
906
+ durableObjectName: this.name,
907
+ parentPath: this.parentPath
908
+ })) {
909
+ connection.close(4003, "Forbidden");
910
+ this.setState({
911
+ type: "chat",
912
+ status: "unauthorized"
913
+ });
914
+ return;
915
+ }
898
916
  }
899
917
  const token = extractTokenFromConnectRequest(ctx.request);
900
918
  if (token) this._pendingUserContextRequest = this.getUserContext?.(token).then((userContext) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "2.7.1",
3
+ "version": "2.8.0",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "license": "MIT",
6
6
  "files": [