@economic/agents 1.7.2 → 1.8.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 +32 -49
- package/dist/index.mjs +367 -179
- package/package.json +3 -2
- package/schema/chat/0003_drop_events.sql +5 -0
package/dist/index.d.mts
CHANGED
|
@@ -48,8 +48,7 @@ declare function buildLLMParams(config: BuildLLMParamsConfig): LLMParams;
|
|
|
48
48
|
//#region src/server/v1/types.d.ts
|
|
49
49
|
/**
|
|
50
50
|
* The context object available throughout an agent's lifetime — passed via
|
|
51
|
-
* `experimental_context` to tool `execute` functions.
|
|
52
|
-
* request body merged with platform capabilities like `logEvent`.
|
|
51
|
+
* `experimental_context` to tool `execute` functions.
|
|
53
52
|
*
|
|
54
53
|
* Define your own body shape and compose:
|
|
55
54
|
* ```typescript
|
|
@@ -57,12 +56,13 @@ declare function buildLLMParams(config: BuildLLMParamsConfig): LLMParams;
|
|
|
57
56
|
* type MyContext = AgentToolContext<MyBody>;
|
|
58
57
|
* ```
|
|
59
58
|
*/
|
|
60
|
-
type AgentToolContext<TBody = Record<string, unknown>,
|
|
61
|
-
|
|
62
|
-
_jwtIdentity?: TJwtIdentity;
|
|
59
|
+
type AgentToolContext<TBody = Record<string, unknown>, TUserContext = Record<string, unknown> | undefined> = TBody & {
|
|
60
|
+
_userContext?: TUserContext;
|
|
63
61
|
};
|
|
64
62
|
interface AgentEnv {
|
|
65
63
|
AGENT_DB: D1Database;
|
|
64
|
+
AGENTS_AUDIT_LOGS: R2Bucket;
|
|
65
|
+
AGENTS_ANALYTICS: AnalyticsEngineDataset;
|
|
66
66
|
}
|
|
67
67
|
interface ChatAgentEnv extends AgentEnv {}
|
|
68
68
|
//#endregion
|
|
@@ -86,16 +86,17 @@ interface JwtAuthConfig<TClaims extends Record<string, unknown> = Record<string,
|
|
|
86
86
|
//#endregion
|
|
87
87
|
//#region src/server/v1/agent/Agent.d.ts
|
|
88
88
|
/**
|
|
89
|
-
* Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading
|
|
90
|
-
*
|
|
89
|
+
* Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading
|
|
90
|
+
* and `buildLLMParams` wiring.
|
|
91
91
|
*
|
|
92
|
-
* Handles CF infrastructure concerns: DO SQLite persistence for loaded skill state
|
|
93
|
-
* and writing audit events to D1.
|
|
92
|
+
* Handles CF infrastructure concerns: DO SQLite persistence for loaded skill state.
|
|
94
93
|
*
|
|
95
94
|
* For chat agents with message history, compaction, and conversation recording,
|
|
96
95
|
* extend {@link ChatAgent} instead.
|
|
97
96
|
*/
|
|
98
|
-
declare abstract class Agent<Env extends Cloudflare.Env = Cloudflare.Env,
|
|
97
|
+
declare abstract class Agent<Env extends Cloudflare.Env = Cloudflare.Env, TUserContext extends Record<string, unknown> = Record<string, unknown>> extends Agent$1<Env & AgentEnv> {
|
|
98
|
+
protected clientIp?: string;
|
|
99
|
+
protected forwardedFor?: string;
|
|
99
100
|
/**
|
|
100
101
|
* Override to enable JWT authentication on WebSocket connections.
|
|
101
102
|
* Return the auth config based on the incoming request, or undefined to skip auth.
|
|
@@ -105,35 +106,25 @@ declare abstract class Agent<Env extends Cloudflare.Env = Cloudflare.Env, TJwtId
|
|
|
105
106
|
*/
|
|
106
107
|
protected getJwtAuthConfig?(request: Request): JwtAuthConfig<Record<string, unknown>> | undefined;
|
|
107
108
|
/**
|
|
108
|
-
* The
|
|
109
|
-
* Define
|
|
109
|
+
* The user context for the session.
|
|
110
|
+
* Define getUserContext to set a user context.
|
|
110
111
|
*/
|
|
111
|
-
protected
|
|
112
|
+
protected get userContext(): TUserContext;
|
|
112
113
|
/**
|
|
113
114
|
* Returns the identity following verification of the JWT token - getJwtAuthConfig is required.
|
|
114
|
-
*
|
|
115
|
-
* @
|
|
115
|
+
* This method should not have side effects - return a single object from it.
|
|
116
|
+
* @returns The user context from the request.
|
|
117
|
+
* @param jwtToken - A valid JWT token following authentication.
|
|
116
118
|
*/
|
|
117
|
-
protected
|
|
119
|
+
protected getUserContext?(jwtToken: string): Promise<TUserContext>;
|
|
118
120
|
/**
|
|
119
121
|
* Returns the user ID from the durable object name.
|
|
120
122
|
*/
|
|
121
123
|
protected getUserId(): string;
|
|
122
124
|
onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
|
|
123
|
-
/**
|
|
124
|
-
* Writes an audit event to D1 if `AGENT_DB` is bound on the environment,
|
|
125
|
-
* otherwise silently does nothing.
|
|
126
|
-
*
|
|
127
|
-
* Called automatically at the end of each LLM turn (from `onFinish` in
|
|
128
|
-
* `buildLLMParams`). Also available via `experimental_context.logEvent` in tool
|
|
129
|
-
* `execute` functions.
|
|
130
|
-
*/
|
|
131
|
-
protected logEvent(message: string, payload?: Record<string, unknown>): Promise<void>;
|
|
132
125
|
/**
|
|
133
126
|
* Builds the parameter object for a `streamText` or `generateText` call,
|
|
134
127
|
* pre-filling `activeSkills` from this agent instance.
|
|
135
|
-
* Injects `logEvent` into `experimental_context` and wires `onFinish` for
|
|
136
|
-
* turn-completed audit events.
|
|
137
128
|
*/
|
|
138
129
|
protected buildLLMParams<TBody = Record<string, unknown>>(config: Omit<BuildLLMParamsConfig, "messages"> & {
|
|
139
130
|
options?: OnChatMessageOptions;
|
|
@@ -148,17 +139,17 @@ interface MessageRating {
|
|
|
148
139
|
//#endregion
|
|
149
140
|
//#region src/server/v1/agent-chat/ChatAgent.d.ts
|
|
150
141
|
/**
|
|
151
|
-
* Chat agent for Cloudflare Agents SDK: lazy skill loading,
|
|
152
|
-
*
|
|
142
|
+
* Chat agent for Cloudflare Agents SDK: lazy skill loading, message persistence,
|
|
143
|
+
* compaction, and conversation metadata in D1.
|
|
153
144
|
*
|
|
154
145
|
* Handles CF infrastructure concerns: DO SQLite for loaded skill state,
|
|
155
|
-
* stripping skill meta-tool messages before persistence, history replay to
|
|
156
|
-
* newly connected clients
|
|
146
|
+
* stripping skill meta-tool messages before persistence, and history replay to
|
|
147
|
+
* newly connected clients.
|
|
157
148
|
*
|
|
158
149
|
* Skill loading, compaction, and LLM calls use `buildLLMParams` from
|
|
159
150
|
* `@economic/agents` inside `onChatMessage`.
|
|
160
151
|
*/
|
|
161
|
-
declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env,
|
|
152
|
+
declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env, TUserContext extends Record<string, unknown> = Record<string, unknown>> extends AIChatAgent<Env & ChatAgentEnv> {
|
|
162
153
|
/**
|
|
163
154
|
* The binding of the Durable Object instance for this agent.
|
|
164
155
|
*/
|
|
@@ -194,6 +185,8 @@ declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env, TJ
|
|
|
194
185
|
* Default is 15.
|
|
195
186
|
*/
|
|
196
187
|
protected maxMessagesBeforeCompaction?: number | undefined;
|
|
188
|
+
protected clientIp?: string;
|
|
189
|
+
protected forwardedFor?: string;
|
|
197
190
|
/**
|
|
198
191
|
* Override to enable JWT authentication on WebSocket connections.
|
|
199
192
|
* Return the auth config based on the incoming request, or undefined to skip auth.
|
|
@@ -203,36 +196,26 @@ declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env, TJ
|
|
|
203
196
|
*/
|
|
204
197
|
protected getJwtAuthConfig?(request: Request): JwtAuthConfig<Record<string, unknown>> | undefined;
|
|
205
198
|
/**
|
|
206
|
-
* The
|
|
207
|
-
* Define
|
|
199
|
+
* The user context for the session.
|
|
200
|
+
* Define getUserContext to set a user context.
|
|
208
201
|
*/
|
|
209
|
-
protected get
|
|
202
|
+
protected get userContext(): TUserContext;
|
|
210
203
|
/**
|
|
211
204
|
* Returns the identity following verification of the JWT token - getJwtAuthConfig is required.
|
|
212
205
|
* This method should not have side effects - return a single object from it.
|
|
213
|
-
* @returns The
|
|
214
|
-
* @param
|
|
206
|
+
* @returns The user context from the request.
|
|
207
|
+
* @param jwtToken - A valid JWT token following authentication.
|
|
215
208
|
*/
|
|
216
|
-
protected
|
|
209
|
+
protected getUserContext?(jwtToken: string): Promise<TUserContext>;
|
|
217
210
|
/**
|
|
218
211
|
* Returns the user ID from the durable object name.
|
|
219
212
|
*/
|
|
220
213
|
protected getUserId(): string;
|
|
221
214
|
onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
|
|
222
|
-
|
|
223
|
-
* Writes an audit event to D1 if `AGENT_DB` is bound on the environment,
|
|
224
|
-
* otherwise silently does nothing.
|
|
225
|
-
*
|
|
226
|
-
* Called automatically at the end of each LLM turn (from `onFinish` in
|
|
227
|
-
* `buildLLMParams`). Also available via `experimental_context.logEvent` in tool
|
|
228
|
-
* `execute` functions.
|
|
229
|
-
*/
|
|
230
|
-
protected logEvent(message: string, payload?: Record<string, unknown>): Promise<void>;
|
|
215
|
+
protected _pendingUserContextRequest?: Promise<void>;
|
|
231
216
|
/**
|
|
232
217
|
* Builds the parameter object for a `streamText` or `generateText` call,
|
|
233
218
|
* pre-filling `messages`, `activeSkills`, and `fastModel` from this agent instance.
|
|
234
|
-
* Injects `logEvent` into `experimental_context` and wires `onFinish` for
|
|
235
|
-
* turn-completed audit events and conversation recording.
|
|
236
219
|
*
|
|
237
220
|
* **Compaction** runs automatically when `fastModel` is set on the class, using
|
|
238
221
|
* `DEFAULT_MAX_MESSAGES_BEFORE_COMPACTION` (30) as the threshold. Override the
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { Output, convertToModelMessages, generateText, jsonSchema, stepCountIs,
|
|
|
2
2
|
import { Agent as Agent$1, callable, getCurrentAgent, routeAgentRequest as routeAgentRequest$1 } from "agents";
|
|
3
3
|
import { AIChatAgent } from "@cloudflare/ai-chat";
|
|
4
4
|
import { createRemoteJWKSet, decodeJwt, errors, jwtVerify } from "jose";
|
|
5
|
+
import { BasicTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
5
6
|
//#region src/server/shared/features/skills/index.ts
|
|
6
7
|
const TOOL_NAME_ACTIVATE_SKILL = "activate_skill";
|
|
7
8
|
const TOOL_NAME_LIST_CAPABILITIES = "list_capabilities";
|
|
@@ -269,98 +270,6 @@ async function routeAgentRequest(request, env, options) {
|
|
|
269
270
|
return response;
|
|
270
271
|
}
|
|
271
272
|
//#endregion
|
|
272
|
-
//#region src/server/shared/features/audit/audit.ts
|
|
273
|
-
/**
|
|
274
|
-
* Inserts a single audit event row into the shared `audit_events` D1 table.
|
|
275
|
-
*
|
|
276
|
-
* Called by `ChatAgentHarness.logEvent()` (and `AgentHarness.logEvent()`). Not intended for direct use.
|
|
277
|
-
*/
|
|
278
|
-
const SENSITIVE_KEYS = /^(password|token|secret|api_key|apikey|authorization|credentials)$/i;
|
|
279
|
-
const REDACTED = "[REDACTED]";
|
|
280
|
-
/** Deep-clone and redact values for keys that look like secrets (for audit logging). */
|
|
281
|
-
function sanitizePayload(value) {
|
|
282
|
-
if (value === null || value === void 0) return value;
|
|
283
|
-
if (Array.isArray(value)) return value.map(sanitizePayload);
|
|
284
|
-
if (typeof value === "object") {
|
|
285
|
-
const result = {};
|
|
286
|
-
for (const [key, val] of Object.entries(value)) result[key] = SENSITIVE_KEYS.test(key) ? REDACTED : sanitizePayload(val);
|
|
287
|
-
return result;
|
|
288
|
-
}
|
|
289
|
-
return value;
|
|
290
|
-
}
|
|
291
|
-
async function insertAuditEvent(db, durableObjectName, message, payload) {
|
|
292
|
-
await db.prepare(`INSERT INTO audit_events (id, durable_object_name, message, payload, created_at)
|
|
293
|
-
VALUES (?, ?, ?, ?, ?)`).bind(crypto.randomUUID(), durableObjectName, message, payload ? JSON.stringify(sanitizePayload(payload)) : null, (/* @__PURE__ */ new Date()).toISOString()).run();
|
|
294
|
-
}
|
|
295
|
-
function stringifyForSkillScan(output) {
|
|
296
|
-
if (typeof output === "string") return output;
|
|
297
|
-
if (output === null || output === void 0) return "";
|
|
298
|
-
try {
|
|
299
|
-
return JSON.stringify(output);
|
|
300
|
-
} catch {
|
|
301
|
-
return "";
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
function forEachStep(event, fn) {
|
|
305
|
-
if (event.steps.length > 0) for (const step of event.steps) fn(step);
|
|
306
|
-
else fn(event);
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* User text from provider request body (`contents` is e.g. Gemini format).
|
|
310
|
-
*/
|
|
311
|
-
function extractUserInputFromRequestBody(body) {
|
|
312
|
-
const firstContent = body?.contents?.[0];
|
|
313
|
-
if (firstContent?.role !== "user" || !firstContent.parts?.length) return "";
|
|
314
|
-
return firstContent.parts.map((p) => p.text).filter((t) => typeof t === "string").join(" ").trim();
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Builds the payload for a "Turn completed" audit event from the AI SDK
|
|
318
|
-
* `OnFinishEvent`.
|
|
319
|
-
*
|
|
320
|
-
* Returns:
|
|
321
|
-
* - `input`: user message text from `request.body.contents[0]` (Gemini-style)
|
|
322
|
-
* - `output`: assistant response text (truncated to 200 chars)
|
|
323
|
-
* - `toolCalls`: array of { toolName, toolInput, toolOutput }
|
|
324
|
-
* - `loadedSkills`: skill names extracted from activate_skill results
|
|
325
|
-
*/
|
|
326
|
-
function buildTurnLogPayload(event) {
|
|
327
|
-
const toolCalls = [];
|
|
328
|
-
let latestSkills;
|
|
329
|
-
const toolOutputs = /* @__PURE__ */ new Map();
|
|
330
|
-
forEachStep(event, (step) => {
|
|
331
|
-
for (const tr of step.toolResults) toolOutputs.set(tr.toolCallId, tr.output);
|
|
332
|
-
});
|
|
333
|
-
forEachStep(event, (step) => {
|
|
334
|
-
for (const tc of step.toolCalls) toolCalls.push({
|
|
335
|
-
name: tc.toolName,
|
|
336
|
-
input: tc.input,
|
|
337
|
-
output: toolOutputs.get(tc.toolCallId)
|
|
338
|
-
});
|
|
339
|
-
const considerToolResultForSkills = (toolName, output) => {
|
|
340
|
-
if (toolName !== "activate_skill") return;
|
|
341
|
-
const s = stringifyForSkillScan(output);
|
|
342
|
-
const sentinelIdx = s.indexOf(SKILL_STATE_SENTINEL);
|
|
343
|
-
if (sentinelIdx === -1) return;
|
|
344
|
-
try {
|
|
345
|
-
const stateJson = s.slice(sentinelIdx + 18);
|
|
346
|
-
latestSkills = JSON.parse(stateJson);
|
|
347
|
-
} catch {}
|
|
348
|
-
};
|
|
349
|
-
for (const tr of step.toolResults) considerToolResultForSkills(tr.toolName, tr.output);
|
|
350
|
-
});
|
|
351
|
-
const input = extractUserInputFromRequestBody(event.request?.body);
|
|
352
|
-
return {
|
|
353
|
-
detail: {
|
|
354
|
-
model: event.model.modelId,
|
|
355
|
-
tokens: event.usage?.totalTokens
|
|
356
|
-
},
|
|
357
|
-
loadedSkills: latestSkills ?? [],
|
|
358
|
-
toolCalls,
|
|
359
|
-
input: input.slice(0, 200),
|
|
360
|
-
output: (event.text ?? "").slice(0, 200)
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
//#endregion
|
|
364
273
|
//#region src/server/shared/features/auth/index.ts
|
|
365
274
|
const jwksByIssuer = /* @__PURE__ */ new Map();
|
|
366
275
|
function getJwksForIssuer(issuer) {
|
|
@@ -461,23 +370,290 @@ async function verifyJwt(request, config) {
|
|
|
461
370
|
};
|
|
462
371
|
}
|
|
463
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
|
|
464
636
|
//#region src/server/v1/agent/Agent.ts
|
|
465
637
|
/**
|
|
466
|
-
* Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading
|
|
467
|
-
*
|
|
638
|
+
* Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading
|
|
639
|
+
* and `buildLLMParams` wiring.
|
|
468
640
|
*
|
|
469
|
-
* Handles CF infrastructure concerns: DO SQLite persistence for loaded skill state
|
|
470
|
-
* and writing audit events to D1.
|
|
641
|
+
* Handles CF infrastructure concerns: DO SQLite persistence for loaded skill state.
|
|
471
642
|
*
|
|
472
643
|
* For chat agents with message history, compaction, and conversation recording,
|
|
473
644
|
* extend {@link ChatAgent} instead.
|
|
474
645
|
*/
|
|
475
646
|
var Agent = class extends Agent$1 {
|
|
647
|
+
clientIp;
|
|
648
|
+
forwardedFor;
|
|
476
649
|
/**
|
|
477
|
-
* The
|
|
478
|
-
* Define
|
|
650
|
+
* The user context for the session.
|
|
651
|
+
* Define getUserContext to set a user context.
|
|
479
652
|
*/
|
|
480
|
-
|
|
653
|
+
get userContext() {
|
|
654
|
+
const { connection } = getCurrentAgent();
|
|
655
|
+
return (connection?.state)?.userContext ?? {};
|
|
656
|
+
}
|
|
481
657
|
/**
|
|
482
658
|
* Returns the user ID from the durable object name.
|
|
483
659
|
*/
|
|
@@ -485,14 +661,22 @@ var Agent = class extends Agent$1 {
|
|
|
485
661
|
return this.name.split(":")[0];
|
|
486
662
|
}
|
|
487
663
|
async onConnect(connection, ctx) {
|
|
664
|
+
this.clientIp = ctx.request.headers.get("CF-Connecting-IP") ?? ctx.request.headers.get("X-Forwarded-For")?.split(",")[0]?.trim();
|
|
665
|
+
this.forwardedFor = ctx.request.headers.get("X-Forwarded-For") ?? void 0;
|
|
488
666
|
if (!this.env.AGENT_DB) {
|
|
489
|
-
console.error("[
|
|
490
|
-
connection.close(3e3, "Could not connect to agent
|
|
667
|
+
console.error("[Agent] Connection rejected: no AGENT_DB bound");
|
|
668
|
+
connection.close(3e3, "Could not connect to agent");
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (!this.env.AGENTS_AUDIT_LOGS) {
|
|
672
|
+
console.error("[Agent] Connection rejected: no AGENTS_AUDIT_LOGS bound. Audit logs are required.");
|
|
673
|
+
connection.close(3e3, "Could not connect to agent");
|
|
491
674
|
return;
|
|
492
675
|
}
|
|
676
|
+
if (!this.env.AGENTS_ANALYTICS) console.warn("[Agent] No AGENTS_ANALYTICS bound. Analytics will not be collected.");
|
|
493
677
|
if (!this.getUserId()) {
|
|
494
|
-
console.error("[
|
|
495
|
-
connection.close(3e3, "Could not connect to agent
|
|
678
|
+
console.error("[Agent] Connection rejected: name must be in the format userId:uniqueChatId");
|
|
679
|
+
connection.close(3e3, "Could not connect to agent");
|
|
496
680
|
return;
|
|
497
681
|
}
|
|
498
682
|
if (this.getJwtAuthConfig) {
|
|
@@ -502,7 +686,7 @@ var Agent = class extends Agent$1 {
|
|
|
502
686
|
try {
|
|
503
687
|
result = await verifyJwt(ctx.request, config);
|
|
504
688
|
} catch (error) {
|
|
505
|
-
console.error("[
|
|
689
|
+
console.error("[Agent] JWT verification error", error);
|
|
506
690
|
connection.close(4001, "Unauthorized");
|
|
507
691
|
return;
|
|
508
692
|
}
|
|
@@ -511,49 +695,49 @@ var Agent = class extends Agent$1 {
|
|
|
511
695
|
return;
|
|
512
696
|
}
|
|
513
697
|
const token = extractTokenFromConnectRequest(ctx.request);
|
|
514
|
-
if (token)
|
|
698
|
+
if (token) {
|
|
699
|
+
const userContext = await this.getUserContext?.(token);
|
|
700
|
+
if (userContext) connection.setState({ userContext });
|
|
701
|
+
}
|
|
515
702
|
}
|
|
516
703
|
}
|
|
517
704
|
return super.onConnect(connection, ctx);
|
|
518
705
|
}
|
|
519
706
|
/**
|
|
520
|
-
* Writes an audit event to D1 if `AGENT_DB` is bound on the environment,
|
|
521
|
-
* otherwise silently does nothing.
|
|
522
|
-
*
|
|
523
|
-
* Called automatically at the end of each LLM turn (from `onFinish` in
|
|
524
|
-
* `buildLLMParams`). Also available via `experimental_context.logEvent` in tool
|
|
525
|
-
* `execute` functions.
|
|
526
|
-
*/
|
|
527
|
-
async logEvent(message, payload) {
|
|
528
|
-
try {
|
|
529
|
-
await insertAuditEvent(this.env.AGENT_DB, this.name, message, payload);
|
|
530
|
-
} catch (error) {
|
|
531
|
-
console.error("[Agent] Failed to write audit event", error);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
/**
|
|
535
707
|
* Builds the parameter object for a `streamText` or `generateText` call,
|
|
536
708
|
* pre-filling `activeSkills` from this agent instance.
|
|
537
|
-
* Injects `logEvent` into `experimental_context` and wires `onFinish` for
|
|
538
|
-
* turn-completed audit events.
|
|
539
709
|
*/
|
|
540
710
|
async buildLLMParams(config) {
|
|
541
711
|
const activeSkills = await getStoredSkills(this.sql.bind(this));
|
|
542
712
|
const experimental_context = {
|
|
543
713
|
...config.experimental_context,
|
|
544
714
|
...config.options?.body,
|
|
545
|
-
|
|
546
|
-
_logEvent: this.logEvent.bind(this)
|
|
547
|
-
};
|
|
548
|
-
const onFinish = async (event) => {
|
|
549
|
-
this.logEvent("Turn completed", buildTurnLogPayload(event));
|
|
550
|
-
await config.onFinish?.(event);
|
|
715
|
+
_userContext: this.userContext
|
|
551
716
|
};
|
|
552
717
|
return buildLLMParams({
|
|
553
718
|
...config,
|
|
554
719
|
activeSkills,
|
|
555
720
|
experimental_context,
|
|
556
|
-
|
|
721
|
+
experimental_telemetry: {
|
|
722
|
+
...config.experimental_telemetry,
|
|
723
|
+
isEnabled: true,
|
|
724
|
+
tracer: createAgentTracer(this.env.AGENTS_AUDIT_LOGS, this.env.AGENTS_ANALYTICS, {
|
|
725
|
+
agentName: this.constructor.name,
|
|
726
|
+
conversationId: this.name,
|
|
727
|
+
userId: this.getUserId(),
|
|
728
|
+
...this.clientIp ? { clientIp: this.clientIp } : {},
|
|
729
|
+
...this.forwardedFor ? { forwardedFor: this.forwardedFor } : {}
|
|
730
|
+
}),
|
|
731
|
+
metadata: {
|
|
732
|
+
agentName: this.constructor.name,
|
|
733
|
+
version: "v1",
|
|
734
|
+
conversationId: this.name,
|
|
735
|
+
userId: this.getUserId(),
|
|
736
|
+
...this.clientIp ? { clientIp: this.clientIp } : {},
|
|
737
|
+
...this.forwardedFor ? { forwardedFor: this.forwardedFor } : {},
|
|
738
|
+
...config.experimental_telemetry?.metadata
|
|
739
|
+
}
|
|
740
|
+
}
|
|
557
741
|
});
|
|
558
742
|
}
|
|
559
743
|
};
|
|
@@ -836,12 +1020,12 @@ async function getMessageRatings(db, durableObjectName) {
|
|
|
836
1020
|
//#endregion
|
|
837
1021
|
//#region src/server/v1/agent-chat/ChatAgent.ts
|
|
838
1022
|
/**
|
|
839
|
-
* Chat agent for Cloudflare Agents SDK: lazy skill loading,
|
|
840
|
-
*
|
|
1023
|
+
* Chat agent for Cloudflare Agents SDK: lazy skill loading, message persistence,
|
|
1024
|
+
* compaction, and conversation metadata in D1.
|
|
841
1025
|
*
|
|
842
1026
|
* Handles CF infrastructure concerns: DO SQLite for loaded skill state,
|
|
843
|
-
* stripping skill meta-tool messages before persistence, history replay to
|
|
844
|
-
* newly connected clients
|
|
1027
|
+
* stripping skill meta-tool messages before persistence, and history replay to
|
|
1028
|
+
* newly connected clients.
|
|
845
1029
|
*
|
|
846
1030
|
* Skill loading, compaction, and LLM calls use `buildLLMParams` from
|
|
847
1031
|
* `@economic/agents` inside `onChatMessage`.
|
|
@@ -861,13 +1045,15 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
861
1045
|
* Default is 15.
|
|
862
1046
|
*/
|
|
863
1047
|
maxMessagesBeforeCompaction = 15;
|
|
1048
|
+
clientIp;
|
|
1049
|
+
forwardedFor;
|
|
864
1050
|
/**
|
|
865
|
-
* The
|
|
866
|
-
* Define
|
|
1051
|
+
* The user context for the session.
|
|
1052
|
+
* Define getUserContext to set a user context.
|
|
867
1053
|
*/
|
|
868
|
-
get
|
|
1054
|
+
get userContext() {
|
|
869
1055
|
const { connection } = getCurrentAgent();
|
|
870
|
-
return (connection?.state)?.
|
|
1056
|
+
return (connection?.state)?.userContext ?? {};
|
|
871
1057
|
}
|
|
872
1058
|
/**
|
|
873
1059
|
* Returns the user ID from the durable object name.
|
|
@@ -876,14 +1062,22 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
876
1062
|
return this.name.split(":")[0];
|
|
877
1063
|
}
|
|
878
1064
|
async onConnect(connection, ctx) {
|
|
1065
|
+
this.clientIp = ctx.request.headers.get("CF-Connecting-IP") ?? ctx.request.headers.get("X-Forwarded-For")?.split(",")[0]?.trim();
|
|
1066
|
+
this.forwardedFor = ctx.request.headers.get("X-Forwarded-For") ?? void 0;
|
|
879
1067
|
if (!this.env.AGENT_DB) {
|
|
880
|
-
console.error("[
|
|
881
|
-
connection.close(3e3, "Could not connect to agent
|
|
1068
|
+
console.error("[Agent] Connection rejected: no AGENT_DB bound");
|
|
1069
|
+
connection.close(3e3, "Could not connect to agent");
|
|
882
1070
|
return;
|
|
883
1071
|
}
|
|
1072
|
+
if (!this.env.AGENTS_AUDIT_LOGS) {
|
|
1073
|
+
console.error("[Agent] Connection rejected: no AGENTS_AUDIT_LOGS bound. Audit logs are required.");
|
|
1074
|
+
connection.close(3e3, "Could not connect to agent");
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
if (!this.env.AGENTS_ANALYTICS) console.warn("[Agent] No AGENTS_ANALYTICS bound. Analytics will not be collected.");
|
|
884
1078
|
if (!this.getUserId()) {
|
|
885
|
-
console.error("[
|
|
886
|
-
connection.close(3e3, "Could not connect to agent
|
|
1079
|
+
console.error("[Agent] Connection rejected: name must be in the format userId:uniqueChatId");
|
|
1080
|
+
connection.close(3e3, "Could not connect to agent");
|
|
887
1081
|
return;
|
|
888
1082
|
}
|
|
889
1083
|
if (this.getJwtAuthConfig) {
|
|
@@ -893,7 +1087,7 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
893
1087
|
try {
|
|
894
1088
|
result = await verifyJwt(ctx.request, config);
|
|
895
1089
|
} catch (error) {
|
|
896
|
-
console.error("[
|
|
1090
|
+
console.error("[Agent] JWT verification error", error);
|
|
897
1091
|
connection.close(4001, "Unauthorized");
|
|
898
1092
|
return;
|
|
899
1093
|
}
|
|
@@ -903,33 +1097,16 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
903
1097
|
}
|
|
904
1098
|
}
|
|
905
1099
|
const token = extractTokenFromConnectRequest(ctx.request);
|
|
906
|
-
if (token) {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
}
|
|
1100
|
+
if (token) this._pendingUserContextRequest = this.getUserContext?.(token).then((userContext) => {
|
|
1101
|
+
if (userContext) connection.setState({ userContext });
|
|
1102
|
+
});
|
|
910
1103
|
}
|
|
911
1104
|
return super.onConnect(connection, ctx);
|
|
912
1105
|
}
|
|
913
|
-
|
|
914
|
-
* Writes an audit event to D1 if `AGENT_DB` is bound on the environment,
|
|
915
|
-
* otherwise silently does nothing.
|
|
916
|
-
*
|
|
917
|
-
* Called automatically at the end of each LLM turn (from `onFinish` in
|
|
918
|
-
* `buildLLMParams`). Also available via `experimental_context.logEvent` in tool
|
|
919
|
-
* `execute` functions.
|
|
920
|
-
*/
|
|
921
|
-
async logEvent(message, payload) {
|
|
922
|
-
try {
|
|
923
|
-
await insertAuditEvent(this.env.AGENT_DB, this.name, message, payload);
|
|
924
|
-
} catch (error) {
|
|
925
|
-
console.error("[ChatAgent] Failed to write audit event", error);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
1106
|
+
_pendingUserContextRequest;
|
|
928
1107
|
/**
|
|
929
1108
|
* Builds the parameter object for a `streamText` or `generateText` call,
|
|
930
1109
|
* pre-filling `messages`, `activeSkills`, and `fastModel` from this agent instance.
|
|
931
|
-
* Injects `logEvent` into `experimental_context` and wires `onFinish` for
|
|
932
|
-
* turn-completed audit events and conversation recording.
|
|
933
1110
|
*
|
|
934
1111
|
* **Compaction** runs automatically when `fastModel` is set on the class, using
|
|
935
1112
|
* `DEFAULT_MAX_MESSAGES_BEFORE_COMPACTION` (30) as the threshold. Override the
|
|
@@ -941,22 +1118,36 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
941
1118
|
const experimental_context = {
|
|
942
1119
|
...config.experimental_context,
|
|
943
1120
|
...config.options?.body,
|
|
944
|
-
|
|
945
|
-
_logEvent: this.logEvent.bind(this)
|
|
1121
|
+
_userContext: this.userContext
|
|
946
1122
|
};
|
|
947
1123
|
const messages = await convertToModelMessages(this.messages);
|
|
948
1124
|
const fastModel = this.getFastModel();
|
|
949
1125
|
const processedMessages = fastModel && this.maxMessagesBeforeCompaction !== void 0 ? await compactIfNeeded(messages, fastModel, this.maxMessagesBeforeCompaction) : messages;
|
|
950
|
-
const onFinish = async (event) => {
|
|
951
|
-
this.logEvent("Turn completed", buildTurnLogPayload(event));
|
|
952
|
-
await config.onFinish?.(event);
|
|
953
|
-
};
|
|
954
1126
|
return buildLLMParams({
|
|
955
1127
|
...config,
|
|
956
1128
|
activeSkills,
|
|
957
1129
|
messages: processedMessages,
|
|
958
1130
|
experimental_context,
|
|
959
|
-
|
|
1131
|
+
experimental_telemetry: {
|
|
1132
|
+
...config.experimental_telemetry,
|
|
1133
|
+
isEnabled: true,
|
|
1134
|
+
tracer: createAgentTracer(this.env.AGENTS_AUDIT_LOGS, this.env.AGENTS_ANALYTICS, {
|
|
1135
|
+
agentName: this.constructor.name,
|
|
1136
|
+
conversationId: this.name,
|
|
1137
|
+
userId: this.getUserId(),
|
|
1138
|
+
...this.clientIp ? { clientIp: this.clientIp } : {},
|
|
1139
|
+
...this.forwardedFor ? { forwardedFor: this.forwardedFor } : {}
|
|
1140
|
+
}),
|
|
1141
|
+
metadata: {
|
|
1142
|
+
agentName: this.constructor.name,
|
|
1143
|
+
version: "v1",
|
|
1144
|
+
conversationId: this.name,
|
|
1145
|
+
userId: this.getUserId(),
|
|
1146
|
+
...this.clientIp ? { clientIp: this.clientIp } : {},
|
|
1147
|
+
...this.forwardedFor ? { forwardedFor: this.forwardedFor } : {},
|
|
1148
|
+
...config.experimental_telemetry?.metadata
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
960
1151
|
});
|
|
961
1152
|
}
|
|
962
1153
|
async persistMessages(messages, excludeBroadcastIds = [], options) {
|
|
@@ -967,7 +1158,7 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
967
1158
|
}
|
|
968
1159
|
async onChatResponse(result) {
|
|
969
1160
|
if (result.error) {
|
|
970
|
-
console.error("[
|
|
1161
|
+
console.error("[Agent] Chat response error", result.error);
|
|
971
1162
|
return;
|
|
972
1163
|
}
|
|
973
1164
|
recordConversationSummary(this.env.AGENT_DB, this.name, this.messages, this.getFastModel());
|
|
@@ -984,11 +1175,7 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
984
1175
|
}
|
|
985
1176
|
@callable({ description: "Delete a conversation by its id" }) async deleteConversation(id) {
|
|
986
1177
|
if (!id.startsWith(`${this.getUserId()}:`)) {
|
|
987
|
-
console.error("[
|
|
988
|
-
conversationName: id,
|
|
989
|
-
userId: this.getUserId()
|
|
990
|
-
});
|
|
991
|
-
this.logEvent("Failed to delete conversation: Not owned by current user", {
|
|
1178
|
+
console.error("[Agent] Failed to delete conversation: Not owned by current user", {
|
|
992
1179
|
conversationName: id,
|
|
993
1180
|
userId: this.getUserId()
|
|
994
1181
|
});
|
|
@@ -997,7 +1184,7 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
997
1184
|
try {
|
|
998
1185
|
await deleteConversationRow(this.env.AGENT_DB, id);
|
|
999
1186
|
} catch (error) {
|
|
1000
|
-
console.error("[
|
|
1187
|
+
console.error("[Agent] Failed to delete conversation row", {
|
|
1001
1188
|
conversationName: id,
|
|
1002
1189
|
error
|
|
1003
1190
|
});
|
|
@@ -1010,12 +1197,12 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
1010
1197
|
for (const connection of this.getConnections()) try {
|
|
1011
1198
|
connection.close(CONVERSATION_EXPIRED_CLOSE_CODE, CONVERSATION_EXPIRED_CLOSE_REASON);
|
|
1012
1199
|
} catch (error) {
|
|
1013
|
-
console.error("[
|
|
1200
|
+
console.error("[Agent] Failed to close expired conversation connection", error);
|
|
1014
1201
|
}
|
|
1015
1202
|
return super.destroy();
|
|
1016
1203
|
}
|
|
1017
1204
|
async deleteConversationCallback() {
|
|
1018
|
-
if (await this.deleteConversation(this.name))
|
|
1205
|
+
if (await this.deleteConversation(this.name)) console.log("[Agent] Conversation deleted due to inactivity", {
|
|
1019
1206
|
conversationName: this.name,
|
|
1020
1207
|
retentionDays: this.conversationRetentionDays ?? null
|
|
1021
1208
|
});
|
|
@@ -1053,6 +1240,7 @@ var ChatAgentHarness = class extends ChatAgent {
|
|
|
1053
1240
|
return [];
|
|
1054
1241
|
}
|
|
1055
1242
|
async onChatMessage(onFinish, options) {
|
|
1243
|
+
if (this._pendingUserContextRequest) await this._pendingUserContextRequest;
|
|
1056
1244
|
const ctx = options?.body;
|
|
1057
1245
|
return streamText(await this.buildLLMParams({
|
|
1058
1246
|
options,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@economic/agents",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "A starter for creating a TypeScript package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"prepublishOnly": "npm run build"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@clack/prompts": "^1.2.0"
|
|
28
|
+
"@clack/prompts": "^1.2.0",
|
|
29
|
+
"@opentelemetry/sdk-trace-base": "^2.7.1"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@cloudflare/ai-chat": "^0.6.2",
|