@economic/agents 1.8.1 → 2.0.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.
@@ -0,0 +1,18 @@
1
+ import { JWTPayload } from "jose";
2
+
3
+ //#region src/server/shared/features/auth/index.d.ts
4
+ interface JwtAuthConfig<TClaims extends Record<string, unknown> = Record<string, unknown>> {
5
+ /** Issuers whose tokens are accepted (exact string or RegExp). */
6
+ allowedIssuers: readonly (string | RegExp)[];
7
+ /** Expected `aud` claim. */
8
+ audience: string;
9
+ /** Required OAuth scopes; token `scope` must include all (empty = no scope check). */
10
+ requiredScopes?: readonly string[];
11
+ /**
12
+ * Extract the claims you need from the verified JWT payload.
13
+ * These will be stored and made available in the agent's tool context.
14
+ */
15
+ getClaims: (payload: JWTPayload) => TClaims;
16
+ }
17
+ //#endregion
18
+ export { JwtAuthConfig as t };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
+ import { t as JwtAuthConfig } from "./index-DzOC3mNl.mjs";
1
2
  import { LanguageModel, StreamTextOnFinishCallback, ToolSet, UIMessage, generateText, streamText } from "ai";
2
3
  import { Agent as Agent$1, AgentOptions, Connection, ConnectionContext } from "agents";
3
4
  import { AIChatAgent, ChatResponseResult, OnChatMessageOptions } from "@cloudflare/ai-chat";
4
- import { JWTPayload } from "jose";
5
5
 
6
6
  //#region src/server/shared/features/skills/index.d.ts
7
7
  /**
@@ -69,21 +69,6 @@ interface ChatAgentEnv extends AgentEnv {}
69
69
  //#region src/server/v1/route-agent-request.d.ts
70
70
  declare function routeAgentRequest<Env>(request: Request, env: Env, options?: AgentOptions<Env>): Promise<Response | null>;
71
71
  //#endregion
72
- //#region src/server/shared/features/auth/index.d.ts
73
- interface JwtAuthConfig<TClaims extends Record<string, unknown> = Record<string, unknown>> {
74
- /** Issuers whose tokens are accepted (exact string or RegExp). */
75
- allowedIssuers: readonly (string | RegExp)[];
76
- /** Expected `aud` claim. */
77
- audience: string;
78
- /** Required OAuth scopes; token `scope` must include all (empty = no scope check). */
79
- requiredScopes?: readonly string[];
80
- /**
81
- * Extract the claims you need from the verified JWT payload.
82
- * These will be stored and made available in the agent's tool context.
83
- */
84
- getClaims: (payload: JWTPayload) => TClaims;
85
- }
86
- //#endregion
87
72
  //#region src/server/v1/agent/Agent.d.ts
