@theokit/sdk-handoff 0.1.2 → 0.1.4
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 +268 -0
- package/LICENSE +2 -2
- package/README.md +13 -0
- package/dist/handoff-D7malWe_.d.cts +255 -0
- package/dist/handoff-D7malWe_.d.ts +255 -0
- package/dist/index.cjs +117 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +143 -28
- package/dist/index.d.ts +143 -28
- package/dist/index.js +118 -39
- package/dist/index.js.map +1 -1
- package/dist/internal/tool-injector.cjs +26 -11
- package/dist/internal/tool-injector.cjs.map +1 -1
- package/dist/internal/tool-injector.d.cts +2 -2
- package/dist/internal/tool-injector.d.ts +2 -2
- package/dist/internal/tool-injector.js +26 -11
- package/dist/internal/tool-injector.js.map +1 -1
- package/package.json +15 -7
- package/dist/handoff-D-Ujv-lA.d.cts +0 -131
- package/dist/handoff-D-Ujv-lA.d.ts +0 -131
|
@@ -189,11 +189,15 @@ function extractUserText(content) {
|
|
|
189
189
|
const text = content.filter((c) => c?.type === "text").map((c) => c.text).join("\n");
|
|
190
190
|
return text.length > 0 ? text : void 0;
|
|
191
191
|
}
|
|
192
|
+
function userTextOf(entry) {
|
|
193
|
+
const m = entry;
|
|
194
|
+
if (m?.type === "user" && m.message?.role === "user") return extractUserText(m.message.content);
|
|
195
|
+
if (m?.role === "user") return extractUserText(m.content);
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
192
198
|
function extractLastUserMessage(history, senderAgentId) {
|
|
193
199
|
for (let i = history.messages.length - 1; i >= 0; i -= 1) {
|
|
194
|
-
const
|
|
195
|
-
if (m?.type !== "user" || m.message?.role !== "user") continue;
|
|
196
|
-
const text = extractUserText(m.message.content);
|
|
200
|
+
const text = userTextOf(history.messages[i]);
|
|
197
201
|
if (text !== void 0) return text;
|
|
198
202
|
}
|
|
199
203
|
return `(Handoff from ${senderAgentId} \u2014 no prior user message in history.)`;
|
|
@@ -226,7 +230,11 @@ async function dispatchHandoff(args) {
|
|
|
226
230
|
toolName: descriptor.resolvedToolName
|
|
227
231
|
});
|
|
228
232
|
try {
|
|
229
|
-
const
|
|
233
|
+
const toolAllowlist = descriptor.options.tools;
|
|
234
|
+
const run = await receiver.send(
|
|
235
|
+
lastUserMessage,
|
|
236
|
+
toolAllowlist !== void 0 ? { activeTools: [...toolAllowlist] } : {}
|
|
237
|
+
);
|
|
230
238
|
const result = await run.wait();
|
|
231
239
|
const reply = buildReply(result, receiver.agentId);
|
|
232
240
|
return {
|
|
@@ -253,6 +261,12 @@ function buildReply(result, receiverAgentId) {
|
|
|
253
261
|
const suffix = result.error !== void 0 ? `: ${result.error.message}` : "";
|
|
254
262
|
return `(Handoff target ${receiverAgentId} returned status=${result.status}${suffix})`;
|
|
255
263
|
}
|
|
264
|
+
|
|
265
|
+
// src/internal/slugify-agent-name.ts
|
|
266
|
+
var MAX_INPUT_LENGTH = 1024;
|
|
267
|
+
function slugifyAgentName(candidate) {
|
|
268
|
+
return candidate.slice(0, MAX_INPUT_LENGTH).replace(/^agent-/i, "").replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) || "anonymous";
|
|
269
|
+
}
|
|
256
270
|
function toJsonSchema(schema, options = { unrepresentable: "any" }) {
|
|
257
271
|
return toJSONSchema(schema, options);
|
|
258
272
|
}
|
|
@@ -287,10 +301,7 @@ function autoWrap(agent) {
|
|
|
287
301
|
}
|
|
288
302
|
function resolveTargetName(agent) {
|
|
289
303
|
const candidate = agent.name ?? agent.agentId ?? "anonymous";
|
|
290
|
-
return
|
|
291
|
-
}
|
|
292
|
-
function slugify(input) {
|
|
293
|
-
return input.replace(/^agent-/i, "").replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) || "anonymous";
|
|
304
|
+
return slugifyAgentName(candidate);
|
|
294
305
|
}
|
|
295
306
|
function buildHandoffTool(parentAgentId, descriptor, maxHandoffDepth) {
|
|
296
307
|
const description = descriptor.options.toolDescription ?? `Transfer the conversation to the ${descriptor.target.agentId} agent. Use this when the user's request matches their specialty.`;
|
|
@@ -302,7 +313,7 @@ function buildHandoffTool(parentAgentId, descriptor, maxHandoffDepth) {
|
|
|
302
313
|
name: descriptor.resolvedToolName,
|
|
303
314
|
description,
|
|
304
315
|
inputSchema,
|
|
305
|
-
handler: async (input) => {
|
|
316
|
+
handler: async (input, ctx) => {
|
|
306
317
|
const chainState = createChainState(parentAgentId, maxHandoffDepth);
|
|
307
318
|
try {
|
|
308
319
|
const { reply, result } = await dispatchHandoff({
|
|
@@ -310,8 +321,12 @@ function buildHandoffTool(parentAgentId, descriptor, maxHandoffDepth) {
|
|
|
310
321
|
senderAgentId: parentAgentId,
|
|
311
322
|
chainState,
|
|
312
323
|
rawInputJson: input,
|
|
313
|
-
|
|
314
|
-
// v1: history replay
|
|
324
|
+
// #354 — the supervisor's transcript, which the SDK hands every tool handler as
|
|
325
|
+
// `ctx.messages`. This used to be `{ messages: [] }` with the note "v1: history replay
|
|
326
|
+
// deferred", so the dispatcher found no user message and sent the receiver the
|
|
327
|
+
// placeholder instead of the question — and `inputFilter`, the documented redaction
|
|
328
|
+
// hook, was handed an empty transcript to redact.
|
|
329
|
+
history: { messages: ctx?.messages ?? [] }
|
|
315
330
|
});
|
|
316
331
|
return JSON.stringify({
|
|
317
332
|
ok: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/handoff.ts","../../src/internal/registry.ts","../../src/internal/telemetry.ts","../../src/internal/dispatcher.ts","../../src/internal/to-json-schema.ts","../../src/internal/tool-injector.ts"],"names":["z"],"mappings":"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","file":"tool-injector.js","sourcesContent":["/**\n * Public types for `Agent.create({ handoffs })` + `Handoff.create()` +\n * `Agent.handoffTo()` (Adoption Roadmap #4; ADRs D214-D229).\n *\n * Pattern: handoff-as-tool. Each handoff destination becomes a synthetic\n * `transfer_to_<receiver>` function tool exposed to the LLM. Runtime\n * intercepts the tool call and routes the next turn to the receiver.\n *\n * T4.1 follow-up (cycle #4 closed): `HandoffDescriptor` + its leaf-friendly\n * sibling types now live in `./handoff-descriptor.ts` (generic over\n * `TAgent`). This module re-exports the leaf types pinned to `SDKAgent`,\n * keeps the runtime error classes, and removes the back-edge to `agent.ts`.\n *\n * @public\n */\n\nimport type { SDKAgent } from \"@theokit/sdk\";\nimport type { ZodType } from \"zod\";\nimport type {\n HandoffContext,\n HandoffDescriptor as HandoffDescriptorGeneric,\n HandoffHistory,\n HandoffOptions,\n HandoffResult,\n} from \"./handoff-descriptor.js\";\n\nexport type { HandoffContext, HandoffHistory, HandoffOptions, HandoffResult };\n\n/**\n * `HandoffDescriptor` pinned to `SDKAgent` — back-compat shape for callers\n * that imported `import type { HandoffDescriptor } from \"@theokit/sdk\"`\n * before T4.1 follow-up.\n *\n * @public\n */\nexport type HandoffDescriptor<TInput extends ZodType = ZodType> = HandoffDescriptorGeneric<\n TInput,\n SDKAgent\n>;\n\n/** Throw when handoff depth exceeds `maxHandoffDepth` (default 5; D218). */\nexport class HandoffLoopError extends Error {\n override readonly name = \"HandoffLoopError\";\n readonly depth: number;\n readonly chain: ReadonlyArray<string>;\n constructor(depth: number, chain: ReadonlyArray<string>) {\n super(\n `Handoff loop exceeded max depth ${depth}. Chain: ${chain.join(\" -> \")}. ` +\n `Use Agent.create({ maxHandoffDepth: N }) to raise the cap.`,\n );\n this.depth = depth;\n this.chain = chain;\n }\n}\n\n/** Throw when the same (sender, receiver) pair invoked twice in one send() (D221). */\nexport class HandoffPairLoopError extends Error {\n override readonly name = \"HandoffPairLoopError\";\n readonly senderAgentId: string;\n readonly receiverAgentId: string;\n constructor(senderAgentId: string, receiverAgentId: string) {\n super(\n `Handoff loop: ${senderAgentId} -> ${receiverAgentId} already invoked in this send() call. ` +\n `Likely a ping-pong loop; revisit your handoff conditions.`,\n );\n this.senderAgentId = senderAgentId;\n this.receiverAgentId = receiverAgentId;\n }\n}\n\n/** Throw when an agent's `handoffs[]` includes a self-reference (EC-6). */\nexport class HandoffSelfReferenceError extends Error {\n override readonly name = \"HandoffSelfReferenceError\";\n readonly agentId: string;\n constructor(agentId: string) {\n super(\n `Agent \"${agentId}\" has a self-reference in its handoffs[]. ` +\n `Self-handoff causes infinite recursion; introduce a sibling agent for re-entry.`,\n );\n this.agentId = agentId;\n }\n}\n\n/** Throw when receiver is disposed at dispatch time (EC-5). */\nexport class HandoffReceiverDisposedError extends Error {\n override readonly name = \"HandoffReceiverDisposedError\";\n readonly receiverAgentId: string;\n constructor(receiverAgentId: string) {\n super(\n `Handoff target agent \"${receiverAgentId}\" is disposed. ` +\n `Don't dispose receivers while their parent is still active.`,\n );\n this.receiverAgentId = receiverAgentId;\n }\n}\n\n/** Throw when two handoffs in the same parent collide on tool name (D215). */\nexport class HandoffNameCollisionError extends Error {\n override readonly name = \"HandoffNameCollisionError\";\n readonly conflictingName: string;\n constructor(conflictingName: string) {\n super(\n `Two handoffs share the same tool name \"${conflictingName}\". ` +\n `Set { toolName } on at least one of them to disambiguate.`,\n );\n this.conflictingName = conflictingName;\n }\n}\n","/**\n * Handoff registry — pure state container per `Agent` instance.\n *\n * Holds the active dispatch chain (for depth + pair tracking) across the\n * lifetime of a single `agent.send()` call. Cleared between calls.\n *\n * @internal\n */\n\nimport { HandoffLoopError, HandoffPairLoopError } from \"../types/handoff.js\";\n\nexport interface HandoffChainState {\n /** Ordered chain of agentIds traversed so far (oldest first). */\n readonly chain: string[];\n /** Set of \"<sender>-><receiver>\" keys for pair-level loop detection (D221). */\n readonly seenPairs: Set<string>;\n /** Caller-supplied max depth (D218). */\n readonly maxDepth: number;\n}\n\nexport function createChainState(rootAgentId: string, maxDepth: number): HandoffChainState {\n return {\n chain: [rootAgentId],\n seenPairs: new Set(),\n maxDepth,\n };\n}\n\n/**\n * Record a handoff hop. Throws on depth-exceed (D218) or pair-loop (D221).\n * Mutates the state in place.\n */\nexport function recordHop(\n state: HandoffChainState,\n senderAgentId: string,\n receiverAgentId: string,\n): void {\n const pairKey = `${senderAgentId}->${receiverAgentId}`;\n if (state.seenPairs.has(pairKey)) {\n throw new HandoffPairLoopError(senderAgentId, receiverAgentId);\n }\n state.seenPairs.add(pairKey);\n state.chain.push(receiverAgentId);\n // chain.length = nodes; depth = hops = chain.length - 1.\n const depth = state.chain.length - 1;\n if (depth > state.maxDepth) {\n throw new HandoffLoopError(state.maxDepth, [...state.chain]);\n }\n}\n\nexport function currentDepth(state: HandoffChainState): number {\n return state.chain.length - 1;\n}\n","/**\n * D220 — Lazy-loaded OTel `handoff.transfer` span emitter.\n *\n * @internal\n */\n\n// Inline tracer-loader (same workaround as @theokit/sdk-cache/internal/telemetry.ts):\n// rollup-plugin-dts emits incomplete index.d.ts for newly-modified internal/ barrels\n// in @theokit/sdk. Runtime via the sub-path works; TypeScript users hit TS2305.\n// Inlining keeps sdk-handoff self-contained for the (small) observability hook.\nimport { createRequire } from \"node:module\";\n\ninterface SpanLike {\n setAttribute(key: string, value: string | number | boolean): SpanLike;\n end(): void;\n}\n\nconst _NOOP_SPAN: SpanLike = {\n setAttribute: () => _NOOP_SPAN,\n end: () => undefined,\n};\n\ninterface TracerLike {\n startSpan(\n name: string,\n options?: { attributes?: Record<string, string | number | boolean> },\n ): SpanLike;\n}\n\ninterface TracerCacheEntry {\n tracer: TracerLike | null;\n}\nconst tracerCache = new Map<string, TracerCacheEntry>();\n\nfunction getTracer(name: string, version = \"1.0.0\"): TracerLike | undefined {\n const cached = tracerCache.get(name);\n if (cached !== undefined) return cached.tracer ?? undefined;\n try {\n const r = createRequire(import.meta.url);\n const otel = r(\"@opentelemetry/api\") as {\n trace?: { getTracer: (n: string, v?: string) => TracerLike };\n };\n if (otel.trace?.getTracer === undefined) {\n tracerCache.set(name, { tracer: null });\n return undefined;\n }\n const tracer = otel.trace.getTracer(name, version);\n tracerCache.set(name, { tracer });\n return tracer;\n } catch {\n tracerCache.set(name, { tracer: null });\n return undefined;\n }\n}\n\nconst TRACER_NAME = \"theokit-sdk-handoff\";\n\ninterface HandoffSpanHandle {\n setAttribute(key: string, value: string | number | boolean): void;\n end(): void;\n}\n\nconst NOOP: HandoffSpanHandle = { setAttribute: () => undefined, end: () => undefined };\n\nfunction safe<T>(fn: () => T, fallback: T): T {\n try {\n return fn();\n } catch {\n return fallback;\n }\n}\n\nexport function startHandoffSpan(attrs: {\n from: string;\n to: string;\n reason: string;\n depth: number;\n toolName: string;\n}): HandoffSpanHandle {\n const tracer = getTracer(TRACER_NAME);\n if (tracer === undefined) return NOOP;\n const span: SpanLike | undefined = safe(\n () =>\n tracer.startSpan(\"handoff.transfer\", {\n attributes: {\n \"handoff.from\": attrs.from,\n \"handoff.to\": attrs.to,\n \"handoff.reason\": attrs.reason,\n \"handoff.depth\": attrs.depth,\n \"handoff.tool_name\": attrs.toolName,\n },\n }),\n undefined,\n );\n if (span === undefined) return NOOP;\n return {\n setAttribute: (k, v) => safe(() => span.setAttribute(k, v), undefined),\n end: () => safe(() => span.end(), undefined),\n };\n}\n","/**\n * Handoff dispatch orchestration.\n *\n * Pragmatic v1: when a handoff fires, the sender calls `receiver.send()`\n * with the (optionally filtered) history. The receiver's reply is returned\n * to the sender, which captures it as the answer to the user's question.\n *\n * NOTE: this is NOT pure peer-to-peer (the sender stays on the call stack\n * until the receiver returns). Pure intercept-and-swap requires deeper\n * agent-loop refactor; deferred to v2. v1 still validates the user-facing\n * value: \"agent A reasoned about routing, agent B answered.\"\n *\n * @internal\n */\n\nimport type { SDKAgent } from \"@theokit/sdk\";\nimport { z } from \"zod\";\nimport type {\n HandoffContext,\n HandoffDescriptor,\n HandoffHistory,\n HandoffResult,\n} from \"../types/handoff.js\";\nimport { HandoffReceiverDisposedError } from \"../types/handoff.js\";\nimport { type HandoffChainState, recordHop } from \"./registry.js\";\nimport { startHandoffSpan } from \"./telemetry.js\";\n\n/**\n * EC-2 / D228 — `safeFilter` wraps `inputFilter`. On exception, falls back\n * to the un-filtered history and warns to stderr once per process.\n */\nlet warnedFilterOnce = false;\nasync function safeFilter(\n filter: ((h: HandoffHistory) => HandoffHistory | Promise<HandoffHistory>) | undefined,\n history: HandoffHistory,\n): Promise<HandoffHistory> {\n if (filter === undefined) return history;\n try {\n const result = filter(history);\n return result instanceof Promise ? await result : result;\n } catch (err) {\n if (!warnedFilterOnce) {\n warnedFilterOnce = true;\n process.stderr.write(\n `[handoff] inputFilter threw, falling back to full history: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n return history;\n }\n}\n\n/**\n * EC-4 / D229 — parse the LLM-provided JSON args. Returns undefined when\n * no `inputType` set; returns parsed value otherwise (default to `{}` on\n * empty/null input before Zod refinements).\n */\nfunction parseHandoffInput(descriptor: HandoffDescriptor, raw: unknown): unknown {\n const inputType = descriptor.options.inputType;\n if (inputType === undefined) return undefined;\n const candidate = raw === null || raw === undefined ? {} : raw;\n return inputType.parse(candidate);\n}\n\nfunction isAgentDisposed(agent: SDKAgent): boolean {\n // SDKAgent doesn't expose `disposed` publicly; check via duck-typing on\n // a known internal flag. Safe fallback: if we can't tell, assume alive.\n const maybe = agent as unknown as { disposed?: boolean };\n return maybe.disposed === true;\n}\n\n/**\n * Run a single handoff hop. Returns the receiver's reply text.\n *\n * Algorithm:\n * 1. EC-5: refuse if receiver disposed.\n * 2. Build HandoffContext.\n * 3. Check isEnabled() — if false, refuse with a clear error message.\n * 4. Parse inputType (D229).\n * 5. Run onHandoff(ctx, parsed) — throw aborts (D227).\n * 6. Apply inputFilter (safeFilter — D228).\n * 7. Record hop in chain state (depth + pair guards).\n * 8. Open OTel span (D220).\n * 9. Receiver: build the user-facing message and `await receiver.send(msg).then(wait)`.\n * 10. Close span + return reply.\n */\nasync function assertHandoffEnabled(\n descriptor: HandoffDescriptor,\n ctx: HandoffContext,\n receiverAgentId: string,\n): Promise<void> {\n const opt = descriptor.options.isEnabled;\n let enabled = true;\n if (typeof opt === \"boolean\") enabled = opt;\n else if (typeof opt === \"function\") {\n const r = opt(ctx);\n enabled = r instanceof Promise ? await r : r;\n }\n if (!enabled) {\n throw new Error(`Handoff to ${receiverAgentId} is disabled (isEnabled returned false)`);\n }\n}\n\nfunction parseAndValidate(descriptor: HandoffDescriptor, rawInputJson: unknown): unknown {\n try {\n return parseHandoffInput(descriptor, rawInputJson);\n } catch (err) {\n const detail =\n err instanceof z.ZodError\n ? (err.issues[0]?.message ?? \"schema_invalid\")\n : err instanceof Error\n ? err.message\n : String(err);\n throw new Error(`Handoff input validation failed: ${detail}`);\n }\n}\n\nasync function runOnHandoff(\n descriptor: HandoffDescriptor,\n ctx: HandoffContext,\n parsedInput: unknown,\n): Promise<void> {\n const onHandoff = descriptor.options.onHandoff;\n if (onHandoff === undefined) return;\n // biome-ignore lint/suspicious/noExplicitAny: parsedInput is typed unknown by design.\n const result = onHandoff(ctx, parsedInput as any);\n if (result instanceof Promise) await result;\n}\n\nfunction extractUserText(content: unknown): string | undefined {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return undefined;\n const text = content\n .filter((c): c is { type: \"text\"; text: string } => (c as { type?: string })?.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\");\n return text.length > 0 ? text : undefined;\n}\n\nfunction extractLastUserMessage(history: HandoffHistory, senderAgentId: string): string {\n for (let i = history.messages.length - 1; i >= 0; i -= 1) {\n const m = history.messages[i] as {\n type?: string;\n message?: { role?: string; content?: unknown };\n };\n if (m?.type !== \"user\" || m.message?.role !== \"user\") continue;\n const text = extractUserText(m.message.content);\n if (text !== undefined) return text;\n }\n return `(Handoff from ${senderAgentId} — no prior user message in history.)`;\n}\n\nexport async function dispatchHandoff(args: {\n descriptor: HandoffDescriptor;\n senderAgentId: string;\n chainState: HandoffChainState;\n rawInputJson: unknown;\n /** The conversation so far (history wrapper). v1: just the LAST user message. */\n history: HandoffHistory;\n /** Override the message text sent to the receiver. Used by `Agent.handoffTo` imperative. */\n messageOverride?: string;\n}): Promise<{ reply: string; result: HandoffResult }> {\n const { descriptor, senderAgentId, chainState, rawInputJson, history, messageOverride } = args;\n const receiver = descriptor.target;\n\n if (isAgentDisposed(receiver)) {\n throw new HandoffReceiverDisposedError(receiver.agentId);\n }\n\n const depthAfterThisHop = chainState.chain.length;\n const ctx: HandoffContext = {\n senderAgentId,\n receiverAgentId: receiver.agentId,\n currentDepth: depthAfterThisHop,\n chain: [...chainState.chain, receiver.agentId],\n };\n\n await assertHandoffEnabled(descriptor, ctx, receiver.agentId);\n const parsedInput = parseAndValidate(descriptor, rawInputJson);\n await runOnHandoff(descriptor, ctx, parsedInput);\n\n // Filter history (D228 — resilient)\n const filteredHistory = await safeFilter(descriptor.options.inputFilter, history);\n\n // Record hop — may throw HandoffLoopError or HandoffPairLoopError\n recordHop(chainState, senderAgentId, receiver.agentId);\n\n const lastUserMessage = messageOverride ?? extractLastUserMessage(filteredHistory, senderAgentId);\n const reason = extractReason(parsedInput);\n\n const span = startHandoffSpan({\n from: senderAgentId,\n to: receiver.agentId,\n reason,\n depth: depthAfterThisHop,\n toolName: descriptor.resolvedToolName,\n });\n\n try {\n const run = await receiver.send(lastUserMessage);\n const result = await run.wait();\n const reply = buildReply(result, receiver.agentId);\n return {\n reply,\n result: {\n from: senderAgentId,\n to: receiver.agentId,\n depth: depthAfterThisHop,\n toolName: descriptor.resolvedToolName,\n ...(reason !== \"\" ? { reasonFromLlm: reason } : {}),\n },\n };\n } finally {\n span.end();\n }\n}\n\nfunction extractReason(parsedInput: unknown): string {\n if (typeof parsedInput !== \"object\" || parsedInput === null) return \"\";\n if (!(\"reason\" in parsedInput)) return \"\";\n return String((parsedInput as { reason: unknown }).reason ?? \"\");\n}\n\nfunction buildReply(\n result: { status: string; result?: string; error?: { message: string } },\n receiverAgentId: string,\n): string {\n if (result.status === \"finished\" && result.result !== undefined) return result.result;\n const suffix = result.error !== undefined ? `: ${result.error.message}` : \"\";\n return `(Handoff target ${receiverAgentId} returned status=${result.status}${suffix})`;\n}\n","/**\n * Zod v4 → JSON Schema adapter for sdk-handoff.\n *\n * Uses Zod v4's native `z.toJSONSchema()` directly. v3 fallback removed\n * after zod-v4-migration plan (ADR D2).\n *\n * @internal\n */\n\nimport { toJSONSchema } from \"zod\";\n\ninterface ToJsonSchemaOptions {\n /** `\"any\"` keeps transforms/refinements as `{}` (loose). Default: `\"any\"`. */\n unrepresentable?: \"any\" | \"throw\";\n}\n\n/**\n * Convert a Zod schema to a JSON Schema object via Zod v4 native.\n *\n * @internal\n */\nexport function toJsonSchema(\n schema: unknown,\n options: ToJsonSchemaOptions = { unrepresentable: \"any\" },\n): Record<string, unknown> {\n // The schema param is `unknown` (callers pass `T extends ZodType` generics that\n // don't structurally satisfy Zod v4's `$ZodType`); cast to the exact parameter\n // type `toJSONSchema` expects rather than `any` — any z.* schema IS valid at runtime.\n return toJSONSchema(schema as Parameters<typeof toJSONSchema>[0], options) as Record<\n string,\n unknown\n >;\n}\n","/**\n * Convert `handoffs[]` entries into synthetic `transfer_to_<receiver>` tools\n * for injection into the agent's tool registry at construction time.\n *\n * The synthesized tool's handler calls `dispatchHandoff` internally and\n * returns the receiver's reply as `tool_result`. v1 trade-off documented\n * in dispatcher.ts.\n *\n * @internal\n */\n\nimport type { CustomTool, SDKAgent } from \"@theokit/sdk\";\nimport { z } from \"zod\";\nimport {\n type HandoffDescriptor,\n HandoffNameCollisionError,\n HandoffSelfReferenceError,\n} from \"../types/handoff.js\";\nimport { dispatchHandoff } from \"./dispatcher.js\";\nimport { createChainState } from \"./registry.js\";\nimport { toJsonSchema } from \"./to-json-schema.js\";\n\ninterface NormalizedHandoff {\n descriptor: HandoffDescriptor;\n}\n\n/**\n * Normalize each `handoffs[]` entry to a `HandoffDescriptor`. Raw `SDKAgent`\n * instances are auto-wrapped with default options. Validates:\n * - EC-6: no self-reference (would cause infinite recursion).\n * - D215: resolved tool names must be unique.\n */\nexport function normalizeHandoffs(\n parentAgentId: string,\n entries: ReadonlyArray<SDKAgent | HandoffDescriptor>,\n): NormalizedHandoff[] {\n if (entries.length === 0) return [];\n const out: NormalizedHandoff[] = [];\n const seenNames = new Set<string>();\n for (const entry of entries) {\n // Detect raw Agent vs HandoffDescriptor by presence of `.target`.\n const isDescriptor =\n typeof entry === \"object\" &&\n entry !== null &&\n \"target\" in entry &&\n \"options\" in entry &&\n \"resolvedToolName\" in entry;\n const descriptor = isDescriptor ? (entry as HandoffDescriptor) : autoWrap(entry as SDKAgent);\n if (descriptor.target.agentId === parentAgentId) {\n throw new HandoffSelfReferenceError(parentAgentId);\n }\n const name = descriptor.resolvedToolName;\n if (seenNames.has(name)) {\n throw new HandoffNameCollisionError(name);\n }\n seenNames.add(name);\n out.push({ descriptor });\n }\n return out;\n}\n\nfunction autoWrap(agent: SDKAgent): HandoffDescriptor {\n const name = resolveTargetName(agent);\n return {\n target: agent,\n options: {},\n resolvedToolName: `transfer_to_${name}`,\n };\n}\n\nfunction resolveTargetName(agent: SDKAgent): string {\n // Prefer a `name` field if exposed; fall back to a short agentId slug.\n const candidate = (agent as unknown as { name?: string }).name ?? agent.agentId ?? \"anonymous\";\n return slugify(candidate);\n}\n\nfunction slugify(input: string): string {\n return (\n input\n .replace(/^agent-/i, \"\")\n .replace(/[^a-zA-Z0-9_-]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 64) || \"anonymous\"\n );\n}\n\n/**\n * Build a `CustomTool` for one handoff descriptor. The handler dispatches\n * the handoff using a fresh chain state per `send()`-level invocation.\n *\n * NOTE: this v1 builds a NEW chain state per tool invocation. Pure\n * cross-tool depth tracking within one send() requires per-Agent context\n * — deferred. The single-flight pair guard catches direct ping-pong even\n * without cross-invocation chain (since each call wraps the same depth\n * counter from 1).\n */\nexport function buildHandoffTool(\n parentAgentId: string,\n descriptor: HandoffDescriptor,\n maxHandoffDepth: number,\n): CustomTool {\n const description =\n descriptor.options.toolDescription ??\n `Transfer the conversation to the ${descriptor.target.agentId} agent. ` +\n `Use this when the user's request matches their specialty.`;\n\n const inputZod =\n descriptor.options.inputType ??\n z.object({\n reason: z.string().optional().describe(\"Brief reason for the transfer (one short sentence).\"),\n });\n // CustomTool.inputSchema expects a JSON schema (Record<string, unknown>),\n // not the raw Zod type. Convert lazily so we don't fail when Zod is missing.\n // Universal Zod 3+4 conversion (feature-detects native v4, falls back to lib on v3).\n const inputSchema = toJsonSchema(inputZod);\n\n return {\n name: descriptor.resolvedToolName,\n description,\n inputSchema,\n handler: async (input: unknown): Promise<string> => {\n const chainState = createChainState(parentAgentId, maxHandoffDepth);\n try {\n const { reply, result } = await dispatchHandoff({\n descriptor,\n senderAgentId: parentAgentId,\n chainState,\n rawInputJson: input,\n history: { messages: [] }, // v1: history replay deferred\n });\n return JSON.stringify({\n ok: true,\n transferred_to: result.to,\n depth: result.depth,\n reply,\n });\n } catch (err) {\n return JSON.stringify({\n ok: false,\n error: err instanceof Error ? err.name : \"HandoffError\",\n message: err instanceof Error ? err.message : String(err),\n });\n }\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/types/handoff.ts","../../src/internal/registry.ts","../../src/internal/telemetry.ts","../../src/internal/dispatcher.ts","../../src/internal/slugify-agent-name.ts","../../src/internal/to-json-schema.ts","../../src/internal/tool-injector.ts"],"names":["z"],"mappings":"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","file":"tool-injector.js","sourcesContent":["/**\n * Public types for `Agent.create({ handoffs })` + `Handoff.create()` +\n * `Agent.handoffTo()` (Adoption Roadmap #4; ADRs D214-D229).\n *\n * Pattern: handoff-as-tool. Each handoff destination becomes a synthetic\n * `transfer_to_<receiver>` function tool exposed to the LLM. Runtime\n * intercepts the tool call and routes the next turn to the receiver.\n *\n * T4.1 follow-up (cycle #4 closed): `HandoffDescriptor` + its leaf-friendly\n * sibling types now live in `./handoff-descriptor.ts` (generic over\n * `TAgent`). This module re-exports the leaf types pinned to `SDKAgent`,\n * keeps the runtime error classes, and removes the back-edge to `agent.ts`.\n *\n * @public\n */\n\nimport type { SDKAgent } from \"@theokit/sdk\";\nimport type { ZodType } from \"zod\";\nimport type {\n HandoffContext,\n HandoffDescriptor as HandoffDescriptorGeneric,\n HandoffHistory,\n HandoffOptions,\n HandoffResult,\n} from \"./handoff-descriptor.js\";\n\nexport type { HandoffContext, HandoffHistory, HandoffOptions, HandoffResult };\n\n/**\n * What `Handoff.create` returns: a target plus its options plus the resolved tool name.\n *\n * Pinned to `SDKAgent` — the back-compat shape for callers who imported\n * `import type { HandoffDescriptor } from \"@theokit/sdk\"` before the T4.1 follow-up. It is a plain\n * data record: constructing one by hand works, and skips the target validation `Handoff.create`\n * performs.\n *\n * @public\n */\nexport type HandoffDescriptor<TInput extends ZodType = ZodType> = HandoffDescriptorGeneric<\n TInput,\n SDKAgent\n>;\n\n/**\n * Thrown when a chain exceeds `maxHandoffDepth` (default 5). `depth` is the CAP that was exceeded,\n * not the depth reached; `chain` is the full path of agent ids.\n *\n * Rare in practice: chain state is rebuilt per dispatch, so depth restarts at 1 on every tool call.\n * Repeated ping-pong surfaces as {@link HandoffPairLoopError} instead. *\n * WHERE YOU SEE IT: only when you drive a handoff yourself, via `handoffTo(...)`. In the\n * tool-based wirings (`Handoff.asPlugin` / `Agent.create({ handoffs })`) the handler catches every\n * error and hands the MODEL a `{\"ok\":false,\"error\":\"<name>\",\"message\":\"…\"}` tool result, so this\n * class is observable there only as that `error` string.\n */\nexport class HandoffLoopError extends Error {\n override readonly name = \"HandoffLoopError\";\n readonly depth: number;\n readonly chain: ReadonlyArray<string>;\n constructor(depth: number, chain: ReadonlyArray<string>) {\n super(\n `Handoff loop exceeded max depth ${depth}. Chain: ${chain.join(\" -> \")}. ` +\n `Use Agent.create({ maxHandoffDepth: N }) to raise the cap.`,\n );\n this.depth = depth;\n this.chain = chain;\n }\n}\n\n/**\n * Thrown when the same `sender -> receiver` pair fires twice inside one dispatch — the ping-pong\n * guard, and the loop protection that actually fires in practice.\n *\n * A -> B -> A is allowed by this check (different pairs); a repeated A -> B is not. *\n * WHERE YOU SEE IT: only when you drive a handoff yourself, via `handoffTo(...)`. In the\n * tool-based wirings (`Handoff.asPlugin` / `Agent.create({ handoffs })`) the handler catches every\n * error and hands the MODEL a `{\"ok\":false,\"error\":\"<name>\",\"message\":\"…\"}` tool result, so this\n * class is observable there only as that `error` string.\n */\nexport class HandoffPairLoopError extends Error {\n override readonly name = \"HandoffPairLoopError\";\n readonly senderAgentId: string;\n readonly receiverAgentId: string;\n constructor(senderAgentId: string, receiverAgentId: string) {\n super(\n `Handoff loop: ${senderAgentId} -> ${receiverAgentId} already invoked in this send() call. ` +\n `Likely a ping-pong loop; revisit your handoff conditions.`,\n );\n this.senderAgentId = senderAgentId;\n this.receiverAgentId = receiverAgentId;\n }\n}\n\n/**\n * Thrown when a target's `agentId` equals the parent's — self-handoff, which recurses forever.\n *\n * Compared against `parentAgentId` as a STRING, which defaults to `\"anonymous\"` in\n * `Handoff.asPlugin`: leave it unset and a genuine self-reference goes undetected.\n *\n * Raised while the target list is normalised, which in `Handoff.asPlugin` happens inside an\n * unawaited async registration — it arrives as an unhandled rejection there, not as a throw from\n * `Agent.create`.\n */\nexport class HandoffSelfReferenceError extends Error {\n override readonly name = \"HandoffSelfReferenceError\";\n readonly agentId: string;\n constructor(agentId: string) {\n super(\n `Agent \"${agentId}\" has a self-reference in its handoffs[]. ` +\n `Self-handoff causes infinite recursion; introduce a sibling agent for re-entry.`,\n );\n this.agentId = agentId;\n }\n}\n\n/**\n * Thrown when the target agent was disposed before the handoff reached it — detected at dispatch\n * time, since nothing unregisters the tool when an agent is disposed.\n *\n * Typical cause: the receiver was created in a narrower scope than the sender and cleaned up first. *\n * WHERE YOU SEE IT: only when you drive a handoff yourself, via `handoffTo(...)`. In the\n * tool-based wirings (`Handoff.asPlugin` / `Agent.create({ handoffs })`) the handler catches every\n * error and hands the MODEL a `{\"ok\":false,\"error\":\"<name>\",\"message\":\"…\"}` tool result, so this\n * class is observable there only as that `error` string.\n */\nexport class HandoffReceiverDisposedError extends Error {\n override readonly name = \"HandoffReceiverDisposedError\";\n readonly receiverAgentId: string;\n constructor(receiverAgentId: string) {\n super(\n `Handoff target agent \"${receiverAgentId}\" is disposed. ` +\n `Don't dispose receivers while their parent is still active.`,\n );\n this.receiverAgentId = receiverAgentId;\n }\n}\n\n/**\n * Thrown when two targets of the same parent resolve to the same `transfer_to_*` name — the model\n * would have no way to pick between them.\n *\n * Easy to hit without duplicate agents, but not in the way the folding rule suggests: `-` and `_`\n * SURVIVE the slug, and only runs of other characters fold to a single `_`, which is then trimmed\n * at both ends. So `\"billing EU\"`, `\"billing_EU\"`, `\"billing.EU\"` and `\"billing (EU)\"` all resolve\n * to `transfer_to_billing_EU` and collide — the last one because the `_` left by the closing paren\n * is trimmed off the end. `\"billing-EU\"` keeps its hyphen, resolves to `transfer_to_billing-EU`,\n * and collides with none of them. Set `toolName` on one of the colliding pair.\n *\n * Raised while the target list is normalised, which in `Handoff.asPlugin` happens inside an\n * unawaited async registration — it arrives as an unhandled rejection there, not as a throw from\n * `Agent.create`.\n */\nexport class HandoffNameCollisionError extends Error {\n override readonly name = \"HandoffNameCollisionError\";\n readonly conflictingName: string;\n constructor(conflictingName: string) {\n super(\n `Two handoffs share the same tool name \"${conflictingName}\". ` +\n `Set { toolName } on at least one of them to disambiguate.`,\n );\n this.conflictingName = conflictingName;\n }\n}\n","/**\n * Handoff registry — pure state container per `Agent` instance.\n *\n * Holds the active dispatch chain (for depth + pair tracking) across the\n * lifetime of a single `agent.send()` call. Cleared between calls.\n *\n * @internal\n */\n\nimport { HandoffLoopError, HandoffPairLoopError } from \"../types/handoff.js\";\n\nexport interface HandoffChainState {\n /** Ordered chain of agentIds traversed so far (oldest first). */\n readonly chain: string[];\n /** Set of \"<sender>-><receiver>\" keys for pair-level loop detection (D221). */\n readonly seenPairs: Set<string>;\n /** Caller-supplied max depth (D218). */\n readonly maxDepth: number;\n}\n\nexport function createChainState(rootAgentId: string, maxDepth: number): HandoffChainState {\n return {\n chain: [rootAgentId],\n seenPairs: new Set(),\n maxDepth,\n };\n}\n\n/**\n * Record a handoff hop. Throws on depth-exceed (D218) or pair-loop (D221).\n * Mutates the state in place.\n */\nexport function recordHop(\n state: HandoffChainState,\n senderAgentId: string,\n receiverAgentId: string,\n): void {\n const pairKey = `${senderAgentId}->${receiverAgentId}`;\n if (state.seenPairs.has(pairKey)) {\n throw new HandoffPairLoopError(senderAgentId, receiverAgentId);\n }\n state.seenPairs.add(pairKey);\n state.chain.push(receiverAgentId);\n // chain.length = nodes; depth = hops = chain.length - 1.\n const depth = state.chain.length - 1;\n if (depth > state.maxDepth) {\n throw new HandoffLoopError(state.maxDepth, [...state.chain]);\n }\n}\n\nexport function currentDepth(state: HandoffChainState): number {\n return state.chain.length - 1;\n}\n","/**\n * D220 — Lazy-loaded OTel `handoff.transfer` span emitter.\n *\n * Emitted ONLY when `@opentelemetry/api` resolves from THIS package's directory.\n * It is an optional peer dependency: not installed for you, and under an isolated\n * node_modules layout a copy installed for some other package is not visible here.\n * When the require fails, `getTracer` caches `null` and every span becomes a no-op\n * — silently, with no warning, unlike `@theokit/sdk`'s own tracer, which prints one\n * when `telemetry.enabled = true` and OTel is absent.\n *\n * @internal\n */\n\n// Inline tracer-loader (same workaround as @theokit/sdk-cache/internal/telemetry.ts):\n// rollup-plugin-dts emits incomplete index.d.ts for newly-modified internal/ barrels\n// in @theokit/sdk. Runtime via the sub-path works; TypeScript users hit TS2305.\n// Inlining keeps sdk-handoff self-contained for the (small) observability hook.\nimport { createRequire } from \"node:module\";\n\ninterface SpanLike {\n setAttribute(key: string, value: string | number | boolean): SpanLike;\n end(): void;\n}\n\nconst _NOOP_SPAN: SpanLike = {\n setAttribute: () => _NOOP_SPAN,\n end: () => undefined,\n};\n\ninterface TracerLike {\n startSpan(\n name: string,\n options?: { attributes?: Record<string, string | number | boolean> },\n ): SpanLike;\n}\n\ninterface TracerCacheEntry {\n tracer: TracerLike | null;\n}\nconst tracerCache = new Map<string, TracerCacheEntry>();\n\nfunction getTracer(name: string, version = \"1.0.0\"): TracerLike | undefined {\n const cached = tracerCache.get(name);\n if (cached !== undefined) return cached.tracer ?? undefined;\n try {\n const r = createRequire(import.meta.url);\n const otel = r(\"@opentelemetry/api\") as {\n trace?: { getTracer: (n: string, v?: string) => TracerLike };\n };\n if (otel.trace?.getTracer === undefined) {\n tracerCache.set(name, { tracer: null });\n return undefined;\n }\n const tracer = otel.trace.getTracer(name, version);\n tracerCache.set(name, { tracer });\n return tracer;\n } catch {\n tracerCache.set(name, { tracer: null });\n return undefined;\n }\n}\n\nconst TRACER_NAME = \"theokit-sdk-handoff\";\n\ninterface HandoffSpanHandle {\n setAttribute(key: string, value: string | number | boolean): void;\n end(): void;\n}\n\nconst NOOP: HandoffSpanHandle = { setAttribute: () => undefined, end: () => undefined };\n\nfunction safe<T>(fn: () => T, fallback: T): T {\n try {\n return fn();\n } catch {\n return fallback;\n }\n}\n\nexport function startHandoffSpan(attrs: {\n from: string;\n to: string;\n reason: string;\n depth: number;\n toolName: string;\n}): HandoffSpanHandle {\n const tracer = getTracer(TRACER_NAME);\n if (tracer === undefined) return NOOP;\n const span: SpanLike | undefined = safe(\n () =>\n tracer.startSpan(\"handoff.transfer\", {\n attributes: {\n \"handoff.from\": attrs.from,\n \"handoff.to\": attrs.to,\n \"handoff.reason\": attrs.reason,\n \"handoff.depth\": attrs.depth,\n \"handoff.tool_name\": attrs.toolName,\n },\n }),\n undefined,\n );\n if (span === undefined) return NOOP;\n return {\n setAttribute: (k, v) => safe(() => span.setAttribute(k, v), undefined),\n end: () => safe(() => span.end(), undefined),\n };\n}\n","/**\n * Handoff dispatch orchestration.\n *\n * Pragmatic v1: when a handoff fires, the sender calls `receiver.send()`\n * with the (optionally filtered) history. The receiver's reply is returned\n * to the sender, which captures it as the answer to the user's question.\n *\n * NOTE: this is NOT pure peer-to-peer (the sender stays on the call stack\n * until the receiver returns). Pure intercept-and-swap requires deeper\n * agent-loop refactor; deferred to v2. v1 still validates the user-facing\n * value: \"agent A reasoned about routing, agent B answered.\"\n *\n * @internal\n */\n\nimport type { SDKAgent } from \"@theokit/sdk\";\nimport { z } from \"zod\";\nimport type {\n HandoffContext,\n HandoffDescriptor,\n HandoffHistory,\n HandoffResult,\n} from \"../types/handoff.js\";\nimport { HandoffReceiverDisposedError } from \"../types/handoff.js\";\nimport { type HandoffChainState, recordHop } from \"./registry.js\";\nimport { startHandoffSpan } from \"./telemetry.js\";\n\n/**\n * EC-2 / D228 — `safeFilter` wraps `inputFilter`. On exception, falls back\n * to the un-filtered history and warns to stderr once per process.\n */\nlet warnedFilterOnce = false;\nasync function safeFilter(\n filter: ((h: HandoffHistory) => HandoffHistory | Promise<HandoffHistory>) | undefined,\n history: HandoffHistory,\n): Promise<HandoffHistory> {\n if (filter === undefined) return history;\n try {\n const result = filter(history);\n return result instanceof Promise ? await result : result;\n } catch (err) {\n if (!warnedFilterOnce) {\n warnedFilterOnce = true;\n process.stderr.write(\n `[handoff] inputFilter threw, falling back to full history: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n return history;\n }\n}\n\n/**\n * EC-4 / D229 — parse the LLM-provided JSON args. Returns undefined when\n * no `inputType` set; returns parsed value otherwise (default to `{}` on\n * empty/null input before Zod refinements).\n */\nfunction parseHandoffInput(descriptor: HandoffDescriptor, raw: unknown): unknown {\n const inputType = descriptor.options.inputType;\n if (inputType === undefined) return undefined;\n const candidate = raw === null || raw === undefined ? {} : raw;\n return inputType.parse(candidate);\n}\n\nfunction isAgentDisposed(agent: SDKAgent): boolean {\n // SDKAgent doesn't expose `disposed` publicly; check via duck-typing on\n // a known internal flag. Safe fallback: if we can't tell, assume alive.\n const maybe = agent as unknown as { disposed?: boolean };\n return maybe.disposed === true;\n}\n\n/**\n * Run a single handoff hop. Returns the receiver's reply text.\n *\n * Algorithm:\n * 1. EC-5: refuse if receiver disposed.\n * 2. Build HandoffContext.\n * 3. Check isEnabled() — if false, refuse with a clear error message.\n * 4. Parse inputType (D229).\n * 5. Run onHandoff(ctx, parsed) — throw aborts (D227).\n * 6. Apply inputFilter (safeFilter — D228).\n * 7. Record hop in chain state (depth + pair guards).\n * 8. Open OTel span (D220).\n * 9. Receiver: build the user-facing message and `await receiver.send(msg).then(wait)`.\n * 10. Close span + return reply.\n */\nasync function assertHandoffEnabled(\n descriptor: HandoffDescriptor,\n ctx: HandoffContext,\n receiverAgentId: string,\n): Promise<void> {\n const opt = descriptor.options.isEnabled;\n let enabled = true;\n if (typeof opt === \"boolean\") enabled = opt;\n else if (typeof opt === \"function\") {\n const r = opt(ctx);\n enabled = r instanceof Promise ? await r : r;\n }\n if (!enabled) {\n throw new Error(`Handoff to ${receiverAgentId} is disabled (isEnabled returned false)`);\n }\n}\n\nfunction parseAndValidate(descriptor: HandoffDescriptor, rawInputJson: unknown): unknown {\n try {\n return parseHandoffInput(descriptor, rawInputJson);\n } catch (err) {\n const detail =\n err instanceof z.ZodError\n ? (err.issues[0]?.message ?? \"schema_invalid\")\n : err instanceof Error\n ? err.message\n : String(err);\n throw new Error(`Handoff input validation failed: ${detail}`);\n }\n}\n\nasync function runOnHandoff(\n descriptor: HandoffDescriptor,\n ctx: HandoffContext,\n parsedInput: unknown,\n): Promise<void> {\n const onHandoff = descriptor.options.onHandoff;\n if (onHandoff === undefined) return;\n // biome-ignore lint/suspicious/noExplicitAny: parsedInput is typed unknown by design.\n const result = onHandoff(ctx, parsedInput as any);\n if (result instanceof Promise) await result;\n}\n\nfunction extractUserText(content: unknown): string | undefined {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return undefined;\n const text = content\n .filter((c): c is { type: \"text\"; text: string } => (c as { type?: string })?.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\");\n return text.length > 0 ? text : undefined;\n}\n\n/**\n * The user turn's text, for either transcript shape reaching this function.\n *\n * `HandoffHistory.messages` is `unknown[]` by design (it must not import the message types), and\n * two real shapes arrive through it: the SDK's flat `ToolContextMessage` (`{ role, content }`),\n * which is what a tool handler's `ctx.messages` carries, and the nested `SDKMessage`\n * (`{ type: \"user\", message: { role, content } }`). Reading only the nested one is half of why\n * #354 went unnoticed — the flat shape would have been skipped even had it been passed.\n */\nfunction userTextOf(entry: unknown): string | undefined {\n const m = entry as {\n type?: string;\n role?: string;\n content?: unknown;\n message?: { role?: string; content?: unknown };\n };\n if (m?.type === \"user\" && m.message?.role === \"user\") return extractUserText(m.message.content);\n if (m?.role === \"user\") return extractUserText(m.content);\n return undefined;\n}\n\nfunction extractLastUserMessage(history: HandoffHistory, senderAgentId: string): string {\n for (let i = history.messages.length - 1; i >= 0; i -= 1) {\n const text = userTextOf(history.messages[i]);\n if (text !== undefined) return text;\n }\n return `(Handoff from ${senderAgentId} — no prior user message in history.)`;\n}\n\nexport async function dispatchHandoff(args: {\n descriptor: HandoffDescriptor;\n senderAgentId: string;\n chainState: HandoffChainState;\n rawInputJson: unknown;\n /** The conversation so far (history wrapper). v1: just the LAST user message. */\n history: HandoffHistory;\n /** Override the message text sent to the receiver. Used by `Agent.handoffTo` imperative. */\n messageOverride?: string;\n}): Promise<{ reply: string; result: HandoffResult }> {\n const { descriptor, senderAgentId, chainState, rawInputJson, history, messageOverride } = args;\n const receiver = descriptor.target;\n\n if (isAgentDisposed(receiver)) {\n throw new HandoffReceiverDisposedError(receiver.agentId);\n }\n\n const depthAfterThisHop = chainState.chain.length;\n const ctx: HandoffContext = {\n senderAgentId,\n receiverAgentId: receiver.agentId,\n currentDepth: depthAfterThisHop,\n chain: [...chainState.chain, receiver.agentId],\n };\n\n await assertHandoffEnabled(descriptor, ctx, receiver.agentId);\n const parsedInput = parseAndValidate(descriptor, rawInputJson);\n await runOnHandoff(descriptor, ctx, parsedInput);\n\n // Filter history (D228 — resilient)\n const filteredHistory = await safeFilter(descriptor.options.inputFilter, history);\n\n // Record hop — may throw HandoffLoopError or HandoffPairLoopError\n recordHop(chainState, senderAgentId, receiver.agentId);\n\n const lastUserMessage = messageOverride ?? extractLastUserMessage(filteredHistory, senderAgentId);\n const reason = extractReason(parsedInput);\n\n const span = startHandoffSpan({\n from: senderAgentId,\n to: receiver.agentId,\n reason,\n depth: depthAfterThisHop,\n toolName: descriptor.resolvedToolName,\n });\n\n try {\n // #356 — `HandoffOptions.tools` was presented as an allowlist and read by nothing, so a caller\n // who set it got no restriction and no warning. It is wired to `SendOptions.activeTools`, the\n // same `withToolWhitelist` path `Agent.fork`'s `allowedTools` uses. An empty list means the\n // empty set (fail-closed), matching that contract; omitting the option means no restriction.\n const toolAllowlist = descriptor.options.tools;\n const run = await receiver.send(\n lastUserMessage,\n toolAllowlist !== undefined ? { activeTools: [...toolAllowlist] } : {},\n );\n const result = await run.wait();\n const reply = buildReply(result, receiver.agentId);\n return {\n reply,\n result: {\n from: senderAgentId,\n to: receiver.agentId,\n depth: depthAfterThisHop,\n toolName: descriptor.resolvedToolName,\n ...(reason !== \"\" ? { reasonFromLlm: reason } : {}),\n },\n };\n } finally {\n span.end();\n }\n}\n\nfunction extractReason(parsedInput: unknown): string {\n if (typeof parsedInput !== \"object\" || parsedInput === null) return \"\";\n if (!(\"reason\" in parsedInput)) return \"\";\n return String((parsedInput as { reason: unknown }).reason ?? \"\");\n}\n\nfunction buildReply(\n result: { status: string; result?: string; error?: { message: string } },\n receiverAgentId: string,\n): string {\n if (result.status === \"finished\" && result.result !== undefined) return result.result;\n const suffix = result.error !== undefined ? `: ${result.error.message}` : \"\";\n return `(Handoff target ${receiverAgentId} returned status=${result.status}${suffix})`;\n}\n","/**\n * Turns an agent's display name into a tool-name-safe slug.\n *\n * Extracted because the identical function existed twice — `handoff.ts`'s `slugifyName` and\n * `tool-injector.ts`'s `slugify`, byte-identical apart from a parameter name — with no test\n * covering either. Two copies of one rule is the DRY violation `CLAUDE.md` § 12 describes: the\n * two would have drifted the moment one of them was adjusted.\n */\n\n/**\n * Longest input the slug rules are applied to.\n *\n * The result is capped at 64 characters regardless, so nothing beyond this bound can survive;\n * bounding the INPUT is what keeps the cost of getting there linear in a value the caller\n * controls. CodeQL flags `/^_+|_+$/g` as `js/polynomial-redos` (alerts #10, #11).\n *\n * **Stated honestly: the quadratic cost could not be reproduced.** V8 resolves 100_000\n * underscores, with and without a trailing non-underscore, in under a millisecond — measured\n * 2026-08-22. So this bound is defence in depth against a regex engine that does not optimise\n * the shape, not a fix for an exploit anyone demonstrated. The duplication above is the defect\n * this change is really about.\n *\n * 1024 rather than 64: truncating to the output width before stripping the `agent-` prefix would\n * change results for real names, and a bound only has to be far below \"unbounded\" to do its job.\n */\nconst MAX_INPUT_LENGTH = 1024;\n\n/**\n * @param candidate - raw agent name, id, or anything a caller supplied\n * @returns a slug of at most 64 characters, or `\"anonymous\"` when nothing survives\n */\nexport function slugifyAgentName(candidate: string): string {\n return (\n candidate\n .slice(0, MAX_INPUT_LENGTH)\n .replace(/^agent-/i, \"\")\n .replace(/[^a-zA-Z0-9_-]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 64) || \"anonymous\"\n );\n}\n","/**\n * Zod v4 → JSON Schema adapter for sdk-handoff.\n *\n * Uses Zod v4's native `z.toJSONSchema()` directly. v3 fallback removed\n * after zod-v4-migration plan (ADR D2).\n *\n * @internal\n */\n\nimport { toJSONSchema } from \"zod\";\n\ninterface ToJsonSchemaOptions {\n /** `\"any\"` keeps transforms/refinements as `{}` (loose). Default: `\"any\"`. */\n unrepresentable?: \"any\" | \"throw\";\n}\n\n/**\n * Convert a Zod schema to a JSON Schema object via Zod v4 native.\n *\n * @internal\n */\nexport function toJsonSchema(\n schema: unknown,\n options: ToJsonSchemaOptions = { unrepresentable: \"any\" },\n): Record<string, unknown> {\n // The schema param is `unknown` (callers pass `T extends ZodType` generics that\n // don't structurally satisfy Zod v4's `$ZodType`); cast to the exact parameter\n // type `toJSONSchema` expects rather than `any` — any z.* schema IS valid at runtime.\n return toJSONSchema(schema as Parameters<typeof toJSONSchema>[0], options) as Record<\n string,\n unknown\n >;\n}\n","/**\n * Convert `handoffs[]` entries into synthetic `transfer_to_<receiver>` tools\n * for injection into the agent's tool registry at construction time.\n *\n * The synthesized tool's handler calls `dispatchHandoff` internally and\n * returns the receiver's reply as `tool_result`. v1 trade-off documented\n * in dispatcher.ts.\n *\n * @internal\n */\n\nimport type { CustomTool, SDKAgent } from \"@theokit/sdk\";\nimport { z } from \"zod\";\nimport {\n type HandoffDescriptor,\n HandoffNameCollisionError,\n HandoffSelfReferenceError,\n} from \"../types/handoff.js\";\nimport { dispatchHandoff } from \"./dispatcher.js\";\nimport { createChainState } from \"./registry.js\";\nimport { slugifyAgentName } from \"./slugify-agent-name.js\";\nimport { toJsonSchema } from \"./to-json-schema.js\";\n\ninterface NormalizedHandoff {\n descriptor: HandoffDescriptor;\n}\n\n/**\n * Normalize each `handoffs[]` entry to a `HandoffDescriptor`. Raw `SDKAgent`\n * instances are auto-wrapped with default options. Validates:\n * - EC-6: no self-reference (would cause infinite recursion).\n * - D215: resolved tool names must be unique.\n */\nexport function normalizeHandoffs(\n parentAgentId: string,\n entries: ReadonlyArray<SDKAgent | HandoffDescriptor>,\n): NormalizedHandoff[] {\n if (entries.length === 0) return [];\n const out: NormalizedHandoff[] = [];\n const seenNames = new Set<string>();\n for (const entry of entries) {\n // Detect raw Agent vs HandoffDescriptor by presence of `.target`.\n const isDescriptor =\n typeof entry === \"object\" &&\n entry !== null &&\n \"target\" in entry &&\n \"options\" in entry &&\n \"resolvedToolName\" in entry;\n const descriptor = isDescriptor ? (entry as HandoffDescriptor) : autoWrap(entry as SDKAgent);\n if (descriptor.target.agentId === parentAgentId) {\n throw new HandoffSelfReferenceError(parentAgentId);\n }\n const name = descriptor.resolvedToolName;\n if (seenNames.has(name)) {\n throw new HandoffNameCollisionError(name);\n }\n seenNames.add(name);\n out.push({ descriptor });\n }\n return out;\n}\n\nfunction autoWrap(agent: SDKAgent): HandoffDescriptor {\n const name = resolveTargetName(agent);\n return {\n target: agent,\n options: {},\n resolvedToolName: `transfer_to_${name}`,\n };\n}\n\nfunction resolveTargetName(agent: SDKAgent): string {\n // Prefer a `name` field if exposed; fall back to a short agentId slug.\n const candidate = (agent as unknown as { name?: string }).name ?? agent.agentId ?? \"anonymous\";\n return slugifyAgentName(candidate);\n}\n\n/**\n * Build a `CustomTool` for one handoff descriptor. The handler dispatches\n * the handoff using a fresh chain state per `send()`-level invocation.\n *\n * NOTE: this v1 builds a NEW chain state per tool invocation. Pure\n * cross-tool depth tracking within one send() requires per-Agent context\n * — deferred. The single-flight pair guard catches direct ping-pong even\n * without cross-invocation chain (since each call wraps the same depth\n * counter from 1).\n */\nexport function buildHandoffTool(\n parentAgentId: string,\n descriptor: HandoffDescriptor,\n maxHandoffDepth: number,\n): CustomTool {\n const description =\n descriptor.options.toolDescription ??\n `Transfer the conversation to the ${descriptor.target.agentId} agent. ` +\n `Use this when the user's request matches their specialty.`;\n\n const inputZod =\n descriptor.options.inputType ??\n z.object({\n reason: z.string().optional().describe(\"Brief reason for the transfer (one short sentence).\"),\n });\n // CustomTool.inputSchema expects a JSON schema (Record<string, unknown>),\n // not the raw Zod type. Convert lazily so we don't fail when Zod is missing.\n // Universal Zod 3+4 conversion (feature-detects native v4, falls back to lib on v3).\n const inputSchema = toJsonSchema(inputZod);\n\n return {\n name: descriptor.resolvedToolName,\n description,\n inputSchema,\n handler: async (\n input: unknown,\n ctx?: { messages?: ReadonlyArray<unknown> },\n ): Promise<string> => {\n const chainState = createChainState(parentAgentId, maxHandoffDepth);\n try {\n const { reply, result } = await dispatchHandoff({\n descriptor,\n senderAgentId: parentAgentId,\n chainState,\n rawInputJson: input,\n // #354 — the supervisor's transcript, which the SDK hands every tool handler as\n // `ctx.messages`. This used to be `{ messages: [] }` with the note \"v1: history replay\n // deferred\", so the dispatcher found no user message and sent the receiver the\n // placeholder instead of the question — and `inputFilter`, the documented redaction\n // hook, was handed an empty transcript to redact.\n history: { messages: ctx?.messages ?? [] },\n });\n return JSON.stringify({\n ok: true,\n transferred_to: result.to,\n depth: result.depth,\n reply,\n });\n } catch (err) {\n return JSON.stringify({\n ok: false,\n error: err instanceof Error ? err.name : \"HandoffError\",\n message: err instanceof Error ? err.message : String(err),\n });\n }\n },\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theokit/sdk-handoff",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Inter-agent dispatch for @theokit/sdk — typed Handoff descriptors, loop protection, plugin-based wiring.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"homepage": "https://github.com/
|
|
7
|
-
"bugs": "https://github.com/
|
|
6
|
+
"homepage": "https://github.com/usetheokit/theokit-sdk#readme",
|
|
7
|
+
"bugs": "https://github.com/usetheokit/theokit-sdk/issues",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
10
|
+
"url": "git+https://github.com/usetheokit/theokit-sdk.git",
|
|
11
11
|
"directory": "packages/sdk-handoff"
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|
|
@@ -55,21 +55,29 @@
|
|
|
55
55
|
"LICENSE"
|
|
56
56
|
],
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@
|
|
58
|
+
"@opentelemetry/api": "^1.9.0",
|
|
59
|
+
"@theokit/sdk": ">=4.54.0",
|
|
59
60
|
"zod": "^4.0.0"
|
|
60
61
|
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"@opentelemetry/api": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
61
67
|
"devDependencies": {
|
|
68
|
+
"@opentelemetry/api": "^1.9.1",
|
|
62
69
|
"tsup": "^8.3.5",
|
|
63
70
|
"typescript": "^5.7.2",
|
|
64
71
|
"vitest": "^4.1.8",
|
|
65
72
|
"zod": "^4.0.0",
|
|
66
|
-
"@theokit/sdk": "4.
|
|
73
|
+
"@theokit/sdk": "4.59.0"
|
|
67
74
|
},
|
|
68
75
|
"publishConfig": {
|
|
76
|
+
"provenance": true,
|
|
69
77
|
"access": "public"
|
|
70
78
|
},
|
|
71
79
|
"scripts": {
|
|
72
|
-
"build": "tsup",
|
|
80
|
+
"build": "tsup && node ../../tools/repair-dts-imports.mjs .",
|
|
73
81
|
"test": "vitest run",
|
|
74
82
|
"typecheck": "tsc --noEmit"
|
|
75
83
|
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { SDKAgent } from '@theokit/sdk';
|
|
2
|
-
import { ZodType } from 'zod';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Type-leaf — `HandoffDescriptor` extracted as a generic over `TAgent` so
|
|
6
|
-
* neither `agent.ts` nor `handoff.ts` need to import the other for type
|
|
7
|
-
* resolution. Closes the audit's last LOW type-only cycle #4
|
|
8
|
-
* (`types/agent.ts ↔ types/handoff.ts`) per plan
|
|
9
|
-
* arch-review-fixes-2026-06-06 § Phase 4 / T4.1 follow-up.
|
|
10
|
-
*
|
|
11
|
-
* BREAKING (per user direction "sem retro compat"): `HandoffDescriptor`
|
|
12
|
-
* gained a second generic parameter `TAgent` for the target shape. Existing
|
|
13
|
-
* consumers using `HandoffDescriptor<MyInput>` now resolve to
|
|
14
|
-
* `HandoffDescriptor<MyInput, SDKAgent>` via the back-compat default in
|
|
15
|
-
* `handoff.ts`'s re-export.
|
|
16
|
-
*
|
|
17
|
-
* @public
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Context handed to `onHandoff` callbacks and `isEnabled` predicates.
|
|
22
|
-
* Read-only snapshot of the handoff dispatch state.
|
|
23
|
-
*/
|
|
24
|
-
interface HandoffContext {
|
|
25
|
-
readonly senderAgentId: string;
|
|
26
|
-
readonly receiverAgentId: string;
|
|
27
|
-
/** Depth counter AT dispatch time (post-increment; first handoff = 1). */
|
|
28
|
-
readonly currentDepth: number;
|
|
29
|
-
/** Chain of agentIds traversed so far in this send(). Always ends with sender. */
|
|
30
|
-
readonly chain: ReadonlyArray<string>;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* The transcript wrapper passed to `inputFilter`. `messages` is widened to
|
|
34
|
-
* `unknown[]` so this type doesn't import from `messages.ts` (avoids cycle
|
|
35
|
-
* — implementations cast to `SDKMessage[]` internally).
|
|
36
|
-
*/
|
|
37
|
-
interface HandoffHistory {
|
|
38
|
-
readonly messages: ReadonlyArray<unknown>;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Options accepted by `Handoff.create(target, opts?)`.
|
|
42
|
-
*
|
|
43
|
-
* @public
|
|
44
|
-
*/
|
|
45
|
-
interface HandoffOptions<TInput extends ZodType = ZodType> {
|
|
46
|
-
/** Override the default tool name `transfer_to_<receiver.name>` (D215). */
|
|
47
|
-
readonly toolName?: string;
|
|
48
|
-
/** Override the default tool description. */
|
|
49
|
-
readonly toolDescription?: string;
|
|
50
|
-
readonly onHandoff?: (ctx: HandoffContext, parsed: TInput extends ZodType ? unknown : undefined) => void | Promise<void>;
|
|
51
|
-
readonly inputType?: TInput;
|
|
52
|
-
readonly inputFilter?: (history: HandoffHistory) => HandoffHistory | Promise<HandoffHistory>;
|
|
53
|
-
readonly tools?: ReadonlyArray<string>;
|
|
54
|
-
readonly isEnabled?: boolean | ((ctx: HandoffContext) => boolean | Promise<boolean>);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Public `Handoff` shape — what `Handoff.create()` returns. Read-only
|
|
58
|
-
* accessors only; behavior lives in the engine.
|
|
59
|
-
*
|
|
60
|
-
* Generic over `TAgent` so this leaf has no dependency on a concrete
|
|
61
|
-
* agent type. Consumers typically import the convenience alias
|
|
62
|
-
* `HandoffDescriptor<TInput>` from `@theokit/sdk` which fixes `TAgent`
|
|
63
|
-
* to `SDKAgent`.
|
|
64
|
-
*
|
|
65
|
-
* @public
|
|
66
|
-
*/
|
|
67
|
-
interface HandoffDescriptor$1<TInput extends ZodType = ZodType, TAgent = unknown> {
|
|
68
|
-
readonly target: TAgent;
|
|
69
|
-
readonly options: HandoffOptions<TInput>;
|
|
70
|
-
/** Resolved tool name (after applying toolName override or default `transfer_to_<receiver>`). */
|
|
71
|
-
readonly resolvedToolName: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Public types for `Agent.create({ handoffs })` + `Handoff.create()` +
|
|
76
|
-
* `Agent.handoffTo()` (Adoption Roadmap #4; ADRs D214-D229).
|
|
77
|
-
*
|
|
78
|
-
* Pattern: handoff-as-tool. Each handoff destination becomes a synthetic
|
|
79
|
-
* `transfer_to_<receiver>` function tool exposed to the LLM. Runtime
|
|
80
|
-
* intercepts the tool call and routes the next turn to the receiver.
|
|
81
|
-
*
|
|
82
|
-
* T4.1 follow-up (cycle #4 closed): `HandoffDescriptor` + its leaf-friendly
|
|
83
|
-
* sibling types now live in `./handoff-descriptor.ts` (generic over
|
|
84
|
-
* `TAgent`). This module re-exports the leaf types pinned to `SDKAgent`,
|
|
85
|
-
* keeps the runtime error classes, and removes the back-edge to `agent.ts`.
|
|
86
|
-
*
|
|
87
|
-
* @public
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* `HandoffDescriptor` pinned to `SDKAgent` — back-compat shape for callers
|
|
92
|
-
* that imported `import type { HandoffDescriptor } from "@theokit/sdk"`
|
|
93
|
-
* before T4.1 follow-up.
|
|
94
|
-
*
|
|
95
|
-
* @public
|
|
96
|
-
*/
|
|
97
|
-
type HandoffDescriptor<TInput extends ZodType = ZodType> = HandoffDescriptor$1<TInput, SDKAgent>;
|
|
98
|
-
/** Throw when handoff depth exceeds `maxHandoffDepth` (default 5; D218). */
|
|
99
|
-
declare class HandoffLoopError extends Error {
|
|
100
|
-
readonly name = "HandoffLoopError";
|
|
101
|
-
readonly depth: number;
|
|
102
|
-
readonly chain: ReadonlyArray<string>;
|
|
103
|
-
constructor(depth: number, chain: ReadonlyArray<string>);
|
|
104
|
-
}
|
|
105
|
-
/** Throw when the same (sender, receiver) pair invoked twice in one send() (D221). */
|
|
106
|
-
declare class HandoffPairLoopError extends Error {
|
|
107
|
-
readonly name = "HandoffPairLoopError";
|
|
108
|
-
readonly senderAgentId: string;
|
|
109
|
-
readonly receiverAgentId: string;
|
|
110
|
-
constructor(senderAgentId: string, receiverAgentId: string);
|
|
111
|
-
}
|
|
112
|
-
/** Throw when an agent's `handoffs[]` includes a self-reference (EC-6). */
|
|
113
|
-
declare class HandoffSelfReferenceError extends Error {
|
|
114
|
-
readonly name = "HandoffSelfReferenceError";
|
|
115
|
-
readonly agentId: string;
|
|
116
|
-
constructor(agentId: string);
|
|
117
|
-
}
|
|
118
|
-
/** Throw when receiver is disposed at dispatch time (EC-5). */
|
|
119
|
-
declare class HandoffReceiverDisposedError extends Error {
|
|
120
|
-
readonly name = "HandoffReceiverDisposedError";
|
|
121
|
-
readonly receiverAgentId: string;
|
|
122
|
-
constructor(receiverAgentId: string);
|
|
123
|
-
}
|
|
124
|
-
/** Throw when two handoffs in the same parent collide on tool name (D215). */
|
|
125
|
-
declare class HandoffNameCollisionError extends Error {
|
|
126
|
-
readonly name = "HandoffNameCollisionError";
|
|
127
|
-
readonly conflictingName: string;
|
|
128
|
-
constructor(conflictingName: string);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export { type HandoffDescriptor as H, type HandoffOptions as a, HandoffLoopError as b, HandoffNameCollisionError as c, HandoffPairLoopError as d, HandoffReceiverDisposedError as e, HandoffSelfReferenceError as f };
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { SDKAgent } from '@theokit/sdk';
|
|
2
|
-
import { ZodType } from 'zod';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Type-leaf — `HandoffDescriptor` extracted as a generic over `TAgent` so
|
|
6
|
-
* neither `agent.ts` nor `handoff.ts` need to import the other for type
|
|
7
|
-
* resolution. Closes the audit's last LOW type-only cycle #4
|
|
8
|
-
* (`types/agent.ts ↔ types/handoff.ts`) per plan
|
|
9
|
-
* arch-review-fixes-2026-06-06 § Phase 4 / T4.1 follow-up.
|
|
10
|
-
*
|
|
11
|
-
* BREAKING (per user direction "sem retro compat"): `HandoffDescriptor`
|
|
12
|
-
* gained a second generic parameter `TAgent` for the target shape. Existing
|
|
13
|
-
* consumers using `HandoffDescriptor<MyInput>` now resolve to
|
|
14
|
-
* `HandoffDescriptor<MyInput, SDKAgent>` via the back-compat default in
|
|
15
|
-
* `handoff.ts`'s re-export.
|
|
16
|
-
*
|
|
17
|
-
* @public
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Context handed to `onHandoff` callbacks and `isEnabled` predicates.
|
|
22
|
-
* Read-only snapshot of the handoff dispatch state.
|
|
23
|
-
*/
|
|
24
|
-
interface HandoffContext {
|
|
25
|
-
readonly senderAgentId: string;
|
|
26
|
-
readonly receiverAgentId: string;
|
|
27
|
-
/** Depth counter AT dispatch time (post-increment; first handoff = 1). */
|
|
28
|
-
readonly currentDepth: number;
|
|
29
|
-
/** Chain of agentIds traversed so far in this send(). Always ends with sender. */
|
|
30
|
-
readonly chain: ReadonlyArray<string>;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* The transcript wrapper passed to `inputFilter`. `messages` is widened to
|
|
34
|
-
* `unknown[]` so this type doesn't import from `messages.ts` (avoids cycle
|
|
35
|
-
* — implementations cast to `SDKMessage[]` internally).
|
|
36
|
-
*/
|
|
37
|
-
interface HandoffHistory {
|
|
38
|
-
readonly messages: ReadonlyArray<unknown>;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Options accepted by `Handoff.create(target, opts?)`.
|
|
42
|
-
*
|
|
43
|
-
* @public
|
|
44
|
-
*/
|
|
45
|
-
interface HandoffOptions<TInput extends ZodType = ZodType> {
|
|
46
|
-
/** Override the default tool name `transfer_to_<receiver.name>` (D215). */
|
|
47
|
-
readonly toolName?: string;
|
|
48
|
-
/** Override the default tool description. */
|
|
49
|
-
readonly toolDescription?: string;
|
|
50
|
-
readonly onHandoff?: (ctx: HandoffContext, parsed: TInput extends ZodType ? unknown : undefined) => void | Promise<void>;
|
|
51
|
-
readonly inputType?: TInput;
|
|
52
|
-
readonly inputFilter?: (history: HandoffHistory) => HandoffHistory | Promise<HandoffHistory>;
|
|
53
|
-
readonly tools?: ReadonlyArray<string>;
|
|
54
|
-
readonly isEnabled?: boolean | ((ctx: HandoffContext) => boolean | Promise<boolean>);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Public `Handoff` shape — what `Handoff.create()` returns. Read-only
|
|
58
|
-
* accessors only; behavior lives in the engine.
|
|
59
|
-
*
|
|
60
|
-
* Generic over `TAgent` so this leaf has no dependency on a concrete
|
|
61
|
-
* agent type. Consumers typically import the convenience alias
|
|
62
|
-
* `HandoffDescriptor<TInput>` from `@theokit/sdk` which fixes `TAgent`
|
|
63
|
-
* to `SDKAgent`.
|
|
64
|
-
*
|
|
65
|
-
* @public
|
|
66
|
-
*/
|
|
67
|
-
interface HandoffDescriptor$1<TInput extends ZodType = ZodType, TAgent = unknown> {
|
|
68
|
-
readonly target: TAgent;
|
|
69
|
-
readonly options: HandoffOptions<TInput>;
|
|
70
|
-
/** Resolved tool name (after applying toolName override or default `transfer_to_<receiver>`). */
|
|
71
|
-
readonly resolvedToolName: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Public types for `Agent.create({ handoffs })` + `Handoff.create()` +
|
|
76
|
-
* `Agent.handoffTo()` (Adoption Roadmap #4; ADRs D214-D229).
|
|
77
|
-
*
|
|
78
|
-
* Pattern: handoff-as-tool. Each handoff destination becomes a synthetic
|
|
79
|
-
* `transfer_to_<receiver>` function tool exposed to the LLM. Runtime
|
|
80
|
-
* intercepts the tool call and routes the next turn to the receiver.
|
|
81
|
-
*
|
|
82
|
-
* T4.1 follow-up (cycle #4 closed): `HandoffDescriptor` + its leaf-friendly
|
|
83
|
-
* sibling types now live in `./handoff-descriptor.ts` (generic over
|
|
84
|
-
* `TAgent`). This module re-exports the leaf types pinned to `SDKAgent`,
|
|
85
|
-
* keeps the runtime error classes, and removes the back-edge to `agent.ts`.
|
|
86
|
-
*
|
|
87
|
-
* @public
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* `HandoffDescriptor` pinned to `SDKAgent` — back-compat shape for callers
|
|
92
|
-
* that imported `import type { HandoffDescriptor } from "@theokit/sdk"`
|
|
93
|
-
* before T4.1 follow-up.
|
|
94
|
-
*
|
|
95
|
-
* @public
|
|
96
|
-
*/
|
|
97
|
-
type HandoffDescriptor<TInput extends ZodType = ZodType> = HandoffDescriptor$1<TInput, SDKAgent>;
|
|
98
|
-
/** Throw when handoff depth exceeds `maxHandoffDepth` (default 5; D218). */
|
|
99
|
-
declare class HandoffLoopError extends Error {
|
|
100
|
-
readonly name = "HandoffLoopError";
|
|
101
|
-
readonly depth: number;
|
|
102
|
-
readonly chain: ReadonlyArray<string>;
|
|
103
|
-
constructor(depth: number, chain: ReadonlyArray<string>);
|
|
104
|
-
}
|
|
105
|
-
/** Throw when the same (sender, receiver) pair invoked twice in one send() (D221). */
|
|
106
|
-
declare class HandoffPairLoopError extends Error {
|
|
107
|
-
readonly name = "HandoffPairLoopError";
|
|
108
|
-
readonly senderAgentId: string;
|
|
109
|
-
readonly receiverAgentId: string;
|
|
110
|
-
constructor(senderAgentId: string, receiverAgentId: string);
|
|
111
|
-
}
|
|
112
|
-
/** Throw when an agent's `handoffs[]` includes a self-reference (EC-6). */
|
|
113
|
-
declare class HandoffSelfReferenceError extends Error {
|
|
114
|
-
readonly name = "HandoffSelfReferenceError";
|
|
115
|
-
readonly agentId: string;
|
|
116
|
-
constructor(agentId: string);
|
|
117
|
-
}
|
|
118
|
-
/** Throw when receiver is disposed at dispatch time (EC-5). */
|
|
119
|
-
declare class HandoffReceiverDisposedError extends Error {
|
|
120
|
-
readonly name = "HandoffReceiverDisposedError";
|
|
121
|
-
readonly receiverAgentId: string;
|
|
122
|
-
constructor(receiverAgentId: string);
|
|
123
|
-
}
|
|
124
|
-
/** Throw when two handoffs in the same parent collide on tool name (D215). */
|
|
125
|
-
declare class HandoffNameCollisionError extends Error {
|
|
126
|
-
readonly name = "HandoffNameCollisionError";
|
|
127
|
-
readonly conflictingName: string;
|
|
128
|
-
constructor(conflictingName: string);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export { type HandoffDescriptor as H, type HandoffOptions as a, HandoffLoopError as b, HandoffNameCollisionError as c, HandoffPairLoopError as d, HandoffReceiverDisposedError as e, HandoffSelfReferenceError as f };
|