diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-adapter-errors.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-adapter-errors.ts new file mode 100644 index 000000000000..75dc165cdafb --- /dev/null +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-adapter-errors.ts @@ -0,0 +1,69 @@ +import type { AgentSessionAccountKind } from '../../../shared/agent-session-availability' +import type { AgentSessionRefusalReason } from '../../../shared/agent-session-wire-refusals' + +// Errors an adapter throws to the host; kept apart so the adapter contract file stays the contract. + +export class AgentSessionAcquisitionRefusal extends Error { + readonly code = 'agent_session_operation_invalid' + + constructor( + message: string, + /** UI refusal reason; message stays in logs. Defaults to providerStartFailed. */ + readonly reason: AgentSessionRefusalReason<'agent_session_operation_invalid'> = 'providerStartFailed', + readonly account?: AgentSessionAccountKind + ) { + super(message) + this.name = 'AgentSessionAcquisitionRefusal' + } + + static historyTooLarge(message: string): AgentSessionAcquisitionRefusal { + return new AgentSessionAcquisitionRefusal(message, 'historyTooLarge') + } +} + +export class AgentSessionPromptUnavailableError extends Error { + constructor(itemId: string) { + super(`The provider is no longer waiting on ${itemId}.`) + this.name = 'AgentSessionPromptUnavailableError' + } +} + +/** Reject before journal commit when a provider reply cannot be constructed. */ +export class AgentSessionPromptAnswerRejectedError extends Error { + constructor(message: string) { + super(message) + this.name = 'AgentSessionPromptAnswerRejectedError' + } +} + +export type AgentSessionPreSpawnReason = Extract< + AgentSessionRefusalReason<'agent_session_operation_invalid'>, + | 'managedAccountEnvOverride' + | 'accountSwitchInProgress' + | 'managedAccountUnsupported' + | 'launchFolderMissing' + | 'historyInOtherAccount' + | 'claudeAccountFolderMissing' + | 'claudeAccountSetupFailed' + | 'agentCommandNotRunnable' + | 'runtimeNotInstalled' +> + +/** First-hand proof that acquisition spawned no process. */ +export class AgentSessionPreSpawnError extends Error { + /** Absent reasons are log-only; wrapped pre-spawn errors retain theirs. */ + readonly reason: AgentSessionPreSpawnReason | undefined + + constructor( + cause: unknown, + options: { reason?: AgentSessionPreSpawnReason; message?: string } = {} + ) { + super(options.message ?? (cause instanceof Error ? cause.message : String(cause)), { cause }) + this.name = 'AgentSessionPreSpawnError' + this.reason = options.reason ?? (isAgentSessionPreSpawnError(cause) ? cause.reason : undefined) + } +} + +export function isAgentSessionPreSpawnError(error: unknown): error is AgentSessionPreSpawnError { + return error instanceof Error && error.name === 'AgentSessionPreSpawnError' +} diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts index 155ce13ccff8..e93a2cab1533 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts @@ -1,5 +1,4 @@ import type { AiVaultConversationCopy } from '../../../shared/ai-vault-resume-preparation' -import type { AgentSessionAccountKind } from '../../../shared/agent-session-availability' import type { AgentSessionRewindFork, AgentSessionRewindReason, @@ -29,7 +28,6 @@ import type { import type { StructuredAgentSessionAtRestCommands } from './structured-agent-session-at-rest-commands' import type { AgentSessionSlashCommand } from '../../../shared/agent-session-wire' import type { AgentSessionThreadGoalChange } from '../../../shared/agent-session-thread-goal-wire' -import type { AgentSessionRefusalReason } from '../../../shared/agent-session-wire-refusals' import type { SubmissionRejectionFact } from '../../../shared/agent-session-failure' import type { AgentSessionCancelOutcome, @@ -60,38 +58,14 @@ import type { } from './structured-agent-session-option-models' export type { StructuredAgentSessionLiveOptions } from './structured-agent-session-option-models' -export class AgentSessionAcquisitionRefusal extends Error { - readonly code = 'agent_session_operation_invalid' - - constructor( - message: string, - /** UI refusal reason; message stays in logs. Defaults to providerStartFailed. */ - readonly reason: AgentSessionRefusalReason<'agent_session_operation_invalid'> = 'providerStartFailed', - readonly account?: AgentSessionAccountKind - ) { - super(message) - this.name = 'AgentSessionAcquisitionRefusal' - } - - static historyTooLarge(message: string): AgentSessionAcquisitionRefusal { - return new AgentSessionAcquisitionRefusal(message, 'historyTooLarge') - } -} - -export class AgentSessionPromptUnavailableError extends Error { - constructor(itemId: string) { - super(`The provider is no longer waiting on ${itemId}.`) - this.name = 'AgentSessionPromptUnavailableError' - } -} - -/** Reject before journal commit when a provider reply cannot be constructed. */ -export class AgentSessionPromptAnswerRejectedError extends Error { - constructor(message: string) { - super(message) - this.name = 'AgentSessionPromptAnswerRejectedError' - } -} +export { + AgentSessionAcquisitionRefusal, + AgentSessionPreSpawnError, + AgentSessionPromptAnswerRejectedError, + AgentSessionPromptUnavailableError, + isAgentSessionPreSpawnError +} from './structured-agent-session-adapter-errors' +export type { AgentSessionPreSpawnReason } from './structured-agent-session-adapter-errors' export { AgentSessionAcquisitionExitProvenError, @@ -107,38 +81,6 @@ export type AgentSessionAcquisition = { acquisitionGeneration: string } -export type AgentSessionPreSpawnReason = Extract< - AgentSessionRefusalReason<'agent_session_operation_invalid'>, - | 'managedAccountEnvOverride' - | 'accountSwitchInProgress' - | 'managedAccountUnsupported' - | 'launchFolderMissing' - | 'historyInOtherAccount' - | 'claudeAccountFolderMissing' - | 'claudeAccountSetupFailed' - | 'agentCommandNotRunnable' - | 'runtimeNotInstalled' -> - -/** First-hand proof that acquisition spawned no process. */ -export class AgentSessionPreSpawnError extends Error { - /** Absent reasons are log-only; wrapped pre-spawn errors retain theirs. */ - readonly reason: AgentSessionPreSpawnReason | undefined - - constructor( - cause: unknown, - options: { reason?: AgentSessionPreSpawnReason; message?: string } = {} - ) { - super(options.message ?? (cause instanceof Error ? cause.message : String(cause)), { cause }) - this.name = 'AgentSessionPreSpawnError' - this.reason = options.reason ?? (isAgentSessionPreSpawnError(cause) ? cause.reason : undefined) - } -} - -export function isAgentSessionPreSpawnError(error: unknown): error is AgentSessionPreSpawnError { - return error instanceof Error && error.name === 'AgentSessionPreSpawnError' -} - /** The host creates the command’s running turn; the child translator ends it and writes its result. */ export type StructuredAgentSessionCommandRun = { clientMessageId: string