88
73
  /**
89
74
  * Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading
package/dist/index.mjs CHANGED
@@ -1,8 +1,7 @@
1
+ import { n as extractTokenFromConnectRequest, r as verifyJwt, t as createAgentTracer } from "./telemetry-a-1XBTxr.mjs";
1
2
  import { Output, convertToModelMessages, generateText, jsonSchema, stepCountIs, streamText, tool } from "ai";
2
3
  import { Agent as Agent$1, callable, getCurrentAgent, routeAgentRequest as routeAgentRequest$1 } from "agents";
3
4
  import { AIChatAgent } from "@cloudflare/ai-chat";
4
- import { createRemoteJWKSet, decodeJwt, errors, jwtVerify } from "jose";
5
- import { BasicTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
6
5
  //#region src/server/shared/features/skills/index.ts
7
6
  const TOOL_NAME_ACTIVATE_SKILL = "activate_skill";
8
7
  const TOOL_NAME_LIST_CAPABILITIES = "list_capabilities";
@@ -270,369 +269,6 @@ async function routeAgentRequest(request, env, options) {
270
269
  return response;
271
270
  }
272
271
  //#endregion
273
- //#region src/server/shared/features/auth/index.ts
274
- const jwksByIssuer = /* @__PURE__ */ new Map();
275
- function getJwksForIssuer(issuer) {
276
- const normalized = issuer.replace(/\/$/, "");
277
- let jwks = jwksByIssuer.get(normalized);
278
- if (!jwks) {
279
- jwks = createRemoteJWKSet(new URL(`${normalized}/.well-known/openid-configuration/jwks`));
280
- jwksByIssuer.set(normalized, jwks);
281
- }
282
- return jwks;
283
- }
284
- function isIssuerAllowed(iss, allowed) {
285
- return allowed.some((rule) => typeof rule === "string" ? rule === iss : rule.test(iss));
286
- }
287
- function extractTokenFromConnectRequest(request) {
288
- const authorization = request.headers.get("Authorization");
289
- if (authorization) {
290
- const match = authorization.match(/^Bearer\s+(.+)$/i);
291
- if (match?.[1]) return match[1].trim();
292
- }
293
- const wsProtocol = request.headers.get("Sec-WebSocket-Protocol");
294
- if (wsProtocol) {
295
- const parts = wsProtocol.split(",").map((p) => p.trim());
296
- const idx = parts.indexOf("bearer");
297
- if (idx !== -1 && parts[idx + 1]) return parts[idx + 1];
298
- }
299
- return null;
300
- }
301
- function hasRequiredScopes(tokenScope, required) {
302
- if (required.length === 0) return true;
303
- if (tokenScope === void 0) return false;
304
- const tokens = Array.isArray(tokenScope) ? tokenScope : tokenScope.split(" ").map((s) => s.trim()).filter(Boolean);
305
- const granted = new Set(tokens);
306
- return required.every((scope) => granted.has(scope));
307
- }
308
- /**
309
- * Verify a JWT from a request and extract claims.
310
- *
311
- * Extracts the token from `Authorization: Bearer` header first,
312
- * then falls back to `Sec-WebSocket-Protocol: bearer, <token>` for WebSocket connections.
313
- *
314
- * Expected auth failures (missing/expired token, insufficient scope) return a failure result.
315
- * Unexpected errors (network issues, malformed requests, untrusted issuers) throw and should
316
- * be caught and logged by the caller.
317
- *
318
- * @param request - The incoming request (from ConnectionContext)
319
- * @param config - JWT verification configuration
320
- * @returns Result object with either verified claims or expected auth failure
321
- * @throws Error for unexpected failures that should be logged
322
- */
323
- async function verifyJwt(request, config) {
324
- const token = extractTokenFromConnectRequest(request);
325
- if (!token) return {
326
- success: false,
327
- status: 401,
328
- message: "Unauthorized: Missing authentication token"
329
- };
330
- let unverifiedPayload;
331
- try {
332
- unverifiedPayload = decodeJwt(token);
333
- } catch {
334
- throw new Error("Invalid token format");
335
- }
336
- const iss = typeof unverifiedPayload.iss === "string" ? unverifiedPayload.iss : void 0;
337
- if (!iss) throw new Error("Missing issuer claim in token");
338
- if (!isIssuerAllowed(iss, config.allowedIssuers)) throw new Error(`Untrusted issuer: ${iss}`);
339
- const jwks = getJwksForIssuer(iss);
340
- let payload;
341
- try {
342
- payload = (await jwtVerify(token, jwks, {
343
- issuer: iss,
344
- audience: config.audience
345
- })).payload;
346
- } catch (error) {
347
- if (error instanceof errors.JWTExpired) return {
348
- success: false,
349
- status: 401,
350
- message: "Unauthorized: Token expired"
351
- };
352
- if (error instanceof errors.JWTClaimValidationFailed) return {
353
- success: false,
354
- status: 401,
355
- message: "Unauthorized: Token claim validation failed"
356
- };
357
- if (error instanceof errors.JWSSignatureVerificationFailed || error instanceof errors.JWKSNoMatchingKey) throw new Error("Invalid token signature");
358
- throw error;
359
- }
360
- const requiredScopes = config.requiredScopes ?? [];
361
- const scopeClaim = payload.scope;
362
- if (!hasRequiredScopes(scopeClaim, requiredScopes)) return {
363
- success: false,
364
- status: 403,
365
- message: "Forbidden: Insufficient scope"
366
- };
367
- return {
368
- success: true,
369
- claims: config.getClaims(payload)
370
- };
371
- }
372
- //#endregion
373
- //#region src/server/shared/features/telemetry/index.ts
374
- function durationMs(duration) {
375
- return duration[0] * 1e3 + duration[1] / 1e6;
376
- }
377
- function stringAttribute(span, key) {
378
- const value = span.attributes[key];
379
- return typeof value === "string" ? value : void 0;
380
- }
381
- function numberAttribute(span, key) {
382
- const value = span.attributes[key];
383
- return typeof value === "number" ? value : void 0;
384
- }
385
- function parseJson(value) {
386
- if (!value) return;
387
- try {
388
- return JSON.parse(value);
389
- } catch {
390
- return;
391
- }
392
- }
393
- function safePathSegment(value, fallback) {
394
- return (value || fallback).replace(/[^a-zA-Z0-9._-]/g, "_");
395
- }
396
- function createAuditLogKey(agentName, conversationId) {
397
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
398
- const id = crypto.randomUUID().slice(0, 8);
399
- return [
400
- safePathSegment(agentName, "unknown-agent"),
401
- safePathSegment(conversationId, "unknown-conversation"),
402
- `${timestamp}-${id}.json`
403
- ].join("/");
404
- }
405
- function textFromContent(content) {
406
- if (typeof content === "string") return content;
407
- if (Array.isArray(content)) return content.map((part) => {
408
- if (!part || typeof part !== "object") return "";
409
- const value = part.text;
410
- return typeof value === "string" ? value : "";
411
- }).filter(Boolean).join("\n");
412
- return "";
413
- }
414
- function extractPrompt(messages) {
415
- const firstUserMessage = messages.find((message) => message.role === "user");
416
- return firstUserMessage ? textFromContent(firstUserMessage.content) : "";
417
- }
418
- function promptCharCount(messages) {
419
- const firstUserMessage = messages.find((message) => message.role === "user");
420
- return firstUserMessage ? textFromContent(firstUserMessage.content).length : 0;
421
- }
422
- function extractTools(messages) {
423
- const tools = [];
424
- for (const message of messages) {
425
- if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
426
- for (const part of message.content) {
427
- if (!part || typeof part !== "object") continue;
428
- const record = part;
429
- if (record.type !== "tool_use") continue;
430
- tools.push({
431
- name: typeof record.name === "string" ? record.name : void 0,
432
- input: record.input,
433
- status: "success"
434
- });
435
- }
436
- }
437
- return tools;
438
- }
439
- /**
440
- * Stores the last ai.streamText.doStream span per conversation.
441
- * Its ai.prompt.messages contains the full conversation history including all
442
- * intermediate tool_use + tool_result turns from the agentic loop.
443
- */
444
- const lastDoStreamByConversation = /* @__PURE__ */ new Map();
445
- const toolCallsByParentSpan = /* @__PURE__ */ new Map();
446
- const toolCallsByConversation = /* @__PURE__ */ new Map();
447
- const pendingToolCalls = [];
448
- const currentSkillByConversation = /* @__PURE__ */ new Map();
449
- function userIndex(userId) {
450
- return userId;
451
- }
452
- function extractSkillName(toolName, input) {
453
- if (!input || typeof input !== "object") return;
454
- const record = input;
455
- if (toolName === "load_context") {
456
- const key = record.key;
457
- return typeof key === "string" ? key : void 0;
458
- }
459
- if (toolName === "activate_skill") {
460
- const skill = record.skill ?? record.name ?? record.key;
461
- return typeof skill === "string" ? skill : void 0;
462
- }
463
- }
464
- function pushToolCall(map, key, toolCall) {
465
- const existing = map.get(key);
466
- if (existing) existing.push(toolCall);
467
- else map.set(key, [toolCall]);
468
- }
469
- function rememberToolCall(span) {
470
- const parentSpanId = span.parentSpanContext?.spanId;
471
- const toolName = stringAttribute(span, "ai.toolCall.name");
472
- const input = parseJson(stringAttribute(span, "ai.toolCall.args"));
473
- const toolCall = {
474
- name: toolName,
475
- input,
476
- status: span.status.code === 0 ? "success" : "error",
477
- skillName: extractSkillName(toolName, input)
478
- };
479
- if (parentSpanId) pushToolCall(toolCallsByParentSpan, parentSpanId, toolCall);
480
- pendingToolCalls.push(toolCall);
481
- }
482
- function attachToolCallsToConversation(span, conversationId) {
483
- const spanId = span.spanContext().spanId;
484
- const toolCalls = toolCallsByParentSpan.get(spanId) ?? pendingToolCalls.splice(0);
485
- if (!toolCalls.length) return;
486
- toolCallsByParentSpan.delete(spanId);
487
- const currentSkill = currentSkillByConversation.get(conversationId);
488
- const attributedToolCalls = toolCalls.map((toolCall) => {
489
- const skillName = toolCall.skillName ?? currentSkill;
490
- if (toolCall.skillName) currentSkillByConversation.set(conversationId, toolCall.skillName);
491
- return {
492
- ...toolCall,
493
- ...skillName ? { skillName } : {}
494
- };
495
- });
496
- toolCallsByConversation.set(conversationId, [...toolCallsByConversation.get(conversationId) ?? [], ...attributedToolCalls]);
497
- }
498
- function buildAuditLog(span, context) {
499
- const lastDoStream = lastDoStreamByConversation.get(context.conversationId);
500
- lastDoStreamByConversation.delete(context.conversationId);
501
- const promptMessages = parseJson(stringAttribute(lastDoStream ?? span, "ai.prompt.messages"));
502
- const fallbackPrompt = parseJson(stringAttribute(span, "ai.prompt"));
503
- const inputMessages = promptMessages ?? fallbackPrompt?.messages ?? [];
504
- const spanToolCalls = toolCallsByConversation.get(context.conversationId) ?? [];
505
- toolCallsByConversation.delete(context.conversationId);
506
- return {
507
- id: createAuditLogKey(context.agentName, context.conversationId),
508
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
509
- conversationId: context.conversationId,
510
- agent: {
511
- name: context.agentName,
512
- llm: {
513
- model: stringAttribute(span, "ai.model.id"),
514
- provider: stringAttribute(span, "ai.model.provider")
515
- }
516
- },
517
- actor: {
518
- userId: context.userId,
519
- ip: {
520
- client: context.clientIp ?? context.forwardedFor?.split(",").map((ip) => ip.trim()).filter(Boolean)[0],
521
- forwardedFor: context.forwardedFor?.split(",").map((ip) => ip.trim()).filter(Boolean) ?? []
522
- }
523
- },
524
- prompt: extractPrompt(inputMessages),
525
- response: stringAttribute(span, "ai.response.text") ?? "",
526
- status: span.status.code === 0 ? "success" : "error",
527
- tools: [...extractTools(inputMessages), ...spanToolCalls]
528
- };
529
- }
530
- async function handleAuditSpan(span, auditLogs, context) {
531
- const auditLog = buildAuditLog(span, context);
532
- if (!auditLogs) return;
533
- await auditLogs.put(auditLog.id, JSON.stringify(auditLog, null, 2), { httpMetadata: { contentType: "application/json" } });
534
- console.log("[AuditLog] Created", auditLog.id);
535
- }
536
- function writeAnalyticsDatapoint(analytics, dataPoint) {
537
- if (!analytics) return;
538
- try {
539
- analytics.writeDataPoint(dataPoint);
540
- } catch (error) {
541
- console.error("[Agent] Failed to write analytics datapoint", error);
542
- }
543
- }
544
- function handleAnalyticsSpan(span, analytics) {
545
- if (span.name === "ai.streamText.doStream") {
546
- const promptMessages = parseJson(stringAttribute(span, "ai.prompt.messages"));
547
- const responseText = stringAttribute(span, "ai.response.text") ?? "";
548
- writeAnalyticsDatapoint(analytics, {
549
- indexes: [userIndex(stringAttribute(span, "ai.telemetry.metadata.userId") ?? "")],
550
- blobs: [
551
- "llm_call",
552
- stringAttribute(span, "ai.telemetry.metadata.agentName") ?? "",
553
- stringAttribute(span, "ai.telemetry.metadata.conversationId") ?? "",
554
- stringAttribute(span, "ai.model.id") ?? "",
555
- stringAttribute(span, "ai.model.provider") ?? "",
556
- stringAttribute(span, "ai.response.finishReason") ?? ""
557
- ],
558
- doubles: [
559
- numberAttribute(span, "ai.usage.inputTokens") ?? 0,
560
- numberAttribute(span, "ai.usage.outputTokens") ?? 0,
561
- numberAttribute(span, "ai.usage.totalTokens") ?? 0,
562
- numberAttribute(span, "ai.usage.inputTokenDetails.cacheReadTokens") ?? 0,
563
- numberAttribute(span, "ai.usage.inputTokenDetails.cacheWriteTokens") ?? 0,
564
- durationMs(span.duration),
565
- promptCharCount(promptMessages ?? []),
566
- responseText.length
567
- ]
568
- });
569
- return;
570
- }
571
- if (span.name === "ai.toolCall") {
572
- const toolName = stringAttribute(span, "ai.toolCall.name");
573
- const toolInput = parseJson(stringAttribute(span, "ai.toolCall.args"));
574
- const conversationId = stringAttribute(span, "ai.telemetry.metadata.conversationId") ?? "";
575
- const skillName = extractSkillName(toolName, toolInput) ?? currentSkillByConversation.get(conversationId) ?? "";
576
- const success = span.status.code === 0;
577
- writeAnalyticsDatapoint(analytics, {
578
- indexes: [userIndex(stringAttribute(span, "ai.telemetry.metadata.userId") ?? "")],
579
- blobs: [
580
- "tool_call",
581
- stringAttribute(span, "ai.telemetry.metadata.agentName") ?? "",
582
- conversationId,
583
- toolName ?? "",
584
- skillName,
585
- success ? "success" : "error"
586
- ],
587
- doubles: [
588
- durationMs(span.duration),
589
- success ? 1 : 0,
590
- 0,
591
- 0,
592
- 0,
593
- 0,
594
- 0,
595
- 0
596
- ]
597
- });
598
- }
599
- }
600
- var AgentSpanExporter = class {
601
- auditLogs;
602
- analytics;
603
- context;
604
- constructor(auditLogs, analytics, context) {
605
- this.auditLogs = auditLogs;
606
- this.analytics = analytics;
607
- this.context = context;
608
- }
609
- export(spans, resultCallback) {
610
- (async () => {
611
- try {
612
- for (const span of spans) if (span.name === "ai.streamText.doStream") {
613
- lastDoStreamByConversation.set(this.context.conversationId, span);
614
- attachToolCallsToConversation(span, this.context.conversationId);
615
- handleAnalyticsSpan(span, this.analytics);
616
- } else if (span.name === "ai.streamText") await handleAuditSpan(span, this.auditLogs, this.context);
617
- else if (span.name === "ai.toolCall") {
618
- rememberToolCall(span);
619
- handleAnalyticsSpan(span, this.analytics);
620
- }
621
- resultCallback({ code: 0 });
622
- } catch (error) {
623
- resultCallback({
624
- code: 1,
625
- error: error instanceof Error ? error : new Error(String(error))
626
- });
627
- }
628
- })();
629
- }
630
- async shutdown() {}
631
- };
632
- function createAgentTracer(auditLogs, analytics, context) {
633
- return new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(new AgentSpanExporter(auditLogs, analytics, context))] }).getTracer("@economic/agents");
634
- }
635
- //#endregion
636
272
  //#region src/server/v1/agent/Agent.ts
637
273
  /**
638
274
  * Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading