@economic/agents 2.9.2 → 2.10.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 +5 -3
- package/dist/index.d.mts +27 -4
- package/dist/index.mjs +653 -11
- package/package.json +1 -2
- package/dist/route-agent-request-9vqbi1bt.d.mts +0 -24
- package/dist/route-agent-request-J98VlcOj.mjs +0 -483
- package/dist/v1.d.mts +0 -264
- package/dist/v1.mjs +0 -1078
- package/schema/v1/0001_initial.sql +0 -35
- package/schema/v1/0002_add_rating_comment.sql +0 -1
- package/schema/v1/0003_drop_events.sql +0 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@economic/agents",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "A starter for creating a TypeScript package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"exports": {
|
|
13
13
|
".": "./dist/index.mjs",
|
|
14
14
|
"./providers": "./dist/providers.mjs",
|
|
15
|
-
"./v1": "./dist/v1.mjs",
|
|
16
15
|
"./package.json": "./package.json"
|
|
17
16
|
},
|
|
18
17
|
"scripts": {
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { AgentOptions } from "agents";
|
|
2
|
-
|
|
3
|
-
//#region src/server/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: Record<string, unknown>) => TClaims;
|
|
16
|
-
/** Derive the actor ID represented by the verified claims. */
|
|
17
|
-
getActorId(claims: TClaims): string;
|
|
18
|
-
}
|
|
19
|
-
declare function createJwtAuthConfig<TClaims extends Record<string, unknown>>(config: JwtAuthConfig<TClaims>): JwtAuthConfig<TClaims>;
|
|
20
|
-
//#endregion
|
|
21
|
-
//#region src/server/route-agent-request.d.ts
|
|
22
|
-
declare function routeAgentRequest$1<Env>(request: Request, env: Env, options?: AgentOptions<Env>): Promise<Response | null>;
|
|
23
|
-
//#endregion
|
|
24
|
-
export { JwtAuthConfig as n, createJwtAuthConfig as r, routeAgentRequest$1 as t };
|
|
@@ -1,483 +0,0 @@
|
|
|
1
|
-
import { routeAgentRequest } from "agents";
|
|
2
|
-
import { createRemoteJWKSet, decodeJwt, errors, jwtVerify } from "jose";
|
|
3
|
-
import { BasicTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
4
|
-
//#region src/server/features/auth/index.ts
|
|
5
|
-
function createJwtAuthConfig(config) {
|
|
6
|
-
return config;
|
|
7
|
-
}
|
|
8
|
-
const jwksByIssuer = /* @__PURE__ */ new Map();
|
|
9
|
-
/** Returns the actor portion of a top-level or parent-scoped agent name. */
|
|
10
|
-
function getTargetActorId(target) {
|
|
11
|
-
const rootName = target.parentPath?.[0]?.name ?? target.durableObjectName;
|
|
12
|
-
const separator = rootName.indexOf(":");
|
|
13
|
-
return separator === -1 ? rootName : rootName.slice(0, separator);
|
|
14
|
-
}
|
|
15
|
-
/** Checks that verified JWT claims belong to the addressed agent instance. */
|
|
16
|
-
function isJwtActorAuthorized(config, claims, target) {
|
|
17
|
-
if (typeof config.getActorId !== "function") return false;
|
|
18
|
-
const authenticatedActorId = config.getActorId(claims);
|
|
19
|
-
return typeof authenticatedActorId === "string" && authenticatedActorId.length > 0 && authenticatedActorId === getTargetActorId(target);
|
|
20
|
-
}
|
|
21
|
-
function getJwksForIssuer(issuer) {
|
|
22
|
-
const normalized = issuer.replace(/\/$/, "");
|
|
23
|
-
let jwks = jwksByIssuer.get(normalized);
|
|
24
|
-
if (!jwks) {
|
|
25
|
-
jwks = createRemoteJWKSet(new URL(`${normalized}/.well-known/openid-configuration/jwks`));
|
|
26
|
-
jwksByIssuer.set(normalized, jwks);
|
|
27
|
-
}
|
|
28
|
-
return jwks;
|
|
29
|
-
}
|
|
30
|
-
function isIssuerAllowed(iss, allowed) {
|
|
31
|
-
return allowed.some((rule) => typeof rule === "string" ? rule === iss : rule.test(iss));
|
|
32
|
-
}
|
|
33
|
-
function extractTokenFromConnectRequest(request) {
|
|
34
|
-
const authorization = request.headers.get("Authorization");
|
|
35
|
-
if (authorization) {
|
|
36
|
-
const match = authorization.match(/^Bearer\s+(.+)$/i);
|
|
37
|
-
if (match?.[1]) return match[1].trim();
|
|
38
|
-
}
|
|
39
|
-
const wsProtocol = request.headers.get("Sec-WebSocket-Protocol");
|
|
40
|
-
if (wsProtocol) {
|
|
41
|
-
const parts = wsProtocol.split(",").map((p) => p.trim());
|
|
42
|
-
const idx = parts.indexOf("bearer");
|
|
43
|
-
if (idx !== -1 && parts[idx + 1]) return parts[idx + 1];
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
function hasRequiredScopes(tokenScope, required) {
|
|
48
|
-
if (required.length === 0) return true;
|
|
49
|
-
if (tokenScope === void 0) return false;
|
|
50
|
-
const tokens = Array.isArray(tokenScope) ? tokenScope : tokenScope.split(" ").map((s) => s.trim()).filter(Boolean);
|
|
51
|
-
const granted = new Set(tokens);
|
|
52
|
-
return required.every((scope) => granted.has(scope));
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Verify a JWT from a request and extract claims.
|
|
56
|
-
*
|
|
57
|
-
* Extracts the token from `Authorization: Bearer` header first,
|
|
58
|
-
* then falls back to `Sec-WebSocket-Protocol: bearer, <token>` for WebSocket connections.
|
|
59
|
-
*
|
|
60
|
-
* Expected auth failures (missing/expired token, insufficient scope) return a failure result.
|
|
61
|
-
* Unexpected errors (network issues, malformed requests, untrusted issuers) throw and should
|
|
62
|
-
* be caught and logged by the caller.
|
|
63
|
-
*
|
|
64
|
-
* @param request - The incoming request (from ConnectionContext)
|
|
65
|
-
* @param config - JWT verification configuration
|
|
66
|
-
* @returns Result object with either verified claims or expected auth failure
|
|
67
|
-
* @throws Error for unexpected failures that should be logged
|
|
68
|
-
*/
|
|
69
|
-
async function verifyJwt(request, config) {
|
|
70
|
-
const token = extractTokenFromConnectRequest(request);
|
|
71
|
-
if (!token) return {
|
|
72
|
-
success: false,
|
|
73
|
-
status: 401,
|
|
74
|
-
message: "Unauthorized: Missing authentication token"
|
|
75
|
-
};
|
|
76
|
-
let unverifiedPayload;
|
|
77
|
-
try {
|
|
78
|
-
unverifiedPayload = decodeJwt(token);
|
|
79
|
-
} catch {
|
|
80
|
-
throw new Error("Invalid token format");
|
|
81
|
-
}
|
|
82
|
-
const iss = typeof unverifiedPayload.iss === "string" ? unverifiedPayload.iss : void 0;
|
|
83
|
-
if (!iss) throw new Error("Missing issuer claim in token");
|
|
84
|
-
if (!isIssuerAllowed(iss, config.allowedIssuers)) throw new Error(`Untrusted issuer: ${iss}`);
|
|
85
|
-
const jwks = getJwksForIssuer(iss);
|
|
86
|
-
let payload;
|
|
87
|
-
try {
|
|
88
|
-
payload = (await jwtVerify(token, jwks, {
|
|
89
|
-
issuer: iss,
|
|
90
|
-
audience: config.audience
|
|
91
|
-
})).payload;
|
|
92
|
-
} catch (error) {
|
|
93
|
-
if (error instanceof errors.JWTExpired) return {
|
|
94
|
-
success: false,
|
|
95
|
-
status: 401,
|
|
96
|
-
message: "Unauthorized: Token expired"
|
|
97
|
-
};
|
|
98
|
-
if (error instanceof errors.JWTClaimValidationFailed) return {
|
|
99
|
-
success: false,
|
|
100
|
-
status: 401,
|
|
101
|
-
message: "Unauthorized: Token claim validation failed"
|
|
102
|
-
};
|
|
103
|
-
if (error instanceof errors.JWSSignatureVerificationFailed || error instanceof errors.JWKSNoMatchingKey) throw new Error("Invalid token signature");
|
|
104
|
-
throw error;
|
|
105
|
-
}
|
|
106
|
-
const requiredScopes = config.requiredScopes ?? [];
|
|
107
|
-
const scopeClaim = payload.scope;
|
|
108
|
-
if (!hasRequiredScopes(scopeClaim, requiredScopes)) return {
|
|
109
|
-
success: false,
|
|
110
|
-
status: 403,
|
|
111
|
-
message: "Forbidden: Insufficient scope"
|
|
112
|
-
};
|
|
113
|
-
return {
|
|
114
|
-
success: true,
|
|
115
|
-
claims: config.getClaims(payload)
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
//#region src/server/features/telemetry/skills.ts
|
|
120
|
-
/**
|
|
121
|
-
* Extracts the skill names loaded by an `activate_skill` tool call.
|
|
122
|
-
*
|
|
123
|
-
* v1 `activate_skill` input is `{ skills: string[] }`; v2 uses
|
|
124
|
-
* `{ name: string }`. Return every requested skill name while ignoring empty
|
|
125
|
-
* values so analytics does not group activations under a blank skill.
|
|
126
|
-
*/
|
|
127
|
-
function extractSkillNames(toolName, input) {
|
|
128
|
-
if (toolName !== "activate_skill" || !input || typeof input !== "object") return [];
|
|
129
|
-
const { name, skills } = input;
|
|
130
|
-
if (typeof name === "string" && name) return [name];
|
|
131
|
-
if (Array.isArray(skills)) return skills.filter((skill) => typeof skill === "string" && skill !== "");
|
|
132
|
-
return [];
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Single-skill variant used where one representative skill name is sufficient
|
|
136
|
-
* (e.g. audit-log attribution and current-skill tracking).
|
|
137
|
-
*/
|
|
138
|
-
function extractSkillName(toolName, input) {
|
|
139
|
-
return extractSkillNames(toolName, input)[0];
|
|
140
|
-
}
|
|
141
|
-
//#endregion
|
|
142
|
-
//#region src/server/features/telemetry/utils.ts
|
|
143
|
-
function durationMs(duration) {
|
|
144
|
-
return duration[0] * 1e3 + duration[1] / 1e6;
|
|
145
|
-
}
|
|
146
|
-
function stringAttribute(span, key) {
|
|
147
|
-
const value = span.attributes[key];
|
|
148
|
-
return typeof value === "string" ? value : void 0;
|
|
149
|
-
}
|
|
150
|
-
function numberAttribute(span, key) {
|
|
151
|
-
const value = span.attributes[key];
|
|
152
|
-
return typeof value === "number" ? value : void 0;
|
|
153
|
-
}
|
|
154
|
-
function parseJson(value) {
|
|
155
|
-
if (!value) return;
|
|
156
|
-
try {
|
|
157
|
-
return JSON.parse(value);
|
|
158
|
-
} catch {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
//#endregion
|
|
163
|
-
//#region src/server/features/telemetry/audit-logs.ts
|
|
164
|
-
function safePathSegment(value, fallback) {
|
|
165
|
-
return (value || fallback).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
166
|
-
}
|
|
167
|
-
function createAuditLogKey(agentName, durableObjectName) {
|
|
168
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
169
|
-
const id = crypto.randomUUID().slice(0, 8);
|
|
170
|
-
return [
|
|
171
|
-
safePathSegment(agentName, "unknown-agent"),
|
|
172
|
-
safePathSegment(durableObjectName, "unknown-chat"),
|
|
173
|
-
`${timestamp}-${id}.json`
|
|
174
|
-
].join("/");
|
|
175
|
-
}
|
|
176
|
-
function textFromContent(content) {
|
|
177
|
-
if (typeof content === "string") return content;
|
|
178
|
-
if (Array.isArray(content)) return content.map((part) => {
|
|
179
|
-
if (!part || typeof part !== "object") return "";
|
|
180
|
-
const value = part.text;
|
|
181
|
-
return typeof value === "string" ? value : "";
|
|
182
|
-
}).filter(Boolean).join("\n");
|
|
183
|
-
return "";
|
|
184
|
-
}
|
|
185
|
-
function extractPrompt(messages) {
|
|
186
|
-
const firstUserMessage = messages.find((message) => message.role === "user");
|
|
187
|
-
return firstUserMessage ? textFromContent(firstUserMessage.content) : "";
|
|
188
|
-
}
|
|
189
|
-
function promptCharCount(messages) {
|
|
190
|
-
const firstUserMessage = messages.find((message) => message.role === "user");
|
|
191
|
-
return firstUserMessage ? textFromContent(firstUserMessage.content).length : 0;
|
|
192
|
-
}
|
|
193
|
-
function extractTools(messages) {
|
|
194
|
-
const tools = [];
|
|
195
|
-
for (const message of messages) {
|
|
196
|
-
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
197
|
-
for (const part of message.content) {
|
|
198
|
-
if (!part || typeof part !== "object") continue;
|
|
199
|
-
const record = part;
|
|
200
|
-
if (record.type !== "tool_use") continue;
|
|
201
|
-
tools.push({
|
|
202
|
-
name: typeof record.name === "string" ? record.name : void 0,
|
|
203
|
-
input: record.input,
|
|
204
|
-
status: "success"
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return tools;
|
|
209
|
-
}
|
|
210
|
-
function pushToolCall(map, key, toolCall) {
|
|
211
|
-
const existing = map.get(key);
|
|
212
|
-
if (existing) existing.push(toolCall);
|
|
213
|
-
else map.set(key, [toolCall]);
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Stores the last ai.streamText.doStream span per chat.
|
|
217
|
-
* Its ai.prompt.messages contains the full chat history including all
|
|
218
|
-
* intermediate tool_use + tool_result turns from the agentic loop.
|
|
219
|
-
*/
|
|
220
|
-
const lastDoStreamByChat = /* @__PURE__ */ new Map();
|
|
221
|
-
const toolCallsByParentSpan = /* @__PURE__ */ new Map();
|
|
222
|
-
const toolCallsByChat = /* @__PURE__ */ new Map();
|
|
223
|
-
const pendingToolCalls = [];
|
|
224
|
-
const currentSkillByChat = /* @__PURE__ */ new Map();
|
|
225
|
-
function rememberToolCall(span) {
|
|
226
|
-
const parentSpanId = span.parentSpanContext?.spanId;
|
|
227
|
-
const toolName = stringAttribute(span, "ai.toolCall.name");
|
|
228
|
-
const input = parseJson(stringAttribute(span, "ai.toolCall.args"));
|
|
229
|
-
const toolCall = {
|
|
230
|
-
name: toolName,
|
|
231
|
-
input,
|
|
232
|
-
status: span.status.code === 0 ? "success" : "error",
|
|
233
|
-
skillName: extractSkillName(toolName, input)
|
|
234
|
-
};
|
|
235
|
-
if (parentSpanId) pushToolCall(toolCallsByParentSpan, parentSpanId, toolCall);
|
|
236
|
-
pendingToolCalls.push(toolCall);
|
|
237
|
-
}
|
|
238
|
-
function attachToolCallsToChat(span, durableObjectName) {
|
|
239
|
-
const spanId = span.spanContext().spanId;
|
|
240
|
-
const toolCalls = toolCallsByParentSpan.get(spanId) ?? pendingToolCalls.splice(0);
|
|
241
|
-
if (!toolCalls.length) return;
|
|
242
|
-
toolCallsByParentSpan.delete(spanId);
|
|
243
|
-
const currentSkill = currentSkillByChat.get(durableObjectName);
|
|
244
|
-
const attributedToolCalls = toolCalls.map((toolCall) => {
|
|
245
|
-
const skillName = toolCall.skillName ?? currentSkill;
|
|
246
|
-
if (toolCall.skillName) currentSkillByChat.set(durableObjectName, toolCall.skillName);
|
|
247
|
-
return {
|
|
248
|
-
...toolCall,
|
|
249
|
-
...skillName ? { skillName } : {}
|
|
250
|
-
};
|
|
251
|
-
});
|
|
252
|
-
toolCallsByChat.set(durableObjectName, [...toolCallsByChat.get(durableObjectName) ?? [], ...attributedToolCalls]);
|
|
253
|
-
}
|
|
254
|
-
function buildAuditLog(span, context) {
|
|
255
|
-
const lastDoStream = lastDoStreamByChat.get(context.durableObjectName);
|
|
256
|
-
lastDoStreamByChat.delete(context.durableObjectName);
|
|
257
|
-
const promptMessages = parseJson(stringAttribute(lastDoStream ?? span, "ai.prompt.messages"));
|
|
258
|
-
const fallbackPrompt = parseJson(stringAttribute(span, "ai.prompt"));
|
|
259
|
-
const inputMessages = promptMessages ?? fallbackPrompt?.messages ?? [];
|
|
260
|
-
const spanToolCalls = toolCallsByChat.get(context.durableObjectName) ?? [];
|
|
261
|
-
toolCallsByChat.delete(context.durableObjectName);
|
|
262
|
-
return {
|
|
263
|
-
id: createAuditLogKey(context.agentName, context.durableObjectName),
|
|
264
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
265
|
-
durableObjectName: context.durableObjectName,
|
|
266
|
-
agent: {
|
|
267
|
-
name: context.agentName,
|
|
268
|
-
llm: {
|
|
269
|
-
model: stringAttribute(span, "ai.model.id"),
|
|
270
|
-
provider: stringAttribute(span, "ai.model.provider")
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
actor: {
|
|
274
|
-
id: context.actorId,
|
|
275
|
-
ip: {
|
|
276
|
-
client: context.clientIp ?? context.forwardedFor?.split(",").map((ip) => ip.trim()).filter(Boolean)[0],
|
|
277
|
-
forwardedFor: context.forwardedFor?.split(",").map((ip) => ip.trim()).filter(Boolean) ?? []
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
prompt: extractPrompt(inputMessages),
|
|
281
|
-
response: stringAttribute(span, "ai.response.text") ?? "",
|
|
282
|
-
status: span.status.code === 0 ? "success" : "error",
|
|
283
|
-
tools: [...extractTools(inputMessages), ...spanToolCalls]
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
function rememberDoStream(span, durableObjectName) {
|
|
287
|
-
lastDoStreamByChat.set(durableObjectName, span);
|
|
288
|
-
}
|
|
289
|
-
async function handleAuditSpan(span, auditLogs, context) {
|
|
290
|
-
const auditLog = buildAuditLog(span, context);
|
|
291
|
-
if (!auditLogs) return;
|
|
292
|
-
await auditLogs.put(auditLog.id, JSON.stringify(auditLog, null, 2), { httpMetadata: { contentType: "application/json" } });
|
|
293
|
-
console.log("[AuditLog] Created", auditLog.id);
|
|
294
|
-
}
|
|
295
|
-
//#endregion
|
|
296
|
-
//#region src/server/features/telemetry/analytics.ts
|
|
297
|
-
const emittedToolCallIds = /* @__PURE__ */ new Set();
|
|
298
|
-
function safeJsonString(value) {
|
|
299
|
-
if (value === void 0) return void 0;
|
|
300
|
-
try {
|
|
301
|
-
return JSON.stringify(value);
|
|
302
|
-
} catch {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
function writeAnalyticsDatapoint(analytics, dataPoint) {
|
|
307
|
-
if (!analytics) return;
|
|
308
|
-
try {
|
|
309
|
-
analytics.writeDataPoint(dataPoint);
|
|
310
|
-
} catch (error) {
|
|
311
|
-
console.error("[Agent] Failed to write analytics datapoint", error);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Writes a message-feedback rating to Analytics Engine so sentiment can be
|
|
316
|
-
* aggregated across all chats without fanning out to every chat DO.
|
|
317
|
-
*
|
|
318
|
-
* Emitted on every rating change; the dashboard takes the latest rating per
|
|
319
|
-
* `messageId` (blob4) so re-rates and clears are idempotent.
|
|
320
|
-
*/
|
|
321
|
-
function recordFeedbackAnalytics(analytics, context, messageId, rating) {
|
|
322
|
-
writeAnalyticsDatapoint(analytics, {
|
|
323
|
-
indexes: [context.actorId],
|
|
324
|
-
blobs: [
|
|
325
|
-
"feedback",
|
|
326
|
-
context.agentName,
|
|
327
|
-
context.durableObjectName,
|
|
328
|
-
messageId
|
|
329
|
-
],
|
|
330
|
-
doubles: [rating]
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
function handleAnalyticsSpan(span, analytics, context) {
|
|
334
|
-
if (span.name === "ai.streamText.doStream") {
|
|
335
|
-
const promptMessages = parseJson(stringAttribute(span, "ai.prompt.messages"));
|
|
336
|
-
const responseText = stringAttribute(span, "ai.response.text") ?? "";
|
|
337
|
-
const responseToolCalls = parseJson(stringAttribute(span, "ai.response.toolCalls"));
|
|
338
|
-
writeAnalyticsDatapoint(analytics, {
|
|
339
|
-
indexes: [context.actorId],
|
|
340
|
-
blobs: [
|
|
341
|
-
"llm_call",
|
|
342
|
-
context.agentName,
|
|
343
|
-
context.durableObjectName,
|
|
344
|
-
stringAttribute(span, "ai.model.id") ?? "",
|
|
345
|
-
stringAttribute(span, "ai.model.provider") ?? "",
|
|
346
|
-
stringAttribute(span, "ai.response.finishReason") ?? ""
|
|
347
|
-
],
|
|
348
|
-
doubles: [
|
|
349
|
-
numberAttribute(span, "ai.usage.inputTokens") ?? 0,
|
|
350
|
-
numberAttribute(span, "ai.usage.outputTokens") ?? 0,
|
|
351
|
-
numberAttribute(span, "ai.usage.totalTokens") ?? 0,
|
|
352
|
-
numberAttribute(span, "ai.usage.inputTokenDetails.cacheReadTokens") ?? 0,
|
|
353
|
-
numberAttribute(span, "ai.usage.inputTokenDetails.cacheWriteTokens") ?? 0,
|
|
354
|
-
durationMs(span.duration),
|
|
355
|
-
promptCharCount(promptMessages ?? []),
|
|
356
|
-
responseText.length
|
|
357
|
-
]
|
|
358
|
-
});
|
|
359
|
-
if (Array.isArray(responseToolCalls)) for (const value of responseToolCalls) {
|
|
360
|
-
if (!value || typeof value !== "object") continue;
|
|
361
|
-
const toolCall = value;
|
|
362
|
-
const toolCallId = typeof toolCall.toolCallId === "string" ? toolCall.toolCallId : void 0;
|
|
363
|
-
if (toolCallId && emittedToolCallIds.has(toolCallId)) continue;
|
|
364
|
-
const toolName = typeof toolCall.toolName === "string" ? toolCall.toolName : void 0;
|
|
365
|
-
if (!toolName) continue;
|
|
366
|
-
const input = toolCall.input;
|
|
367
|
-
const argsRaw = safeJsonString(input);
|
|
368
|
-
const success = span.status.code === 0;
|
|
369
|
-
const activatedSkills = extractSkillNames(toolName, input);
|
|
370
|
-
const skillNames = activatedSkills.length ? activatedSkills : [currentSkillByChat.get(context.durableObjectName) ?? ""];
|
|
371
|
-
if (toolCallId) emittedToolCallIds.add(toolCallId);
|
|
372
|
-
for (const skillName of skillNames) writeAnalyticsDatapoint(analytics, {
|
|
373
|
-
indexes: [context.actorId],
|
|
374
|
-
blobs: [
|
|
375
|
-
"tool_call",
|
|
376
|
-
context.agentName,
|
|
377
|
-
context.durableObjectName,
|
|
378
|
-
toolName,
|
|
379
|
-
skillName,
|
|
380
|
-
success ? "success" : "error"
|
|
381
|
-
],
|
|
382
|
-
doubles: [
|
|
383
|
-
0,
|
|
384
|
-
success ? 1 : 0,
|
|
385
|
-
argsRaw?.length ?? 0,
|
|
386
|
-
0,
|
|
387
|
-
0,
|
|
388
|
-
0,
|
|
389
|
-
0,
|
|
390
|
-
0
|
|
391
|
-
]
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
if (span.name === "ai.toolCall") {
|
|
397
|
-
const toolName = stringAttribute(span, "ai.toolCall.name");
|
|
398
|
-
const toolCallId = stringAttribute(span, "ai.toolCall.id");
|
|
399
|
-
if (toolCallId && emittedToolCallIds.has(toolCallId)) return;
|
|
400
|
-
if (toolCallId) emittedToolCallIds.add(toolCallId);
|
|
401
|
-
const argsRaw = stringAttribute(span, "ai.toolCall.args");
|
|
402
|
-
const toolInput = parseJson(argsRaw);
|
|
403
|
-
const success = span.status.code === 0;
|
|
404
|
-
const duration = durationMs(span.duration);
|
|
405
|
-
const argsSize = argsRaw?.length ?? 0;
|
|
406
|
-
const resultSize = stringAttribute(span, "ai.toolCall.result")?.length ?? 0;
|
|
407
|
-
const activatedSkills = extractSkillNames(toolName, toolInput);
|
|
408
|
-
const skillNames = activatedSkills.length ? activatedSkills : [currentSkillByChat.get(context.durableObjectName) ?? ""];
|
|
409
|
-
for (const skillName of skillNames) writeAnalyticsDatapoint(analytics, {
|
|
410
|
-
indexes: [context.actorId],
|
|
411
|
-
blobs: [
|
|
412
|
-
"tool_call",
|
|
413
|
-
context.agentName,
|
|
414
|
-
context.durableObjectName,
|
|
415
|
-
toolName ?? "",
|
|
416
|
-
skillName,
|
|
417
|
-
success ? "success" : "error"
|
|
418
|
-
],
|
|
419
|
-
doubles: [
|
|
420
|
-
duration,
|
|
421
|
-
success ? 1 : 0,
|
|
422
|
-
argsSize,
|
|
423
|
-
resultSize,
|
|
424
|
-
0,
|
|
425
|
-
0,
|
|
426
|
-
0,
|
|
427
|
-
0
|
|
428
|
-
]
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
//#endregion
|
|
433
|
-
//#region src/server/features/telemetry/index.ts
|
|
434
|
-
var AgentSpanExporter = class {
|
|
435
|
-
auditLogs;
|
|
436
|
-
analytics;
|
|
437
|
-
context;
|
|
438
|
-
constructor(auditLogs, analytics, context) {
|
|
439
|
-
this.auditLogs = auditLogs;
|
|
440
|
-
this.analytics = analytics;
|
|
441
|
-
this.context = context;
|
|
442
|
-
}
|
|
443
|
-
export(spans, resultCallback) {
|
|
444
|
-
(async () => {
|
|
445
|
-
try {
|
|
446
|
-
for (const span of spans) if (span.name === "ai.streamText.doStream") {
|
|
447
|
-
rememberDoStream(span, this.context.durableObjectName);
|
|
448
|
-
attachToolCallsToChat(span, this.context.durableObjectName);
|
|
449
|
-
handleAnalyticsSpan(span, this.analytics, this.context);
|
|
450
|
-
} else if (span.name === "ai.streamText") await handleAuditSpan(span, this.auditLogs, this.context);
|
|
451
|
-
else if (span.name === "ai.toolCall") {
|
|
452
|
-
rememberToolCall(span);
|
|
453
|
-
handleAnalyticsSpan(span, this.analytics, this.context);
|
|
454
|
-
}
|
|
455
|
-
resultCallback({ code: 0 });
|
|
456
|
-
} catch (error) {
|
|
457
|
-
resultCallback({
|
|
458
|
-
code: 1,
|
|
459
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
})();
|
|
463
|
-
}
|
|
464
|
-
async shutdown() {}
|
|
465
|
-
};
|
|
466
|
-
function createAgentTracer(auditLogs, analytics, context) {
|
|
467
|
-
return new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(new AgentSpanExporter(auditLogs, analytics, context))] }).getTracer("@economic/agents");
|
|
468
|
-
}
|
|
469
|
-
//#endregion
|
|
470
|
-
//#region src/server/route-agent-request.ts
|
|
471
|
-
async function routeAgentRequest$1(request, env, options) {
|
|
472
|
-
const response = await routeAgentRequest(request, env, options);
|
|
473
|
-
if (!response) return null;
|
|
474
|
-
const protocol = request.headers.get("Sec-WebSocket-Protocol");
|
|
475
|
-
if (response.status === 101 && protocol) {
|
|
476
|
-
const newResponse = new Response(null, response);
|
|
477
|
-
newResponse.headers.set("Sec-WebSocket-Protocol", protocol.split(",")[0].trim());
|
|
478
|
-
return newResponse;
|
|
479
|
-
}
|
|
480
|
-
return response;
|
|
481
|
-
}
|
|
482
|
-
//#endregion
|
|
483
|
-
export { extractTokenFromConnectRequest as a, verifyJwt as c, createJwtAuthConfig as i, createAgentTracer as n, getTargetActorId as o, recordFeedbackAnalytics as r, isJwtActorAuthorized as s, routeAgentRequest$1 as t };
|