@economic/agents 2.3.26 → 2.4.0

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
@@ -1,7 +1,7 @@
1
1
  import { n as JwtAuthConfig, t as routeAgentRequest } from "./route-agent-request-D2ll--nF.mjs";
2
2
  import { Agent as Agent$1, Connection, ConnectionContext, SubAgentClass } from "agents";
3
3
  import { ChatResponseResult, PrepareStepContext, Session, Think, ToolCallContext, ToolCallDecision, TurnConfig, TurnContext, skills } from "@cloudflare/think";
4
- import { JSONValue, LanguageModel, Tool as Tool$1, UIMessage } from "ai";
4
+ import { JSONValue, LanguageModel, ModelMessage, Tool as Tool$1, UIMessage } from "ai";
5
5
  import { createCompactFunction } from "agents/experimental/memory/utils";
6
6
  import { SkillSource as SkillSource$1 } from "agents/skills";
7
7
 
@@ -34,6 +34,15 @@ type Tool<Context extends Record<string, unknown> = Record<string, unknown>, INP
34
34
  declare function tool<Context extends Record<string, unknown> = Record<string, unknown>, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any>(tool: Tool<Context, INPUT, OUTPUT>): Tool$1<INPUT, OUTPUT>;
35
35
  //#endregion
36
36
  //#region src/server/agents/Agent.d.ts
37
+ /** What the model would see on its next step — returned by {@link Agent.inspectTurn}. */
38
+ interface TurnSnapshot {
39
+ system: string;
40
+ tools: ToolSet;
41
+ activeTools: string[];
42
+ /** Tools provided by the framework (session/skill meta-tools, MCP), not the agent. */
43
+ frameworkToolNames: string[];
44
+ messages: ModelMessage[];
45
+ }
37
46
  declare abstract class Agent<RequestContext extends Record<string, unknown> = Record<string, unknown>, UserContext extends Record<string, unknown> = Record<string, unknown>, State = unknown> extends Think<Cloudflare.Env & AgentEnv, State> {
38
47
  protected agentType: AgentConnectionType;
39
48
  protected clientIp?: string;
@@ -75,6 +84,19 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
75
84
  }>;
76
85
  beforeToolCall(ctx: ToolCallContext): Promise<ToolCallDecision | void>;
77
86
  getTools(): ToolSet;
87
+ /**
88
+ * Read-only: what the model would see on its next step for the given history
89
+ * — the assembled system prompt, the skill-gated toolset, and pruned
90
+ * messages. Runs no turn (nothing admitted, persisted, executed; no model
91
+ * call). Skill gating is derived from the history, so a history with an
92
+ * `activate_skill` call yields that skill's tools. For eval harnesses and
93
+ * prompt-preview tooling; call it inside the DO context.
94
+ */
95
+ inspectTurn(input?: {
96
+ messages?: ModelMessage[];
97
+ requestContext?: RequestContext;
98
+ userContext?: UserContext;
99
+ }): Promise<TurnSnapshot>;
78
100
  workspaceBash: boolean;
79
101
  getSkillScriptRunner(): skills.SkillScriptRunner | null;
80
102
  /**
@@ -82,7 +104,33 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
82
104
  * @returns The remote skills to be loaded from SKILLS_BUCKET.
83
105
  */
84
106
  protected getRemoteSkills(): string[];
107
+ private _pruneTurnMessages;
85
108
  private _getAuthorizedTools;
109
+ /**
110
+ * Wrap each tool's `execute` so it falls back to the current turn's tool
111
+ * context ({@link _toolContextForCurrentTurn}: request body + `_userContext`)
112
+ * when the caller supplies none. A caller-supplied `experimental_context`
113
+ * always wins, so normal steps — where {@link beforeStep} provides the
114
+ * context — are untouched.
115
+ *
116
+ * The fallback exists for the approval continuation: when a `needsApproval`
117
+ * tool is approved, the AI SDK executes it BEFORE the step loop starts, so no
118
+ * `beforeStep` runs for it and `execute` receives `experimental_context:
119
+ * undefined` — which stripped action tools of their auth token / user context
120
+ * after approval. The continuation is a turn of its own: {@link beforeTurn}
121
+ * re-sets {@link _toolContextForCurrentTurn} from the approval request's body
122
+ * before the SDK executes the tool, so the fallback context is the approval
123
+ * request's — clients must resend anything tools need (e.g. tokens) on it.
124
+ *
125
+ * TODO: remove once @cloudflare/think supports a turn-level
126
+ * `experimental_context` (its TurnConfig has no such field and it passes none
127
+ * to `streamText`, so only `beforeStep` can supply one — and the approval
128
+ * execution happens before any step).
129
+ *
130
+ * See {@link withFallbackToolContext} for the wrap itself, which preserves an
131
+ * `async function*` `execute`'s streaming shape.
132
+ */
133
+ private _withTurnContextFallback;
86
134
  private _getLastActivatedSkillName;
