feat(llm): add LLM call support to worker IPC chain

- ProviderManager: align API with contracts ProviderAdapter
- WorkerProtocol: add llm.request/llm.response message types
- WorkerRuntime: add call_llm() for worker→parent→LLM flow
- WorkerManager: support tool_registry and provider_manager injection

P1-1 complete, P1-2 protocol layer complete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AirCoding
2026-06-04 18:42:06 +08:00
parent df36c43829
commit 8fd680cf84
4 changed files with 198 additions and 23 deletions

View File

@@ -24,6 +24,20 @@ export interface ToolCallResult {
content: Record<string, unknown>
}
export interface LLMRequest {
messages: Array<{ role: string; content: unknown }>
model?: string
max_tokens?: number
temperature?: number
tools?: unknown[]
}
export interface LLMResponse {
content: string
usage?: { input_tokens: number; output_tokens: number }
tool_calls?: Array<{ name: string; arguments: Record<string, unknown> }>
}
export class WorkerRuntime {
private agent_id: string
private session_id: string
@@ -63,6 +77,36 @@ export class WorkerRuntime {
return promise
}
/**
* Call LLM through parent process via IPC.
* INV-3: This is the ONLY way workers access LLM.
*/
async call_llm(request: LLMRequest): Promise<LLMResponse> {
const call_id = crypto.randomUUID()
const promise = new Promise<LLMResponse>((resolve, reject) => {
this.pending_calls.set(call_id, { resolve: resolve as any, reject })
// Set timeout (LLM calls can be long)
setTimeout(() => {
this.pending_calls.delete(call_id)
reject(new Error(`LLM call timeout: ${request.model || 'default'}`))
}, 300000) // 5 minutes
})
// Send llm.request via IPC
this.send_message('llm.request', {
call_id,
messages: request.messages,
model: request.model || 'claude-haiku-4-5-20251001',
max_tokens: request.max_tokens || 4096,
temperature: request.temperature,
tools: request.tools
})
return promise
}
/**
* Emit an event to the parent.
*/
@@ -118,6 +162,22 @@ export class WorkerRuntime {
// Respond to ping
this.send_message('worker.heartbeat', { timestamp: new Date().toISOString() })
break
case 'llm.response': {
const call_id = payload.call_id as string
const pending = this.pending_calls.get(call_id)
if (pending) {
this.pending_calls.delete(call_id)
// Resolve with LLM response
const response: LLMResponse = {
content: (payload.content as string) || '',
usage: payload.usage as LLMResponse['usage'],
tool_calls: payload.tool_calls as LLMResponse['tool_calls']
}
pending.resolve(response as any)
}
break
}
}
}