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)),
),
);
}