mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-23 18:17:44 +02:00
refactor: Extract ACP client factory into standalone module
Move client creation logic from Conductor.createClient into a new AcpClientFactory module for better separation of concerns and testability. No behavior changes. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
91
src/main/conductor/AcpClientFactory.ts
Normal file
91
src/main/conductor/AcpClientFactory.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* AcpClientFactory - Creates ACP SDK Client implementations
|
||||
*
|
||||
* Handles session update notifications and permission requests from agents.
|
||||
*/
|
||||
import type {
|
||||
Client,
|
||||
SessionNotification,
|
||||
RequestPermissionRequest,
|
||||
RequestPermissionResponse,
|
||||
} from '@agentclientprotocol/sdk'
|
||||
import type { SessionStore } from '../session/SessionStore'
|
||||
|
||||
export interface AcpClientCallbacks {
|
||||
onSessionUpdate?: (update: SessionNotification) => void
|
||||
onPermissionRequest?: (
|
||||
params: RequestPermissionRequest
|
||||
) => Promise<RequestPermissionResponse>
|
||||
}
|
||||
|
||||
export interface AcpClientFactoryOptions {
|
||||
sessionStore: SessionStore | null
|
||||
callbacks: AcpClientCallbacks
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ACP Client implementation for a session
|
||||
*
|
||||
* @param sessionId - The Multica session ID (used for persistence)
|
||||
* @param options - Factory options including store and callbacks
|
||||
*/
|
||||
export function createAcpClient(
|
||||
sessionId: string,
|
||||
options: AcpClientFactoryOptions
|
||||
): Client {
|
||||
const { sessionStore, callbacks } = options
|
||||
|
||||
return {
|
||||
// Handle session updates from agent
|
||||
sessionUpdate: async (params: SessionNotification) => {
|
||||
// Log the update type
|
||||
const update = params.update
|
||||
if ('sessionUpdate' in update) {
|
||||
const updateType = update.sessionUpdate
|
||||
if (updateType === 'agent_message_chunk') {
|
||||
const contentType = update.content?.type || 'unknown'
|
||||
console.log(`[ACP] Session ${sessionId} update: ${updateType} (${contentType})`)
|
||||
} else if (updateType === 'tool_call') {
|
||||
console.log(`[ACP] Session ${sessionId} update: ${updateType} - ${update.title} [${update.status}]`)
|
||||
} else if (updateType === 'tool_call_update') {
|
||||
console.log(`[ACP] Session ${sessionId} update: ${updateType} [${update.status}]`)
|
||||
} else {
|
||||
console.log(`[ACP] Session ${sessionId} update: ${updateType}`, update)
|
||||
}
|
||||
} else {
|
||||
console.log(`[ACP] Session ${sessionId} update (raw):`, params)
|
||||
}
|
||||
|
||||
// Store raw update to SessionStore (if available)
|
||||
if (sessionStore) {
|
||||
try {
|
||||
await sessionStore.appendUpdate(sessionId, params)
|
||||
} catch (err) {
|
||||
console.error('[Conductor] Failed to store session update:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger UI callback
|
||||
if (callbacks.onSessionUpdate) {
|
||||
callbacks.onSessionUpdate(params)
|
||||
}
|
||||
},
|
||||
|
||||
// Handle permission requests from agent
|
||||
requestPermission: async (
|
||||
params: RequestPermissionRequest
|
||||
): Promise<RequestPermissionResponse> => {
|
||||
if (callbacks.onPermissionRequest) {
|
||||
return callbacks.onPermissionRequest(params)
|
||||
}
|
||||
// Default: auto-approve (V1 simplification)
|
||||
console.log(`[Conductor] Auto-approving: ${params.toolCall.title}`)
|
||||
return {
|
||||
outcome: {
|
||||
outcome: 'selected',
|
||||
optionId: params.options[0]?.optionId ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
/**
|
||||
* Conductor - Central orchestrator for ACP agent communication
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Session lifecycle management (create, resume, load, delete)
|
||||
* - Agent process orchestration
|
||||
* - Prompt handling with history replay
|
||||
*/
|
||||
import {
|
||||
ClientSideConnection,
|
||||
ndJsonStream,
|
||||
PROTOCOL_VERSION,
|
||||
type Client,
|
||||
type SessionNotification,
|
||||
type RequestPermissionRequest,
|
||||
type RequestPermissionResponse,
|
||||
@@ -13,6 +17,7 @@ import {
|
||||
import { AgentProcess } from './AgentProcess'
|
||||
import { SessionStore } from '../session/SessionStore'
|
||||
import { DEFAULT_AGENTS } from '../config/defaults'
|
||||
import { createAcpClient } from './AcpClientFactory'
|
||||
import type {
|
||||
AgentConfig,
|
||||
MulticaSession,
|
||||
@@ -106,7 +111,14 @@ export class Conductor {
|
||||
|
||||
// Create client-side connection with our Client implementation
|
||||
const connection = new ClientSideConnection(
|
||||
(_agent) => this.createClient(sessionId),
|
||||
(_agent) =>
|
||||
createAcpClient(sessionId, {
|
||||
sessionStore: this.sessionStore,
|
||||
callbacks: {
|
||||
onSessionUpdate: this.events.onSessionUpdate,
|
||||
onPermissionRequest: this.events.onPermissionRequest,
|
||||
},
|
||||
}),
|
||||
stream
|
||||
)
|
||||
|
||||
@@ -491,63 +503,4 @@ export class Conductor {
|
||||
getProcessingSessionIds(): string[] {
|
||||
return Array.from(this.processingSessions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Client implementation for ACP SDK
|
||||
*/
|
||||
private createClient(sessionId: string): Client {
|
||||
return {
|
||||
// Handle session updates from agent
|
||||
sessionUpdate: async (params: SessionNotification) => {
|
||||
// Log the update type
|
||||
const update = params.update
|
||||
if ('sessionUpdate' in update) {
|
||||
const updateType = update.sessionUpdate
|
||||
if (updateType === 'agent_message_chunk') {
|
||||
const contentType = update.content?.type || 'unknown'
|
||||
console.log(`[ACP] Session ${sessionId} update: ${updateType} (${contentType})`)
|
||||
} else if (updateType === 'tool_call') {
|
||||
console.log(`[ACP] Session ${sessionId} update: ${updateType} - ${update.title} [${update.status}]`)
|
||||
} else if (updateType === 'tool_call_update') {
|
||||
console.log(`[ACP] Session ${sessionId} update: ${updateType} [${update.status}]`)
|
||||
} else {
|
||||
console.log(`[ACP] Session ${sessionId} update: ${updateType}`, update)
|
||||
}
|
||||
} else {
|
||||
console.log(`[ACP] Session ${sessionId} update (raw):`, params)
|
||||
}
|
||||
|
||||
// Store raw update to SessionStore (if available)
|
||||
if (this.sessionStore) {
|
||||
try {
|
||||
await this.sessionStore.appendUpdate(sessionId, params)
|
||||
} catch (err) {
|
||||
console.error('[Conductor] Failed to store session update:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger UI callback
|
||||
if (this.events.onSessionUpdate) {
|
||||
this.events.onSessionUpdate(params)
|
||||
}
|
||||
},
|
||||
|
||||
// Handle permission requests from agent
|
||||
requestPermission: async (
|
||||
params: RequestPermissionRequest
|
||||
): Promise<RequestPermissionResponse> => {
|
||||
if (this.events.onPermissionRequest) {
|
||||
return this.events.onPermissionRequest(params)
|
||||
}
|
||||
// Default: auto-approve (V1 simplification)
|
||||
console.log(`[Conductor] Auto-approving: ${params.toolCall.title}`)
|
||||
return {
|
||||
outcome: {
|
||||
outcome: 'selected',
|
||||
optionId: params.options[0]?.optionId ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export { Conductor } from './Conductor'
|
||||
export { AgentProcess } from './AgentProcess'
|
||||
export { createAcpClient } from './AcpClientFactory'
|
||||
export type { SessionUpdateCallback, ConductorEvents } from './Conductor'
|
||||
export type { AcpClientCallbacks, AcpClientFactoryOptions } from './AcpClientFactory'
|
||||
|
||||
Reference in New Issue
Block a user