@economic/agents 0.0.1-alpha.17 → 0.0.1-alpha.18

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
@@ -113,28 +113,17 @@ declare abstract class AIChatAgent<Env extends Cloudflare.Env = Cloudflare.Env>
113
113
  */
114
114
  protected log(message: string, payload?: Record<string, unknown>): Promise<void>;
115
115
  /**
116
- * Records this conversation in the `conversations` D1 table and resets
117
- * the idle summarization timer. Called automatically from `persistMessages`
118
- * after every turn.
119
- *
120
- * After each upsert, any pending `generateSummary` schedule is cancelled
121
- * and a new one is set for 30 minutes from now. If the user sends another
122
- * message before the timer fires, the schedule is cancelled and reset again
123
- * (debounce). When the conversation goes idle, `generateSummary` fires and
124
- * writes the LLM-generated title and summary to D1.
116
+ * Records this conversation in the `conversations` D1 table and triggers
117
+ * LLM-based title/summary generation when appropriate. Called automatically
118
+ * from `persistMessages` after every turn.
119
+ *
120
+ * On the first turn (no existing row), awaits `generateTitleAndSummary` and
121
+ * inserts the row with title and summary already populated. On subsequent
122
+ * turns, upserts the timestamp and fire-and-forgets a summary refresh every
123
+ * `SUMMARY_CONTEXT_MESSAGES` messages (when the context window fully turns
124
+ * over). Neither path blocks the response to the client.
125
125
  */
126
126
  private recordConversation;
127
- /**
128
- * Generates a title and summary for the conversation after 30 minutes of
129
- * inactivity. Invoked automatically by the Cloudflare Agents SDK scheduler
130
- * — do not call this directly.
131
- *
132
- * Delegates to `generateConversationSummary` in `features/conversations`,
133
- * which fetches the previous summary, slices to the last
134
- * `SUMMARY_CONTEXT_MESSAGES` messages, calls `fastModel` with a structured
135
- * output schema, and writes the result back to D1.
136
- */
137
- generateSummary(): Promise<void>;
138
127
  /**
139
128
  * Builds the parameter object for a `streamText` or `generateText` call,
140
129
  * pre-filling `messages`, `activeSkills`, and `fastModel` from this agent instance.
package/dist/index.mjs CHANGED
@@ -3724,21 +3724,19 @@ function superRefine(fn) {
3724
3724
  * Records a conversation row in the `conversations` D1 table.
3725
3725
  *
3726
3726
  * Called by `AIChatAgent` after every turn. On first call for a given
3727
- * `durableObjectId` the row is inserted with `created_at` set to now.
3727
+ * `durableObjectId` the row is inserted with `created_at` set to now,
3728
+ * and with the provided `title` and `summary` if supplied.
3728
3729
  * On subsequent calls only `user_id` and `updated_at` are refreshed —
3729
- * `created_at`, `title`, and `summary` are never overwritten.
3730
- *
3731
- * After upserting, `AIChatAgent` cancels any pending `generateSummary`
3732
- * schedule and resets it to fire 30 minutes from now, debouncing the
3733
- * idle timer on every turn.
3730
+ * `created_at`, `title`, and `summary` are never overwritten, preserving
3731
+ * any user edits.
3734
3732
  */
3735
- async function recordConversation(db, durableObjectId, userId) {
3733
+ async function recordConversation(db, durableObjectId, userId, title, summary) {
3736
3734
  const now = (/* @__PURE__ */ new Date()).toISOString();
3737
3735
  await db.prepare(`INSERT INTO conversations (durable_object_id, user_id, title, summary, created_at, updated_at)
3738
- VALUES (?, ?, NULL, NULL, ?, ?)
3736
+ VALUES (?, ?, ?, ?, ?, ?)
3739
3737
  ON CONFLICT(durable_object_id) DO UPDATE SET
3740
3738
  user_id = excluded.user_id,
3741
- updated_at = excluded.updated_at`).bind(durableObjectId, userId, now, now).run();
3739
+ updated_at = excluded.updated_at`).bind(durableObjectId, userId, title ?? null, summary ?? null, now, now).run();
3742
3740
  }
3743
3741
  /**
3744
3742
  * Returns the current `title` and `summary` for a conversation row,
@@ -3754,19 +3752,19 @@ async function updateConversationSummary(db, durableObjectId, title, summary) {
3754
3752
  await db.prepare(`UPDATE conversations SET title = ?, summary = ? WHERE durable_object_id = ?`).bind(title, summary, durableObjectId).run();
3755
3753
  }
3756
3754
  /**
3757
- * Generates a title and summary for a conversation using the provided model
3758
- * and writes the result back to D1.
3755
+ * Generates a title and summary for a conversation using the provided model.
3756
+ * Returns the result without writing to D1.
3759
3757
  *
3760
- * Fetches any existing summary first so the model can detect direction changes.
3761
- * Only the last `SUMMARY_CONTEXT_MESSAGES` messages are passed to keep the
3762
- * prompt bounded regardless of total conversation length.
3758
+ * Pass `existingSummary` so the model can detect direction changes when
3759
+ * updating an existing summary. Omit it (or pass undefined) for the initial
3760
+ * generation.
3763
3761
  *
3764
- * Called by `AIChatAgent.generateSummary()` after the idle timer fires.
3762
+ * Only the last `SUMMARY_CONTEXT_MESSAGES` messages are used to keep the
3763
+ * prompt bounded regardless of total conversation length.
3765
3764
  */
3766
- async function generateConversationSummary(db, durableObjectId, messages, model) {
3767
- const existing = await getConversationSummary(db, durableObjectId);
3765
+ async function generateTitleAndSummary(messages, model, existingSummary) {
3768
3766
  const recentMessages = await convertToModelMessages(messages.slice(-30));
3769
- const previousContext = existing?.summary ? `Previous summary: ${existing.summary}\n\nMost recent messages:` : "Conversation:";
3767
+ const previousContext = existingSummary ? `Previous summary: ${existingSummary}\n\nMost recent messages:` : "Conversation:";
3770
3768
  const { output } = await generateText({
3771
3769
  model,
3772
3770
  output: Output.object({ schema: object({
@@ -3775,7 +3773,22 @@ async function generateConversationSummary(db, durableObjectId, messages, model)
3775
3773
  }) }),
3776
3774
  prompt: `${previousContext}\n\n${formatMessagesForSummary(recentMessages)}`
3777
3775
  });
3778
- await updateConversationSummary(db, durableObjectId, output.title, output.summary);
3776
+ return output;
3777
+ }
3778
+ /**
3779
+ * Generates a title and summary for a conversation using the provided model
3780
+ * and writes the result back to D1.
3781
+ *
3782
+ * Fetches any existing summary first so the model can detect direction changes.
3783
+ * Only the last `SUMMARY_CONTEXT_MESSAGES` messages are passed to keep the
3784
+ * prompt bounded regardless of total conversation length.
3785
+ *
3786
+ * Called by `AIChatAgent` every `SUMMARY_CONTEXT_MESSAGES` messages after
3787
+ * the first turn.
3788
+ */
3789
+ async function generateConversationSummary(db, durableObjectId, messages, model) {
3790
+ const { title, summary } = await generateTitleAndSummary(messages, model, (await getConversationSummary(db, durableObjectId))?.summary ?? void 0);
3791
+ await updateConversationSummary(db, durableObjectId, title, summary);
3779
3792
  }
3780
3793
  //#endregion
3781
3794
  //#region src/agents/AIChatAgent.ts
@@ -3828,39 +3841,31 @@ var AIChatAgent = class extends AIChatAgent$1 {
3828
3841
  await insertAuditEvent(context.db, this.ctx.id.toString(), context.userId, message, payload);
3829
3842
  }
3830
3843
  /**
3831
- * Records this conversation in the `conversations` D1 table and resets
3832
- * the idle summarization timer. Called automatically from `persistMessages`
3833
- * after every turn.
3844
+ * Records this conversation in the `conversations` D1 table and triggers
3845
+ * LLM-based title/summary generation when appropriate. Called automatically
3846
+ * from `persistMessages` after every turn.
3834
3847
  *
3835
- * After each upsert, any pending `generateSummary` schedule is cancelled
3836
- * and a new one is set for 30 minutes from now. If the user sends another
3837
- * message before the timer fires, the schedule is cancelled and reset again
3838
- * (debounce). When the conversation goes idle, `generateSummary` fires and
3839
- * writes the LLM-generated title and summary to D1.
3848
+ * On the first turn (no existing row), awaits `generateTitleAndSummary` and
3849
+ * inserts the row with title and summary already populated. On subsequent
3850
+ * turns, upserts the timestamp and fire-and-forgets a summary refresh every
3851
+ * `SUMMARY_CONTEXT_MESSAGES` messages (when the context window fully turns
3852
+ * over). Neither path blocks the response to the client.
3840
3853
  */
3841
- async recordConversation() {
3854
+ async recordConversation(messageCount) {
3842
3855
  const context = this.resolveD1Context();
3843
3856
  if (!context) return;
3844
- await recordConversation(context.db, this.ctx.id.toString(), context.userId);
3845
- const existing = this.getSchedules({ type: "delayed" }).find((s) => s.callback === "generateSummary");
3846
- if (existing) await this.cancelSchedule(existing.id);
3847
- await this.schedule(1800, "generateSummary", {});
3848
- }
3849
- /**
3850
- * Generates a title and summary for the conversation after 30 minutes of
3851
- * inactivity. Invoked automatically by the Cloudflare Agents SDK scheduler
3852
- * do not call this directly.
3853
- *
3854
- * Delegates to `generateConversationSummary` in `features/conversations`,
3855
- * which fetches the previous summary, slices to the last
3856
- * `SUMMARY_CONTEXT_MESSAGES` messages, calls `fastModel` with a structured
3857
- * output schema, and writes the result back to D1.
3858
- */
3859
- async generateSummary() {
3860
- const context = this.resolveD1Context();
3861
- if (!context) return;
3862
- await generateConversationSummary(context.db, this.ctx.id.toString(), this.messages, this.fastModel);
3863
- this.log("conversation summary generated");
3857
+ const durableObjectId = this.ctx.id.toString();
3858
+ if (!await getConversationSummary(context.db, durableObjectId)) {
3859
+ const { title, summary } = await generateTitleAndSummary(this.messages, this.fastModel);
3860
+ await recordConversation(context.db, durableObjectId, context.userId, title, summary);
3861
+ this.log("conversation summary generated");
3862
+ } else {
3863
+ await recordConversation(context.db, durableObjectId, context.userId);
3864
+ if (messageCount % 30 === 0) {
3865
+ generateConversationSummary(context.db, durableObjectId, this.messages, this.fastModel);
3866
+ this.log("conversation summary updated");
3867
+ }
3868
+ }
3864
3869
  }
3865
3870
  /**
3866
3871
  * Builds the parameter object for a `streamText` or `generateText` call,
@@ -3913,7 +3918,7 @@ var AIChatAgent = class extends AIChatAgent$1 {
3913
3918
  * Returns an empty array if no skills have been loaded yet.
3914
3919
  */
3915
3920
  async getLoadedSkills() {
3916
- return getStoredSkills(this.sql);
3921
+ return getStoredSkills(this.sql.bind(this));
3917
3922
  }
3918
3923
  /**
3919
3924
  * Extracts skill state from activate_skill results, persists to DO SQLite,
@@ -3944,9 +3949,9 @@ var AIChatAgent = class extends AIChatAgent$1 {
3944
3949
  } catch {}
3945
3950
  }
3946
3951
  }
3947
- if (latestSkillState !== void 0) saveStoredSkills(this.sql, latestSkillState);
3952
+ if (latestSkillState !== void 0) saveStoredSkills(this.sql.bind(this), latestSkillState);
3948
3953
  this.log("turn completed", buildTurnSummary(messages, latestSkillState ?? []));
3949
- this.recordConversation();
3954
+ this.recordConversation(messages.length);
3950
3955
  const filtered = filterEphemeralMessages(messages);
3951
3956
  return super.persistMessages(filtered, excludeBroadcastIds, options);
3952
3957
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "0.0.1-alpha.17",
3
+ "version": "0.0.1-alpha.18",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "homepage": "https://github.com/author/library#readme",
6
6
  "bugs": {