@economic/agents 3.0.0 → 3.0.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 CHANGED
@@ -341,6 +341,61 @@ type Tool<Context extends Record<string, unknown> = Record<string, unknown>, INP
341
341
  executeTask?: ExecuteTask<Context, INPUT, NoInfer<OUTPUT> & TaskValue>;
342
342
  };
343
343
  export declare function tool<Context extends Record<string, unknown> = Record<string, unknown>, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any>(definition: Tool<Context, INPUT, OUTPUT>): Tool<Context, INPUT, OUTPUT>;
344
+ /** Tool execution options plus the additional context supplied by native Think actions. */
345
+ type ActionContext<Context extends Record<string, unknown> = Record<string, unknown>> = ToolExecutionOptions<Context> & ActionContext$1 & {
346
+ /** The action's resolved domain key, or its tool-call fallback when none was declared. */
347
+ idempotencyKey: string;
348
+ };
349
+ type Action<Context extends Record<string, unknown> = Record<string, unknown>, Input = any, Output = any> = Omit<ActionConfig<FlexibleSchema<Input>, Output>, "approval" | "execute" | "idempotencyKey" | "inputSchema" | "permissions"> & {
350
+ description: string;
351
+ inputSchema: FlexibleSchema<Input>;
352
+ approval?: ActionApprovalPolicy<Input>;
353
+ idempotencyKey?: ActionIdempotencyKey<Input>;
354
+ permissions?: ActionPermissionSpec<Input>;
355
+ authorize?: (ctx?: Context) => boolean;
356
+ onUnauthorized?: UnauthorizedPolicy<Context>;
357
+ execute(input: Input, ctx: ActionContext<Context>): Promise<Output> | Output;
358
+ };
359
+ type RegisteredAction<Context extends Record<string, unknown> = Record<string, unknown>, InputSchema extends FlexibleSchema = FlexibleSchema, Output = unknown> = Action$1<InputSchema, Output> & {
360
+ authorize?: (ctx?: Context) => boolean;
361
+ onUnauthorized?: UnauthorizedPolicy<Context>;
362
+ };
363
+ /** A native Think action with the SDK's standard authorization policy. */
364
+ export declare function action<Context extends Record<string, unknown> = Record<string, unknown>, Input = any, Output = any>(definition: Action<Context, Input, Output>): RegisteredAction<Context, FlexibleSchema<Input>, Output>;
365
+ interface SkillDefinition<Context extends Record<string, unknown>> {
366
+ name: string;
367
+ description: string;
368
+ instructions: string;
369
+ tools?: Record<string, Tool<Context>>;
370
+ actions?: Record<string, RegisteredAction<Context, any, any>>;
371
+ authorize?: (ctx?: Context) => boolean;
372
+ onUnauthorized?: UnauthorizedPolicy<Context>;
373
+ }
374
+ /**
375
+ * When the skill is active:
376
+ * - `"auto"`: once the model starts it, when it's relevant. The default.
377
+ * - `"always"`: on every turn, without being started.
378
+ * - `"manual"`: once the user starts it with its `command`. It's hidden from
379
+ * the model, which can't start it itself.
380
+ */
381
+ type SkillActivation = "auto" | "always" | "manual";
382
+ type Skill<Context extends Record<string, unknown> = Record<string, unknown>> = SkillDefinition<Context> & ({
383
+ activation?: "auto" | "always";
384
+ /** Lets the user start the skill from chat with `/command`. */
385
+ command?: string;
386
+ } | {
387
+ activation: "manual";
388
+ command: string;
389
+ });
390
+ interface SkillSource<Context extends Record<string, unknown> = Record<string, unknown>> extends SkillSource$1 {
391
+ command?: string;
392
+ activation?: SkillActivation;
393
+ tools?: Record<string, Tool<Context>>;
394
+ actions?: Record<string, RegisteredAction<Context, any, any>>;
395
+ authorize?: (ctx?: Context | undefined) => boolean;
396
+ onUnauthorized?: UnauthorizedPolicy<Context>;
397
+ }
398
+ export declare function skill<Context extends Record<string, unknown> = Record<string, unknown>>(definition: Skill<Context>): SkillSource<Context>;
344
399
  interface SpanIds {
345
400
  traceId: string;
346
401
  spanId: string;
@@ -463,6 +518,7 @@ declare class Tracing {
463
518
  type MessagePart = UIMessage["parts"][number];
464
519
  interface TurnInspection<RequestContext extends Record<string, unknown> = Record<string, unknown>, UserContext extends Record<string, unknown> = Record<string, unknown>> {
465
520
  messages?: ModelMessage[];
521
+ continuation?: boolean;
466
522
  requestContext?: RequestContext;
467
523
  userContext?: UserContext;
468
524
  }
@@ -479,6 +535,8 @@ type CurrentTurnState<RequestContext extends Record<string, unknown>, UserContex
479
535
  context?: ToolContext<RequestContext, UserContext>;
480
536
  inputTools: ToolSet;
481
537
  preparedTools: ToolSet;
538
+ activeSkillNames: Set<string>;
539
+ skillSourcesByName?: Promise<Map<string, SkillSource>>;
482
540
  unavailableSkillIds: Set<string>;
483
541
  skillsSystemBlock: string;
484
542
  visibleSkillNames: string[];
@@ -655,7 +713,7 @@ export declare abstract class Agent<RequestContext extends Record<string, unknow
655
713
  workspaceTools: boolean;
656
714
  sendReasoning: boolean;
657
715
  getSkillScriptRunner(): skills.SkillScriptRunner | null;
658
- protected pruneTurnMessages(messages: ModelMessage[]): ModelMessage[];
716
+ protected pruneTurnMessages(messages: ModelMessage[], continuation?: boolean): ModelMessage[];
659
717
  private _prepareTools;
660
718
  private _renderSkillsSystemBlock;
661
719
  /**
@@ -666,7 +724,6 @@ export declare abstract class Agent<RequestContext extends Record<string, unknow
666
724
  private _filterSkillHelperTools;
667
725
  private _filterActivateSkillTool;
668
726
  private _skillSources?;
669
- private _skillSourcesByName?;
670
727
  private _getUnauthorizedReason;
671
728
  private _getSkillSources;
672
729
  /** Each skill's source, by the skill's name. A source can hold several skills. */
@@ -703,12 +760,10 @@ export declare abstract class Agent<RequestContext extends Record<string, unknow
703
760
  private _withTurnContextFallback;
704
761
  /** The current turn's tool context, for every tool prepared for the turn. */
705
762
  private _toolsContext;
706
- /**
707
- * The skill the last `activate_skill` call started. A manual skill counts
708
- * only when its command started it, not a call the model made itself.
709
- */
710
- private _activeSkillName;
711
- private _getLastActivatedSkillName;
763
+ private _skillTurnStart;
764
+ private _activeSkillNamesForTurn;
765
+ private _activatedSkillNames;
766
+ private _getActivatedSkillNames;
712
767
  /**
713
768
  * The caller's identity, user context and credentials, kept until the
714
769
  * token expires so turns after hibernation, and scheduled turns, can still
@@ -796,61 +851,6 @@ type MessageFeedback = {
796
851
  created_at: number;
797
852
  updated_at: number;
798
853
  };
799
- /** Tool execution options plus the additional context supplied by native Think actions. */
800
- type ActionContext<Context extends Record<string, unknown> = Record<string, unknown>> = ToolExecutionOptions<Context> & ActionContext$1 & {
801
- /** The action's resolved domain key, or its tool-call fallback when none was declared. */
802
- idempotencyKey: string;
803
- };
804
- type Action<Context extends Record<string, unknown> = Record<string, unknown>, Input = any, Output = any> = Omit<ActionConfig<FlexibleSchema<Input>, Output>, "approval" | "execute" | "idempotencyKey" | "inputSchema" | "permissions"> & {
805
- description: string;
806
- inputSchema: FlexibleSchema<Input>;
807
- approval?: ActionApprovalPolicy<Input>;
808
- idempotencyKey?: ActionIdempotencyKey<Input>;
809
- permissions?: ActionPermissionSpec<Input>;
810
- authorize?: (ctx?: Context) => boolean;
811
- onUnauthorized?: UnauthorizedPolicy<Context>;
812
- execute(input: Input, ctx: ActionContext<Context>): Promise<Output> | Output;
813
- };
814
- type RegisteredAction<Context extends Record<string, unknown> = Record<string, unknown>, InputSchema extends FlexibleSchema = FlexibleSchema, Output = unknown> = Action$1<InputSchema, Output> & {
815
- authorize?: (ctx?: Context) => boolean;
816
- onUnauthorized?: UnauthorizedPolicy<Context>;
817
- };
818
- /** A native Think action with the SDK's standard authorization policy. */
819
- export declare function action<Context extends Record<string, unknown> = Record<string, unknown>, Input = any, Output = any>(definition: Action<Context, Input, Output>): RegisteredAction<Context, FlexibleSchema<Input>, Output>;
820
- interface SkillDefinition<Context extends Record<string, unknown>> {
821
- name: string;
822
- description: string;
823
- instructions: string;
824
- tools?: Record<string, Tool<Context>>;
825
- actions?: Record<string, RegisteredAction<Context, any, any>>;
826
- authorize?: (ctx?: Context) => boolean;
827
- onUnauthorized?: UnauthorizedPolicy<Context>;
828
- }
829
- /**
830
- * When the skill is active:
831
- * - `"auto"`: once the model starts it, when it's relevant. The default.
832
- * - `"always"`: on every turn, without being started.
833
- * - `"manual"`: once the user starts it with its `command`. It's hidden from
834
- * the model, which can't start it itself.
835
- */
836
- type SkillActivation = "auto" | "always" | "manual";
837
- type Skill<Context extends Record<string, unknown> = Record<string, unknown>> = SkillDefinition<Context> & ({
838
- activation?: "auto" | "always";
839
- /** Lets the user start the skill from chat with `/command`. */
840
- command?: string;
841
- } | {
842
- activation: "manual";
843
- command: string;
844
- });
845
- interface SkillSource<Context extends Record<string, unknown> = Record<string, unknown>> extends SkillSource$1 {
846
- command?: string;
847
- activation?: SkillActivation;
848
- tools?: Record<string, Tool<Context>>;
849
- actions?: Record<string, RegisteredAction<Context, any, any>>;
850
- authorize?: (ctx?: Context | undefined) => boolean;
851
- onUnauthorized?: UnauthorizedPolicy<Context>;
852
- }
853
- export declare function skill<Context extends Record<string, unknown> = Record<string, unknown>>(definition: Skill<Context>): SkillSource<Context>;
854
854
  interface InvocableSkill {
855
855
  /** Slash command without the leading `/`. */
856
856
  command: string;
package/dist/index.mjs CHANGED
@@ -2180,6 +2180,7 @@ var Agent = class Agent extends Think {
2180
2180
  _turn = {
2181
2181
  inputTools: {},
2182
2182
  preparedTools: {},
2183
+ activeSkillNames: /* @__PURE__ */ new Set(),
2183
2184
  unavailableSkillIds: /* @__PURE__ */ new Set(),
2184
2185
  skillsSystemBlock: "",
2185
2186
  visibleSkillNames: [],
@@ -2206,14 +2207,15 @@ var Agent = class Agent extends Think {
2206
2207
  */
2207
2208
  async beforeTurn(ctx) {
2208
2209
  await this._inspection;
2210
+ this._turn.skillSourcesByName = void 0;
2209
2211
  this._turn.context = this.createToolContextForCurrentTurn(ctx.body);
2210
2212
  const config = await this.getAgentConfig(this._turn.context);
2211
2213
  if (config.inputSchema && !ctx.continuation) this._refuseUnexpectedInput(config.inputSchema);
2212
2214
  this.tracing.turnStarted(ctx.messages, ctx.continuation);
2213
2215
  this.registerInstanceActivity();
2214
- const activeSkillName = await this._activeSkillName(ctx.messages);
2216
+ this._turn.activeSkillNames = await this._activeSkillNamesForTurn(ctx.messages, ctx.continuation);
2215
2217
  this._turn.inputTools = ctx.tools;
2216
- const { tools, activeTools } = await this._prepareTools(ctx.tools, activeSkillName, true);
2218
+ const { tools, activeTools } = await this._prepareTools(ctx.tools, this._turn.activeSkillNames, true);
2217
2219
  const baseSystem = await this.context.refreshSystemPrompt();
2218
2220
  this._turn.baseSystem = [baseSystem, this._turn.skillsSystemBlock].filter(Boolean).join("\n\n");
2219
2221
  const turnTracing = await this.tracing.turn(this._turn.context ?? {}, this.messages);
@@ -2231,7 +2233,7 @@ var Agent = class Agent extends Think {
2231
2233
  ...config.outputSchema && { output: Output.object({ schema: config.outputSchema }) },
2232
2234
  system: this._turn.baseSystem,
2233
2235
  model: this.getModel(this._turn.context),
2234
- messages: this.pruneTurnMessages(ctx.messages),
2236
+ messages: this.pruneTurnMessages(ctx.messages, ctx.continuation),
2235
2237
  tools: this._withTurnContextFallback(this._withTaskToolExecution(tools)),
2236
2238
  activeTools,
2237
2239
  experimental_transform: buildSourcesTransform(),
@@ -2263,10 +2265,11 @@ var Agent = class Agent extends Think {
2263
2265
  * @returns The step config.
2264
2266
  */
2265
2267
  async beforeStep(ctx) {
2266
- const activeSkillName = await this._activeSkillName(ctx.steps);
2268
+ const activatedSkillNames = await this._activatedSkillNames(ctx.steps);
2267
2269
  const toolsContext = this._toolsContext();
2268
- if (!activeSkillName) return { toolsContext };
2269
- const { activeTools } = await this._prepareTools(this._turn.inputTools, activeSkillName);
2270
+ if (activatedSkillNames.length === 0) return { toolsContext };
2271
+ for (const name of activatedSkillNames) this._turn.activeSkillNames.add(name);
2272
+ const { activeTools } = await this._prepareTools(this._turn.inputTools, this._turn.activeSkillNames);
2270
2273
  return {
2271
2274
  activeTools,
2272
2275
  system: this._turn.baseSystem,
@@ -2280,6 +2283,10 @@ var Agent = class Agent extends Think {
2280
2283
  async beforeToolCall(ctx) {
2281
2284
  const toolCtx = this._turn.context ?? {};
2282
2285
  const skill = await this._skillOfCall(ctx.toolName, ctx.input);
2286
+ if (SKILL_TOOL_NAMES.has(ctx.toolName) && !skill) return {
2287
+ action: "block",
2288
+ reason: GENERIC_UNAVAILABLE_TOOL_GUIDANCE
2289
+ };
2283
2290
  if (skill?.authorize?.(toolCtx) === false) return {
2284
2291
  action: "block",
2285
2292
  reason: this._getUnauthorizedReason(skill.onUnauthorized, toolCtx) ?? "Say naturally that you don't have that capability, then stop. Do not mention tools, apologize, invent a reason or remedy, or offer further help."
@@ -2563,6 +2570,7 @@ var Agent = class Agent extends Think {
2563
2570
  },
2564
2571
  inputTools: {},
2565
2572
  preparedTools: {},
2573
+ activeSkillNames: /* @__PURE__ */ new Set(),
2566
2574
  unavailableSkillIds: /* @__PURE__ */ new Set(),
2567
2575
  skillsSystemBlock: "",
2568
2576
  visibleSkillNames: [],
@@ -2583,7 +2591,8 @@ var Agent = class Agent extends Think {
2583
2591
  ...frameworkTools
2584
2592
  };
2585
2593
  this._turn.inputTools = inputTools;
2586
- const { tools, activeTools } = await this._prepareTools(inputTools, await this._activeSkillName(messages), true);
2594
+ this._turn.activeSkillNames = await this._activeSkillNamesForTurn(messages, input.continuation);
2595
+ const { tools, activeTools } = await this._prepareTools(inputTools, this._turn.activeSkillNames, true);
2587
2596
  const baseSystem = await this.context.refreshSystemPrompt();
2588
2597
  this._turn.baseSystem = [baseSystem, this._turn.skillsSystemBlock].filter(Boolean).join("\n\n");
2589
2598
  const snapshotContext = this._turn.context;
@@ -2592,7 +2601,7 @@ var Agent = class Agent extends Think {
2592
2601
  tools: withFallbackToolContext(this._withTaskToolExecution(tools), () => snapshotContext),
2593
2602
  activeTools,
2594
2603
  frameworkToolNames: Object.keys(frameworkTools),
2595
- messages: this.pruneTurnMessages(messages)
2604
+ messages: this.pruneTurnMessages(messages, input.continuation)
2596
2605
  };
2597
2606
  } finally {
2598
2607
  this._turn = previousTurn;
@@ -2610,19 +2619,21 @@ var Agent = class Agent extends Think {
2610
2619
  if (!("LOADER" in this.env)) return null;
2611
2620
  return skills.runner({ loader: this.env.LOADER });
2612
2621
  }
2613
- pruneTurnMessages(messages) {
2622
+ pruneTurnMessages(messages, continuation = false) {
2623
+ const turnStart = this._skillTurnStart(messages, continuation);
2624
+ const retainedSkillMessages = Math.max(5, messages.length - turnStart);
2614
2625
  return pruneMessages({
2615
2626
  messages,
2616
2627
  toolCalls: [{
2617
2628
  type: "before-last-2-messages",
2618
2629
  tools: []
2619
2630
  }, {
2620
- type: "before-last-5-messages",
2631
+ type: `before-last-${retainedSkillMessages}-messages`,
2621
2632
  tools: ["activate_skill"]
2622
2633
  }]
2623
2634
  });
2624
2635
  }
2625
- async _prepareTools(inputTools, activeSkillName, updateSkillsSystemBlock = false) {
2636
+ async _prepareTools(inputTools, activeSkillNames, updateSkillsSystemBlock = false) {
2626
2637
  const ctx = this._turn.context;
2627
2638
  const tools = {};
2628
2639
  const activeTools = [];
@@ -2720,8 +2731,16 @@ var Agent = class Agent extends Think {
2720
2731
  this._turn.authorizedSkillNames = /* @__PURE__ */ new Set([...catalog.authorizedSkillNames, ...await skillNames(manualSkillSources)]);
2721
2732
  }
2722
2733
  this._filterActivateSkillTool(tools, activeTools, this._turn.visibleSkillNames);
2723
- const activeSkill = skillSources.find((skill) => skill.id === activeSkillName);
2724
- if (activeSkill && skillAuthorization.get(activeSkill) !== false) {
2734
+ const activeSources = /* @__PURE__ */ new Set();
2735
+ if (activeSkillNames.size > 0) {
2736
+ const sourcesByName = await this._getSkillSourcesByName();
2737
+ for (const name of activeSkillNames) {
2738
+ const source = sourcesByName.get(name);
2739
+ if (source) activeSources.add(source);
2740
+ }
2741
+ }
2742
+ for (const activeSkill of skillSources) {
2743
+ if (!activeSources.has(activeSkill) || skillAuthorization.get(activeSkill) === false) continue;
2725
2744
  const activeSkillTools = Object.entries(activeSkill.tools ?? {});
2726
2745
  for (const [registrationName, definition] of Object.entries(activeSkill.actions ?? {})) {
2727
2746
  const name = actionToolName(registrationName, definition);
@@ -2730,7 +2749,7 @@ var Agent = class Agent extends Think {
2730
2749
  }
2731
2750
  for (const [name, tool] of activeSkillTools) if (configureTool(name, tool, actionsByToolName.get(name) ?? tool)) activeTools.push(name);
2732
2751
  }
2733
- if (!hasAlwaysActiveSkill) this._filterSkillHelperTools(activeTools, activeSkillName);
2752
+ if (!hasAlwaysActiveSkill) this._filterSkillHelperTools(activeTools, activeSkillNames);
2734
2753
  this._turn.preparedTools = tools;
2735
2754
  return {
2736
2755
  tools,
@@ -2777,8 +2796,8 @@ var Agent = class Agent extends Think {
2777
2796
  provider: { get: async () => source.authorize?.(this._turn.context) !== false && !this._turn.unavailableSkillIds.has(source.id) ? (await source.load(name))?.body.trim() ?? "" : "" }
2778
2797
  }))))).flat();
2779
2798
  }
2780
- _filterSkillHelperTools(activeTools, activeSkillName) {
2781
- if (activeSkillName && this._turn.authorizedSkillNames.has(activeSkillName)) return;
2799
+ _filterSkillHelperTools(activeTools, activeSkillNames) {
2800
+ for (const name of activeSkillNames) if (this._turn.authorizedSkillNames.has(name)) return;
2782
2801
  for (const name of SKILL_HELPER_TOOL_NAMES) {
2783
2802
  const index = activeTools.indexOf(name);
2784
2803
  if (index !== -1) activeTools.splice(index, 1);
@@ -2806,7 +2825,6 @@ var Agent = class Agent extends Think {
2806
2825
  };
2807
2826
  }
2808
2827
  _skillSources;
2809
- _skillSourcesByName;
2810
2828
  _getUnauthorizedReason(policy, ctx) {
2811
2829
  return typeof policy === "function" ? policy(ctx) : policy;
2812
2830
  }
@@ -2815,9 +2833,14 @@ var Agent = class Agent extends Think {
2815
2833
  }
2816
2834
  /** Each skill's source, by the skill's name. A source can hold several skills. */
2817
2835
  _getSkillSourcesByName() {
2818
- return this._skillSourcesByName ??= this._getSkillSources().then(async (sources) => {
2836
+ return this._turn.skillSourcesByName ??= this._getSkillSources().then(async (sources) => {
2819
2837
  const byName = /* @__PURE__ */ new Map();
2820
- for (const source of sources) for (const descriptor of await source.list()) if (!byName.has(descriptor.name)) byName.set(descriptor.name, source);
2838
+ for (const source of sources) try {
2839
+ for (const descriptor of await source.list()) if (!byName.has(descriptor.name)) byName.set(descriptor.name, source);
2840
+ } catch (error) {
2841
+ console.warn(`[Agent] Failed to resolve skill source "${source.id}"`, error);
2842
+ break;
2843
+ }
2821
2844
  return byName;
2822
2845
  });
2823
2846
  }
@@ -2864,36 +2887,63 @@ var Agent = class Agent extends Think {
2864
2887
  const context = this._turn.context ?? {};
2865
2888
  return Object.fromEntries(Object.keys(this._turn.preparedTools).map((name) => [name, context]));
2866
2889
  }
2867
- /**
2868
- * The skill the last `activate_skill` call started. A manual skill counts
2869
- * only when its command started it, not a call the model made itself.
2870
- */
2871
- async _activeSkillName(items) {
2872
- const manual = await skillNames((await this._getSkillSources()).filter((source) => source.activation === "manual"));
2873
- return this._getLastActivatedSkillName(items, new Set(manual));
2874
- }
2875
- _getLastActivatedSkillName(items = [], manualSkillNames) {
2876
- for (let index = items.length - 1; index >= 0; index--) {
2877
- const item = items[index];
2878
- if (!item || typeof item !== "object") continue;
2879
- const record = item;
2880
- const input = record.input;
2881
- if ((record.toolName === "activate_skill" || record.type === "tool-activate_skill") && input && typeof input === "object") {
2882
- const { name } = input;
2883
- if (typeof name === "string" && (!manualSkillNames.has(name) || String(record.toolCallId).startsWith("skill-command-"))) return name;
2890
+ _skillTurnStart(messages, continuation) {
2891
+ for (let index = messages.length - 1; index >= 0; index--) {
2892
+ if (messages[index].role !== "user") continue;
2893
+ if (continuation && index === messages.length - 1) {
2894
+ if (this.messages.at(-1)?.role !== "user") continue;
2884
2895
  }
2885
- for (const key of [
2886
- "toolResults",
2887
- "parts",
2888
- "content"
2889
- ]) {
2890
- const nestedItems = record[key];
2891
- if (Array.isArray(nestedItems)) {
2892
- const skillName = this._getLastActivatedSkillName(nestedItems, manualSkillNames);
2893
- if (skillName) return skillName;
2896
+ return index + 1;
2897
+ }
2898
+ return 0;
2899
+ }
2900
+ async _activeSkillNamesForTurn(messages, continuation = false) {
2901
+ const turnStart = this._skillTurnStart(messages, continuation);
2902
+ const previousSkills = await this._activatedSkillNames(messages.slice(0, turnStart));
2903
+ const currentSkills = await this._activatedSkillNames(messages.slice(turnStart));
2904
+ const previousSkill = previousSkills.at(-1);
2905
+ return new Set(previousSkill ? [previousSkill, ...currentSkills] : currentSkills);
2906
+ }
2907
+ async _activatedSkillNames(items) {
2908
+ const manual = await skillNames((await this._getSkillSources()).filter((source) => source.activation === "manual"));
2909
+ return this._getActivatedSkillNames(items, new Set(manual));
2910
+ }
2911
+ _getActivatedSkillNames(items, manualSkillNames) {
2912
+ const calls = /* @__PURE__ */ new Map();
2913
+ const outputs = /* @__PURE__ */ new Map();
2914
+ const collect = (entries) => {
2915
+ for (const item of entries) {
2916
+ if (!item || typeof item !== "object") continue;
2917
+ const record = item;
2918
+ const { type, toolCallId, input } = record;
2919
+ const uiActivation = type === "tool-activate_skill";
2920
+ if (typeof toolCallId === "string" && (uiActivation || record.toolName === "activate_skill")) {
2921
+ if ((type === "tool-call" || uiActivation) && input && typeof input === "object") {
2922
+ const { name } = input;
2923
+ if (typeof name === "string" && !calls.has(toolCallId)) calls.set(toolCallId, name);
2924
+ }
2925
+ if (type === "tool-result" && !record.preliminary || uiActivation && record.state === "output-available") outputs.set(toolCallId, record.output);
2926
+ }
2927
+ for (const key of [
2928
+ "content",
2929
+ "parts",
2930
+ "toolCalls",
2931
+ "toolResults"
2932
+ ]) {
2933
+ const nestedItems = record[key];
2934
+ if (Array.isArray(nestedItems)) collect(nestedItems);
2894
2935
  }
2895
2936
  }
2937
+ };
2938
+ collect(items);
2939
+ const names = [];
2940
+ for (const [toolCallId, name] of calls) {
2941
+ if (manualSkillNames.has(name) && !toolCallId.startsWith("skill-command-")) continue;
2942
+ let output = outputs.get(toolCallId);
2943
+ if (output && typeof output === "object" && "type" in output && output.type === "text") output = "value" in output ? output.value : void 0;
2944
+ if (typeof output === "string" && output.startsWith(`<skill_content name="${name}"`)) names.push(name);
2896
2945
  }
2946
+ return names;
2897
2947
  }
2898
2948
  /**
2899
2949
  * The caller's identity, user context and credentials, kept until the
@@ -3618,22 +3668,20 @@ var Assistant = class extends Agent$1 {
3618
3668
  };
3619
3669
  const COMPACTION_MAX_OUTPUT_TOKENS = 2e3;
3620
3670
  const COMPACTION_HEAD_MESSAGES = 2;
3671
+ const COMPACTION_TAIL_MESSAGES = 1;
3621
3672
  const COMPACTION_MESSAGE_CHARS = 2e4;
3622
3673
  const MODEL_COMPACTION_MESSAGE_ID = "compaction_model_context";
3623
- /**
3624
- * Summarizes everything between the first two messages and the newest one, so
3625
- * a single large answer is compacted before the prompt that follows it.
3626
- */
3674
+ /** Summarizes older content while retaining the latest request and active skill exchanges. */
3627
3675
  const createCompactFn = (model) => async (messages) => {
3628
- const middle = messages.slice(COMPACTION_HEAD_MESSAGES, -1);
3629
- const previous = middle.find((message) => message.id === MODEL_COMPACTION_MESSAGE_ID);
3630
- const compacted = middle.filter((message) => message.id !== MODEL_COMPACTION_MESSAGE_ID);
3631
- const first = compacted[0];
3632
- const last = compacted.at(-1);
3676
+ const { compacted } = partitionCompactionRange(messages, COMPACTION_HEAD_MESSAGES, messages.length - COMPACTION_TAIL_MESSAGES - 1);
3677
+ const previous = compacted.find((message) => message.id === MODEL_COMPACTION_MESSAGE_ID);
3678
+ const candidates = compacted.filter((message) => message.id !== MODEL_COMPACTION_MESSAGE_ID);
3679
+ const first = candidates[0];
3680
+ const last = candidates.at(-1);
3633
3681
  if (!first || !last) return null;
3634
3682
  const { text } = await generateText({
3635
3683
  model,
3636
- prompt: summaryPrompt(compacted, previous),
3684
+ prompt: summaryPrompt(candidates, previous),
3637
3685
  maxOutputTokens: COMPACTION_MAX_OUTPUT_TOKENS
3638
3686
  });
3639
3687
  if (!text.trim()) return null;
@@ -3718,6 +3766,40 @@ function migrateSessionCompaction(sql) {
3718
3766
  sql`DELETE FROM cf_agents_session_compactions WHERE session_id = ''`;
3719
3767
  return true;
3720
3768
  }
3769
+ function partitionCompactionRange(messages, from, to) {
3770
+ const turnStart = messages.findLastIndex((message) => message.role === "user");
3771
+ const retained = [];
3772
+ const compacted = [];
3773
+ for (let index = from; index <= to; index++) {
3774
+ const message = messages[index];
3775
+ if (index === turnStart) {
3776
+ retained.push(message);
3777
+ continue;
3778
+ }
3779
+ const retainedParts = [];
3780
+ const compactedParts = [];
3781
+ for (const part of message.parts) if (part.type === "step-start") retainedParts.push(part);
3782
+ else if (index > turnStart && message.role === "assistant" && retainTurnToolPart(part)) retainedParts.push(part);
3783
+ else compactedParts.push(part);
3784
+ if (retainedParts.some((part) => part.type !== "step-start")) retained.push({
3785
+ ...message,
3786
+ parts: retainedParts
3787
+ });
3788
+ if (compactedParts.length > 0) compacted.push({
3789
+ ...message,
3790
+ parts: compactedParts
3791
+ });
3792
+ }
3793
+ return {
3794
+ retained,
3795
+ compacted
3796
+ };
3797
+ }
3798
+ function retainTurnToolPart(part) {
3799
+ if (!isToolUIPart(part)) return false;
3800
+ if (getToolName(part) === "activate_skill" || part.approval) return true;
3801
+ return part.state === "input-streaming" || part.state === "input-available" || part.state === "output-available" && part.preliminary === true;
3802
+ }
3721
3803
  function applyModelCompaction(messages, state) {
3722
3804
  const fromIndex = messages.findIndex((message) => message.id === state.fromMessageId);
3723
3805
  const toIndex = messages.findIndex((message) => message.id === state.toMessageId);
@@ -3732,6 +3814,7 @@ function applyModelCompaction(messages, state) {
3732
3814
  text: state.summary
3733
3815
  }]
3734
3816
  },
3817
+ ...partitionCompactionRange(messages, fromIndex, toIndex).retained,
3735
3818
  ...messages.slice(toIndex + 1)
3736
3819
  ];
3737
3820
  }
@@ -3756,10 +3839,23 @@ async function prepareModelHistory(messages, existingState, compact, systemPromp
3756
3839
  state,
3757
3840
  stateChanged
3758
3841
  };
3842
+ let fromIndex = messages.findIndex((message) => message.id === result.fromMessageId);
3843
+ let toIndex = messages.findIndex((message) => message.id === result.toMessageId);
3844
+ if (fromIndex === -1 || toIndex < fromIndex) return {
3845
+ messages: projected,
3846
+ state,
3847
+ stateChanged
3848
+ };
3849
+ if (state) {
3850
+ const previousStart = messages.findIndex((message) => message.id === state.fromMessageId);
3851
+ const previousEnd = messages.findIndex((message) => message.id === state.toMessageId);
3852
+ fromIndex = Math.min(previousStart, fromIndex);
3853
+ toIndex = Math.max(previousEnd, toIndex);
3854
+ }
3759
3855
  const nextState = {
3760
3856
  summary: result.summary,
3761
- fromMessageId: state?.fromMessageId ?? result.fromMessageId,
3762
- toMessageId: result.toMessageId
3857
+ fromMessageId: messages[fromIndex].id,
3858
+ toMessageId: messages[toIndex].id
3763
3859
  };
3764
3860
  const nextProjection = applyModelCompaction(messages, nextState);
3765
3861
  if (!nextProjection) return {
@@ -3797,6 +3893,7 @@ async function withModelCompaction(modelMessages, transcript, state, tools) {
3797
3893
  text: state.summary
3798
3894
  }]
3799
3895
  },
3896
+ ...await convert(partitionCompactionRange(transcript, from, to).retained),
3800
3897
  ...modelMessages.slice((await convert(transcript.slice(0, to + 1))).length)
3801
3898
  ];
3802
3899
  }
@@ -4098,7 +4195,7 @@ var ChatAgent = class extends Agent {
4098
4195
  const messages = await withModelCompaction(ctx.messages, this.messages, prepared.state, tools);
4099
4196
  if (messages) return {
4100
4197
  ...config,
4101
- messages: this.pruneTurnMessages([...messages, ...skillInvocation?.messages ?? []])
4198
+ messages: this.pruneTurnMessages([...messages, ...skillInvocation?.messages ?? []], ctx.continuation)
4102
4199
  };
4103
4200
  }
4104
4201
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "license": "MIT",
6
6
  "files": [