// src/tools/registry.ts
import { zodToJsonSchema } from "zod-to-json-schema";
var ToolScopeMismatchError = class extends Error {
  toolName;
  toolScope;
  agentScope;
  constructor(args) {
    super(
      `Tool '${args.toolName}' has scope '${args.toolScope}' but agent scope is '${args.agentScope}'. Business agents must not register personal-scope tools, and vice versa.`
    );
    this.name = "ToolScopeMismatchError";
    this.toolName = args.toolName;
    this.toolScope = args.toolScope;
    this.agentScope = args.agentScope;
  }
};
var ToolRegistry = class {
  tools = /* @__PURE__ */ new Map();
  agentScope;
  constructor(agentScope) {
    this.agentScope = agentScope;
  }
  get scope() {
    return this.agentScope;
  }
  register(tool) {
    if (tool.scope !== this.agentScope && tool.scope !== "public") {
      throw new ToolScopeMismatchError({
        toolName: tool.name,
        toolScope: tool.scope,
        agentScope: this.agentScope
      });
    }
    if (this.tools.has(tool.name)) {
      throw new Error(`Tool '${tool.name}' is already registered`);
    }
    this.tools.set(tool.name, tool);
  }
  registerAll(tools) {
    for (const tool of tools) {
      this.register(tool);
    }
  }
  get(name) {
    return this.tools.get(name);
  }
  has(name) {
    return this.tools.has(name);
  }
  names() {
    return [...this.tools.keys()];
  }
  size() {
    return this.tools.size;
  }
  /**
   * Exports the registered tools in the shape the Anthropic SDK's
   * `messages.create({ tools })` parameter expects.
   *
   * The Zod input schema is converted to JSON Schema via `zod-to-json-schema`
   * targeting OpenAPI 3 — the dialect Anthropic's tool-use endpoint accepts.
   */
  toAnthropicTools() {
    return [...this.tools.values()].map((t) => ({
      name: t.name,
      description: t.description,
      input_schema: zodToJsonSchema(t.inputSchema, {
        target: "openApi3",
        $refStrategy: "none"
      })
    }));
  }
};

// src/tools/dispatcher.ts
function serializeError(err) {
  if (err instanceof Error) {
    return { name: err.name, message: err.message, stack: err.stack };
  }
  return { value: String(err) };
}
function makeErrorResult(toolUseId, message) {
  return {
    type: "tool_result",
    tool_use_id: toolUseId,
    content: message,
    is_error: true
  };
}
function serializeOutput(output) {
  if (typeof output === "string") return output;
  try {
    return JSON.stringify(output);
  } catch {
    return String(output);
  }
}
async function dispatchToolUse(registry, toolUseBlock, ctx) {
  const tool = registry.get(toolUseBlock.name);
  if (!tool) {
    ctx.log.warn("dispatcher: unknown tool", {
      requested: toolUseBlock.name,
      known: registry.names()
    });
    return makeErrorResult(toolUseBlock.id, `Unknown tool: ${toolUseBlock.name}`);
  }
  const parsed = tool.inputSchema.safeParse(toolUseBlock.input);
  if (!parsed.success) {
    ctx.log.warn("dispatcher: input validation failed", {
      tool: tool.name,
      issues: parsed.error.issues
    });
    return makeErrorResult(
      toolUseBlock.id,
      `Invalid input for ${tool.name}: ${parsed.error.message}`
    );
  }
  const startedAt = Date.now();
  try {
    await ctx.memory.toolInvocationStart({
      agentId: ctx.agentId,
      sessionId: ctx.sessionId,
      toolName: tool.name,
      input: parsed.data
    });
  } catch (err) {
    ctx.log.error("dispatcher: memory.toolInvocationStart threw", {
      tool: tool.name,
      error: serializeError(err)
    });
  }
  try {
    const output = await tool.handler(parsed.data, ctx);
    const durationMs = Date.now() - startedAt;
    try {
      await ctx.memory.toolInvocationSuccess({
        agentId: ctx.agentId,
        sessionId: ctx.sessionId,
        toolName: tool.name,
        durationMs
      });
    } catch (memErr) {
      ctx.log.error("dispatcher: memory.toolInvocationSuccess threw", {
        tool: tool.name,
        error: serializeError(memErr)
      });
    }
    return {
      type: "tool_result",
      tool_use_id: toolUseBlock.id,
      content: serializeOutput(output)
    };
  } catch (err) {
    const durationMs = Date.now() - startedAt;
    ctx.log.error("dispatcher: tool handler threw", {
      tool: tool.name,
      error: serializeError(err)
    });
    try {
      await ctx.memory.toolInvocationError({
        agentId: ctx.agentId,
        sessionId: ctx.sessionId,
        toolName: tool.name,
        durationMs,
        error: serializeError(err)
      });
    } catch (memErr) {
      ctx.log.error("dispatcher: memory.toolInvocationError threw", {
        tool: tool.name,
        error: serializeError(memErr)
      });
    }
    return makeErrorResult(
      toolUseBlock.id,
      `Tool '${tool.name}' failed: ${err instanceof Error ? err.message : String(err)}`
    );
  }
}

// src/tools/run-with-tools.ts
function isToolUseBlock(block) {
  return block.type === "tool_use";
}
async function runWithTools(args) {
  const maxIterations = args.maxIterations ?? 10;
  const maxTokens = args.maxTokens ?? 4096;
  const tools = args.registry.toAnthropicTools();
  const history = [...args.messages];
  let iterations = 0;
  let stoppedAtMaxIterations = false;
  let finalResponse;
  while (iterations < maxIterations) {
    iterations += 1;
    const response = await args.client.messages.create({
      model: args.model,
      max_tokens: maxTokens,
      ...args.system !== void 0 ? { system: args.system } : {},
      messages: history,
      ...tools.length > 0 ? { tools } : {}
    });
    finalResponse = response;
    history.push({ role: "assistant", content: response.content });
    if (response.stop_reason !== "tool_use") {
      return {
        messages: history,
        finalResponse: response,
        iterations,
        stoppedAtMaxIterations: false
      };
    }
    const toolUseBlocks = response.content.filter(isToolUseBlock);
    if (toolUseBlocks.length === 0) {
      return {
        messages: history,
        finalResponse: response,
        iterations,
        stoppedAtMaxIterations: false
      };
    }
    const results = await Promise.all(
      toolUseBlocks.map((block) => dispatchToolUse(args.registry, block, args.ctx))
    );
    history.push({ role: "user", content: results });
    if (iterations >= maxIterations) {
      stoppedAtMaxIterations = true;
      break;
    }
  }
  args.ctx.log.warn("runWithTools: hit maxIterations", {
    maxIterations,
    agentId: args.ctx.agentId,
    sessionId: args.ctx.sessionId
  });
  return {
    messages: history,
    // Safe non-null assertion: the loop always assigns finalResponse on its
    // first iteration before any early return, and maxIterations >= 1.
    finalResponse,
    iterations,
    stoppedAtMaxIterations
  };
}
export {
  ToolRegistry,
  ToolScopeMismatchError,
  dispatchToolUse,
  runWithTools
};
//# sourceMappingURL=index.mjs.map