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
|
|
@ -5,6 +5,8 @@
|
|||
import { isDetachedMode, getDetachedConfig } from './lib/utils/detach';
|
||||
import { startAgentDispatcher, stopAgentDispatcher } from './lib/agent-dispatcher';
|
||||
import { startHealthTick, stopHealthTick, clearHealthTracking } from './lib/stores/health.svelte';
|
||||
import { registerProvider } from './lib/providers/registry.svelte';
|
||||
import { CLAUDE_PROVIDER } from './lib/providers/claude';
|
||||
import { loadWorkspace, getActiveTab, setActiveTab, setActiveProject, getEnabledProjects } from './lib/stores/workspace.svelte';
|
||||
|
||||
// Workspace components
|
||||
|
|
@ -65,6 +67,7 @@
|
|||
getSetting('project_max_aspect').then(v => {
|
||||
if (v) document.documentElement.style.setProperty('--project-max-aspect', v);
|
||||
});
|
||||
registerProvider(CLAUDE_PROVIDER);
|
||||
startAgentDispatcher();
|
||||
startHealthTick();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@
|
|||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
|
||||
|
||||
import type { ProviderId } from '../providers/types';
|
||||
|
||||
export interface AgentQueryOptions {
|
||||
provider?: ProviderId;
|
||||
session_id: string;
|
||||
prompt: string;
|
||||
cwd?: string;
|
||||
|
|
@ -17,6 +20,7 @@ export interface AgentQueryOptions {
|
|||
model?: string;
|
||||
claude_config_dir?: string;
|
||||
additional_directories?: string[];
|
||||
provider_config?: Record<string, unknown>;
|
||||
remote_machine_id?: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { adaptSDKMessage } from './sdk-messages';
|
||||
import type { InitContent, TextContent, ThinkingContent, ToolCallContent, ToolResultContent, StatusContent, CostContent } from './sdk-messages';
|
||||
import { adaptSDKMessage } from './claude-messages';
|
||||
import type { InitContent, TextContent, ThinkingContent, ToolCallContent, ToolResultContent, StatusContent, CostContent } from './claude-messages';
|
||||
|
||||
// Mock crypto.randomUUID for deterministic IDs when uuid is missing
|
||||
beforeEach(() => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// SDK Message Adapter — insulates UI from Claude Agent SDK wire format changes
|
||||
// This is the ONLY place that knows SDK internals.
|
||||
// Claude Message Adapter — transforms Claude Agent SDK wire format to internal AgentMessage format
|
||||
// This is the ONLY place that knows Claude SDK internals.
|
||||
|
||||
export type AgentMessageType =
|
||||
| 'init'
|
||||
29
v2/src/lib/adapters/message-adapters.ts
Normal file
29
v2/src/lib/adapters/message-adapters.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Message Adapter Registry — routes raw provider messages to the correct parser
|
||||
// Each provider registers its own adapter; the dispatcher calls adaptMessage()
|
||||
|
||||
import type { AgentMessage } from './claude-messages';
|
||||
import type { ProviderId } from '../providers/types';
|
||||
import { adaptSDKMessage } from './claude-messages';
|
||||
|
||||
/** Function signature for a provider message adapter */
|
||||
export type MessageAdapter = (raw: Record<string, unknown>) => AgentMessage[];
|
||||
|
||||
const adapters = new Map<ProviderId, MessageAdapter>();
|
||||
|
||||
/** Register a message adapter for a provider */
|
||||
export function registerMessageAdapter(providerId: ProviderId, adapter: MessageAdapter): void {
|
||||
adapters.set(providerId, adapter);
|
||||
}
|
||||
|
||||
/** Adapt a raw message using the appropriate provider adapter */
|
||||
export function adaptMessage(providerId: ProviderId, raw: Record<string, unknown>): AgentMessage[] {
|
||||
const adapter = adapters.get(providerId);
|
||||
if (!adapter) {
|
||||
console.warn(`No message adapter for provider: ${providerId}, falling back to claude`);
|
||||
return adaptSDKMessage(raw);
|
||||
}
|
||||
return adapter(raw);
|
||||
}
|
||||
|
||||
// Register Claude adapter by default
|
||||
registerMessageAdapter('claude', adaptSDKMessage);
|
||||
26
v2/src/lib/adapters/provider-bridge.ts
Normal file
26
v2/src/lib/adapters/provider-bridge.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Provider Bridge — generic adapter that delegates to provider-specific bridges
|
||||
// Currently only Claude is implemented; future providers add their own bridge files
|
||||
|
||||
import type { ProviderId } from '../providers/types';
|
||||
import { listProfiles as claudeListProfiles, listSkills as claudeListSkills, readSkill as claudeReadSkill, type ClaudeProfile, type ClaudeSkill } from './claude-bridge';
|
||||
|
||||
// Re-export types for consumers
|
||||
export type { ClaudeProfile, ClaudeSkill };
|
||||
|
||||
/** List profiles for a given provider (only Claude supports this) */
|
||||
export async function listProviderProfiles(provider: ProviderId): Promise<ClaudeProfile[]> {
|
||||
if (provider === 'claude') return claudeListProfiles();
|
||||
return [];
|
||||
}
|
||||
|
||||
/** List skills for a given provider (only Claude supports this) */
|
||||
export async function listProviderSkills(provider: ProviderId): Promise<ClaudeSkill[]> {
|
||||
if (provider === 'claude') return claudeListSkills();
|
||||
return [];
|
||||
}
|
||||
|
||||
/** Read a skill file (only Claude supports this) */
|
||||
export async function readProviderSkill(provider: ProviderId, path: string): Promise<string> {
|
||||
if (provider === 'claude') return claudeReadSkill(path);
|
||||
return '';
|
||||
}
|
||||
|
|
@ -51,8 +51,10 @@ vi.mock('./adapters/agent-bridge', () => ({
|
|||
restartAgent: (...args: unknown[]) => mockRestartAgent(...args),
|
||||
}));
|
||||
|
||||
vi.mock('./adapters/sdk-messages', () => ({
|
||||
adaptSDKMessage: vi.fn((raw: Record<string, unknown>) => {
|
||||
vi.mock('./providers/types', () => ({}));
|
||||
|
||||
vi.mock('./adapters/message-adapters', () => ({
|
||||
adaptMessage: vi.fn((_provider: string, raw: Record<string, unknown>) => {
|
||||
if (raw.type === 'system' && raw.subtype === 'init') {
|
||||
return [{
|
||||
id: 'msg-1',
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
// Single listener that routes sidecar messages to the correct agent session
|
||||
|
||||
import { onSidecarMessage, onSidecarExited, restartAgent, type SidecarMessage } from './adapters/agent-bridge';
|
||||
import { adaptSDKMessage } from './adapters/sdk-messages';
|
||||
import type { InitContent, CostContent, ToolCallContent } from './adapters/sdk-messages';
|
||||
import { adaptMessage } from './adapters/message-adapters';
|
||||
import type { InitContent, CostContent, ToolCallContent } from './adapters/claude-messages';
|
||||
import type { ProviderId } from './providers/types';
|
||||
import {
|
||||
updateAgentStatus,
|
||||
setAgentSdkSessionId,
|
||||
|
|
@ -34,14 +35,18 @@ let unlistenExit: (() => void) | null = null;
|
|||
// Map sessionId -> projectId for persistence routing
|
||||
const sessionProjectMap = new Map<string, string>();
|
||||
|
||||
// Map sessionId -> provider for message adapter routing
|
||||
const sessionProviderMap = new Map<string, ProviderId>();
|
||||
|
||||
// Map sessionId -> start timestamp for metrics
|
||||
const sessionStartTimes = new Map<string, number>();
|
||||
|
||||
// In-flight persistence counter — prevents teardown from racing with async saves
|
||||
let pendingPersistCount = 0;
|
||||
|
||||
export function registerSessionProject(sessionId: string, projectId: string): void {
|
||||
export function registerSessionProject(sessionId: string, projectId: string, provider: ProviderId = 'claude'): void {
|
||||
sessionProjectMap.set(sessionId, projectId);
|
||||
sessionProviderMap.set(sessionId, provider);
|
||||
}
|
||||
|
||||
// Sidecar liveness — checked by UI components
|
||||
|
|
@ -149,7 +154,8 @@ const SUBAGENT_TOOL_NAMES = new Set(['Agent', 'Task', 'dispatch_agent']);
|
|||
const toolUseToChildPane = new Map<string, string>();
|
||||
|
||||
function handleAgentEvent(sessionId: string, event: Record<string, unknown>): void {
|
||||
const messages = adaptSDKMessage(event);
|
||||
const provider = sessionProviderMap.get(sessionId) ?? 'claude';
|
||||
const messages = adaptMessage(provider, event);
|
||||
|
||||
// Route messages with parentId to the appropriate child pane
|
||||
const mainMessages: typeof messages = [];
|
||||
|
|
@ -401,5 +407,6 @@ export function stopAgentDispatcher(): void {
|
|||
// Clear routing maps to prevent unbounded memory growth
|
||||
toolUseToChildPane.clear();
|
||||
sessionProjectMap.clear();
|
||||
sessionProviderMap.clear();
|
||||
sessionStartTimes.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
20
v2/src/lib/providers/claude.ts
Normal file
20
v2/src/lib/providers/claude.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Claude Provider — metadata and capabilities for Claude Code
|
||||
|
||||
import type { ProviderMeta } from './types';
|
||||
|
||||
export const CLAUDE_PROVIDER: ProviderMeta = {
|
||||
id: 'claude',
|
||||
name: 'Claude Code',
|
||||
description: 'Anthropic Claude Code agent via SDK',
|
||||
capabilities: {
|
||||
hasProfiles: true,
|
||||
hasSkills: true,
|
||||
hasModelSelection: true,
|
||||
hasSandbox: false,
|
||||
supportsSubagents: true,
|
||||
supportsCost: true,
|
||||
supportsResume: true,
|
||||
},
|
||||
sidecarRunner: 'claude-runner.mjs',
|
||||
defaultModel: 'claude-sonnet-4-20250514',
|
||||
};
|
||||
26
v2/src/lib/providers/registry.svelte.ts
Normal file
26
v2/src/lib/providers/registry.svelte.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Provider Registry — singleton registry of available providers (Svelte 5 runes)
|
||||
|
||||
import type { ProviderId, ProviderMeta } from './types';
|
||||
|
||||
const providers = $state(new Map<ProviderId, ProviderMeta>());
|
||||
|
||||
export function registerProvider(meta: ProviderMeta): void {
|
||||
providers.set(meta.id, meta);
|
||||
}
|
||||
|
||||
export function getProvider(id: ProviderId): ProviderMeta | undefined {
|
||||
return providers.get(id);
|
||||
}
|
||||
|
||||
export function getProviders(): ProviderMeta[] {
|
||||
return Array.from(providers.values());
|
||||
}
|
||||
|
||||
export function getDefaultProviderId(): ProviderId {
|
||||
return 'claude';
|
||||
}
|
||||
|
||||
/** Check if a specific provider is registered */
|
||||
export function hasProvider(id: ProviderId): boolean {
|
||||
return providers.has(id);
|
||||
}
|
||||
34
v2/src/lib/providers/types.ts
Normal file
34
v2/src/lib/providers/types.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Provider abstraction types — defines the interface for multi-provider agent support
|
||||
|
||||
export type ProviderId = 'claude' | 'codex' | 'ollama';
|
||||
|
||||
/** What a provider can do — UI gates features on these flags */
|
||||
export interface ProviderCapabilities {
|
||||
hasProfiles: boolean;
|
||||
hasSkills: boolean;
|
||||
hasModelSelection: boolean;
|
||||
hasSandbox: boolean;
|
||||
supportsSubagents: boolean;
|
||||
supportsCost: boolean;
|
||||
supportsResume: boolean;
|
||||
}
|
||||
|
||||
/** Static metadata about a provider */
|
||||
export interface ProviderMeta {
|
||||
id: ProviderId;
|
||||
name: string;
|
||||
description: string;
|
||||
capabilities: ProviderCapabilities;
|
||||
/** Name of the sidecar runner file (e.g. 'claude-runner.mjs') */
|
||||
sidecarRunner: string;
|
||||
/** Default model identifier, if applicable */
|
||||
defaultModel?: string;
|
||||
}
|
||||
|
||||
/** Per-provider configuration (stored in settings) */
|
||||
export interface ProviderSettings {
|
||||
enabled: boolean;
|
||||
defaultModel?: string;
|
||||
/** Provider-specific config blob */
|
||||
config: Record<string, unknown>;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Agent tracking state — Svelte 5 runes
|
||||
// Manages agent session lifecycle and message history
|
||||
|
||||
import type { AgentMessage } from '../adapters/sdk-messages';
|
||||
import type { AgentMessage } from '../adapters/claude-messages';
|
||||
|
||||
export type AgentStatus = 'idle' | 'starting' | 'running' | 'done' | 'error';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import type { ProviderId } from '../providers/types';
|
||||
|
||||
export interface ProjectConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
@ -7,6 +9,8 @@ export interface ProjectConfig {
|
|||
cwd: string;
|
||||
profile: string;
|
||||
enabled: boolean;
|
||||
/** Agent provider for this project (defaults to 'claude') */
|
||||
provider?: ProviderId;
|
||||
/** When true, agents for this project use git worktrees for isolation */
|
||||
useWorktrees?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { buildAgentTree, countTreeNodes, subtreeCost } from './agent-tree';
|
||||
import type { AgentMessage, ToolCallContent, ToolResultContent } from '../adapters/sdk-messages';
|
||||
import type { AgentMessage, ToolCallContent, ToolResultContent } from '../adapters/claude-messages';
|
||||
import type { AgentTreeNode } from './agent-tree';
|
||||
|
||||
// Helper to create typed AgentMessages
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Agent tree builder — constructs hierarchical tree from agent messages
|
||||
// Subagents are identified by parent_tool_use_id on their messages
|
||||
|
||||
import type { AgentMessage, ToolCallContent, CostContent } from '../adapters/sdk-messages';
|
||||
import type { AgentMessage, ToolCallContent, CostContent } from '../adapters/claude-messages';
|
||||
|
||||
export interface AgentTreeNode {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { extractFilePaths, extractWritePaths, extractWorktreePath } from './tool-files';
|
||||
import type { ToolCallContent } from '../adapters/sdk-messages';
|
||||
import type { ToolCallContent } from '../adapters/claude-messages';
|
||||
|
||||
function makeTc(name: string, input: unknown): ToolCallContent {
|
||||
return { toolUseId: `tu-${Math.random()}`, name, input };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Extracts file paths from agent tool_call inputs
|
||||
// Used by ContextTab (all file ops) and conflicts store (write ops only)
|
||||
|
||||
import type { ToolCallContent } from '../adapters/sdk-messages';
|
||||
import type { ToolCallContent } from '../adapters/claude-messages';
|
||||
|
||||
export interface ToolFileRef {
|
||||
path: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue