@theokit/sdk 2.22.0 → 2.24.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,57 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.24.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7be1f18: **SE16 — `outputSchema` on `defineTool` (validate + infer the tool's return).**
8
+
9
+ `defineTool` (from `@theokit/sdk`) gains an optional `outputSchema` (a Zod schema). When set, the handler returns the STRUCTURED output inferred from it (`z.infer<outputSchema>`), the value is validated against the schema, and the tool result becomes its serialization — a string stays as-is, an object is JSON-stringified. A validation failure raises `ZodError` (converted to a `tool_result(isError)`), so a malformed tool output fails loudly instead of silently reaching the model.
10
+
11
+ Additive + fully backward-compatible: with no `outputSchema` the handler returns a plain `string` exactly as before (the handler return type is `string` when `outputSchema` is absent, `z.infer<outputSchema>` when present, via a conditional type). Mirrors Mastra `createTool`'s `outputSchema`. Pairs with SE17 (`toModelOutput`). From the Mastra Tools comparison (SDK Evolution roadmap SE16).
12
+
13
+ - f621734: **SE17 — `toModelOutput` on `defineTool` (model-facing vs app-facing output split).**
14
+
15
+ `defineTool` (from `@theokit/sdk`) gains an optional `toModelOutput`. The handler returns the FULL result (validated by SE16's `outputSchema`); `toModelOutput(output)` maps it to the compact / multimodal representation the MODEL sees in the `tool_result` — so rich app-facing detail is not forced into model context. It returns a `string` OR SE7 `ToolResultContentBlock[]` (text + image). Absent ⇒ the tool result is the serialized handler output (SE16 / pre-SE17 behavior, unchanged).
16
+
17
+ Mirrors Mastra's `toModelOutput` and the Vercel AI SDK. Additive + backward-compatible. From the Mastra Tools comparison (SDK Evolution roadmap SE17).
18
+
19
+ - 72435db: **SE18 — `SendOptions.activeTools` (per-send runtime tool subset).**
20
+
21
+ `agent.send(input, { activeTools })` restricts, per send, which of the agent's registered tools the model may actually call. A tool whose canonical name is not in the list is vetoed at dispatch (its handler never runs) — reusing the existing `withToolWhitelist` path that `Agent.fork`'s `allowedTools` uses, NOT `PermissionEngine`. Composes with `toolChoice`: `activeTools` narrows the set, `toolChoice` gates calling within it. Absent ⇒ the full toolset is available (unchanged).
22
+
23
+ The loop runs inside a `withToolWhitelist(new Set(activeTools))` scope when set. Additive + backward-compatible. Mirrors Mastra `activeTools` + the Vercel AI SDK. From the Mastra Tools comparison (SDK Evolution roadmap SE18).
24
+
25
+ - f92f720: **SE19 — `workflowAsTool` (expose a Workflow as an agent tool).**
26
+
27
+ `workflowAsTool(workflow, { name, description, inputSchema })` (from `@theokit/sdk/workflow`) turns a `Workflow` into an agent `CustomTool`, completing the Mastra "X as tools" trio (tools; agents-as-tools via `defineSubAgent`; workflows-as-tools). The handler validates the model's args against `spec.inputSchema`, runs the workflow, and returns its output (a string as-is, else JSON). A run that does not reach `status: "completed"` raises a typed `WorkflowToolError` (workflow step errors do NOT throw — they surface via `run.status === "failed"`).
28
+
29
+ Because a `Workflow` carries no top-level schema (`WorkflowOptions` is `name`/`persistence`/`workflowId`; schemas are per-step), the caller supplies the tool `inputSchema` in the spec (like `defineTool`). Accepts any `{ run }`-shaped workflow (structural), so it never imports the `Workflow` class. New exports: `workflowAsTool`, `WorkflowToolError`, `WorkflowAsToolSpec`. Additive. From the Mastra Tools comparison (SDK Evolution roadmap SE19).
30
+
31
+ ## 2.23.0
32
+
33
+ ### Minor Changes
34
+
35
+ - 271f6e4: **SE13 — `modifiedMaxSteps` on `onDelegationStart` (cap the subagent's iterations).**
36
+
37
+ `DelegationStartDecision` (from `@theokit/sdk/a2a`) gains `modifiedMaxSteps?: number`. When an `onDelegationStart` hook returns it (and does not reject), `defineSubAgent` forwards it as `SendOptions.maxIterations` to the child `agent.send`, capping how many tool-loop rounds the subagent may run. Composes with SE10 (`signal`) and SE12 (`messageFilter` preamble) onto a single child `send`. Absent ⇒ the child uses its default iteration ceiling (unchanged).
38
+
39
+ Completes the SE11 `onDelegationStart` decision contract (the deferred `modifiedMaxSteps` — the `SendOptions.maxIterations` plumbing already existed). Additive + backward-compatible. From the Mastra supervisor-agents comparison (SDK Evolution roadmap SE13).
40
+
41
+ - b51dc6a: **SE14 — subagent result-context control (`SubAgentSpec.includeToolResults`).**
42
+
43
+ `defineSubAgent()` (from `@theokit/sdk/a2a`) gains an opt-in `includeToolResults`. When `true`, the child's completed tool-call results (name + result) are appended to the delegation payload returned to the supervisor, inside a delimited `<subagent-tool-results>` block; when absent/`false` the delegation returns the child's final text only — **text-only stays the default** (Mastra's scoped posture).
44
+
45
+ Implemented as a `run.stream()` replay after `run.wait()` (a proven, safe idiom — the run buffers events and `stream()` replays them) collecting `tool_call` events with `status: "completed"`. **No `RunResult` change** — reads the existing public stream surface; tool _args_ are never surfaced (only completed results). Rationale + the `RunResult`-field alternative are recorded in ADR 0006.
46
+
47
+ Additive + backward-compatible (default `false` never touches the stream). From the Mastra supervisor-agents comparison (SDK Evolution roadmap SE14).
48
+
49
+ - 30e02d9: **SE15 — `iteration` count on the delegation-hook context (reject-after-N).**
50
+
51
+ `DelegationStartContext` and `DelegationCompleteContext` (from `@theokit/sdk/a2a`) gain `iteration: number` — a 1-based per-`defineSubAgent`-instance invocation counter, incremented before `onDelegationStart` runs (a rejected delegation still counts). This enables the Mastra reject-after-N-iterations pattern: `onDelegationStart: (ctx) => ctx.iteration > 8 ? { proceed: false, rejectionReason } : { proceed: true }`. `onDelegationComplete` sees the same iteration its `onDelegationStart` did.
52
+
53
+ Also fixes a delegation-hook DX regression: `onDelegationStart` / `onDelegationComplete` now accept a **side-effect-only (void-returning) callback** (e.g. `(ctx) => { log(ctx) }`) — the common case, mirroring Mastra's `async ctx => { … }` hooks — via a shared `DelegationHookResult<T>` return type. Additive + backward-compatible. From the Mastra supervisor-agents comparison (SDK Evolution roadmap SE15).
54
+
3
55
  ## 2.22.0
4
56
 
5
57
  ### Minor Changes
@@ -12922,6 +12922,7 @@ function buildLoopInputs(options, runId, userText) {
12922
12922
  ...options.onStep !== void 0 ? { onStep: options.onStep } : {},
12923
12923
  ...options.onDelta !== void 0 ? { onDelta: options.onDelta } : {},
12924
12924
  ...options.sendOptions.toolChoice !== void 0 ? { toolChoice: options.sendOptions.toolChoice } : {},
12925
+ ...options.sendOptions.activeTools !== void 0 ? { activeTools: options.sendOptions.activeTools } : {},
12925
12926
  ...options.priorMessages !== void 0 ? { priorMessages: options.priorMessages } : {},
12926
12927
  ...options.memoryTools !== void 0 && options.memoryTools.length > 0 ? { memoryTools: options.memoryTools } : {},
12927
12928
  ...buildCustomToolsInput(
@@ -13021,6 +13022,7 @@ var init_real_local_run = __esm({
13021
13022
  init_register_plugin_providers();
13022
13023
  init_tracer();
13023
13024
  init_personality_filter();
13025
+ init_async_local_storage();
13024
13026
  init_fixture_run_base();
13025
13027
  init_run_registry();
13026
13028
  pluginProvidersAnnounced = false;
@@ -13070,7 +13072,7 @@ var init_real_local_run = __esm({
13070
13072
  }
13071
13073
  async executeAgentLoop(inputs) {
13072
13074
  try {
13073
- const output = await runAgentLoop(inputs);
13075
+ const output = inputs.activeTools !== void 0 ? await withToolWhitelist(new Set(inputs.activeTools), () => runAgentLoop(inputs)) : await runAgentLoop(inputs);
13074
13076
  this.applyAgentLoopOutput(output);
13075
13077
  this.transitionTo(output.finalStatus);
13076
13078
  } catch (cause) {
@@ -18426,15 +18428,33 @@ var MaxDelegationDepthError = class extends Error {
18426
18428
  maxDepth;
18427
18429
  code = "max_delegation_depth";
18428
18430
  };
18429
- async function applyDelegationStart(spec, input) {
18431
+ async function applyDelegationStart(spec, input, iteration) {
18430
18432
  if (spec.onDelegationStart === void 0) return { input };
18431
- const decision = await spec.onDelegationStart({ input, name: spec.name });
18433
+ const decision = await spec.onDelegationStart({ input, name: spec.name, iteration });
18432
18434
  if (decision === void 0) return { input };
18433
18435
  if (decision.proceed === false)
18434
18436
  return { reject: decision.rejectionReason ?? "(delegation rejected)" };
18435
- return { input: decision.modifiedInput ?? input };
18437
+ return {
18438
+ input: decision.modifiedInput ?? input,
18439
+ ...decision.modifiedMaxSteps !== void 0 ? { maxSteps: decision.modifiedMaxSteps } : {}
18440
+ };
18436
18441
  }
18437
- async function runChildAgent(spec, input, signal) {
18442
+ async function collectChildToolResults(run) {
18443
+ const lines = [];
18444
+ for await (const event of run.stream()) {
18445
+ if (event.type === "tool_call" && event.status === "completed") {
18446
+ const rendered = typeof event.result === "string" ? event.result : JSON.stringify(event.result ?? null);
18447
+ lines.push(`${event.name}: ${rendered}`);
18448
+ }
18449
+ }
18450
+ if (lines.length === 0) return "";
18451
+ return `
18452
+
18453
+ <subagent-tool-results>
18454
+ ${lines.join("\n")}
18455
+ </subagent-tool-results>`;
18456
+ }
18457
+ async function runChildAgent(spec, input, signal, maxSteps) {
18438
18458
  const { Agent: Agent2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
18439
18459
  const agent = await Agent2.create({
18440
18460
  ...spec.model ? { model: { id: spec.model } } : {},
@@ -18442,17 +18462,22 @@ async function runChildAgent(spec, input, signal) {
18442
18462
  tools: spec.tools ?? []
18443
18463
  });
18444
18464
  try {
18445
- const run = signal !== void 0 ? await agent.send(input, { signal }) : await agent.send(input);
18465
+ const sendOptions = {
18466
+ ...signal !== void 0 ? { signal } : {},
18467
+ ...maxSteps !== void 0 ? { maxIterations: maxSteps } : {}
18468
+ };
18469
+ const run = Object.keys(sendOptions).length > 0 ? await agent.send(input, sendOptions) : await agent.send(input);
18446
18470
  const result = await run.wait();
18447
- return result.result ?? "(no response)";
18471
+ const text = result.result ?? "(no response)";
18472
+ return spec.includeToolResults === true ? text + await collectChildToolResults(run) : text;
18448
18473
  } finally {
18449
18474
  agent.dispose();
18450
18475
  }
18451
18476
  }
18452
- async function notifyDelegationError(spec, input, error) {
18477
+ async function notifyDelegationError(spec, input, error, iteration) {
18453
18478
  if (spec.onDelegationComplete === void 0) return;
18454
18479
  try {
18455
- await spec.onDelegationComplete({ input, name: spec.name, error });
18480
+ await spec.onDelegationComplete({ input, name: spec.name, error, iteration });
18456
18481
  } catch {
18457
18482
  }
18458
18483
  }
@@ -18467,9 +18492,9 @@ ${preamble}
18467
18492
  Task:
18468
18493
  ${input}`;
18469
18494
  }
18470
- async function applyDelegationComplete(spec, input, result) {
18495
+ async function applyDelegationComplete(spec, input, result, iteration) {
18471
18496
  if (spec.onDelegationComplete === void 0) return result;
18472
- const completion = await spec.onDelegationComplete({ input, name: spec.name, result });
18497
+ const completion = await spec.onDelegationComplete({ input, name: spec.name, result, iteration });
18473
18498
  return completion?.feedback !== void 0 ? result + completion.feedback : result;
18474
18499
  }
18475
18500
  function defineSubAgent(spec, _parentDepth = 0) {
@@ -18481,23 +18506,26 @@ function defineSubAgent(spec, _parentDepth = 0) {
18481
18506
  const inputSchema = zod.z.object({
18482
18507
  input: zod.z.string().describe("Task for the subagent")
18483
18508
  });
18509
+ let iteration = 0;
18484
18510
  return {
18485
18511
  name: spec.name,
18486
18512
  description: spec.description,
18487
18513
  inputSchema,
18488
18514
  handler: async (rawInput, ctx) => {
18489
18515
  const { input: parsed } = inputSchema.parse(rawInput);
18490
- const start = await applyDelegationStart(spec, parsed);
18516
+ iteration += 1;
18517
+ const capturedIteration = iteration;
18518
+ const start = await applyDelegationStart(spec, parsed, capturedIteration);
18491
18519
  if ("reject" in start) return start.reject;
18492
18520
  const input = applyMessageFilter(spec, start.input, ctx?.messages);
18493
18521
  let result;
18494
18522
  try {
18495
- result = await runChildAgent(spec, input, ctx?.signal);
18523
+ result = await runChildAgent(spec, input, ctx?.signal, start.maxSteps);
18496
18524
  } catch (error) {
18497
- await notifyDelegationError(spec, input, error);
18525
+ await notifyDelegationError(spec, input, error, capturedIteration);
18498
18526
  throw error;
18499
18527
  }
18500
- return applyDelegationComplete(spec, input, result);
18528
+ return applyDelegationComplete(spec, input, result, capturedIteration);
18501
18529
  }
18502
18530
  };
18503
18531
  }