feat(provider-adapter): implement multi-provider abstraction layer (Phase 1)
Add provider types, registry, capabilities, and message adapter registry. Rename sdk-messages→claude-messages, agent-runner→claude-runner, ClaudeSession→AgentSession. Update Rust AgentQueryOptions with provider and provider_config fields. Capability-driven AgentPane rendering.
This commit is contained in:
parent
d8d7ad16f3
commit
1efcb13869
27 changed files with 276 additions and 49 deletions
|
|
@ -21,7 +21,8 @@
|
|||
CostContent,
|
||||
ErrorContent,
|
||||
StatusContent,
|
||||
} from '../../adapters/sdk-messages';
|
||||
} from '../../adapters/claude-messages';
|
||||
import type { ProviderId, ProviderCapabilities } from '../../providers/types';
|
||||
|
||||
// Tool-aware truncation limits
|
||||
const MAX_BASH_LINES = 500;
|
||||
|
|
@ -29,15 +30,28 @@
|
|||
const MAX_GLOB_LINES = 20;
|
||||
const MAX_DEFAULT_LINES = 30;
|
||||
|
||||
// Default capabilities (Claude — all enabled)
|
||||
const DEFAULT_CAPABILITIES: ProviderCapabilities = {
|
||||
hasProfiles: true,
|
||||
hasSkills: true,
|
||||
hasModelSelection: true,
|
||||
hasSandbox: false,
|
||||
supportsSubagents: true,
|
||||
supportsCost: true,
|
||||
supportsResume: true,
|
||||
};
|
||||
|
||||
interface Props {
|
||||
sessionId: string;
|
||||
prompt?: string;
|
||||
cwd?: string;
|
||||
profile?: string;
|
||||
provider?: ProviderId;
|
||||
capabilities?: ProviderCapabilities;
|
||||
onExit?: () => void;
|
||||
}
|
||||
|
||||
let { sessionId, prompt: initialPrompt = '', cwd: initialCwd, profile: profileName, onExit }: Props = $props();
|
||||
let { sessionId, prompt: initialPrompt = '', cwd: initialCwd, profile: profileName, provider: providerId = 'claude', capabilities = DEFAULT_CAPABILITIES, onExit }: Props = $props();
|
||||
|
||||
let session = $derived(getAgentSession(sessionId));
|
||||
let inputPrompt = $state(initialPrompt);
|
||||
|
|
@ -106,9 +120,10 @@
|
|||
|
||||
onMount(async () => {
|
||||
await getHighlighter();
|
||||
// Only load profiles/skills for providers that support them
|
||||
const [profileList, skillList] = await Promise.all([
|
||||
listProfiles().catch(() => []),
|
||||
listSkills().catch(() => []),
|
||||
capabilities.hasProfiles ? listProfiles().catch(() => []) : Promise.resolve([]),
|
||||
capabilities.hasSkills ? listSkills().catch(() => []) : Promise.resolve([]),
|
||||
]);
|
||||
profiles = profileList;
|
||||
skills = skillList;
|
||||
|
|
@ -144,6 +159,7 @@
|
|||
|
||||
const profile = profileName ? profiles.find(p => p.name === profileName) : undefined;
|
||||
await queryAgent({
|
||||
provider: providerId,
|
||||
session_id: sessionId,
|
||||
prompt: text,
|
||||
cwd: initialCwd || undefined,
|
||||
|
|
@ -335,7 +351,7 @@
|
|||
{#if msg.type === 'init'}
|
||||
<div class="msg-init">
|
||||
<span class="label">Session started</span>
|
||||
<span class="model">{(msg.content as import('../../adapters/sdk-messages').InitContent).model}</span>
|
||||
<span class="model">{(msg.content as import('../../adapters/claude-messages').InitContent).model}</span>
|
||||
</div>
|
||||
{:else if msg.type === 'text'}
|
||||
{@const textContent = (msg.content as TextContent).text}
|
||||
|
|
@ -490,19 +506,21 @@
|
|||
</svg>
|
||||
New Session
|
||||
</button>
|
||||
<button class="session-btn session-btn-continue" onclick={() => promptRef?.focus()}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
Continue
|
||||
</button>
|
||||
{#if capabilities.supportsResume}
|
||||
<button class="session-btn session-btn-continue" onclick={() => promptRef?.focus()}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
Continue
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Unified prompt input -->
|
||||
<div class="prompt-container" class:disabled={isRunning}>
|
||||
<div class="prompt-wrapper">
|
||||
{#if showSkillMenu && filteredSkills.length > 0}
|
||||
{#if capabilities.hasSkills && showSkillMenu && filteredSkills.length > 0}
|
||||
<div class="skill-menu">
|
||||
{#each filteredSkills as skill, i (skill.name)}
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
ErrorContent,
|
||||
TextContent,
|
||||
AgentMessage,
|
||||
} from '../../adapters/sdk-messages';
|
||||
} from '../../adapters/claude-messages';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
setAgentSdkSessionId,
|
||||
getAgentSession,
|
||||
} from '../../stores/agents.svelte';
|
||||
import type { AgentMessage } from '../../adapters/sdk-messages';
|
||||
import type { AgentMessage } from '../../adapters/claude-messages';
|
||||
import { getProvider, getDefaultProviderId } from '../../providers/registry.svelte';
|
||||
import AgentPane from '../Agent/AgentPane.svelte';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -26,6 +27,9 @@
|
|||
|
||||
let { project, onsessionid }: Props = $props();
|
||||
|
||||
let providerId = $derived(project.provider ?? getDefaultProviderId());
|
||||
let providerMeta = $derived(getProvider(providerId));
|
||||
|
||||
let sessionId = $state(crypto.randomUUID());
|
||||
let lastState = $state<ProjectAgentState | null>(null);
|
||||
let loading = $state(true);
|
||||
|
|
@ -35,7 +39,7 @@
|
|||
sessionId = crypto.randomUUID();
|
||||
hasRestoredHistory = false;
|
||||
lastState = null;
|
||||
registerSessionProject(sessionId, project.id);
|
||||
registerSessionProject(sessionId, project.id, providerId);
|
||||
trackProject(project.id, sessionId);
|
||||
onsessionid?.(sessionId);
|
||||
}
|
||||
|
|
@ -70,7 +74,7 @@
|
|||
} finally {
|
||||
loading = false;
|
||||
// Register session -> project mapping for persistence + health tracking
|
||||
registerSessionProject(sessionId, project.id);
|
||||
registerSessionProject(sessionId, project.id, providerId);
|
||||
trackProject(project.id, sessionId);
|
||||
onsessionid?.(sessionId);
|
||||
}
|
||||
|
|
@ -112,7 +116,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="claude-session">
|
||||
<div class="agent-session">
|
||||
{#if loading}
|
||||
<div class="loading-state">Loading session...</div>
|
||||
{:else}
|
||||
|
|
@ -120,13 +124,15 @@
|
|||
{sessionId}
|
||||
cwd={project.cwd}
|
||||
profile={project.profile || undefined}
|
||||
provider={providerId}
|
||||
capabilities={providerMeta?.capabilities}
|
||||
onExit={handleNewSession}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.claude-session {
|
||||
.agent-session {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { getAgentSession, getTotalCost, type AgentSession } from '../../stores/agents.svelte';
|
||||
import type { AgentMessage, ToolCallContent, CostContent, CompactionContent } from '../../adapters/sdk-messages';
|
||||
import type { AgentMessage, ToolCallContent, CostContent, CompactionContent } from '../../adapters/claude-messages';
|
||||
import { extractFilePaths } from '../../utils/tool-files';
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import type { ProjectConfig } from '../../types/groups';
|
||||
import { PROJECT_ACCENTS } from '../../types/groups';
|
||||
import ProjectHeader from './ProjectHeader.svelte';
|
||||
import ClaudeSession from './ClaudeSession.svelte';
|
||||
import AgentSession from './AgentSession.svelte';
|
||||
import TerminalTabs from './TerminalTabs.svelte';
|
||||
import TeamAgentsPanel from './TeamAgentsPanel.svelte';
|
||||
import ProjectFiles from './ProjectFiles.svelte';
|
||||
|
|
@ -148,7 +148,7 @@
|
|||
<div class="project-content-area">
|
||||
<!-- PERSISTED-EAGER: always mounted, toggled via display -->
|
||||
<div class="content-pane" style:display={activeTab === 'model' ? 'flex' : 'none'}>
|
||||
<ClaudeSession {project} onsessionid={(id) => mainSessionId = id} />
|
||||
<AgentSession {project} onsessionid={(id) => mainSessionId = id} />
|
||||
{#if mainSessionId}
|
||||
<TeamAgentsPanel {mainSessionId} />
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue