feat: implémentation et QA ticket99 - agent model configuration v2

- backend A-C: VO Codex/Claude, renderers/projecteurs modèle, SecretRef/env, catalogues/use cases/Tauri commands
- frontend D: profils Codex/Claude provider->model->secret, validations, conservation SecretRef
- fix: app-tauri embedded_server isolant IDEA_WEB_ROOT

QA: backend 57/57 tests, frontend 74/74 tests OK
This commit is contained in:
2026-07-26 11:56:25 +02:00
parent 13fb538880
commit dae07d35bb
27 changed files with 2358 additions and 143 deletions

View File

@ -15,6 +15,8 @@ import type {
Agent,
AgentDrift,
AgentProfile,
ClaudeProviderCatalogEntry,
CodexProviderCatalogEntry,
EffectivePermissions,
EmbedderEngines,
EmbedderProfile,
@ -65,6 +67,8 @@ import type {
PermissionGateway,
ProfileGateway,
ProjectGateway,
SaveClaudeProviderProfileInput,
SaveCodexProviderProfileInput,
SaveOpenCodeProviderProfileInput,
SkillGateway,
TemplateGateway,
@ -202,6 +206,37 @@ export class HttpProfileGateway implements ProfileGateway {
},
});
}
listCodexProviders(): Promise<CodexProviderCatalogEntry[]> {
return this.http.invoke<CodexProviderCatalogEntry[]>("list_codex_providers");
}
saveCodexProviderProfile(
input: SaveCodexProviderProfileInput,
): Promise<AgentProfile> {
return this.http.invoke<AgentProfile>("save_codex_provider_profile", {
request: {
profile: input.profile,
providerId: input.providerId,
model: input.model,
apiKey: input.apiKey,
custom: input.custom,
},
});
}
listClaudeProviders(): Promise<ClaudeProviderCatalogEntry[]> {
return this.http.invoke<ClaudeProviderCatalogEntry[]>("list_claude_providers");
}
saveClaudeProviderProfile(
input: SaveClaudeProviderProfileInput,
): Promise<AgentProfile> {
return this.http.invoke<AgentProfile>("save_claude_provider_profile", {
request: {
profile: input.profile,
providerId: input.providerId,
model: input.model,
apiKey: input.apiKey,
},
});
}
}
export class HttpModelServerGateway implements ModelServerGateway {

View File

@ -9,6 +9,8 @@ import type {
AgentDrift,
AppExitWorkGuardState,
AgentProfile,
ClaudeProviderCatalogEntry,
CodexProviderCatalogEntry,
DiagnosticWarning,
DomainEvent,
EmbedderEngines,
@ -103,6 +105,8 @@ import type {
ReattachResult,
RemoteGateway,
ReviewPluginPackageInput,
SaveClaudeProviderProfileInput,
SaveCodexProviderProfileInput,
SaveOpenCodeProviderProfileInput,
SkillGateway,
StoppedLiveAgent,
@ -1250,6 +1254,7 @@ export const MOCK_REFERENCE_PROFILES: AgentProfile[] = [
contextInjection: { strategy: "conventionFile", target: "CLAUDE.md" },
detect: "claude --version",
cwdTemplate: "{projectRoot}",
structuredAdapter: "claude",
},
{
id: "mock-codex",
@ -1259,6 +1264,7 @@ export const MOCK_REFERENCE_PROFILES: AgentProfile[] = [
contextInjection: { strategy: "conventionFile", target: "AGENTS.md" },
detect: "codex --version",
cwdTemplate: "{projectRoot}",
structuredAdapter: "codex",
},
{
id: "mock-opencode",
@ -1291,6 +1297,25 @@ const MOCK_OPENCODE_PROVIDERS: OpenCodeProviderCatalogEntry[] = [
},
];
/** Static mock catalogue mirroring the backend Codex provider list. */
const MOCK_CODEX_PROVIDERS: CodexProviderCatalogEntry[] = [
{
providerId: "openai",
displayName: "OpenAI",
models: ["gpt-5", "gpt-5-mini", "gpt-5-codex", "o3"],
customSupported: true,
},
];
/** Static mock catalogue mirroring the backend Claude provider list. */
const MOCK_CLAUDE_PROVIDERS: ClaudeProviderCatalogEntry[] = [
{
providerId: "anthropic",
displayName: "Anthropic",
models: ["claude-sonnet-4-5", "claude-opus-4-1", "claude-haiku-3-5"],
},
];
/**
* 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
@ -1386,6 +1411,53 @@ export class MockProfileGateway implements ProfileGateway {
this.configured = true;
return structuredClone(saved);
}
async listCodexProviders(): Promise<CodexProviderCatalogEntry[]> {
return structuredClone(MOCK_CODEX_PROVIDERS);
}
async saveCodexProviderProfile(
input: SaveCodexProviderProfileInput,
): Promise<AgentProfile> {
const saved: AgentProfile = {
...structuredClone(input.profile),
codexProvider: {
providerId: input.providerId,
model: input.model,
apiKeyRef:
input.profile.codexProvider?.apiKeyRef ?? `mock-secret-${input.profile.id}`,
custom: input.custom,
},
};
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);
}
async listClaudeProviders(): Promise<ClaudeProviderCatalogEntry[]> {
return structuredClone(MOCK_CLAUDE_PROVIDERS);
}
async saveClaudeProviderProfile(
input: SaveClaudeProviderProfileInput,
): Promise<AgentProfile> {
const saved: AgentProfile = {
...structuredClone(input.profile),
claudeProvider: {
providerId: input.providerId,
model: input.model,
apiKeyRef:
input.profile.claudeProvider?.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

@ -10,6 +10,8 @@ import { invoke } from "@tauri-apps/api/core";
import type {
AgentProfile,
ClaudeProviderCatalogEntry,
CodexProviderCatalogEntry,
FirstRunState,
OpenCodeProviderCatalogEntry,
ProfileAvailability,
@ -17,6 +19,8 @@ import type {
import type {
CloneOpenCodeProfileFromSeedInput,
ProfileGateway,
SaveClaudeProviderProfileInput,
SaveCodexProviderProfileInput,
SaveOpenCodeProviderProfileInput,
} from "@/ports";
@ -78,4 +82,39 @@ export class TauriProfileGateway implements ProfileGateway {
},
});
}
listCodexProviders(): Promise<CodexProviderCatalogEntry[]> {
return invoke<CodexProviderCatalogEntry[]>("list_codex_providers");
}
saveCodexProviderProfile(
input: SaveCodexProviderProfileInput,
): Promise<AgentProfile> {
return invoke<AgentProfile>("save_codex_provider_profile", {
request: {
profile: input.profile,
providerId: input.providerId,
model: input.model,
apiKey: input.apiKey,
custom: input.custom,
},
});
}
listClaudeProviders(): Promise<ClaudeProviderCatalogEntry[]> {
return invoke<ClaudeProviderCatalogEntry[]>("list_claude_providers");
}
saveClaudeProviderProfile(
input: SaveClaudeProviderProfileInput,
): Promise<AgentProfile> {
return invoke<AgentProfile>("save_claude_provider_profile", {
request: {
profile: input.profile,
providerId: input.providerId,
model: input.model,
apiKey: input.apiKey,
},
});
}
}