feat(frontend): support des providers OpenCode cloud (#92)

Ajoute l'UI de configuration des providers OpenCode cloud dans le wizard
first-run (sélection, édition, sauvegarde, suppression), le domaine et
les ports associés, ainsi que les adapters HTTP/mock correspondants.

Build + 952 tests verts, comportements clés vérifiés en exécution réelle,
y compris un test de couverture ajouté pour le parcours d'édition.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 08:18:02 +02:00
parent 23a3c2788f
commit c181b43d04
8 changed files with 649 additions and 6 deletions

View File

@ -34,6 +34,7 @@ import type {
MemoryType,
McpToolPolicy,
ModelServerCommandPreview,
OpenCodeProviderCatalogEntry,
PermissionSet,
Project,
ProjectMcpToolPermissions,
@ -61,6 +62,7 @@ import type {
PermissionGateway,
ProfileGateway,
ProjectGateway,
SaveOpenCodeProviderProfileInput,
SkillGateway,
TemplateGateway,
WorkStateGateway,
@ -181,6 +183,21 @@ export class HttpProfileGateway implements ProfileGateway {
request: { name: input.name, opencode: input.opencode },
});
}
listOpenCodeProviders(): Promise<OpenCodeProviderCatalogEntry[]> {
return this.http.invoke<OpenCodeProviderCatalogEntry[]>("list_opencode_providers");
}
saveOpenCodeProviderProfile(
input: SaveOpenCodeProviderProfileInput,
): Promise<AgentProfile> {
return this.http.invoke<AgentProfile>("save_opencode_provider_profile", {
request: {
profile: input.profile,
providerId: input.providerId,
model: input.model,
apiKey: input.apiKey,
},
});
}
}
export class HttpModelServerGateway implements ModelServerGateway {

View File

@ -34,6 +34,7 @@ import type {
MemoryType,
McpToolCatalogue,
McpToolPolicy,
OpenCodeProviderCatalogEntry,
EffectivePermissions,
PairedDevice,
PairingCode,
@ -99,6 +100,7 @@ import type {
ReattachResult,
RemoteGateway,
ReviewPluginPackageInput,
SaveOpenCodeProviderProfileInput,
SkillGateway,
StoppedLiveAgent,
SystemGateway,
@ -1272,6 +1274,20 @@ export const MOCK_REFERENCE_PROFILES: AgentProfile[] = [
},
];
/** Static mock catalogue mirroring the backend OpenCode cloud-provider list. */
const MOCK_OPENCODE_PROVIDERS: OpenCodeProviderCatalogEntry[] = [
{
providerId: "anthropic",
displayName: "Anthropic",
models: ["claude-sonnet-5", "claude-opus-4-8", "claude-haiku-4-5"],
},
{
providerId: "openrouter",
displayName: "OpenRouter",
models: ["openrouter/auto", "qwen/qwen3-coder"],
},
];
/**
* In-memory profiles gateway. Tracks configured profiles and a first-run flag so
* the wizard can be driven and tested fully offline. By default it reports the
@ -1342,6 +1358,30 @@ export class MockProfileGateway implements ProfileGateway {
opencode: input.opencode ?? seed.opencode,
});
}
async listOpenCodeProviders(): Promise<OpenCodeProviderCatalogEntry[]> {
return structuredClone(MOCK_OPENCODE_PROVIDERS);
}
async saveOpenCodeProviderProfile(
input: SaveOpenCodeProviderProfileInput,
): Promise<AgentProfile> {
const saved: AgentProfile = {
...structuredClone(input.profile),
opencode: undefined,
opencodeProvider: {
providerId: input.providerId,
model: input.model,
// The mock never seals a real secret; the ref is opaque either way.
apiKeyRef: `mock-secret-${input.profile.id}`,
},
};
const i = this.profiles.findIndex((p) => p.id === saved.id);
if (i >= 0) this.profiles[i] = saved;
else this.profiles.push(saved);
this.configured = true;
return structuredClone(saved);
}
}
/**

View File

@ -8,8 +8,17 @@
import { invoke } from "@tauri-apps/api/core";
import type { AgentProfile, FirstRunState, ProfileAvailability } from "@/domain";
import type { CloneOpenCodeProfileFromSeedInput, ProfileGateway } from "@/ports";
import type {
AgentProfile,
FirstRunState,
OpenCodeProviderCatalogEntry,
ProfileAvailability,
} from "@/domain";
import type {
CloneOpenCodeProfileFromSeedInput,
ProfileGateway,
SaveOpenCodeProviderProfileInput,
} from "@/ports";
export class TauriProfileGateway implements ProfileGateway {
firstRunState(): Promise<FirstRunState> {
@ -51,4 +60,21 @@ export class TauriProfileGateway implements ProfileGateway {
request: { name: input.name, opencode: input.opencode },
});
}
listOpenCodeProviders(): Promise<OpenCodeProviderCatalogEntry[]> {
return invoke<OpenCodeProviderCatalogEntry[]>("list_opencode_providers");
}
saveOpenCodeProviderProfile(
input: SaveOpenCodeProviderProfileInput,
): Promise<AgentProfile> {
return invoke<AgentProfile>("save_opencode_provider_profile", {
request: {
profile: input.profile,
providerId: input.providerId,
model: input.model,
apiKey: input.apiKey,
},
});
}
}