"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineTool = defineTool;
exports.createToolset = createToolset;
exports.toolsetToOpenAIDefinitions = toolsetToOpenAIDefinitions;
exports.createToolsetRenderer = createToolsetRenderer;
exports.applyToolResult = applyToolResult;
const jsx_runtime_1 = require("react/jsx-runtime");
const ToolMessage_1 = require("../../components/organisms/ToolMessage/index.js");
const messageTypeRegistry_1 = require("../messageTypeRegistry.js");
const i18n_1 = require("./i18n/index.js");
/**
 * Wrap a typed tool definition into an erased runtime entry that the
 * toolset renderer can dispatch by `toolName`. The literal `name` is
 * preserved as a type parameter so `createToolset` can derive its keys.
 */
function defineTool(definition) {
    return {
        name: definition.name,
        description: definition.description,
        parameters: definition.parameters,
        validate: definition.schema.validate,
        Renderer: definition.component,
        execute: ({ args, result, toolCallId }) => definition.execute
            ? definition.execute({
                args: args,
                result: result,
                toolCallId,
            })
            : result,
    };
}
/**
 * Build a `Toolset` from a list of `defineTool(...)` results. Keys are
 * derived from `definition.name`, so the call site cannot drift between
 * a literal-object key and the embedded `name`. The return type narrows
 * its keys to the union of literal tool names, giving name-autocomplete
 * on lookups. Throws on duplicates.
 */
function createToolset(...tools) {
    const result = {};
    for (const tool of tools) {
        if (Object.prototype.hasOwnProperty.call(result, tool.name)) {
            throw new Error(`createToolset: duplicate tool name "${tool.name}"`);
        }
        result[tool.name] = tool;
    }
    return result;
}
/**
 * Map a `Toolset` to the OpenAI `tools[]` shape so chat clients can pass
 * tool definitions to the model without reimplementing the conversion.
 */
function toolsetToOpenAIDefinitions(toolset) {
    return Object.values(toolset).map((tool) => ({
        type: 'function',
        function: {
            name: tool.name,
            description: tool.description,
            parameters: tool.parameters,
        },
    }));
}
const OUTCOME_KEYS = new Set(['status', 'result', 'error']);
function isToolExecutionOutcome(value) {
    if (!value || typeof value !== 'object')
        return false;
    const status = value.status;
    if (status !== 'success' && status !== 'error' && status !== 'cancelled')
        return false;
    // A bare TResult may coincidentally carry a `status` field. Require all
    // own keys to be in {status, result, error} so e.g. `{status: 'success',
    // id: '123'}` is treated as a result, not a (result-less) outcome.
    for (const key of Object.keys(value)) {
        if (!OUTCOME_KEYS.has(key))
            return false;
    }
    return true;
}
function normalizeOutcome(value) {
    if (isToolExecutionOutcome(value))
        return value;
    return { status: 'success', result: value };
}
/**
 * Build a `MessageRendererRegistry` whose `tool` renderer dispatches
 * by `toolName` into the provided toolset. Unknown tools and invalid
 * args fall back to a generic `<ToolMessage status="error" />`. Errors
 * thrown inside `execute` are surfaced via an `error` outcome.
 */
function createToolsetRenderer(toolset, options) {
    var _a;
    const { onToolResult } = options;
    const registry = Object.assign({}, ((_a = options.registry) !== null && _a !== void 0 ? _a : (0, messageTypeRegistry_1.createMessageRendererRegistry)()));
    const toolRenderer = {
        component: ({ part }) => {
            const toolPart = part.data;
            const toolDef = toolset[toolPart.toolName];
            if (!toolDef) {
                return ((0, jsx_runtime_1.jsx)(ToolMessage_1.ToolMessage, { toolName: toolPart.toolName, status: "error", expandable: true, initialExpanded: true, bodyContent: (0, i18n_1.i18n)('fallback-unknown-tool', {
                        toolName: toolPart.toolName,
                    }) }));
            }
            const validation = toolDef.validate(toolPart.args);
            if (!validation.success) {
                return ((0, jsx_runtime_1.jsx)(ToolMessage_1.ToolMessage, { toolName: toolPart.toolName, status: "error", expandable: true, initialExpanded: true, bodyContent: validation.error.message }));
            }
            const submitResult = (result) => {
                Promise.resolve()
                    .then(() => toolDef.execute({
                    args: validation.data,
                    result,
                    toolCallId: toolPart.toolCallId,
                }))
                    .then((raw) => normalizeOutcome(raw))
                    .catch((err) => ({
                    status: 'error',
                    error: { message: err instanceof Error ? err.message : String(err) },
                }))
                    .then((outcome) => {
                    onToolResult({
                        toolCallId: toolPart.toolCallId,
                        toolName: toolPart.toolName,
                        status: outcome.status,
                        result: outcome.result,
                        error: 'error' in outcome ? outcome.error : undefined,
                    });
                });
            };
            const { Renderer } = toolDef;
            return ((0, jsx_runtime_1.jsx)(Renderer, { args: validation.data, result: toolPart.result, submitResult: submitResult }));
        },
    };
    (0, messageTypeRegistry_1.registerMessageRenderer)(registry, 'tool', toolRenderer);
    return registry;
}
function isToolPartContent(part) {
    return part.type === 'tool' && typeof part.data === 'object' && part.data !== null;
}
function toContentParts(content) {
    if (typeof content === 'string') {
        return content ? [{ type: 'text', data: { text: content } }] : [];
    }
    return Array.isArray(content) ? content : [content];
}
/**
 * Merge a tool result into the matching `tool` part of the chat history.
 * Honors `event.status` so the part reflects success / error / cancelled.
 * For backward compatibility, a missing `status` is treated as `'success'`.
 * Returns the original array reference when nothing matched, so React state
 * setters can skip needless updates.
 */
function applyToolResult(messages, event) {
    var _a;
    let changed = false;
    const status = (_a = event.status) !== null && _a !== void 0 ? _a : 'success';
    const isError = status === 'error';
    const next = messages.map((msg) => {
        if (msg.role !== 'assistant')
            return msg;
        const parts = toContentParts(msg.content);
        const hasMatch = parts.some((part) => isToolPartContent(part) && part.data.toolCallId === event.toolCallId);
        if (!hasMatch)
            return msg;
        changed = true;
        const updatedParts = parts.map((part) => {
            var _a;
            if (!isToolPartContent(part) || part.data.toolCallId !== event.toolCallId) {
                return part;
            }
            const nextData = Object.assign(Object.assign({}, part.data), { status, result: event.result });
            if (isError && ((_a = event.error) === null || _a === void 0 ? void 0 : _a.message)) {
                nextData.bodyContent = event.error.message;
                nextData.expandable = true;
                nextData.initialExpanded = true;
            }
            return Object.assign(Object.assign({}, part), { data: nextData });
        });
        return Object.assign(Object.assign({}, msg), { content: updatedParts });
    });
    return changed ? next : messages;
}
//# sourceMappingURL=index.js.map
