@theokit/sdk 2.21.0 → 2.22.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/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.22.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 12cb30d: **SE10 — subagent delegation forwards the parent's `AbortSignal` (cancellation propagation).**
8
+
9
+ `defineSubAgent()` (from `@theokit/sdk/a2a`) now threads the run's cancellation into the child agent. When the agent loop dispatches the subagent tool it already passes the run's `AbortSignal` as the handler's `ctx.signal`; the subagent handler now forwards that signal to the child `agent.send(input, { signal })`. Aborting the parent run cancels the in-flight subagent at its next step instead of letting it run to completion (and burn tokens).
10
+
11
+ - Additive + backward-compatible: a handler invoked with no `ctx` (single-arg call sites) behaves exactly as before — no signal, no cancellation.
12
+ - The child agent is still disposed in `finally`, including on cancel.
13
+
14
+ Matches the Mastra supervisor-agents "abortSignal forwarded to delegated subagents" behavior (SDK Evolution roadmap SE10).
15
+
16
+ - 8e3249d: **SE11 — delegation lifecycle hooks on `defineSubAgent` (`onDelegationStart` / `onDelegationComplete`).**
17
+
18
+ `SubAgentSpec` (from `@theokit/sdk/a2a`) gains two optional hooks that let the caller intercept a delegation as it happens:
19
+
20
+ - `onDelegationStart({ input, name })` — return `{ proceed: false, rejectionReason }` to reject the delegation (the child never runs; `rejectionReason` becomes the tool result), or `{ modifiedInput }` to rewrite the prompt sent to the child.
21
+ - `onDelegationComplete({ input, name, result?, error? })` — runs after the delegation settles; on success an optional `{ feedback }` is appended to the child's result, and on failure `ctx.error` is set (the error is still re-thrown — never swallowed, Unbreakable Rule 8).
22
+
23
+ Additive + backward-compatible: specs without hooks behave exactly as before. New exported types: `DelegationStartContext`, `DelegationStartDecision`, `DelegationCompleteContext`, `DelegationCompleteDecision`.
24
+
25
+ Matches the Mastra supervisor `onDelegationStart` / `onDelegationComplete` control points (SDK Evolution roadmap SE11).
26
+
27
+ - d2d0d16: **SE12 — opt-in parent-context forwarding for subagents (`messageFilter`).**
28
+
29
+ `SubAgentSpec` (from `@theokit/sdk/a2a`) gains an optional `messageFilter`. When set, `defineSubAgent` forwards a filtered view of the supervisor's conversation to the child; when absent, the child runs input-only — **memory isolation stays the default**.
30
+
31
+ - New `ctx.messages` on the custom-tool handler `ToolContext`: a **read-only, text-only** projection of the current turn's transcript (`ToolContextMessage[]`), threaded by the agent loop the same way `ctx.signal` (#65) and `ctx.context` (M7) are. Non-text parts (tool calls / results) are dropped — a tool never sees raw wire parts or nested tool args.
32
+ - `messageFilter({ messages, input, name })` returns the subset to forward; `defineSubAgent` prepends it to the delegated input as a role-tagged context preamble. A filter returning `[]` forwards nothing. A filter that drops sensitive turns (e.g. anything `confidential`) provably keeps them out of the child context.
33
+
34
+ New exported types: `ToolContextMessage`, `MessageFilterArgs`. Additive + backward-compatible. Rationale + the transcript-exposure trade-off are recorded in ADR 0005. From the Mastra supervisor-agents comparison (SDK Evolution roadmap SE12).
35
+
3
36
  ## 2.21.0
4
37
 
5
38
  ### Minor Changes
@@ -8326,21 +8326,21 @@ async function executeTool(inputs, resolved, call) {
8326
8326
  if (resolved.origin === "shell") return runShellTool(inputs, call);
8327
8327
  if (resolved.origin === "memory") return runMemoryTool(resolved, call, inputs.context);
8328
8328
  if (resolved.origin === "custom")
8329
- return runCustomTool(resolved, call, inputs.signal, inputs.context);
8329
+ return runCustomTool(resolved, call, inputs.signal, inputs.context, inputs.messages);
8330
8330
  return runMcpTool(inputs, resolved, call);
8331
8331
  }
8332
8332
  async function runMemoryTool(resolved, call, context) {
8333
8333
  return runHandlerTool("memory", resolved.memoryHandler, call, void 0, context);
8334
8334
  }
