/**
 * Per-tool contract for `request_credential` — host-mediated user-gesture flow.
 * Content strings are load-bearing because downstream renderers and the agent
 * both interpret success/cancel/failure summaries.
 */
import { Type } from '@sinclair/typebox';
import { HostToolResultStatus, buildRendererArtifact } from '../../../contracts/host-tool.js';
import { withAbort } from '../../../utils/abort.js';
import { REQUEST_CREDENTIAL } from '../tool-names.js';
export const REQUEST_CREDENTIAL_KIND = 'requestCredential';
const CREDENTIAL_TYPES = new Set([
    'ACCESS_TOKEN',
    'API_KEY',
    'PASSWORD'
]);
export const REQUEST_CREDENTIAL_TOOL_DESCRIPTION = 'Request a credential (API key, access token, or password) from the user. ' +
    'Use this when `safe credentials <domain>` shows no stored credential for a domain ' +
    'and you need one to make authenticated HTTP requests via safe. ' +
    'The user will be prompted to enter the credential securely in the UI. ' +
    'You will never see the actual credential value — it is stored securely and ' +
    'resolved by safe at request time via placeholders like -ACCESS_TOKEN-.';
export const REQUEST_CREDENTIAL_DOMAIN_DESCRIPTION = "The service domain (e.g. 'github.com')";
export const REQUEST_CREDENTIAL_CREDENTIAL_TYPE_DESCRIPTION = 'One of: ACCESS_TOKEN, API_KEY, PASSWORD';
export const REQUEST_CREDENTIAL_URL_DESCRIPTION = "Help link where user can create the credential (e.g. 'https://github.com/settings/tokens')";
export const REQUEST_CREDENTIAL_MESSAGE_DESCRIPTION = 'Explanation shown to the user about why the credential is needed';
const requestCredentialLlmSchema = Type.Object({
    domain: Type.String({
        description: REQUEST_CREDENTIAL_DOMAIN_DESCRIPTION,
        minLength: 1
    }),
    credential_type: Type.Union([Type.Literal('ACCESS_TOKEN'), Type.Literal('API_KEY'), Type.Literal('PASSWORD')], { description: REQUEST_CREDENTIAL_CREDENTIAL_TYPE_DESCRIPTION }),
    url: Type.Optional(Type.String({ description: REQUEST_CREDENTIAL_URL_DESCRIPTION })),
    message: Type.Optional(Type.String({ description: REQUEST_CREDENTIAL_MESSAGE_DESCRIPTION }))
});
function isCredentialType(value) {
    return typeof value === 'string' && CREDENTIAL_TYPES.has(value);
}
function isRequestCredentialStatus(value) {
    return value === 'success' || value === 'cancelled';
}
function validateParams(value) {
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return (typeof candidate.domain === 'string' &&
        isCredentialType(candidate.credential_type) &&
        (candidate.url === undefined || typeof candidate.url === 'string') &&
        (candidate.message === undefined || typeof candidate.message === 'string'));
}
function validateResultDetails(value) {
    if (value === undefined)
        return true;
    if (!value || typeof value !== 'object')
        return false;
    const candidate = value;
    return (isRequestCredentialStatus(candidate.status) &&
        (candidate.domain === undefined || typeof candidate.domain === 'string') &&
        (candidate.credential_type === undefined || isCredentialType(candidate.credential_type)));
}
function normalizeResultDetails(value) {
    return value === undefined ? undefined : { ...value };
}
function getResultObservabilityFields(details) {
    if (!validateResultDetails(details) || details === undefined)
        return undefined;
    return {
        status: details.status,
        ...(details.credential_type !== undefined ? { credential_type: details.credential_type } : {})
    };
}
function evaluateApproval() {
    return { required: false };
}
function successContent(domain, credentialType) {
    return `Credential stored: ${domain} ${credentialType}`;
}
const CANCELLED_CONTENT = 'User cancelled the credential request.';
function failedContent(reason) {
    return `Credential request failed: ${reason}`;
}
function formatResult(details, isError, userMessage, params) {
    // Failed/Cancelled: surface as error with the canonical userMessage, but
    // preserve the actual status in details so renderers can distinguish a
    // bridge cancel from a user gesture rather than collapsing both to
    // 'cancelled'.
    if (isError) {
        const text = details?.status === 'success' || details?.status === 'cancelled' || details === undefined
            ? (userMessage ?? 'Credential request was cancelled.')
            : failedContent(userMessage ?? 'unknown');
        return {
            content: [{ type: 'text', text }],
            details: buildRendererArtifact({ status: (details?.status ?? 'cancelled') }, userMessage),
            isError: true
        };
    }
    if (details?.status === 'success') {
        // The success content string must always name a domain + type; fall back
        // to request params if the adapter echoed empty fields.
        const domain = details.domain ?? params.domain;
        const credentialType = isCredentialType(details.credential_type)
            ? details.credential_type
            : params.credential_type;
        return {
            content: [{ type: 'text', text: successContent(domain, credentialType) }],
            details: { status: 'success', domain, credential_type: credentialType },
            isError: false
        };
    }
    return {
        content: [{ type: 'text', text: CANCELLED_CONTENT }],
        details: { status: 'cancelled' },
        isError: false
    };
}
function createLlmTool(options) {
    void options.sessionPolicy;
    return {
        name: REQUEST_CREDENTIAL,
        label: 'Request credential',
        isReadOnly: false,
        description: REQUEST_CREDENTIAL_TOOL_DESCRIPTION,
        parameters: requestCredentialLlmSchema,
        async execute(toolCallId, llmArgs, signal) {
            const args = llmArgs;
            const params = {
                domain: args.domain,
                credential_type: args.credential_type,
                ...(args.url !== undefined ? { url: args.url } : {}),
                ...(args.message !== undefined ? { message: args.message } : {})
            };
            const pendingHostToolRequests = options.pendingHostToolRequests;
            const request = {
                approval: evaluateApproval(),
                heartbeat: {
                    label: 'Requesting credential...',
                    toolName: REQUEST_CREDENTIAL
                },
                kind: REQUEST_CREDENTIAL_KIND,
                params,
                requestId: toolCallId
            };
            const result = await withAbort(signal, async () => pendingHostToolRequests.request(request));
            const details = result.details;
            const isError = result.status === HostToolResultStatus.Failed ||
                result.status === HostToolResultStatus.Cancelled;
            return formatResult(details, isError, result.userMessage, params);
        }
    };
}
export const requestCredentialContract = {
    kind: REQUEST_CREDENTIAL_KIND,
    createLlmTool,
    evaluateApproval,
    getResultObservabilityFields,
    validateParams,
    validateResultDetails,
    normalizeResultDetails
};
//# sourceMappingURL=contract.js.map