@economic/agents 2.3.19 → 2.3.21

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 CHANGED
@@ -114,6 +114,21 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
114
114
  * cold start, so registration is not allowed to block the connection.
115
115
  */
116
116
  protected registerInstance(): Promise<void>;
117
+ /**
118
+ * The `(agent_name, durable_object_name)` key of the registered row for this
119
+ * DO. For a parented (facet) agent this resolves to the top-level parent's
120
+ * row, since facets are not registered themselves.
121
+ */
122
+ protected getRegisteredInstanceKey(): {
123
+ agentName: string;
124
+ durableObjectName: string;
125
+ };
126
+ /**
127
+ * Records activity against this DO's registered row by advancing its
128
+ * `updated_at`, so instances can be ordered by recency of activity.
129
+ * Best-effort and non-blocking — never allowed to interfere with a turn.
130
+ */
131
+ protected touchInstance(): Promise<void>;
117
132
  /**
118
133
  * Resolves the analytics/telemetry agent name to the entry-point agent: the
119
134
  * top-level DO registered in the `agent` catalog table.
@@ -249,6 +264,13 @@ declare abstract class Assistant<State = unknown> extends Agent$1<Cloudflare.Env
249
264
  * Best-effort and idempotent: no-ops on conflict, retried on the next start.
250
265
  */
251
266
  private registerInstance;
267
+ /**
268
+ * Records activity against this Assistant's registry row by advancing its
269
+ * `updated_at`, so instances can be ordered by recency of activity. An
270
+ * Assistant is a top-level DO, so its row is keyed by its own class + name.
271
+ * Best-effort and non-blocking.
272
+ */
273
+ private touchInstance;
252
274
  /**
253
275
  * Binding of the legacy v1 chat Durable Object class, used to migrate a
254
276
  * user's v1 chats into facets the first time they connect. Set this on the
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-sspiKVkm.mjs";
1
+ import { a as verifyJwt, i as extractTokenFromConnectRequest, n as createAgentTracer, r as recordFeedbackAnalytics, t as routeAgentRequest } from "./route-agent-request-GWXHyI2L.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";
@@ -28,12 +28,31 @@ const TURN_PROTOCOL_RULES_PROMPT = `These rules are specific for responding to u
28
28
  * Registers a top-level DO in the global registry.
29
29
  *
30
30
  * Idempotent: keyed on `(agent_name, durable_object_name)`, so repeated calls
31
- * across cold starts no-op and `created_at` reflects the first registration.
31
+ * across cold starts no-op and timestamps reflect the first registration.
32
+ * `updated_at` is seeded to `created_at`; ongoing activity is recorded
33
+ * separately via {@link touchAgentInstance} so cold-start re-registration
34
+ * never inflates the activity timestamp.
32
35
  */
33
36
  async function registerAgentInstance(db, input) {
34
- await db.prepare(`INSERT INTO agent_registry (agent_name, durable_object_name, actor_id, created_at)
35
- VALUES (?, ?, ?, ?)
36
- ON CONFLICT (agent_name, durable_object_name) DO NOTHING`).bind(input.agentName, input.durableObjectName, input.actorId, input.createdAt).run();
37
+ await db.prepare(`INSERT INTO agent_registry (agent_name, durable_object_name, actor_id, created_at, updated_at)
38
+ VALUES (?, ?, ?, ?, ?)
39
+ ON CONFLICT (agent_name, durable_object_name) DO NOTHING`).bind(input.agentName, input.durableObjectName, input.actorId, input.createdAt, input.createdAt).run();
40
+ }
41
+ /**
42
+ * Records activity on a registered top-level DO by advancing its `updated_at`.
43
+ *
44
+ * Used to order instances by recency of activity. Keyed on
45
+ * `(agent_name, durable_object_name)` to target the exact registered row —
46
+ * for a parented (facet) agent this is the top-level parent's row, since
47
+ * facets are not registered themselves.
48
+ *
49
+ * Best-effort: a no-op if the row does not yet exist (registration is
50
+ * retried on the next cold start).
51
+ */
52
+ async function touchAgentInstance(db, input) {
53
+ await db.prepare(`UPDATE agent_registry
54
+ SET updated_at = ?
55
+ WHERE agent_name = ? AND durable_object_name = ?`).bind(input.updatedAt, input.agentName, input.durableObjectName).run();
37
56
  }
