@economic/agents 2.13.0 → 2.14.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/README.md CHANGED
@@ -421,6 +421,7 @@ import { z } from "zod";
421
421
 
422
422
  export const weatherSkill = skill({
423
423
  name: "weather",
424
+ command: "weather",
424
425
  description: "Look up current weather and forecasts. Use for any weather question.",
425
426
  instructions: `
426
427
  # Weather
@@ -445,6 +446,16 @@ Use this skill whenever the user asks about current conditions or a forecast.
445
446
 
446
447
  Return skills from `getSkills()`. Add `authorize(ctx)` to gate a skill (and its tools). Denied skills are hidden from the catalog and `activate_skill` by default. Set `onUnauthorized` to a reason string, or a function that returns one, to expose a denied skill with user-facing guidance. A function can return `undefined` to keep it hidden. `skill()` throws if `description` or `instructions` is missing.
447
448
 
449
+ Set `command` to make a code-defined skill directly invocable from chat. For example,
450
+ `/weather in Copenhagen` loads the weather instructions and enables its tools before the
451
+ first model step; the model still interprets `in Copenhagen` and chooses the appropriate
452
+ tool. Commands use lower-case letters, numbers, and hyphens, without the leading `/`.
453
+
454
+ `ChatAgent` also exposes an authenticated `GET getInvocableSkills` action for command
455
+ pickers. It returns the commands available to the current user as
456
+ `{ command, name, description }[]`. The existing skill `description` is used for both the
457
+ model catalog and the human-facing picker.
458
+
448
459
  ### Native Agent Skills
449
460
 
450
461
  Native Agent Skills are folders containing a `SKILL.md` file, with optional references and scripts:
package/dist/index.d.mts CHANGED
@@ -153,6 +153,11 @@ type AnalyticsEventPayload = {
153
153
  durationMs: number;
154
154
  inputSize: number;
155
155
  outputSize: number;
156
+ } | {
157
+ type: "skill.invoked";
158
+ skillName: string;
159
+ command: string;
160
+ source: "slash-command";
156
161
  } | {
157
162
  type: "feedback.submitted";
158
163
  messageId: string;
@@ -207,6 +212,7 @@ declare class Analytics {
207
212
  promptReceived(messages: readonly ModelMessage[], continuation: boolean): void;
208
213
  turnCompleted(requestId: string, status: string, continuation: boolean, response: unknown): void;
209
214
  feedbackSubmitted(messageId: string, rating: 1 | -1 | 0): void;
215
+ skillInvoked(skillName: string, command: string): void;
210
216
  event(payload: AnalyticsEventPayload | (() => AnalyticsEventPayload)): void;
211
217
  }
212
218
  //#endregion
@@ -220,6 +226,16 @@ interface TurnSnapshot {
220
226
  frameworkToolNames: string[];
221
227
  messages: ModelMessage[];
222
228
  }
229
+ type CurrentTurnState<RequestContext extends Record<string, unknown>, UserContext extends Record<string, unknown>> = {
230
+ context?: ToolContext<RequestContext, UserContext>;
231
+ inputTools: ToolSet;
232
+ preparedTools: ToolSet;
233
+ unavailableSkillIds: Set<string>;
234
+ skillsSystemBlock: string;
235
+ visibleSkillNames: string[];
236
+ authorizedSkillNames: Set<string>;
237
+ baseSystem: string;
238
+ };
223
239
  type Authentication<UserContext> = {
224
240
  success: true;
225
241
  userContext?: UserContext;
@@ -243,8 +259,9 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
243
259
  onStart(): Promise<void>;
244
260
  configureSession(session: Session): Session;
245
261
  onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
246
- private _turn;
247
- private getToolContextForCurrentTurn;
262
+ protected _turn: CurrentTurnState<RequestContext, UserContext>;
263
+ protected createToolContextForCurrentTurn(requestContext?: RequestContext): ToolContext<RequestContext, UserContext>;
264
+ protected getToolContextForCurrentTurn(): ToolContext<RequestContext, UserContext> | undefined;
248
265
  /**
249
266
  * Merges the client request `body` into `experimental_context` for tools
250
267
  * returned from {@link getTools} only (Think-internal tools are unchanged).
@@ -432,12 +449,69 @@ type MessageFeedback = {
432
449
  updated_at: number;
433
450
  };
434
451
  //#endregion
452
+ //#region src/server/util/actions.d.ts
453
+ /** Tool execution options plus the additional context supplied by native Think actions. */
454
+ type ActionContext<Context extends Record<string, unknown> = Record<string, unknown>> = Omit<ToolExecutionOptions, "experimental_context"> & ActionContext$1 & {
455
+ experimental_context: Context; /** The action's resolved domain key, or its tool-call fallback when none was declared. */
456
+ idempotencyKey: string;
457
+ };
458
+ type Action<Context extends Record<string, unknown> = Record<string, unknown>, Input = any, Output = any> = Omit<ActionConfig<FlexibleSchema<Input>, Output>, "approval" | "execute" | "idempotencyKey" | "inputSchema" | "permissions"> & {
459
+ description: string;
460
+ inputSchema: FlexibleSchema<Input>;
461
+ approval?: ActionApprovalPolicy<Input>;
462
+ idempotencyKey?: ActionIdempotencyKey<Input>;
463
+ permissions?: ActionPermissionSpec<Input>;
464
+ authorize?: (ctx?: Context) => boolean;
465
+ onUnauthorized?: UnauthorizedPolicy<Context>;
466
+ execute(input: Input, ctx: ActionContext<Context>): Promise<Output> | Output;
467
+ };
468
+ type RegisteredAction<Context extends Record<string, unknown> = Record<string, unknown>, InputSchema extends FlexibleSchema = FlexibleSchema, Output = unknown> = Action$1<InputSchema, Output> & {
469
+ authorize?: (ctx?: Context) => boolean;
470
+ onUnauthorized?: UnauthorizedPolicy<Context>;
471
+ };
472
+ /** A native Think action with the SDK's standard authorization policy. */
473
+ 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>;
474
+ //#endregion
475
+ //#region src/server/util/skills.d.ts
476
+ interface Skill<Context extends Record<string, unknown> = Record<string, unknown>> {
477
+ name: string;
478
+ description: string;
479
+ instructions: string;
480
+ /** Enables direct invocation from chat with `/command`. */
481
+ command?: string;
482
+ tools?: Record<string, Tool<Context>>;
483
+ actions?: Record<string, RegisteredAction<Context, any, any>>;
484
+ authorize?: (ctx?: Context) => boolean;
485
+ onUnauthorized?: UnauthorizedPolicy<Context>;
486
+ }
487
+ interface SkillSource<Context extends Record<string, unknown> = Record<string, unknown>> extends SkillSource$1 {
488
+ command?: string;
489
+ tools?: Record<string, Tool<Context>>;
490
+ actions?: Record<string, RegisteredAction<Context, any, any>>;
491
+ authorize?: (ctx?: Context | undefined) => boolean;
492
+ onUnauthorized?: UnauthorizedPolicy<Context>;
493
+ }
494
+ declare function skill<Context extends Record<string, unknown> = Record<string, unknown>>(definition: Skill<Context>): SkillSource<Context>;
495
+ //#endregion
496
+ //#region src/server/util/skillCommands.d.ts
497
+ interface InvocableSkill {
498
+ /** Slash command without the leading `/`. */
499
+ command: string;
500
+ /** Name of the skill the command activates. */
501
+ name: string;
502
+ /** Human- and model-readable summary from the skill definition. */
503
+ description: string;
504
+ }
505
+ //#endregion
435
506
  //#region src/server/agents/ChatAgent.d.ts
436
507
  declare abstract class ChatAgent<RequestContext extends Record<string, unknown> = Record<string, unknown>, UserContext extends Record<string, unknown> = Record<string, unknown>, State = unknown> extends Agent<RequestContext, UserContext, State> {
437
508
  protected agentType: AgentConnectionType;
438
509
  onStart(): Promise<void>;
439
510
  configureSession(session: Session): Session;
440
511
  beforeTurn(ctx: TurnContext): Promise<void | TurnConfig>;
512
+ private getSlashCommandSkill;
513
+ private invokeSkill;
514
+ protected getInvocableSkills(userContext?: UserContext | undefined, requestContext?: RequestContext): Promise<InvocableSkill[]>;
441
515
  onChatResponse(result: ChatResponseResult): Promise<void>;
442
516
  onRequest(request: Request): Promise<Response>;
443
517
  /**
@@ -512,51 +586,10 @@ declare abstract class Assistant<State = unknown> extends Agent$1<Cloudflare.Env
512
586
  observability?: Observability;
513
587
  }
514
588
  //#endregion
515
- //#region src/server/util/actions.d.ts
516
- /** Tool execution options plus the additional context supplied by native Think actions. */
517
- type ActionContext<Context extends Record<string, unknown> = Record<string, unknown>> = Omit<ToolExecutionOptions, "experimental_context"> & ActionContext$1 & {
518
- experimental_context: Context; /** The action's resolved domain key, or its tool-call fallback when none was declared. */
519
- idempotencyKey: string;
520
- };
521
- type Action<Context extends Record<string, unknown> = Record<string, unknown>, Input = any, Output = any> = Omit<ActionConfig<FlexibleSchema<Input>, Output>, "approval" | "execute" | "idempotencyKey" | "inputSchema" | "permissions"> & {
522
- description: string;
523
- inputSchema: FlexibleSchema<Input>;
524
- approval?: ActionApprovalPolicy<Input>;
525
- idempotencyKey?: ActionIdempotencyKey<Input>;
526
- permissions?: ActionPermissionSpec<Input>;
527
- authorize?: (ctx?: Context) => boolean;
528
- onUnauthorized?: UnauthorizedPolicy<Context>;
529
- execute(input: Input, ctx: ActionContext<Context>): Promise<Output> | Output;
530
- };
531
- type RegisteredAction<Context extends Record<string, unknown> = Record<string, unknown>, InputSchema extends FlexibleSchema = FlexibleSchema, Output = unknown> = Action$1<InputSchema, Output> & {
532
- authorize?: (ctx?: Context) => boolean;
533
- onUnauthorized?: UnauthorizedPolicy<Context>;
534
- };
535
- /** A native Think action with the SDK's standard authorization policy. */
536
- 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>;
537
- //#endregion
538
- //#region src/server/util/skills.d.ts
539
- interface Skill<Context extends Record<string, unknown> = Record<string, unknown>> {
540
- name: string;
541
- description: string;
542
- instructions: string;
543
- tools?: Record<string, Tool<Context>>;
544
- actions?: Record<string, RegisteredAction<Context, any, any>>;
545
- authorize?: (ctx?: Context) => boolean;
546
- onUnauthorized?: UnauthorizedPolicy<Context>;
547
- }
548
- interface SkillSource<Context extends Record<string, unknown> = Record<string, unknown>> extends SkillSource$1 {
549
- tools?: Record<string, Tool<Context>>;
550
- actions?: Record<string, RegisteredAction<Context, any, any>>;
551
- authorize?: (ctx?: Context | undefined) => boolean;
552
- onUnauthorized?: UnauthorizedPolicy<Context>;
553
- }
554
- declare function skill<Context extends Record<string, unknown> = Record<string, unknown>>(definition: Skill<Context>): SkillSource<Context>;
555
- //#endregion
556
589
  //#region src/server/route-agent-request.d.ts
557
590
  declare function routeAgentRequest<Env>(request: Request, env: Env, options?: AgentOptions<Env>): Promise<Response | null>;
558
591
  //#endregion
559
592
  //#region src/server/index.d.ts
560
593
  declare function getCurrentToolContext(): any;
561
594
  //#endregion
562
- export { type Action, type ActionContext, Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, type AnalyticsAttribute, type AnalyticsAttributes, type AnalyticsEvent, type AnalyticsEventPayload, type AnalyticsHandler, type AnalyticsUsage, Assistant, ChatAgent, type ExchangedToken, type JwtAuthConfig, type RegisteredAction, type Skill, type SkillSource, type TokenExchangeConfig, type TokenExchangeResult, type Tool, type ToolContext, type ToolSet, type TurnSnapshot, type UnauthorizedPolicy, type UnauthorizedToolPolicy, action, createAnalyticsEngineHandler, createAnalyticsEvent, createJwtAuthConfig, dispatchAnalyticsEvent, exchangeToken, getCurrentToolContext, routeAgentRequest, skill, tool };
595
+ export { type Action, type ActionContext, Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, type AnalyticsAttribute, type AnalyticsAttributes, type AnalyticsEvent, type AnalyticsEventPayload, type AnalyticsHandler, type AnalyticsUsage, Assistant, ChatAgent, type ExchangedToken, type InvocableSkill, type JwtAuthConfig, type RegisteredAction, type Skill, type SkillSource, type TokenExchangeConfig, type TokenExchangeResult, type Tool, type ToolContext, type ToolSet, type TurnSnapshot, type UnauthorizedPolicy, type UnauthorizedToolPolicy, action, createAnalyticsEngineHandler, createAnalyticsEvent, createJwtAuthConfig, dispatchAnalyticsEvent, exchangeToken, getCurrentToolContext, routeAgentRequest, skill, tool };
package/dist/index.mjs CHANGED
@@ -905,6 +905,19 @@ function toAnalyticsEnginePoints(event) {
905
905
  0
906
906
  ]
907
907
  }));
908
+ case "skill.invoked": return [{
909
+ indexes,
910
+ blobs: [
911
+ "skill_invoked",
912
+ event.agentName,
913
+ event.durableObjectName,
914
+ event.skillName,
915
+ event.command,
916
+ event.source,
917
+ ...common
918
+ ],
919
+ doubles: [1]
920
+ }];
908
921
  case "feedback.submitted": return [{
909
922
  indexes,
910
923
  blobs: [
@@ -998,6 +1011,14 @@ var Analytics = class {
998
1011
  rating
999
1012
  });
1000
1013
  }
1014
+ skillInvoked(skillName, command) {
1015
+ this.emit({
1016
+ type: "skill.invoked",
1017
+ skillName,
1018
+ command,
1019
+ source: "slash-command"
1020
+ });
1021
+ }
1001
1022
  event(payload) {
1002
1023
  this.emit(payload);
1003
1024
  }
@@ -1137,13 +1158,17 @@ var Agent = class extends Think {
1137
1158
  authorizedSkillNames: /* @__PURE__ */ new Set(),
1138
1159
  baseSystem: ""
1139
1160
  };
1140
- getToolContextForCurrentTurn(requestContext = {}) {
1161
+ createToolContextForCurrentTurn(requestContext = {}) {
1141
1162
  const connectionState = getCurrentAgent().connection?.state;
1142
1163
  return {
1164
+ ...this._turn.context,
1143
1165
  ...requestContext,
1144
1166
  _userContext: connectionState?.userContext ?? this.userContext
1145
1167
  };
1146
1168
  }
1169
+ getToolContextForCurrentTurn() {
1170
+ return this._turn.context;
1171
+ }
1147
1172
  /**
1148
1173
  * Merges the client request `body` into `experimental_context` for tools
1149
1174
  * returned from {@link getTools} only (Think-internal tools are unchanged).
@@ -1154,14 +1179,16 @@ var Agent = class extends Think {
1154
1179
  async beforeTurn(ctx) {
1155
1180
  this.analytics.promptReceived(ctx.messages, ctx.continuation);
1156
1181
  this.registerInstanceActivity();
1157
- const body = await this._applyTokenExchange(ctx.body);
1158
- this._turn.context = this.getToolContextForCurrentTurn(body);
1182
+ const body = await this._applyTokenExchange({
1183
+ ...this._turn.context,
1184
+ ...ctx.body
1185
+ });
1186
+ this._turn.context = this.createToolContextForCurrentTurn(body);
1159
1187
  this._turn.inputTools = ctx.tools;
1160
1188
  const activeSkillName = this._getLastActivatedSkillName(ctx.messages);
1161
1189
  const { tools, activeTools } = await this._prepareTools(ctx.tools, activeSkillName, true);
1162
1190
  const baseSystem = await this.session.refreshSystemPrompt();
1163
1191
  this._turn.baseSystem = [baseSystem, this._turn.skillsSystemBlock].filter(Boolean).join("\n\n");
1164
- console.log(this._turn.baseSystem, "\n\n// end system prompt\n\n");
1165
1192
  return {
1166
1193
  system: this._turn.baseSystem,
1167
1194
  model: this.getModel(this._turn.context),
@@ -2338,6 +2365,38 @@ var Assistant = class extends Agent$1 {
2338
2365
  agentClass: this.constructor.name
2339
2366
  }) });
2340
2367
  };
2368
+ //#endregion
2369
+ //#region src/server/util/skillCommands.ts
2370
+ async function listInvocableSkills(sources) {
2371
+ const commands = /* @__PURE__ */ new Set();
2372
+ const skills = [];
2373
+ for (const source of sources) {
2374
+ if (!source.command) continue;
2375
+ if (commands.has(source.command)) throw new Error(`Duplicate skill command "/${source.command}"`);
2376
+ const descriptor = (await source.list()).find((skill) => skill.name === source.id);
2377
+ if (!descriptor) continue;
2378
+ commands.add(source.command);
2379
+ skills.push({
2380
+ command: source.command,
2381
+ name: descriptor.name,
2382
+ description: descriptor.description
2383
+ });
2384
+ }
2385
+ return skills;
2386
+ }
2387
+ function parseSkillCommand(messages, skills) {
2388
+ const message = messages.findLast((candidate) => candidate.role === "user");
2389
+ if (!message) return;
2390
+ const text = getMessageText(message);
2391
+ if (!text?.startsWith("/")) return;
2392
+ const match = /^\/([^\s]+)/.exec(text);
2393
+ if (!match) return;
2394
+ return skills.find((skill) => skill.command === match[1]);
2395
+ }
2396
+ function getMessageText(message) {
2397
+ if (typeof message.content === "string") return message.content;
2398
+ return message.content.filter((part) => part.type === "text").map((part) => part.text).join("") || void 0;
2399
+ }
2341
2400
  const COMPACTION_MAX_OUTPUT_TOKENS = 2e3;
2342
2401
  const createCompactFn = (model) => createCompactFunction({
2343
2402
  protectHead: 2,
@@ -2675,7 +2734,22 @@ var ChatAgent = class extends Agent {
2675
2734
  async beforeTurn(ctx) {
2676
2735
  const parent = await this.getParentAgent();
2677
2736
  if (parent?.registerChat) await parent.registerChat(this.name);
2678
- const config = await super.beforeTurn(ctx) ?? {};
2737
+ const slashCommandSkill = await this.getSlashCommandSkill(ctx);
2738
+ let skillInvocation;
2739
+ if (slashCommandSkill) skillInvocation = await this.invokeSkill(ctx, slashCommandSkill);
2740
+ const turnContext = skillInvocation ? {
2741
+ ...ctx,
2742
+ messages: [...ctx.messages, ...skillInvocation.messages]
2743
+ } : ctx;
2744
+ let config = await super.beforeTurn(turnContext) ?? {};
2745
+ if (skillInvocation) {
2746
+ const transforms = config.experimental_transform ? Array.isArray(config.experimental_transform) ? config.experimental_transform : [config.experimental_transform] : [];
2747
+ config = {
2748
+ ...config,
2749
+ activeTools: config.activeTools?.filter((name) => name !== "activate_skill"),
2750
+ experimental_transform: [skillInvocation.transform, ...transforms]
2751
+ };
2752
+ }
2679
2753
  try {
2680
2754
  const prepared = await prepareModelHistory(this.messages, getModelCompaction(this.sql.bind(this)), createCompactFn(config.model ?? ctx.model), config.system ?? ctx.system);
2681
2755
  if (prepared.stateChanged) if (prepared.state) setModelCompaction(this.sql.bind(this), prepared.state);
@@ -2691,7 +2765,7 @@ var ChatAgent = class extends Agent {
2691
2765
  });
2692
2766
  return {
2693
2767
  ...config,
2694
- messages: this.pruneTurnMessages(modelMessages)
2768
+ messages: this.pruneTurnMessages([...modelMessages, ...skillInvocation?.messages ?? []])
2695
2769
  };
2696
2770
  }
2697
2771
  } catch (error) {
@@ -2699,6 +2773,67 @@ var ChatAgent = class extends Agent {
2699
2773
  }
2700
2774
  return config;
2701
2775
  }
2776
+ async getSlashCommandSkill(context) {
2777
+ if (context.continuation) return;
2778
+ const connectionState = getCurrentAgent()?.connection?.state;
2779
+ const invocableSkills = await this.getInvocableSkills(connectionState?.userContext ?? this.userContext, context.body);
2780
+ return parseSkillCommand(context.messages, invocableSkills);
2781
+ }
2782
+ async invokeSkill(context, skill) {
2783
+ const activateSkill = context.tools.activate_skill;
2784
+ if (!activateSkill?.execute) return;
2785
+ const input = { name: skill.name };
2786
+ const toolCallId = crypto.randomUUID();
2787
+ const output = await activateSkill.execute(input, {
2788
+ toolCallId,
2789
+ messages: context.messages
2790
+ });
2791
+ if (typeof output !== "string") throw new Error("activate_skill returned an invalid result");
2792
+ this.analytics.skillInvoked(skill.name, skill.command);
2793
+ const messages = await convertToModelMessages([{
2794
+ id: crypto.randomUUID(),
2795
+ role: "assistant",
2796
+ parts: [{
2797
+ type: "tool-activate_skill",
2798
+ toolCallId,
2799
+ state: "output-available",
2800
+ input,
2801
+ output
2802
+ }]
2803
+ }], { tools: context.tools });
2804
+ const transform = () => {
2805
+ let emitted = false;
2806
+ return new TransformStream({ transform(chunk, controller) {
2807
+ controller.enqueue(chunk);
2808
+ if (emitted || chunk.type !== "start-step") return;
2809
+ emitted = true;
2810
+ controller.enqueue({
2811
+ type: "tool-call",
2812
+ toolCallId,
2813
+ toolName: "activate_skill",
2814
+ input
2815
+ });
2816
+ controller.enqueue({
2817
+ type: "tool-result",
2818
+ toolCallId,
2819
+ toolName: "activate_skill",
2820
+ input,
2821
+ output
2822
+ });
2823
+ } });
2824
+ };
2825
+ return {
2826
+ messages,
2827
+ transform
2828
+ };
2829
+ }
2830
+ async getInvocableSkills(userContext = this.userContext, requestContext = {}) {
2831
+ const context = {
2832
+ ...requestContext,
2833
+ _userContext: userContext
2834
+ };
2835
+ return listInvocableSkills((await this.getSkills()).filter((source) => source.authorize?.(context) !== false));
2836
+ }
2702
2837
  async onChatResponse(result) {
2703
2838
  this.analytics.turnCompleted(result.requestId, result.status, result.continuation, result.message.parts);
2704
2839
  const parent = await this.getParentAgent();
@@ -2709,6 +2844,7 @@ var ChatAgent = class extends Agent {
2709
2844
  if (!auth.success) return Response.json({ error: auth.message }, { status: auth.status });
2710
2845
  const action = new URL(request.url).pathname.split("/").pop();
2711
2846
  if (action === "getMessages" && request.method === "GET") return Response.json(await this.getMessages());
2847
+ if (action === "getInvocableSkills" && request.method === "GET") return Response.json(await this.getInvocableSkills(auth.userContext));
2712
2848
  if (action === "sendMessage" && request.method === "POST") return this.streamMessage(request);
2713
2849
  if (action === "cancelChat" && request.method === "POST") {
2714
2850
  const { requestId } = await request.json();
@@ -2722,38 +2858,48 @@ var ChatAgent = class extends Agent {
2722
2858
  * returned immediately and each event reaches the client as it is produced.
2723
2859
  */
2724
2860
  async streamMessage(request) {
2725
- let message;
2861
+ let payload;
2726
2862
  try {
2727
- ({message} = await request.json());
2863
+ payload = await request.json();
2728
2864
  } catch {
2729
- return Response.json({ error: "Invalid request body" }, { status: 400 });
2865
+ return Response.json({ error: "Request body must be valid JSON" }, { status: 400 });
2730
2866
  }
2731
- if (typeof message === "string") message = {
2732
- id: crypto.randomUUID(),
2733
- role: "user",
2734
- parts: [{
2735
- type: "text",
2736
- text: message
2737
- }]
2738
- };
2739
- else if (!message || message.role !== "user" || !Array.isArray(message.parts)) return Response.json({ error: "message must be a user message" }, { status: 400 });
2867
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return Response.json({ error: "Request body must be an object" }, { status: 400 });
2868
+ let { message, context } = payload;
2869
+ if (context !== void 0 && (context === null || typeof context !== "object" || Array.isArray(context))) return Response.json({ error: "Context must be an object" }, { status: 400 });
2870
+ if (typeof message === "string") {
2871
+ if (!message.trim()) return Response.json({ error: "Message must not be empty" }, { status: 400 });
2872
+ message = {
2873
+ id: crypto.randomUUID(),
2874
+ role: "user",
2875
+ parts: [{
2876
+ type: "text",
2877
+ text: message
2878
+ }]
2879
+ };
2880
+ } else if (!message || typeof message !== "object" || Array.isArray(message) || typeof message.id !== "string" || !message.id || message.role !== "user" || !Array.isArray(message.parts)) return Response.json({ error: "Message must be a non-empty string or a user UIMessage" }, { status: 400 });
2881
+ const input = context ? () => {
2882
+ this._turn.context = this.createToolContextForCurrentTurn(context);
2883
+ return [message];
2884
+ } : message;
2740
2885
  const encoder = new TextEncoder();
2741
2886
  const readable = new ReadableStream({ start: async (controller) => {
2742
2887
  const send = (event, data) => controller.enqueue(encoder.encode(`event: ${event}\ndata: ${data}\n\n`));
2888
+ const sendError = (error) => send("error", JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
2743
2889
  try {
2744
2890
  await this.runTurn({
2745
2891
  mode: "stream",
2746
- input: message,
2892
+ input,
2747
2893
  callback: {
2748
2894
  onStart: (event) => send("start", JSON.stringify(event)),
2749
2895
  onEvent: (json) => send("chunk", json),
2750
2896
  onDone: () => send("done", "{}"),
2751
- onError: (error) => send("error", JSON.stringify({ error })),
2897
+ onError: sendError,
2752
2898
  onInterrupted: () => send("interrupted", "{}")
2753
2899
  }
2754
2900
  });
2755
2901
  } catch (error) {
2756
- send("error", JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
2902
+ sendError(error);
2757
2903
  } finally {
2758
2904
  controller.close();
2759
2905
  }
@@ -2828,6 +2974,7 @@ function skill(definition) {
2828
2974
  if (!definition.name) throw new Error("Skill name is required");
2829
2975
  if (!definition.description) throw new Error("Skill description is required");
2830
2976
  if (!definition.instructions) throw new Error("Skill content is required");
2977
+ if (definition.command !== void 0 && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(definition.command)) throw new Error("Skill command must use lower-case letters, numbers, and hyphens");
2831
2978
  return {
2832
2979
  id: definition.name,
2833
2980
  fingerprint: definition.name,
@@ -2845,6 +2992,7 @@ function skill(definition) {
2845
2992
  body: definition.instructions
2846
2993
  };
2847
2994
  },
2995
+ command: definition.command,
2848
2996
  tools: definition.tools,
2849
2997
  actions: definition.actions,
2850
2998
  authorize: definition.authorize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "2.13.0",
3
+ "version": "2.14.1",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@cloudflare/think": "^0.17.0",
48
- "@cloudflare/vite-plugin": "^1.43.0 <1.43.2",
48
+ "@cloudflare/vite-plugin": "^1.45.1",
49
49
  "@cloudflare/worker-bundler": "^0.2.1",
50
50
  "agents": "^0.22.0",
51
51
  "vite": ">=8.0.0"