client-py connected
This commit is contained in:
@@ -8,6 +8,7 @@ import { MCPClientConnector } from './mcp-client.js';
|
||||
import { CONTEXT_URIS, type ResourceContent } from '../types/resources.js';
|
||||
import { LLMProviderFactory, type ProviderConfig } from '../llm/provider.js';
|
||||
import { ModelRouter, RoutingStrategy } from '../llm/router.js';
|
||||
import type { WorkspaceManager } from '../workspace/workspace-manager.js';
|
||||
|
||||
export interface AgentHarnessConfig {
|
||||
userId: string;
|
||||
@@ -15,6 +16,7 @@ export interface AgentHarnessConfig {
|
||||
license: UserLicense;
|
||||
providerConfig: ProviderConfig;
|
||||
logger: FastifyBaseLogger;
|
||||
workspaceManager?: WorkspaceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,9 +35,13 @@ export class AgentHarness {
|
||||
private modelFactory: LLMProviderFactory;
|
||||
private modelRouter: ModelRouter;
|
||||
private mcpClient: MCPClientConnector;
|
||||
private workspaceManager?: WorkspaceManager;
|
||||
private lastWorkspaceSeq: number = 0;
|
||||
private isFirstMessage: boolean = true;
|
||||
|
||||
constructor(config: AgentHarnessConfig) {
|
||||
this.config = config;
|
||||
this.workspaceManager = config.workspaceManager;
|
||||
|
||||
this.modelFactory = new LLMProviderFactory(config.providerConfig, config.logger);
|
||||
this.modelRouter = new ModelRouter(this.modelFactory, config.logger);
|
||||
@@ -102,18 +108,14 @@ export class AgentHarness {
|
||||
// 7. Extract text response (tool handling TODO)
|
||||
const assistantMessage = response.content as string;
|
||||
|
||||
// 8. Save messages to user's MCP server
|
||||
this.config.logger.debug('Saving messages to MCP');
|
||||
await this.mcpClient.callTool('save_message', {
|
||||
role: 'user',
|
||||
content: message.content,
|
||||
timestamp: message.timestamp.toISOString(),
|
||||
});
|
||||
await this.mcpClient.callTool('save_message', {
|
||||
role: 'assistant',
|
||||
content: assistantMessage,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
// TODO: Save messages to Iceberg conversation table instead of MCP
|
||||
// Should batch-insert periodically or on session end to avoid many small Parquet files
|
||||
// await icebergConversationStore.appendMessages([...]);
|
||||
|
||||
// Mark first message as processed
|
||||
if (this.isFirstMessage) {
|
||||
this.isFirstMessage = false;
|
||||
}
|
||||
|
||||
return {
|
||||
messageId: `msg_${Date.now()}`,
|
||||
@@ -157,17 +159,17 @@ export class AgentHarness {
|
||||
yield content;
|
||||
}
|
||||
|
||||
// Save after streaming completes
|
||||
await this.mcpClient.callTool('save_message', {
|
||||
role: 'user',
|
||||
content: message.content,
|
||||
timestamp: message.timestamp.toISOString(),
|
||||
});
|
||||
await this.mcpClient.callTool('save_message', {
|
||||
role: 'assistant',
|
||||
content: fullResponse,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
// TODO: Save messages to Iceberg conversation table instead of MCP
|
||||
// Should batch-insert periodically or on session end to avoid many small Parquet files
|
||||
// await icebergConversationStore.appendMessages([
|
||||
// { role: 'user', content: message.content, timestamp: message.timestamp },
|
||||
// { role: 'assistant', content: fullResponse, timestamp: new Date() }
|
||||
// ]);
|
||||
|
||||
// Mark first message as processed
|
||||
if (this.isFirstMessage) {
|
||||
this.isFirstMessage = false;
|
||||
}
|
||||
} catch (error) {
|
||||
this.config.logger.error({ error }, 'Error streaming message');
|
||||
throw error;
|
||||
@@ -224,6 +226,15 @@ export class AgentHarness {
|
||||
});
|
||||
}
|
||||
|
||||
// Add workspace delta (for subsequent turns)
|
||||
const workspaceDelta = this.buildWorkspaceDelta();
|
||||
if (workspaceDelta) {
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: workspaceDelta,
|
||||
});
|
||||
}
|
||||
|
||||
// Add current user message
|
||||
messages.push({
|
||||
role: 'user',
|
||||
@@ -273,9 +284,18 @@ Available features: ${JSON.stringify(this.config.license.features, null, 2)}`;
|
||||
prompt += `\n\n# User Profile\n${userProfile.text}`;
|
||||
}
|
||||
|
||||
// Add workspace context
|
||||
// Add workspace context from MCP resource (if available)
|
||||
if (workspaceState?.text) {
|
||||
prompt += `\n\n# Current Workspace\n${workspaceState.text}`;
|
||||
prompt += `\n\n# Current Workspace (from MCP)\n${workspaceState.text}`;
|
||||
}
|
||||
|
||||
// Add full workspace state from WorkspaceManager (first message only)
|
||||
if (this.isFirstMessage && this.workspaceManager) {
|
||||
const workspaceJSON = this.workspaceManager.serializeState();
|
||||
prompt += `\n\n# Workspace State (JSON)\n\`\`\`json\n${workspaceJSON}\n\`\`\``;
|
||||
|
||||
// Record current workspace sequence for delta tracking
|
||||
this.lastWorkspaceSeq = this.workspaceManager.getCurrentSeq();
|
||||
}
|
||||
|
||||
// Add user's custom instructions (highest priority)
|
||||
@@ -286,6 +306,30 @@ Available features: ${JSON.stringify(this.config.license.features, null, 2)}`;
|
||||
return prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build workspace delta message for subsequent turns.
|
||||
* Returns null if no changes since last message.
|
||||
*/
|
||||
private buildWorkspaceDelta(): string | null {
|
||||
if (!this.workspaceManager || this.isFirstMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const changes = this.workspaceManager.getChangesSince(this.lastWorkspaceSeq);
|
||||
|
||||
if (Object.keys(changes).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Format changes as JSON
|
||||
const deltaJSON = JSON.stringify(changes, null, 2);
|
||||
|
||||
// Update sequence marker
|
||||
this.lastWorkspaceSeq = this.workspaceManager.getCurrentSeq();
|
||||
|
||||
return `[Workspace Changes Since Last Turn]\n\`\`\`json\n${deltaJSON}\n\`\`\``;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user