client-py connected

This commit is contained in:
2026-03-27 16:33:40 -04:00
parent c76887ab92
commit c3a8fae132
55 changed files with 1598 additions and 426 deletions

View File

@@ -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\`\`\``;
}
/**