import type { AnalysisError } from "./analysisErrorBase.js"; import { AnalysisAccessDeniedError, AnalysisArtifactChangedError, AnalysisInputError, AnalysisOutputError, AnalysisCapabilityUnavailableError, AnalysisResourceConstraintError, AnalysisCancelledError, AnalysisTimeoutError, } from "./analysisErrorCore.js"; import { redactExplicitText } from "./explicitSensitiveValues.js"; import type { JsonValue } from "./jsonValue.js"; import { ProviderAdapterError } from "./providerAdapterError.js"; import { ProviderCleanupError } from "./providerCleanupError.js"; /** Keep a real JSON-pointer ancestor when a property identity is explicitly excluded. */ export const redactExplicitPointer = ( pointer: string, values: readonly string[], ): string => { let parent = ""; for (const segment of pointer.split("/").slice(1)) { const decoded = segment.replaceAll("~1", "/").replaceAll("~0", "~"); const child = `${parent}/${segment}`; if ( values.some( (literal) => decoded.includes(literal) || child.includes(literal), ) ) return parent; parent = child; } return parent; }; /** Exclude explicit literals from common acquisition/decoder failures, preserving typed diagnostics. */ export const redactExplicitFailure = ( error: AnalysisError, values: readonly string[], ): AnalysisError => { if (values.length === 0) return error; const text = (value: string): string => redactExplicitText(value, values); const path = ( parts: readonly (string | number)[], ordinaryNames: boolean, ): (string | number)[] => { const retained: (string | number)[] = []; for (const part of parts) { if (typeof part !== "string") { retained.push(part); } else if (!ordinaryNames && part.startsWith("/")) { const pointer = redactExplicitPointer(part, values); retained.push(pointer); if (pointer !== part) break; } else { if (values.some((literal) => part.includes(literal))) break; retained.push(part); } } return retained; }; const diagnostics = ( value: Readonly>, ): Record => Object.fromEntries( Object.entries(value) .filter(([key]) => !values.some((literal) => key.includes(literal))) .map(([key, item]) => [key, visit(item)]), ); const visit = (value: JsonValue): JsonValue => { if (typeof value === "string") return text(value); if (Array.isArray(value)) return value.map(visit); if (typeof value === "object" && value !== null) return diagnostics(value); return value; }; const context = { cause: error, ...(error.diagnostics === undefined ? {} : { diagnostics: diagnostics(error.diagnostics) }), ...(error.capturedOutput === undefined ? {} : { capturedOutput: { ...error.capturedOutput, stdout: text(error.capturedOutput.stdout), stderr: text(error.capturedOutput.stderr), }, }), ...(error.cleanup === undefined ? {} : { cleanup: { reason: text(error.cleanup.reason), resources: error.cleanup.resources.map(text), }, }), }; if (error instanceof AnalysisCancelledError) return new AnalysisCancelledError(error.operation, { ...context, partialObservation: error.partialObservation, }); if (error instanceof AnalysisTimeoutError) return new AnalysisTimeoutError(error.operation, error.timeoutMs, { ...context, partialObservation: error.partialObservation, }); if (error instanceof AnalysisAccessDeniedError) return new AnalysisAccessDeniedError( error.operation, text(error.path), error.systemCode, context, ); if (error instanceof AnalysisArtifactChangedError) return new AnalysisArtifactChangedError( error.operation, text(error.path), text(error.reason), context, ); if (error instanceof AnalysisInputError) return new AnalysisInputError( error.operation, context, error.issues.map((issue) => ({ ...issue, // Unknown-argument paths contain literal field names, including names // beginning with '/'; they are not producer JSON-pointer coordinates. path: path(issue.path, issue.reason === "unknown_argument"), ...(issue.message === undefined ? {} : { message: text(issue.message) }), ...(issue.expected === undefined ? {} : { expected: visit(issue.expected) }), })), ); if (error instanceof AnalysisOutputError) return new AnalysisOutputError( error.operation, text(error.reason), context, ); if (error instanceof AnalysisCapabilityUnavailableError) return new AnalysisCapabilityUnavailableError( error.providerId, error.operation, text(error.reason), { ...context, ...(error.userMessage === undefined ? {} : { userMessage: text(error.userMessage) }), ...(error.retryable ? { retryable: true } : {}), }, ); if (error instanceof AnalysisResourceConstraintError) return new AnalysisResourceConstraintError( error.operation, error.resource, text(error.reason), error.reportedLimits === null ? null : diagnostics(error.reportedLimits), { ...context, ...(error.remediationAction === undefined ? {} : { remediationAction: text(error.remediationAction) }), }, ); if (error instanceof ProviderCleanupError) return new ProviderCleanupError( error.providerId, error.cleanupResources.map(text), diagnostics(error.diagnostics ?? {}), { operation: error.operation, cause: error }, ); if (error instanceof ProviderAdapterError) return new ProviderAdapterError(error.providerId, error.operation, context); return error; };