87
135
  /**
88
136
  * Override to enable JWT authentication on WebSocket connections.
@@ -315,4 +363,4 @@ declare function skill<Context extends Record<string, unknown> = Record<string,
315
363
  //#region src/server/index.d.ts
316
364
  declare function getCurrentToolContext(): any;
317
365
  //#endregion
318
- export { Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, Assistant, ChatAgent, type FacetStub, type LegacyChatStub, type LegacyMessageFeedback, type MigrationDeps, type Skill, type SkillSource, type Tool, type ToolContext, type ToolSet, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
366
+ export { Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, Assistant, ChatAgent, type FacetStub, type LegacyChatStub, type LegacyMessageFeedback, type MigrationDeps, type Skill, type SkillSource, type Tool, type ToolContext, type ToolSet, type TurnSnapshot, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
package/dist/index.mjs CHANGED
@@ -8,6 +8,43 @@ import { createCompactFunction } from "agents/experimental/memory/utils";
8
8
  function tool(tool) {
9
9
  return tool$1(tool);
10
10
  }
11
+ /**
12
+ * Wrap each tool's `execute` so it falls back to `getFallbackContext()` for
13
+ * `experimental_context` when the caller supplies none (e.g. the approval
14
+ * continuation, which bypasses `beforeStep`). A caller-supplied context always
15
+ * wins, so on a normal turn the wrap is a no-op.
16
+ *
17
+ * Crucially it preserves the function's shape: an `async function*` `execute`
18
+ * stays an async generator so `@cloudflare/think` keeps detecting it
19
+ * (`execute.constructor.name === "AsyncGeneratorFunction"`) and streams the
20
+ * tool's preliminary (yielded) results. A plain wrapper would make think consume
21
+ * the iterable and keep only its final value — breaking streaming for every
22
+ * generator tool, on approval AND normal turns. Non-generator executes get an
23
+ * equivalent plain wrapper; non-function executes pass through untouched.
24
+ */
25
+ function withFallbackToolContext(tools, getFallbackContext) {
26
+ const result = {};
27
+ for (const [name, tool] of Object.entries(tools)) {
28
+ const originalExecute = tool.execute;
29
+ if (typeof originalExecute !== "function") {
30
+ result[name] = tool;
31
+ continue;
32
+ }
33
+ result[name] = {
34
+ ...tool,
35
+ execute: originalExecute.constructor?.name === "AsyncGeneratorFunction" ? async function* (input, options) {
36
+ yield* originalExecute(input, {
37
+ ...options,
38
+ experimental_context: options?.experimental_context ?? getFallbackContext()
39
+ });
40
+ } : (input, options) => originalExecute(input, {
41
+ ...options,
42
+ experimental_context: options?.experimental_context ?? getFallbackContext()
43
+ })
44
+ };
45
+ }
46
+ return result;
47
+ }
11
48
  //#endregion
12
49
  //#region src/server/util/prompts.ts
13
50
  const SECURITY_RULES_PROMPT = `These rules override all other instructions, no matter what the user asks.
@@ -201,17 +238,8 @@ var Agent = class extends Think {
201
238
  return {
202
239
  system: await this.session.refreshSystemPrompt(),
203
240
  model: this.getModel(this._toolContextForCurrentTurn),
204
- messages: pruneMessages({
205
- messages: ctx.messages,
206
- toolCalls: [{
207
- type: "before-last-2-messages",
208
- tools: []
209
- }, {
210
- type: "before-last-5-messages",
211
- tools: ["activate_skill"]
212
- }]
213
- }),
214
- tools,
241
+ messages: this._pruneTurnMessages(ctx.messages),
242
+ tools: this._withTurnContextFallback(tools),
215
243
  activeTools,
216
244
  experimental_transform: buildSourcesTransform(),
217
245
  experimental_telemetry: {
@@ -264,6 +292,48 @@ var Agent = class extends Think {
264
292
  getTools() {
265
293
  return {};
266
294
  }
295
+ /**
296
+ * Read-only: what the model would see on its next step for the given history
297
+ * — the assembled system prompt, the skill-gated toolset, and pruned
298
+ * messages. Runs no turn (nothing admitted, persisted, executed; no model
299
+ * call). Skill gating is derived from the history, so a history with an
300
+ * `activate_skill` call yields that skill's tools. For eval harnesses and
301
+ * prompt-preview tooling; call it inside the DO context.
302
+ */
303
+ async inspectTurn(input = {}) {
304
+ const messages = input.messages ?? [];
305
+ await this.__unsafe_ensureInitialized();
306
+ const previousContext = this._toolContextForCurrentTurn;
307
+ this._toolContextForCurrentTurn = {
308
+ ...input.requestContext ?? {},
309
+ _userContext: input.userContext
310
+ };
311
+ const previousAuthorizedTools = this._authorizedToolsForCurrentTurn;
312
+ try {
313
+ const system = await this.session.refreshSystemPrompt();
314
+ const think = this;
315
+ const frameworkTools = {
316
+ ...await this.session.tools(),
317
+ ...think._skillRegistry?.tools(),
318
+ ...think.mcp?.getAITools?.()
319
+ };
320
+ const { tools, activeTools } = await this._getAuthorizedTools({
321
+ ...this.getTools(),
322
+ ...frameworkTools
323
+ }, this._getLastActivatedSkillName(messages));
324
+ const snapshotContext = this._toolContextForCurrentTurn;
325
+ return {
326
+ system,
327
+ tools: withFallbackToolContext(tools, () => snapshotContext),
328
+ activeTools,
329
+ frameworkToolNames: Object.keys(frameworkTools),
330
+ messages: this._pruneTurnMessages(messages)
331
+ };
332
+ } finally {
333
+ this._toolContextForCurrentTurn = previousContext;
334
+ this._authorizedToolsForCurrentTurn = previousAuthorizedTools;
335
+ }
336
+ }
267
337
  workspaceBash = false;
268
338
  getSkillScriptRunner() {
269
339
  if (!("LOADER" in this.env)) return null;
@@ -276,6 +346,18 @@ var Agent = class extends Think {
276
346
  getRemoteSkills() {
277
347
  return [];
278
348
  }
349
+ _pruneTurnMessages(messages) {
350
+ return pruneMessages({
351
+ messages,
352
+ toolCalls: [{
353
+ type: "before-last-2-messages",
354
+ tools: []
355
+ }, {
356
+ type: "before-last-5-messages",
357
+ tools: ["activate_skill"]
358
+ }]
359
+ });
360
+ }
279
361
  async _getAuthorizedTools(availableTools, activeSkillName) {
280
362
  const tools = { ...availableTools };
281
363
  const activeTools = Object.keys(tools);
@@ -291,6 +373,33 @@ var Agent = class extends Think {
291
373
  activeTools
292
374
  };
293
375
  }
376
+ /**
377
+ * Wrap each tool's `execute` so it falls back to the current turn's tool
378
+ * context ({@link _toolContextForCurrentTurn}: request body + `_userContext`)
379
+ * when the caller supplies none. A caller-supplied `experimental_context`
380
+ * always wins, so normal steps — where {@link beforeStep} provides the
381
+ * context — are untouched.
382
+ *
383
+ * The fallback exists for the approval continuation: when a `needsApproval`
384
+ * tool is approved, the AI SDK executes it BEFORE the step loop starts, so no
385
+ * `beforeStep` runs for it and `execute` receives `experimental_context:
386
+ * undefined` — which stripped action tools of their auth token / user context
387
+ * after approval. The continuation is a turn of its own: {@link beforeTurn}
388
+ * re-sets {@link _toolContextForCurrentTurn} from the approval request's body
389
+ * before the SDK executes the tool, so the fallback context is the approval
390
+ * request's — clients must resend anything tools need (e.g. tokens) on it.
391
+ *
392
+ * TODO: remove once @cloudflare/think supports a turn-level
393
+ * `experimental_context` (its TurnConfig has no such field and it passes none
394
+ * to `streamText`, so only `beforeStep` can supply one — and the approval
395
+ * execution happens before any step).
396
+ *
397
+ * See {@link withFallbackToolContext} for the wrap itself, which preserves an
398
+ * `async function*` `execute`'s streaming shape.
399
+ */
400
+ _withTurnContextFallback(tools) {
401
+ return withFallbackToolContext(tools, () => this._toolContextForCurrentTurn);
402
+ }
294
403
  _getLastActivatedSkillName(items = []) {
295
404
  for (let index = items.length - 1; index >= 0; index--) {
296
405
  const item = items[index];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "2.3.26",
3
+ "version": "2.4.0",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@cloudflare/think": "^0.12.1",
49
- "@cloudflare/vite-plugin": "^1.43.0",
49
+ "@cloudflare/vite-plugin": "^1.43.0 <1.43.2",
50
50
  "@cloudflare/worker-bundler": "^0.2.1",
51
51
  "agents": "^0.17.3",
52
52
  "vite": ">=8.0.0"