/**
 * Per-tool contract for `task` — embedded subagent dispatch. Unlike other
 * host tools, this one runs in-process via `run-subagent.ts` rather than
 * crossing the host/worker boundary.
 */
import { Type } from '@sinclair/typebox';
import { buildRendererArtifact, HostToolResultStatus } from '../../../contracts/host-tool.js';
import { SHORTCUT_MODEL_ID } from '../../../model-ids.js';
import { withAbort } from '../../../utils/abort.js';
import { ShortcutXLAgentModelId } from '../../model-registry.js';
import { TASK } from '../tool-names.js';
import { getSubagent, getSubagentNames, SUBAGENT_CATALOG } from './agents/index.js';
import { runSubagent } from './run-subagent.js';
export const TASK_KIND = 'task';
export const TASK_QUERY_FILE_KIND = 'taskQueryFile';
const TASK_QUERY_FILE_TOOL_NAME = 'task_query_file';
export const TASK_SELECTABLE_MODEL_IDS = [
    ShortcutXLAgentModelId.ClaudeOpus48,
    ShortcutXLAgentModelId.Gpt55
];
/** Build the `task` tool description by enumerating every registered subagent. */
export function buildTaskToolDescription(allowedSubagents, disabledToolNames) {
    const allowed = allowedSubagents === undefined ? undefined : new Set(allowedSubagents);
    const lines = [];
    lines.push('Spawn subagents to handle tasks. Never spawn independent subagents sequentially. Issue all tasks in parallel so they run concurrently.');
    lines.push('');
    lines.push('You may spawn up to ~80 subagents in parallel. Available subagent_type values:');
    for (const [typeName, def] of Object.entries(SUBAGENT_CATALOG)) {
        if (allowed && !allowed.has(typeName))
            continue;
        const description = def.buildDescription?.({ disabledToolNames }) ?? def.description;
        lines.push(`- \`${typeName}\`: ${description}`);
    }
    return lines.join('\n');
}
export const TASK_SUBAGENT_TYPE_DESCRIPTION = 'Which subagent to spawn. Must be one of the documented subagent_type values.';
export const TASK_NAME_DESCRIPTION = 'A short (3-6 word) human-readable name for this subagent run. Shown in the UI; does not affect behavior.';
export const TASK_QUERY_DESCRIPTION = 'The full prompt the subagent will run against. Be explicit — the subagent does not see your conversation. A single sandbox .txt file path may be passed to load the prompt from that file.';
export const TASK_MODEL_DESCRIPTION = `Optional model override for this subagent only. Allowed: ${SHORTCUT_MODEL_ID.ClaudeOpus48}, ${SHORTCUT_MODEL_ID.Gpt55}. Omit to use the subagent default.`;
/** If a query string is a bare `.txt` path, return it; otherwise undefined. */
export function resolveTaskQueryFilePath(query) {
    const stripped = query.trim();
    if (!stripped.endsWith('.txt'))
        return undefined;
    if (stripped.includes(' '))
        return undefined;
    return stripped;
}
/** Build a typebox union of string literals, collapsing single-value unions to a literal. */
function literalUnion(values, options) {
    return values.length === 1
        ? Type.Literal(values[0], options)
        : Type.Union(values.map((value) => Type.Literal(value)), options);
}
/** Type guard: is the value a model id selectable from a task tool call? */
function isSelectableModelId(value) {
    return (typeof value === 'string' && TASK_SELECTABLE_MODEL_IDS.includes(value));
}
/** Build the typebox schema describing the task tool's LLM-visible parameters. */
function buildTaskLlmSchema(allowedSubagents) {
    const allowed = allowedSubagents === undefined ? undefined : new Set(allowedSubagents);
    const names = getSubagentNames().filter((name) => !allowed || allowed.has(name));
    // `Type.Union` needs at least one element — guard against an empty catalog.
    const [firstName] = names;
    if (firstName === undefined) {
        throw new Error('[task] SUBAGENT_CATALOG is empty — cannot build task tool schema.');
    }
    const subagentTypeSchema = names.length === 1
        ? Type.Literal(firstName, { description: TASK_SUBAGENT_TYPE_DESCRIPTION })
        : Type.Union(names.map((name) => Type.Literal(name)), { description: TASK_SUBAGENT_TYPE_DESCRIPTION });
    return Type.Object({
        subagent_type: subagentTypeSchema,
        name: Type.String({ description: TASK_NAME_DESCRIPTION, minLength: 1 }),
        query: Type.String({ description: TASK_QUERY_DESCRIPTION, minLength: 1 }),
        model: Type.Optional(literalUnion(TASK_SELECTABLE_MODEL_IDS, { description: TASK_MODEL_DESCRIPTION }))
    }, { additionalProperties: false });
}
/** Task spawns never require host approval — the parent already cleared the gate. */
function evaluateApproval() {
    return { required: false };
}
/** Type guard for incoming task_query_file params. */
function validateTaskQueryFileParams(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    const candidate = value;
    return typeof candidate.query === 'string';
}
/** Type guard for the details payload of a task_query_file result. */
function validateTaskQueryFileDetails(value) {
    if (value === undefined)
        return true;
    if (typeof value !== 'object' || value === null)
        return false;
    const candidate = value;
    return ((candidate.error === undefined || typeof candidate.error === 'boolean') &&
        (candidate.query_file === undefined || typeof candidate.query_file === 'string') &&
        (candidate.resolved === undefined || typeof candidate.resolved === 'boolean') &&
        (candidate.resolved_query === undefined || typeof candidate.resolved_query === 'string') &&
        (candidate.user_message === undefined || typeof candidate.user_message === 'string') &&
        // Invariant: a resolved query must include the resolved text.
        (candidate.resolved !== true || typeof candidate.resolved_query === 'string'));
}
/** Build a defensively-copied details record for a task_query_file result. */
function normalizeTaskQueryFileDetails(value) {
    if (value === undefined)
        return undefined;
    return {
        ...(value.error !== undefined ? { error: value.error } : {}),
        ...(value.query_file !== undefined ? { query_file: value.query_file } : {}),
        ...(value.resolved !== undefined ? { resolved: value.resolved } : {}),
        ...(value.resolved_query !== undefined ? { resolved_query: value.resolved_query } : {}),
        ...(value.user_message !== undefined ? { user_message: value.user_message } : {})
    };
}
/** Type guard for incoming task tool params (subagent type, name, query, optional model). */
function validateParams(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    const candidate = value;
    return (typeof candidate.subagent_type === 'string' &&
        typeof candidate.name === 'string' &&
        typeof candidate.query === 'string' &&
        getSubagent(candidate.subagent_type) !== undefined &&
        (candidate.model === undefined || isSelectableModelId(candidate.model)) &&
        candidate.thinking_level === undefined);
}
/** Type guard for the details payload of a task result. */
function validateResultDetails(value) {
    if (value === undefined)
        return true;
    if (typeof value !== 'object' || value === null)
        return false;
    const candidate = value;
    return (typeof candidate.subagent_type === 'string' &&
        (candidate.model === undefined || typeof candidate.model === 'string') &&
        (candidate.thinking_level === undefined || typeof candidate.thinking_level === 'string') &&
        (candidate.error === undefined || typeof candidate.error === 'boolean'));
}
/** Build a defensively-copied details record for a task result. */
function normalizeResultDetails(value) {
    if (value === undefined)
        return undefined;
    return {
        subagent_type: value.subagent_type,
        ...(value.model !== undefined ? { model: value.model } : {}),
        ...(value.thinking_level !== undefined ? { thinking_level: value.thinking_level } : {}),
        ...(value.error !== undefined ? { error: value.error } : {})
    };
}
function getTaskObservabilityFields(details) {
    if (!validateResultDetails(details) || details === undefined)
        return undefined;
    const metadata = {
        subagent_type: details.subagent_type,
        ...(details.model !== undefined ? { model: details.model } : {}),
        ...(details.thinking_level !== undefined ? { thinking_level: details.thinking_level } : {}),
        ...(details.error !== undefined ? { error: details.error } : {})
    };
    return metadata;
}
/** Build an error-flagged task tool result with subagent metadata. */
function errorResult(subagentType, message, metadata = {}) {
    return {
        content: [{ type: 'text', text: message }],
        details: buildRendererArtifact({ subagent_type: subagentType, ...metadata, error: true }, message),
        isError: true
    };
}
/** Build a successful task tool result with subagent metadata. */
function successResult(subagentType, text, metadata = {}) {
    return {
        content: [{ type: 'text', text }],
        details: {
            subagent_type: subagentType,
            ...metadata
        },
        isError: false
    };
}
/** Resolve a `.txt` query path to its file contents via a host-tool roundtrip. */
async function resolveTaskQuery(query, options, toolCallId, signal) {
    const queryFile = resolveTaskQueryFilePath(query);
    if (queryFile === undefined)
        return query;
    const request = {
        approval: evaluateApproval(),
        kind: TASK_QUERY_FILE_KIND,
        params: { query: queryFile },
        requestId: `${toolCallId}:query-file`
    };
    try {
        const result = await withAbort(signal, () => options.pendingHostToolRequests.request(request));
        if (result.status === HostToolResultStatus.Succeeded &&
            validateTaskQueryFileDetails(result.details) &&
            result.details?.resolved === true) {
            // Validator enforces `resolved_query: string` when `resolved === true`.
            return result.details.resolved_query;
        }
    }
    catch (error) {
        if (signal?.aborted)
            throw error;
    }
    return query;
}
/** Build the LLM-facing `task` tool wired to the embedded subagent runner. */
function createLlmTool(options) {
    return {
        name: TASK,
        label: 'Task',
        isReadOnly: false,
        description: buildTaskToolDescription(options.allowedSubagents, options.disabledToolNames),
        parameters: buildTaskLlmSchema(options.allowedSubagents),
        getResultObservabilityFields: getTaskObservabilityFields,
        async execute(toolCallId, llmArgs, signal) {
            const args = llmArgs;
            const subagentType = args.subagent_type;
            const definition = getSubagent(subagentType);
            if (!definition) {
                return errorResult(subagentType, `Unknown subagent_type '${subagentType}'. Available: ${getSubagentNames().join(', ')}`);
            }
            if (args.model !== undefined && !isSelectableModelId(args.model)) {
                return errorResult(subagentType, `Model '${args.model}' is not selectable for subagents. Allowed: ${TASK_SELECTABLE_MODEL_IDS.join(', ')}`);
            }
            const requestedModelId = args.model ?? definition.model;
            const inheritedModel = options.preferCurrentModelForSubagents
                ? options.getCurrentModel?.()
                : undefined;
            const effectiveModelId = inheritedModel?.id ?? requestedModelId;
            const effectiveThinkingLevel = definition.thinkingLevel;
            const metadata = {
                model: effectiveModelId,
                thinking_level: effectiveThinkingLevel
            };
            const model = inheritedModel ?? options.resolveModel(effectiveModelId);
            if (!model) {
                return errorResult(subagentType, `Subagent '${subagentType}' requires model '${effectiveModelId}' but it is not available in this runtime's model registry.`, metadata);
            }
            const spreadsheetReadOnly = definition.spreadsheetReadOnly === true;
            // Inherits parent pending host-tool requests/mode/runtime so nested `task` works; spreadsheetReadOnly is per-subagent.
            const childTools = options.buildChildTools({
                sessionPolicy: options.sessionPolicy,
                spreadsheetReadOnly,
                toolNames: definition.tools,
                allowedSubagents: definition.allowedSubagents
            });
            // Resolve the subagent's system prompt. Static-string prompts pass
            // through; builder prompts get the platform handle and the
            // read-only flag so they can pick the matching API filter.
            let resolvedSystemPrompt;
            if (typeof definition.systemPrompt === 'string') {
                resolvedSystemPrompt = definition.systemPrompt;
            }
            else {
                if (!options.promptPlatform) {
                    return errorResult(subagentType, `Subagent '${subagentType}' requires a prompt platform but none was supplied to the task tool. ` +
                        'The runtime controller must thread `promptPlatform` through `buildHostToolList`.');
                }
                resolvedSystemPrompt = definition.systemPrompt({
                    platform: options.promptPlatform,
                    spreadsheetReadOnly,
                    disabledToolNames: options.disabledToolNames
                });
            }
            // Some test harnesses skip the signal — use a never-firing fallback.
            const effectiveSignal = signal ?? new AbortController().signal;
            // Stamp metadata here so the runner can stay naming-agnostic.
            const onChildAgentEvent = options.emitChildAgentEvent
                ? (event) => options.emitChildAgentEvent(toolCallId, subagentType, args.name, event, {
                    model: effectiveModelId,
                    thinkingLevel: effectiveThinkingLevel
                })
                : undefined;
            const result = await runSubagent({
                definition,
                systemPrompt: resolvedSystemPrompt,
                query: await resolveTaskQuery(args.query, options, toolCallId, signal),
                childTools,
                streamFn: options.streamFn,
                model,
                thinkingLevel: effectiveThinkingLevel,
                speed: options.getSpeed?.(),
                getApiKey: options.getApiKey,
                signal: effectiveSignal,
                onChildAgentEvent
            });
            if (result.error) {
                return errorResult(subagentType, result.error, metadata);
            }
            return successResult(subagentType, result.finalContent ?? '', metadata);
        }
    };
}
const taskQueryFileLlmSchema = Type.Object({
    query: Type.String({ minLength: 1 })
});
/** Build the internal `task_query_file` helper tool — never directly model-callable. */
function createTaskQueryFileLlmTool() {
    return {
        name: TASK_QUERY_FILE_TOOL_NAME,
        label: 'Task Query File',
        isReadOnly: true,
        description: 'Internal helper for resolving task query .txt files. Do not expose this tool to models.',
        parameters: taskQueryFileLlmSchema,
        async execute() {
            const message = 'Internal helper; not model-callable.';
            return {
                content: [{ type: 'text', text: message }],
                details: buildRendererArtifact({ error: true, resolved: false }, message),
                isError: true
            };
        }
    };
}
export const taskContract = {
    kind: TASK_KIND,
    createLlmTool,
    evaluateApproval,
    getResultObservabilityFields: getTaskObservabilityFields,
    validateParams,
    validateResultDetails,
    normalizeResultDetails
};
export const taskQueryFileContract = {
    kind: TASK_QUERY_FILE_KIND,
    createLlmTool: createTaskQueryFileLlmTool,
    evaluateApproval,
    validateParams: validateTaskQueryFileParams,
    validateResultDetails: validateTaskQueryFileDetails,
    normalizeResultDetails: normalizeTaskQueryFileDetails
};
//# sourceMappingURL=contract.js.map