38
57
  //#endregion
39
58
  //#region src/server/util/agent.ts
@@ -172,6 +191,7 @@ var Agent = class extends Think {
172
191
  * `returned.tools` into your own `TurnConfig.tools` if you return tools.
173
192
  */
174
193
  async beforeTurn(ctx) {
194
+ this.touchInstance();
175
195
  this._toolContextForCurrentTurn = this.getToolContextForCurrentTurn(ctx.body);
176
196
  this._toolsForCurrentTurn = ctx.tools;
177
197
  const activeSkillName = this._getLastActivatedSkillName(ctx.messages);
@@ -313,6 +333,32 @@ var Agent = class extends Think {
313
333
  }
314
334
  }
315
335
  /**
336
+ * The `(agent_name, durable_object_name)` key of the registered row for this
337
+ * DO. For a parented (facet) agent this resolves to the top-level parent's
338
+ * row, since facets are not registered themselves.
339
+ */
340
+ getRegisteredInstanceKey() {
341
+ return {
342
+ agentName: this.parentPath[0]?.className ?? this.constructor.name,
343
+ durableObjectName: this.parentPath[0]?.name ?? this.name
344
+ };
345
+ }
346
+ /**
347
+ * Records activity against this DO's registered row by advancing its
348
+ * `updated_at`, so instances can be ordered by recency of activity.
349
+ * Best-effort and non-blocking — never allowed to interfere with a turn.
350
+ */
351
+ async touchInstance() {
352
+ try {
353
+ await touchAgentInstance(this.env.AGENTS_DB, {
354
+ ...this.getRegisteredInstanceKey(),
355
+ updatedAt: Date.now()
356
+ });
357
+ } catch (error) {
358
+ console.error("[Agent] Failed to update agent_registry activity", error);
359
+ }
360
+ }
361
+ /**
316
362
  * Resolves the analytics/telemetry agent name to the entry-point agent: the
317
363
  * top-level DO registered in the `agent` catalog table.
318
364
  *
@@ -718,6 +764,7 @@ var Assistant = class extends Agent$1 {
718
764
  const now = Date.now();
719
765
  await this.subAgent(this.agent, id);
720
766
  registerChat(this.sql.bind(this), id, now);
767
+ this.touchInstance(now);
721
768
  return id;
722
769
  }
723
770
  async deleteChat(id) {
@@ -762,6 +809,23 @@ var Assistant = class extends Agent$1 {
762
809
  }
763
810
  }
764
811
  /**
812
+ * Records activity against this Assistant's registry row by advancing its
813
+ * `updated_at`, so instances can be ordered by recency of activity. An
814
+ * Assistant is a top-level DO, so its row is keyed by its own class + name.
815
+ * Best-effort and non-blocking.
816
+ */
817
+ async touchInstance(updatedAt) {
818
+ try {
819
+ await touchAgentInstance(this.env.AGENTS_DB, {
820
+ agentName: this.constructor.name,
821
+ durableObjectName: this.name,
822
+ updatedAt
823
+ });
824
+ } catch (error) {
825
+ console.error("[Assistant] Failed to update agent_registry activity", error);
826
+ }
827
+ }
828
+ /**
765
829
  * Binding of the legacy v1 chat Durable Object class, used to migrate a
766
830
  * user's v1 chats into facets the first time they connect. Set this on the
767
831
  * concrete subclass to enable lazy v1 -> v2 migration; leave undefined to
@@ -277,6 +277,15 @@ async function handleAuditSpan(span, auditLogs, context) {
277
277
  }
278
278
  //#endregion
279
279
  //#region src/server/features/telemetry/analytics.ts
280
+ const emittedToolCallIds = /* @__PURE__ */ new Set();
281
+ function safeJsonString(value) {
282
+ if (value === void 0) return void 0;
283
+ try {
284
+ return JSON.stringify(value);
285
+ } catch {
286
+ return;
287
+ }
288
+ }
280
289
  function writeAnalyticsDatapoint(analytics, dataPoint) {
281
290
  if (!analytics) return;
282
291
  try {
@@ -308,6 +317,7 @@ function handleAnalyticsSpan(span, analytics, context) {
308
317
  if (span.name === "ai.streamText.doStream") {
309
318
  const promptMessages = parseJson(stringAttribute(span, "ai.prompt.messages"));
310
319
  const responseText = stringAttribute(span, "ai.response.text") ?? "";
320
+ const responseToolCalls = parseJson(stringAttribute(span, "ai.response.toolCalls"));
311
321
  writeAnalyticsDatapoint(analytics, {
312
322
  indexes: [context.actorId],
313
323
  blobs: [
@@ -329,10 +339,48 @@ function handleAnalyticsSpan(span, analytics, context) {
329
339
  responseText.length
330
340
  ]
331
341
  });
342
+ if (Array.isArray(responseToolCalls)) for (const value of responseToolCalls) {
343
+ if (!value || typeof value !== "object") continue;
344
+ const toolCall = value;
345
+ const toolCallId = typeof toolCall.toolCallId === "string" ? toolCall.toolCallId : void 0;
346
+ if (toolCallId && emittedToolCallIds.has(toolCallId)) continue;
347
+ const toolName = typeof toolCall.toolName === "string" ? toolCall.toolName : void 0;
348
+ if (!toolName) continue;
349
+ const input = toolCall.input;
350
+ const argsRaw = safeJsonString(input);
351
+ const success = span.status.code === 0;
352
+ const activatedSkills = extractSkillNames(toolName, input);
353
+ const skillNames = activatedSkills.length ? activatedSkills : [currentSkillByChat.get(context.durableObjectName) ?? ""];
354
+ if (toolCallId) emittedToolCallIds.add(toolCallId);
355
+ for (const skillName of skillNames) writeAnalyticsDatapoint(analytics, {
356
+ indexes: [context.actorId],
357
+ blobs: [
358
+ "tool_call",
359
+ context.agentName,
360
+ context.durableObjectName,
361
+ toolName,
362
+ skillName,
363
+ success ? "success" : "error"
364
+ ],
365
+ doubles: [
366
+ 0,
367
+ success ? 1 : 0,
368
+ argsRaw?.length ?? 0,
369
+ 0,
370
+ 0,
371
+ 0,
372
+ 0,
373
+ 0
374
+ ]
375
+ });
376
+ }
332
377
  return;
333
378
  }
334
379
  if (span.name === "ai.toolCall") {
335
380
  const toolName = stringAttribute(span, "ai.toolCall.name");
381
+ const toolCallId = stringAttribute(span, "ai.toolCall.id");
382
+ if (toolCallId && emittedToolCallIds.has(toolCallId)) return;
383
+ if (toolCallId) emittedToolCallIds.add(toolCallId);
336
384
  const argsRaw = stringAttribute(span, "ai.toolCall.args");
337
385
  const toolInput = parseJson(argsRaw);
338
386
  const success = span.status.code === 0;
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-sspiKVkm.mjs";
1
+ import { a as verifyJwt, i as extractTokenFromConnectRequest, n as createAgentTracer, t as routeAgentRequest } from "./route-agent-request-GWXHyI2L.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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "2.3.19",
3
+ "version": "2.3.21",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -0,0 +1,13 @@
1
+ -- ─── agent_registry.updated_at ────────────────────────────────────────────────────
2
+ -- Adds an activity timestamp to the registry so top-level DOs (one row per
3
+ -- Assistant, i.e. per user) can be ordered by recency of activity. Bumped by
4
+ -- the owning DO whenever a chat is created or has a turn.
5
+ --
6
+ -- Existing rows are backfilled to created_at so ordering remains sensible
7
+ -- before any new activity updates the column.
8
+
9
+ ALTER TABLE agent_registry ADD COLUMN updated_at INTEGER;
10
+
11
+ UPDATE agent_registry SET updated_at = created_at WHERE updated_at IS NULL;
12
+
13
+ CREATE INDEX IF NOT EXISTS agent_registry_updated ON agent_registry(updated_at);