/** * Core.invoke:工具调用的唯一出口(架构设计 4 / 术语表规则 3)。 * * 职责:解析能力 → 判幂等 → 建执行记录 → 按截止时间调用 Provider → 归一化错误与回执。 * A4 会把策略判定与凭据代理插在「建执行记录」与「调用 Provider」之间;现在留出接缝。 */ import { randomUUID } from 'node:crypto'; import type { ExecutionReceipt } from '@aps/contracts'; import { isErrorKind, ProviderError, type ErrorKind, type ExecutionContext } from '@aps/provider-sdk'; import type { CapabilityRegistry } from '../registry/registry.js'; import { digestParams, ExecutionStore, type ExecutionRecord } from './store.js'; export interface InvokeRequest { /** 调用者身份(A4 起由身份上下文补齐,现在只需要用户与租户) */ subject: { userId: string; tenantId: string }; sessionId: string; projectId?: string | null; capabilityId: string; params: unknown; idempotencyKey?: string; /** 覆盖默认超时(毫秒) */ timeoutMs?: number; } export interface CoreOptions { registry: CapabilityRegistry; executions?: ExecutionStore; defaultTimeoutMs?: number; } export interface Core { invoke(request: InvokeRequest): Promise; executions: ExecutionStore; registry: CapabilityRegistry; } const DEFAULT_TIMEOUT_MS = 30_000; function failed(executionId: string, errorKind: ErrorKind, message: string): ExecutionReceipt { return { executionId, status: 'failed', errorKind, result: { message } }; } /** 把任意异常归一化成六分类之一(实施设计 5) */ function normalizeError(error: unknown): { kind: ErrorKind; message: string } { if (error instanceof ProviderError) return { kind: error.kind, message: error.message }; if (error instanceof Error) return { kind: 'unknown_result', message: error.message }; return { kind: 'unknown_result', message: 'Provider 抛出未知错误' }; } export function createCore(options: CoreOptions): Core { const executions = options.executions ?? new ExecutionStore(); const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS; async function invoke(request: InvokeRequest): Promise { const capabilityId = request.capabilityId; if (typeof capabilityId !== 'string' || capabilityId.trim() === '') { return failed(`exec-${randomUUID()}`, 'invalid_argument', 'capabilityId 不能为空'); } // 幂等:同一键的重试返回原回执,不重复执行 if (request.idempotencyKey) { const existing = executions.findByIdempotencyKey(request.idempotencyKey); if (existing) return existing.receipt; } const resolved = options.registry.resolve(capabilityId); const executionId = `exec-${randomUUID()}`; if (!resolved) { return failed(executionId, 'invalid_argument', `能力 ${capabilityId} 未注册`); } const timeoutMs = request.timeoutMs ?? defaultTimeoutMs; const context: ExecutionContext = { executionId, sessionId: request.sessionId, projectId: request.projectId ?? null, subject: { userId: request.subject.userId, tenantId: request.subject.tenantId }, ...(request.idempotencyKey === undefined ? {} : { idempotencyKey: request.idempotencyKey }), deadlineAt: Date.now() + timeoutMs, }; executions.create({ executionId, capabilityId, providerId: resolved.providerId, sessionId: request.sessionId, projectId: request.projectId ?? null, subjectUserId: request.subject.userId, paramsDigest: digestParams(request.params), ...(request.idempotencyKey === undefined ? {} : { idempotencyKey: request.idempotencyKey }), receipt: { executionId, status: 'accepted' }, }); // 超时判定:超时不等于成功也不等于失败——结果未知(实施设计 5) const timeout = new Promise((resolve) => { setTimeout(() => resolve(failed(executionId, 'unknown_result', `调用超过 ${timeoutMs}ms,结果未知`)), timeoutMs).unref(); }); let receipt: ExecutionReceipt; try { receipt = await Promise.race([resolved.provider.invoke(context, { capabilityId, params: request.params }), timeout]); } catch (error) { const normalized = normalizeError(error); receipt = failed(executionId, normalized.kind, normalized.message); } // Provider 返回的分类非法时归一为 unknown_result,避免脏值进记录 if (receipt.status === 'failed' && receipt.errorKind !== undefined && !isErrorKind(receipt.errorKind)) { receipt = failed(executionId, 'unknown_result', `Provider 返回了未知错误分类 ${String(receipt.errorKind)}`); } executions.complete(executionId, receipt); return receipt; } return { invoke, executions, registry: options.registry }; } export type { ExecutionRecord };