{"version":3,"file":"router-factory.mjs","names":[],"sources":["../../../../../../../ai/src/supervisor/router-factory.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { agent } from \"../agent/agent\";\nimport type { AgentEventHandlers } from \"../agent/agent-config.type\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport { END } from \"../contracts/end.type\";\nimport type { ModelCallOptions, ModelContract } from \"../contracts/model.contract\";\nimport type { Placeholders } from \"../contracts/placeholders.type\";\nimport type { SupervisorIntentValue } from \"../contracts/supervisor/intent-entry.type\";\nimport type { Next } from \"../contracts/supervisor/next.type\";\nimport type { SystemPromptContract } from \"../contracts/system-prompt.contract\";\n\n/**\n * Output shape every router agent produced by {@link router} emits —\n * the canonical `{ next, reasoning }` contract the supervisor's\n * dispatch loop reads. Exposed so callers can type a router result\n * they handle directly.\n */\nexport type RouterOutput = {\n  /** Chosen intent name, a fan-out array, or the `END` sentinel. */\n  next: Next;\n  /** One-sentence justification for the routing choice. */\n  reasoning: string;\n};\n\n/**\n * Description source for one intent the router can pick from. Accepts\n * the same value-shapes the supervisor's `intents` map does (bare\n * agent / workflow / callback / object entry) so a caller can pass the\n * very same `intents` object to both `router()` and `ai.supervisor()`.\n *\n * The router only needs each intent's NAME (the map key) and a\n * human-readable DESCRIPTION — it never dispatches anything itself, so\n * the underlying unit is read for its `description` only.\n */\nexport type RouterIntents = Record<string, SupervisorIntentValue>;\n\n/**\n * Config for {@link router}. Mirrors the relevant slice of `AgentConfig`\n * — the router IS an agent — plus the `intents` map it routes over.\n *\n * Everything except `model` and `intents` is optional; the helper\n * generates the output schema and the routing system prompt for you.\n */\nexport type RouterConfig = {\n  /**\n   * Stable identifier for the router agent. Defaults to\n   * `\"<supervisor-ish>-router\"` is NOT assumed — when omitted the helper\n   * uses `\"router\"` so the agent carries a meaningful (non-anonymous)\n   * name, which `ai.supervisor({ router })` is happy to accept.\n   */\n  name?: string;\n  /** The routing LLM. Required — a router with no model can't decide. */\n  model: ModelContract;\n  /**\n   * The intents the router chooses among. Same object you pass to\n   * `ai.supervisor({ intents })`. Their descriptions are rendered into\n   * the generated routing system prompt so the LLM knows what each\n   * option does.\n   */\n  intents: RouterIntents;\n  /**\n   * Extra guidance prepended to the framework-generated routing system\n   * prompt. Use it for domain framing (\"You coordinate a support\n   * team.\"); the mechanical \"here are your options, emit `next`\"\n   * scaffolding is appended automatically.\n   */\n  systemPrompt?: SystemPromptContract | string;\n  /** Placeholder values merged into the router's system prompt template. */\n  placeholders?: Placeholders;\n  /** Base model call options forwarded to the underlying agent. */\n  modelOptions?: ModelCallOptions;\n  /**\n   * Hard cap on LLM trips for the router agent. A router is a\n   * single-shot decision maker, so this defaults to `1` — override\n   * only if the router itself calls tools mid-decision.\n   */\n  maxTrips?: number;\n  /** Factory-level event handlers forwarded to the underlying agent. */\n  on?: AgentEventHandlers;\n};\n\n/**\n * Build a routing agent for `ai.supervisor({ router })` without\n * hand-writing the output schema or the \"pick one of these intents\"\n * system prompt.\n *\n * **What it does for you.**\n * - Generates the canonical `{ next, reasoning }` output schema\n *   (baked onto the agent so it's a valid router standalone, and\n *   identical to what the supervisor injects per-turn) — the model is\n *   steered to emit a single intent name or the `END` sentinel.\n * - Auto-builds a system prompt that lists every intent + its\n *   description + the reserved `END` value + terse routing rules, with\n *   any caller-supplied `systemPrompt` framing kept on top.\n *\n * The result is a plain {@link AgentContract}; pass it straight to\n * `ai.supervisor({ router: ... })`. Because the supervisor also injects\n * the same schema per-turn and prepends its own per-turn context\n * message, the baked schema/prompt are belt-and-suspenders — they make\n * the agent a correct router even when invoked directly.\n *\n * @example\n * const intents = { triage, orderLookup, billingLookup, resolver };\n *\n * const supportRouter = ai.router({\n *   model,\n *   intents,\n *   systemPrompt: \"You coordinate a customer-support team.\",\n * });\n *\n * const support = ai.supervisor({\n *   name: \"customer-support\",\n *   router: supportRouter,\n *   intents,\n *   maxIterations: 6,\n * });\n */\nexport function router(config: RouterConfig): AgentContract<RouterOutput> {\n  if (!config.model) {\n    throw new TypeError(\"ai.router: `model` is required\");\n  }\n\n  if (!config.intents || typeof config.intents !== \"object\") {\n    throw new TypeError(\"ai.router: `intents` is required and must be an object\");\n  }\n\n  const intentNames = Object.keys(config.intents);\n\n  if (intentNames.length === 0) {\n    throw new TypeError(\"ai.router: `intents` must contain at least one entry\");\n  }\n\n  const routingPrompt = buildRoutingSystemPrompt(config.intents, resolvePrefix(config.systemPrompt));\n\n  return agent<RouterOutput>({\n    name: config.name ?? \"router\",\n    description: \"Routes a supervisor run to the next intent (or terminates it).\",\n    model: config.model,\n    systemPrompt: routingPrompt,\n    output: routerOutputSchema(intentNames),\n    placeholders: config.placeholders,\n    modelOptions: config.modelOptions,\n    maxTrips: config.maxTrips ?? 1,\n    on: config.on,\n  });\n}\n\n/**\n * Resolve a caller-supplied `systemPrompt` (string or contract) to\n * plain text for prepending to the generated routing block. Returns\n * `undefined` when none was supplied.\n */\nfunction resolvePrefix(prompt: SystemPromptContract | string | undefined): string | undefined {\n  if (!prompt) {\n    return undefined;\n  }\n\n  return typeof prompt === \"string\" ? prompt : prompt.resolve();\n}\n\n/**\n * Assemble the routing system prompt: optional caller framing on top,\n * then the mechanical block listing every intent + description, the\n * reserved `END` sentinel, and the rules for emitting `next`.\n */\nfunction buildRoutingSystemPrompt(intents: RouterIntents, prefix: string | undefined): string {\n  const intentLines = Object.entries(intents).map(([name, value]) => {\n    const description = resolveIntentDescription(value);\n\n    return description ? `- ${name}: ${description}` : `- ${name}`;\n  });\n\n  const sections: string[] = [];\n\n  if (prefix && prefix.trim().length > 0) {\n    sections.push(prefix.trim(), \"\");\n  }\n\n  sections.push(\n    \"You are a router. Pick the single best intent to handle the next step, or terminate the run.\",\n    \"\",\n    \"Available intents:\",\n    ...intentLines,\n    \"\",\n    \"Reserved values:\",\n    `- ${END} = terminate the run when no further intent is needed`,\n    \"\",\n    \"Rules:\",\n    \"- Respond with the `next` field set to exactly one intent name from the list above, or the END sentinel.\",\n    \"- Put a one-sentence justification in the `reasoning` field.\",\n    \"- Never invent an intent name that is not listed.\",\n  );\n\n  return sections.join(\"\\n\");\n}\n\n/**\n * Read the human-readable description off a supervisor-intent value,\n * regardless of which accepted shape it is (bare agent / workflow,\n * object entry with a `description` override, callback entry). Bare\n * callbacks have no description source — returns `undefined`, and the\n * prompt simply lists the intent by name.\n */\nfunction resolveIntentDescription(value: SupervisorIntentValue): string | undefined {\n  if (!value || typeof value === \"function\") {\n    return undefined;\n  }\n\n  const entry = value as {\n    description?: unknown;\n    agent?: { description?: unknown };\n  };\n\n  if (typeof entry.description === \"string\" && entry.description.trim().length > 0) {\n    return entry.description.trim();\n  }\n\n  const agentDescription = entry.agent?.description;\n\n  if (typeof agentDescription === \"string\" && agentDescription.trim().length > 0) {\n    return agentDescription.trim();\n  }\n\n  return undefined;\n}\n\n/**\n * Build the canonical router output Standard Schema. The same shape the\n * supervisor injects per-turn — `{ next: string, reasoning: string }` —\n * with the JSON Schema extension carrying the intent names as an `enum`\n * (plus the `END` sentinel) so capable providers enforce the choice\n * natively rather than via soft prompt coaching. Validation still\n * accepts `string` / `string[]` for framework-level fan-out.\n */\nfunction routerOutputSchema(intentNames: string[]): StandardSchemaV1<RouterOutput> {\n  const nextEnum = [...intentNames, END];\n\n  const jsonSchema = {\n    type: \"object\",\n    properties: {\n      next: {\n        type: \"string\",\n        enum: nextEnum,\n        description: \"Name of the intent to dispatch next, or the END sentinel to terminate.\",\n      },\n      reasoning: {\n        type: \"string\",\n        description: \"One-sentence justification for the routing choice.\",\n      },\n    },\n    required: [\"next\", \"reasoning\"],\n    additionalProperties: false,\n  };\n\n  return {\n    \"~standard\": {\n      version: 1,\n      vendor: \"warlock-router\",\n      jsonSchema: {\n        input: () => jsonSchema,\n      },\n      validate(value: unknown): StandardSchemaV1.Result<RouterOutput> {\n        if (!value || typeof value !== \"object\") {\n          return { issues: [{ message: \"router output must be an object\" }] };\n        }\n\n        const record = value as { next?: unknown; reasoning?: unknown };\n        const rawNext = record.next;\n\n        const nextIsValid =\n          typeof rawNext === \"string\" ||\n          (Array.isArray(rawNext) && rawNext.every((element) => typeof element === \"string\"));\n\n        if (!nextIsValid) {\n          return {\n            issues: [\n              { message: \"router output `next` must be a string, string[], or the END sentinel\" },\n            ],\n          };\n        }\n\n        const reasoning = typeof record.reasoning === \"string\" ? record.reasoning : \"\";\n\n        return {\n          value: { next: rawNext as Next, reasoning },\n        };\n      },\n    } as StandardSchemaV1<RouterOutput>[\"~standard\"] & {\n      jsonSchema: { input: () => Record<string, unknown> };\n    },\n  };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqHA,SAAgB,OAAO,QAAmD;CACxE,IAAI,CAAC,OAAO,OACV,MAAM,IAAI,UAAU,gCAAgC;CAGtD,IAAI,CAAC,OAAO,WAAW,OAAO,OAAO,YAAY,UAC/C,MAAM,IAAI,UAAU,wDAAwD;CAG9E,MAAM,cAAc,OAAO,KAAK,OAAO,OAAO;CAE9C,IAAI,YAAY,WAAW,GACzB,MAAM,IAAI,UAAU,sDAAsD;CAG5E,MAAM,gBAAgB,yBAAyB,OAAO,SAAS,cAAc,OAAO,YAAY,CAAC;CAEjG,OAAO,MAAoB;EACzB,MAAM,OAAO,QAAQ;EACrB,aAAa;EACb,OAAO,OAAO;EACd,cAAc;EACd,QAAQ,mBAAmB,WAAW;EACtC,cAAc,OAAO;EACrB,cAAc,OAAO;EACrB,UAAU,OAAO,YAAY;EAC7B,IAAI,OAAO;CACb,CAAC;AACH;;;;;;AAOA,SAAS,cAAc,QAAuE;CAC5F,IAAI,CAAC,QACH;CAGF,OAAO,OAAO,WAAW,WAAW,SAAS,OAAO,QAAQ;AAC9D;;;;;;AAOA,SAAS,yBAAyB,SAAwB,QAAoC;CAC5F,MAAM,cAAc,OAAO,QAAQ,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW;EACjE,MAAM,cAAc,yBAAyB,KAAK;EAElD,OAAO,cAAc,KAAK,KAAK,IAAI,gBAAgB,KAAK;CAC1D,CAAC;CAED,MAAM,WAAqB,CAAC;CAE5B,IAAI,UAAU,OAAO,KAAK,CAAC,CAAC,SAAS,GACnC,SAAS,KAAK,OAAO,KAAK,GAAG,EAAE;CAGjC,SAAS,KACP,gGACA,IACA,sBACA,GAAG,aACH,IACA,oBACA,KAAK,IAAI,wDACT,IACA,UACA,4GACA,gEACA,mDACF;CAEA,OAAO,SAAS,KAAK,IAAI;AAC3B;;;;;;;;AASA,SAAS,yBAAyB,OAAkD;CAClF,IAAI,CAAC,SAAS,OAAO,UAAU,YAC7B;CAGF,MAAM,QAAQ;CAKd,IAAI,OAAO,MAAM,gBAAgB,YAAY,MAAM,YAAY,KAAK,CAAC,CAAC,SAAS,GAC7E,OAAO,MAAM,YAAY,KAAK;CAGhC,MAAM,mBAAmB,MAAM,OAAO;CAEtC,IAAI,OAAO,qBAAqB,YAAY,iBAAiB,KAAK,CAAC,CAAC,SAAS,GAC3E,OAAO,iBAAiB,KAAK;AAIjC;;;;;;;;;AAUA,SAAS,mBAAmB,aAAuD;CAGjF,MAAM,aAAa;EACjB,MAAM;EACN,YAAY;GACV,MAAM;IACJ,MAAM;IACN,MAAM,CAPM,GAAG,aAAa,GAOf;IACb,aAAa;GACf;GACA,WAAW;IACT,MAAM;IACN,aAAa;GACf;EACF;EACA,UAAU,CAAC,QAAQ,WAAW;EAC9B,sBAAsB;CACxB;CAEA,OAAO,EACL,aAAa;EACX,SAAS;EACT,QAAQ;EACR,YAAY,EACV,aAAa,WACf;EACA,SAAS,OAAuD;GAC9D,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,kCAAkC,CAAC,EAAE;GAGpE,MAAM,SAAS;GACf,MAAM,UAAU,OAAO;GAMvB,IAAI,EAHF,OAAO,YAAY,YAClB,MAAM,QAAQ,OAAO,KAAK,QAAQ,OAAO,YAAY,OAAO,YAAY,QAAQ,IAGjF,OAAO,EACL,QAAQ,CACN,EAAE,SAAS,uEAAuE,CACpF,EACF;GAKF,OAAO,EACL,OAAO;IAAE,MAAM;IAAiB,WAHhB,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY;GAGhC,EAC5C;EACF;CACF,EAGF;AACF"}