8335
- async function runCustomTool(resolved, call, signal, context) {
8336
- return runHandlerTool("custom", resolved.customHandler, call, signal, context);
8335
+ async function runCustomTool(resolved, call, signal, context, messages) {
8336
+ return runHandlerTool("custom", resolved.customHandler, call, signal, context, messages);
8337
8337
  }
8338
- async function runHandlerTool(kind, handler, call, signal, context) {
8338
+ async function runHandlerTool(kind, handler, call, signal, context, messages) {
8339
8339
  if (handler === void 0) {
8340
8340
  return { stdout: "", stderr: `${kind} tool ${call.name} has no handler`, exitCode: 127 };
8341
8341
  }
8342
8342
  try {
8343
- const out = await handler(call.input, { signal, context });
8343
+ const out = await handler(call.input, { signal, context, messages });
8344
8344
  if (typeof out !== "string") return { stdout: "", stderr: "", exitCode: 0, content: out };
8345
8345
  return { stdout: out, stderr: "", exitCode: 0 };
8346
8346
  } catch (cause) {
@@ -9165,6 +9165,14 @@ var init_usage_and_cost = __esm({
9165
9165
  });
9166
9166
 
9167
9167
  // src/internal/agent-loop/loop.ts
9168
+ function projectToolContextMessages(messages) {
9169
+ const projected = [];
9170
+ for (const m of messages) {
9171
+ const content = m.content.flatMap((p) => p.type === "text" ? [p.text] : []).join("");
9172
+ if (content !== "") projected.push({ role: m.role, content });
9173
+ }
9174
+ return projected;
9175
+ }
9168
9176
  async function runAgentLoop(inputs) {
9169
9177
  const sendSpan = inputs.telemetry?.startSpan("agent.send", {
9170
9178
  agentId: inputs.agentId,
@@ -9405,7 +9413,9 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
9405
9413
  const outText = await transformLlmOutputText(inputs, llmOutput.text, tCtx);
9406
9414
  ctx.messages.push(buildAssistantTurn(outText, llmOutput.toolCalls));
9407
9415
  const rawResults = await dispatchTools(
9408
- inputs,
9416
+ // SE12 — forward a read-only text projection of the transcript-so-far to tool
9417
+ // handlers via `ctx.messages` (consumed by defineSubAgent's messageFilter).
9418
+ { ...inputs, messages: projectToolContextMessages(ctx.messages) },
9409
9419
  ctx.tools,
9410
9420
  llmOutput.toolCalls,
9411
9421
  ctx.events,
@@ -18416,6 +18426,52 @@ var MaxDelegationDepthError = class extends Error {
18416
18426
  maxDepth;
18417
18427
  code = "max_delegation_depth";
18418
18428
  };
18429
+ async function applyDelegationStart(spec, input) {
18430
+ if (spec.onDelegationStart === void 0) return { input };
18431
+ const decision = await spec.onDelegationStart({ input, name: spec.name });
18432
+ if (decision === void 0) return { input };
18433
+ if (decision.proceed === false)
18434
+ return { reject: decision.rejectionReason ?? "(delegation rejected)" };
18435
+ return { input: decision.modifiedInput ?? input };
18436
+ }
18437
+ async function runChildAgent(spec, input, signal) {
18438
+ const { Agent: Agent2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
18439
+ const agent = await Agent2.create({
18440
+ ...spec.model ? { model: { id: spec.model } } : {},
18441
+ systemPrompt: spec.instructions,
18442
+ tools: spec.tools ?? []
18443
+ });
18444
+ try {
18445
+ const run = signal !== void 0 ? await agent.send(input, { signal }) : await agent.send(input);
18446
+ const result = await run.wait();
18447
+ return result.result ?? "(no response)";
18448
+ } finally {
18449
+ agent.dispose();
18450
+ }
18451
+ }
18452
+ async function notifyDelegationError(spec, input, error) {
18453
+ if (spec.onDelegationComplete === void 0) return;
18454
+ try {
18455
+ await spec.onDelegationComplete({ input, name: spec.name, error });
18456
+ } catch {
18457
+ }
18458
+ }
18459
+ function applyMessageFilter(spec, input, messages) {
18460
+ if (spec.messageFilter === void 0 || messages === void 0) return input;
18461
+ const filtered = spec.messageFilter({ messages, input, name: spec.name });
18462
+ if (filtered.length === 0) return input;
18463
+ const preamble = filtered.map((m) => `${m.role}: ${m.content}`).join("\n");
18464
+ return `Prior conversation:
18465
+ ${preamble}
18466
+
18467
+ Task:
18468
+ ${input}`;
18469
+ }
18470
+ async function applyDelegationComplete(spec, input, result) {
18471
+ if (spec.onDelegationComplete === void 0) return result;
18472
+ const completion = await spec.onDelegationComplete({ input, name: spec.name, result });
18473
+ return completion?.feedback !== void 0 ? result + completion.feedback : result;
18474
+ }
18419
18475
  function defineSubAgent(spec, _parentDepth = 0) {
18420
18476
  const currentDepth = _parentDepth + 1;
18421
18477
  const maxDepth = spec.maxDelegationDepth ?? 3;
@@ -18429,21 +18485,19 @@ function defineSubAgent(spec, _parentDepth = 0) {
18429
18485
  name: spec.name,
18430
18486
  description: spec.description,
18431
18487
  inputSchema,
18432
- handler: async (rawInput) => {
18433
- const { input } = inputSchema.parse(rawInput);
18434
- const { Agent: Agent2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
18435
- const agent = await Agent2.create({
18436
- ...spec.model ? { model: { id: spec.model } } : {},
18437
- systemPrompt: spec.instructions,
18438
- tools: spec.tools ?? []
18439
- });
18488
+ handler: async (rawInput, ctx) => {
18489
+ const { input: parsed } = inputSchema.parse(rawInput);
18490
+ const start = await applyDelegationStart(spec, parsed);
18491
+ if ("reject" in start) return start.reject;
18492
+ const input = applyMessageFilter(spec, start.input, ctx?.messages);
18493
+ let result;
18440
18494
  try {
18441
- const run = await agent.send(input);
18442
- const result = await run.wait();
18443
- return result.result ?? "(no response)";
18444
- } finally {
18445
- agent.dispose();
18495
+ result = await runChildAgent(spec, input, ctx?.signal);
18496
+ } catch (error) {
18497
+ await notifyDelegationError(spec, input, error);
18498
+ throw error;
18446
18499
  }
18500
+ return applyDelegationComplete(spec, input, result);
18447
18501
  }
18448
18502
  };
18449
18503
  }