import * as Effect from "effect/Effect"; import { MAX_MEMORIES } from "../shared/memory.ts"; import { MAX_TASKS } from "../shared/tasks.ts"; import { AgentFailure, agentCall } from "./agent-io.ts"; import { MCP_OPERATION_ERRORS } from "../shared/mcp.ts"; const messages = { skill_permissions_changed: "Skill permissions changed. Reload before saving.", skill_bundled: "Bundled Skills cannot be removed. Disable the Skill instead.", skill_file_missing: "This file is not in the current Skill package.", skill_invalid_request: "Choose a current Skill and a valid package file.", skill_changed: "Skill changed. Reload Skills before saving.", skill_invalid_origin: "Installed Skills need an upload or URL origin.", skill_full: "Installed Skill limit reached (64). Remove a Skill first.", skill_unreadable: "Skill package is missing or failed its checksum. Reinstall it.", ...MCP_OPERATION_ERRORS, invalid_model: "Select a supported provider and model", invalid_effort: "Select a supported effort for this model", provider_disabled: "Enable OpenRouter in Settings before using this provider", credential_unreadable: "Stored provider API key cannot be read; replace it in settings", credential_save_failed: "Provider API key could not be saved", conversation_missing: "Conversation not found", memory_deleted: "Memory was deleted", memory_full: `Memory limit reached (${MAX_MEMORIES}). Delete a fact first.`, memory_edit_conflict: "Memory changed or was deleted. Reload memories before editing.", memory_delete_conflict: "Memory changed. Reload memories before deleting.", task_deleted: "Task was deleted", task_creation_conflict: "Task creation ID was already used for different input", task_past: "New one-off task needs a future instant", task_full: `Task limit reached (${MAX_TASKS})`, }; export type OperationError = keyof typeof messages; export class OperationFailure extends AgentFailure { readonly code: OperationError; constructor(code: OperationError) { super({ message: messages[code] }); this.code = code; } } export type OperationResult = { ok: true; value: A } | { ok: false; error: OperationError }; // Like the registry boundary, transport declared failures as plain codes. // Unexpected errors stay rejected and are sanitized by the calling RPC adapter. export function operationResult( program: Effect.Effect, ): Promise> { return Effect.runPromise( program.pipe( Effect.catchDefect((error) => error instanceof OperationFailure ? Effect.fail(error) : Effect.die(error), ), Effect.map((value): OperationResult => ({ ok: true, value })), Effect.catch((error) => error instanceof OperationFailure ? Effect.succeed>({ ok: false, error: error.code }) : Effect.fail(error), ), ), ); } export function operationCall(call: () => PromiseLike>) { return agentCall(call).pipe( Effect.flatMap((result) => result.ok ? Effect.succeed(result.value) : Effect.fail(new OperationFailure(result.error)), ), ); }