/** * HTTP request/response gateways for the web transport — ticket #13, lot F1. * * Each class implements a UI port by forwarding the **exact** backend command * name + camelCase argument envelope its Tauri sibling uses (the shared backend * core, so identical contracts) through the generic {@link HttpInvoker}. No DTO * shape is re-derived here; only the transport changes (Tauri `invoke` → HTTP * `POST /api/invoke`). Normalizers that are transport-neutral (`workState`, * `conversation`) are reused from the sibling adapters. * * Lives in `src/adapters/**`; touches no `@tauri-apps/api`. */ import type { Agent, AgentDrift, AgentProfile, EffectivePermissions, EmbedderEngines, EmbedderProfile, FirstRunState, GitBranches, GitCommit, GitFileStatus, GraphCommit, LayoutKind, LayoutList, LayoutOperation, LayoutTree, LocalModelServerConfig, Memory, MemoryIndexEntry, MemoryLink, MemoryType, McpToolPolicy, ModelServerCommandPreview, OpenCodeProviderCatalogEntry, PermissionSet, Project, ProjectMcpToolPermissions, ProjectPermissions, ProjectWorkState, ProjectSystemPermissions, ProfileAvailability, ProfileModelCatalog, ResolvedAgentSystemPermissions, SystemPermissionSet, Skill, SkillScope, Template, TurnPage, } from "@/domain"; import type { CloneProfileFromSeedInput, CloneOpenCodeProfileFromSeedInput, ConversationGateway, ConversationPageRequest, CreateMemoryInput, CreateSkillInput, CreateTemplateInput, EmbedderGateway, GitGateway, InputGateway, LayoutGateway, MemoryGateway, ModelServerGateway, PermissionGateway, ProfileGateway, ProjectGateway, SaveOpenCodeProviderProfileInput, SkillGateway, TemplateGateway, WorkStateGateway, BackgroundTaskAttachment, } from "@/ports"; import { normalizeProjectWorkState } from "../workStateNormalization"; import { unsupportedOnWeb } from "./unsupported"; import { normalizeTurnPage } from "../conversationNormalization"; import { normalizeProfileModelCatalog } from "../profileCatalog"; import type { HttpInvoker } from "./httpInvoker"; export class HttpProjectGateway implements ProjectGateway { constructor(private readonly http: HttpInvoker) {} listProjects(): Promise { return this.http.invoke("list_projects"); } createProject(name: string, root: string): Promise { return this.http.invoke("create_project", { request: { name, root } }); } openProject(projectId: string): Promise { return this.http.invoke("open_project", { projectId }); } async closeProject(projectId: string): Promise { await this.http.invoke("close_project", { projectId }); } readProjectContext(projectId: string): Promise { return this.http.invoke("read_project_context", { projectId }); } async updateProjectContext(projectId: string, content: string): Promise { await this.http.invoke("update_project_context", { request: { projectId, content } }); } } export class HttpLayoutGateway implements LayoutGateway { constructor(private readonly http: HttpInvoker) {} loadLayout(projectId: string, layoutId?: string): Promise { return this.http.invoke("load_layout", { projectId, layoutId }); } mutateLayout( projectId: string, operation: LayoutOperation, layoutId?: string, ): Promise { return this.http.invoke("mutate_layout", { projectId, layoutId, operation }); } listLayouts(projectId: string): Promise { return this.http.invoke("list_layouts", { projectId }); } createLayout(projectId: string, name: string, kind?: LayoutKind): Promise<{ layoutId: string }> { return this.http.invoke<{ layoutId: string }>("create_layout", { request: { projectId, name, kind } }); } renameLayout(projectId: string, layoutId: string, name: string): Promise { return this.http.invoke("rename_layout", { request: { projectId, layoutId, name } }); } deleteLayout(projectId: string, layoutId: string): Promise<{ activeId: string }> { return this.http.invoke<{ activeId: string }>("delete_layout", { request: { projectId, layoutId } }); } setActiveLayout(projectId: string, layoutId: string): Promise<{ activeId: string }> { return this.http.invoke<{ activeId: string }>("set_active_layout", { request: { projectId, layoutId } }); } } export class HttpGitGateway implements GitGateway { constructor(private readonly http: HttpInvoker) {} status(projectId: string): Promise { return this.http.invoke("git_status", { projectId }); } async stage(projectId: string, path: string): Promise { await this.http.invoke("git_stage", { request: { projectId, path } }); } async unstage(projectId: string, path: string): Promise { await this.http.invoke("git_unstage", { request: { projectId, path } }); } commit(projectId: string, message: string): Promise { return this.http.invoke("git_commit", { request: { projectId, message } }); } branches(projectId: string): Promise { return this.http.invoke("git_branches", { projectId }); } async checkout(projectId: string, branch: string): Promise { await this.http.invoke("git_checkout", { request: { projectId, branch } }); } log(projectId: string, limit: number): Promise { return this.http.invoke("git_log", { projectId, limit }); } async init(projectId: string): Promise { await this.http.invoke("git_init", { projectId }); } graph(projectId: string, limit: number): Promise { return this.http.invoke("git_graph", { projectId, limit }); } } export class HttpProfileGateway implements ProfileGateway { constructor(private readonly http: HttpInvoker) {} firstRunState(): Promise { return this.http.invoke("first_run_state"); } referenceProfiles(): Promise { return this.http.invoke("reference_profiles"); } detectProfiles(candidates: AgentProfile[]): Promise { return this.http.invoke("detect_profiles", { request: { candidates } }); } listProfiles(): Promise { return this.http.invoke("list_profiles"); } saveProfile(profile: AgentProfile): Promise { return this.http.invoke("save_profile", { request: { profile } }); } async deleteProfile(profileId: string): Promise { await this.http.invoke("delete_profile", { profileId }); } cloneProfileFromSeed(input: CloneProfileFromSeedInput): Promise { return this.http.invoke("clone_profile_from_seed", { request: { seedProfileId: input.seedProfileId, name: input.name, model: input.model }, }); } async listClaudeModels(): Promise { return normalizeProfileModelCatalog(await this.http.invoke("list_claude_models")); } async listCodexModels(): Promise { return normalizeProfileModelCatalog(await this.http.invoke("list_codex_models")); } configureProfiles(profiles: AgentProfile[]): Promise { return this.http.invoke("configure_profiles", { request: { profiles } }); } cloneOpenCodeProfileFromSeed( input: CloneOpenCodeProfileFromSeedInput = {}, ): Promise { return this.http.invoke("clone_opencode_profile_from_seed", { request: { name: input.name, opencode: input.opencode }, }); } listOpenCodeProviders(): Promise { return this.http.invoke("list_opencode_providers"); } saveOpenCodeProviderProfile( input: SaveOpenCodeProviderProfileInput, ): Promise { return this.http.invoke("save_opencode_provider_profile", { request: { profile: input.profile, providerId: input.providerId, model: input.model, apiKey: input.apiKey, custom: input.custom, }, }); } } export class HttpModelServerGateway implements ModelServerGateway { constructor(private readonly http: HttpInvoker) {} listModelServers(): Promise { return this.http.invoke("list_model_servers"); } saveModelServer(config: LocalModelServerConfig): Promise { return this.http.invoke("save_model_server", { request: { config } }); } async deleteModelServer(serverId: string): Promise { await this.http.invoke("delete_model_server", { serverId }); } async deleteModelArtifact(serverId: string): Promise { await this.http.invoke("delete_model_artifact", { serverId }); } previewModelServerCommand(config: LocalModelServerConfig): Promise { return this.http.invoke("preview_model_server_command", { config }); } } export class HttpTemplateGateway implements TemplateGateway { constructor(private readonly http: HttpInvoker) {} listTemplates(): Promise { return this.http.invoke("list_templates"); } createTemplate(input: CreateTemplateInput): Promise