From 5525be04f5867c242a193c80119c6a38209349b2 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 30 Oct 2025 16:53:48 +0000 Subject: [PATCH 1/5] feat: Update SDK API to use Agent and Conversation aliases with explicit workspace creation - Add Agent type alias for AgentBase to provide cleaner user-facing API - Add Conversation const alias for RemoteConversation for consistency with Python SDK - Update create/load methods to require explicit RemoteWorkspace parameter - Modify ConversationManager to create RemoteWorkspace instances explicitly - Update all examples and documentation to use new API pattern: - Use Agent instead of AgentBase - Use Conversation instead of RemoteConversation - Explicitly construct RemoteWorkspace objects - Update example React app and basic usage examples - Maintain backward compatibility by keeping original exports This change aligns the TypeScript SDK more closely with the Python SDK architecture and provides a more intuitive API for end users. Co-authored-by: openhands --- README.md | 26 +++++++++++++---- .../src/components/ConversationManager.tsx | 28 +++++++++++++++---- example/src/utils/serverStatus.ts | 15 ++++++---- examples/basic-usage.ts | 26 +++++++++++++---- src/conversation/conversation-manager.ts | 27 ++++++++++++++---- src/conversation/remote-conversation.ts | 24 +++++++--------- src/index.ts | 6 ++-- src/types/base.ts | 3 ++ 8 files changed, 113 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 6ad3ddc..029e8e6 100644 --- a/README.md +++ b/README.md @@ -21,19 +21,27 @@ npm install @openhands/agent-server-typescript-client ### Creating a Conversation ```typescript -import { RemoteConversation, AgentBase } from '@openhands/agent-server-typescript-client'; +import { Conversation, Agent, RemoteWorkspace } from '@openhands/agent-server-typescript-client'; -const agent: AgentBase = { - name: 'CodeActAgent', +const agent: Agent = { + kind: 'CodeActAgent', llm: { model: 'gpt-4', api_key: 'your-openai-api-key' } }; -const conversation = await RemoteConversation.create( +// Create a remote workspace +const workspace = new RemoteWorkspace({ + host: 'http://localhost:3000', + workingDir: '/tmp', + apiKey: 'your-session-api-key' +}); + +const conversation = await Conversation.create( 'http://localhost:3000', // Agent server URL agent, + workspace, { apiKey: 'your-session-api-key', initialMessage: 'Hello, can you help me write some code?', @@ -54,9 +62,17 @@ await conversation.run(); ### Loading an Existing Conversation ```typescript -const conversation = await RemoteConversation.load( +// Create a remote workspace for the existing conversation +const workspace = new RemoteWorkspace({ + host: 'http://localhost:3000', + workingDir: '/tmp', + apiKey: 'your-session-api-key' +}); + +const conversation = await Conversation.load( 'http://localhost:3000', 'conversation-id-here', + workspace, { apiKey: 'your-session-api-key' } diff --git a/example/src/components/ConversationManager.tsx b/example/src/components/ConversationManager.tsx index 9541bfd..800da4c 100644 --- a/example/src/components/ConversationManager.tsx +++ b/example/src/components/ConversationManager.tsx @@ -2,8 +2,10 @@ import React, { useState, useEffect } from 'react'; import { ConversationManager as SDKConversationManager, ConversationInfo, + Conversation, RemoteConversation, - AgentBase, + Agent, + RemoteWorkspace, Event } from '@openhands/agent-server-typescript-client'; import { useSettings } from '../contexts/SettingsContext'; @@ -118,7 +120,7 @@ export const ConversationManager: React.FC = () => { useEffect(() => { return () => { if (selectedConversation?.remoteConversation) { - selectedConversation.remoteConversation.stopWebSocketClient().catch(err => { + selectedConversation.remoteConversation.stopWebSocketClient().catch((err: any) => { console.warn('Failed to stop WebSocket client on unmount:', err); }); } @@ -190,7 +192,7 @@ export const ConversationManager: React.FC = () => { setError(null); try { // Create a simple agent configuration - const agent: AgentBase = { + const agent: Agent = { kind: 'Agent', llm: { model: settings.modelName, @@ -198,9 +200,17 @@ export const ConversationManager: React.FC = () => { } }; - const conversation = await RemoteConversation.create( + // Create a remote workspace + const workspace = new RemoteWorkspace({ + host: manager.host, + workingDir: '/tmp', + apiKey: manager.apiKey + }); + + const conversation = await Conversation.create( manager.host, agent, + workspace, { apiKey: manager.apiKey, initialMessage: 'Hello! I\'m ready to help you with your tasks.', @@ -320,10 +330,18 @@ export const ConversationManager: React.FC = () => { } }; + // Create a remote workspace for the existing conversation + const workspace = new RemoteWorkspace({ + host: manager.host, + workingDir: '/tmp', + apiKey: manager.apiKey + }); + // Load conversation with callback - const remoteConversation = await RemoteConversation.load( + const remoteConversation = await Conversation.load( manager.host, conversationId, + workspace, { apiKey: manager.apiKey, callback: eventCallback, diff --git a/example/src/utils/serverStatus.ts b/example/src/utils/serverStatus.ts index 9332bc5..3576196 100644 --- a/example/src/utils/serverStatus.ts +++ b/example/src/utils/serverStatus.ts @@ -1,5 +1,5 @@ import { Settings } from '../components/SettingsModal'; -import { HttpClient, RemoteConversation } from '@openhands/agent-server-typescript-client'; +import { HttpClient, RemoteConversation, RemoteWorkspace } from '@openhands/agent-server-typescript-client'; export interface ServerStatus { isConnected: boolean; @@ -51,6 +51,13 @@ export const testLLMConfiguration = async (settings: Settings): Promise<{ succes return { success: false, error: `Server not reachable: ${healthCheck.error}` }; } + // Create a workspace for the test conversation + const workspace = new RemoteWorkspace({ + host: settings.agentServerUrl, + workingDir: '/tmp/test-workspace', + apiKey: settings.agentServerApiKey, + }); + // Create a test conversation using the SDK const conversation = await RemoteConversation.create( settings.agentServerUrl, @@ -61,13 +68,9 @@ export const testLLMConfiguration = async (settings: Settings): Promise<{ succes api_key: settings.apiKey, } }, + workspace, { apiKey: settings.agentServerApiKey, - workspace: { - type: 'local', - path: '/tmp/test-workspace', - working_dir: '/tmp/test-workspace' - } } ); diff --git a/examples/basic-usage.ts b/examples/basic-usage.ts index b6f264f..1a3a9b5 100644 --- a/examples/basic-usage.ts +++ b/examples/basic-usage.ts @@ -2,12 +2,12 @@ * Basic usage example for the OpenHands Agent Server TypeScript Client */ -import { RemoteConversation, AgentBase, AgentExecutionStatus } from '../src/index.js'; +import { Conversation, Agent, RemoteWorkspace, AgentExecutionStatus } from '../src/index.js'; async function main() { // Define the agent configuration - const agent: AgentBase = { - name: 'CodeActAgent', + const agent: Agent = { + kind: 'CodeActAgent', llm: { model: 'gpt-4', api_key: process.env.OPENAI_API_KEY || 'your-openai-api-key', @@ -15,11 +15,19 @@ async function main() { }; try { + // Create a remote workspace + const workspace = new RemoteWorkspace({ + host: 'http://localhost:3000', + workingDir: '/tmp', + apiKey: process.env.SESSION_API_KEY || 'your-session-api-key' + }); + // Create a new conversation console.log('Creating conversation...'); - const conversation = await RemoteConversation.create( + const conversation = await Conversation.create( 'http://localhost:3000', // Replace with your agent server URL agent, + workspace, { apiKey: process.env.SESSION_API_KEY || 'your-session-api-key', initialMessage: 'Hello! Can you help me write a simple Python script?', @@ -80,9 +88,17 @@ async function main() { // Example of loading an existing conversation async function loadExistingConversation() { try { - const conversation = await RemoteConversation.load( + // Create a remote workspace for the existing conversation + const workspace = new RemoteWorkspace({ + host: 'http://localhost:3000', + workingDir: '/tmp', + apiKey: process.env.SESSION_API_KEY || 'your-session-api-key' + }); + + const conversation = await Conversation.load( 'http://localhost:3000', 'existing-conversation-id', + workspace, { apiKey: process.env.SESSION_API_KEY || 'your-session-api-key', } diff --git a/src/conversation/conversation-manager.ts b/src/conversation/conversation-manager.ts index 3d73907..38f4805 100644 --- a/src/conversation/conversation-manager.ts +++ b/src/conversation/conversation-manager.ts @@ -4,6 +4,7 @@ import { HttpClient } from '../client/http-client'; import { RemoteConversation } from './remote-conversation'; +import { RemoteWorkspace } from '../workspace/remote-workspace'; import { ConversationInfo, ConversationSearchRequest, @@ -86,20 +87,36 @@ export class ConversationManager { initialMessage?: string; maxIterations?: number; stuckDetection?: boolean; - workspace?: any; + workingDir?: string; } = {} ): Promise { - return RemoteConversation.create(this.host, agent, { + // Create a workspace for the conversation + const workspace = new RemoteWorkspace({ + host: this.host, + workingDir: options.workingDir || '/tmp', apiKey: this.apiKey, - ...options, + }); + + return RemoteConversation.create(this.host, agent, workspace, { + apiKey: this.apiKey, + initialMessage: options.initialMessage, + maxIterations: options.maxIterations, + stuckDetection: options.stuckDetection, }); } /** * Load an existing conversation */ - async loadConversation(conversationId: ConversationID): Promise { - return RemoteConversation.load(this.host, conversationId, { + async loadConversation(conversationId: ConversationID, workingDir: string = '/tmp'): Promise { + // Create a workspace for the existing conversation + const workspace = new RemoteWorkspace({ + host: this.host, + workingDir, + apiKey: this.apiKey, + }); + + return RemoteConversation.load(this.host, conversationId, workspace, { apiKey: this.apiKey, }); } diff --git a/src/conversation/remote-conversation.ts b/src/conversation/remote-conversation.ts index f398af9..0a47fe0 100644 --- a/src/conversation/remote-conversation.ts +++ b/src/conversation/remote-conversation.ts @@ -189,12 +189,12 @@ export class RemoteConversation { static async create( host: string, agent: AgentBase, + workspace: RemoteWorkspace, options: { apiKey?: string; initialMessage?: string; maxIterations?: number; stuckDetection?: boolean; - workspace?: any; callback?: ConversationCallbackType; } = {} ): Promise { @@ -220,7 +220,7 @@ export class RemoteConversation { initial_message: initialMessage, max_iterations: options.maxIterations || 50, stuck_detection: options.stuckDetection ?? true, - workspace: options.workspace || { type: 'local', working_dir: '/tmp' }, + workspace: { type: 'local', working_dir: workspace.workingDir }, }; console.log('Full request object:', JSON.stringify(request, null, 2)); @@ -235,12 +235,8 @@ export class RemoteConversation { callback: options.callback, }); - // Initialize workspace - conversation._workspace = new RemoteWorkspace({ - host, - workingDir: conversationInfo.workspace?.working_dir || '/tmp', - apiKey: options.apiKey, - }); + // Use the provided workspace + conversation._workspace = workspace; return conversation; } @@ -248,6 +244,7 @@ export class RemoteConversation { static async load( host: string, conversationId: string, + workspace: RemoteWorkspace, options: { apiKey?: string; callback?: ConversationCallbackType; @@ -266,12 +263,8 @@ export class RemoteConversation { ); const conversationInfo = response.data; - // Initialize workspace - conversation._workspace = new RemoteWorkspace({ - host, - workingDir: conversationInfo.workspace?.working_dir || '/tmp', - apiKey: options.apiKey, - }); + // Use the provided workspace + conversation._workspace = workspace; return conversation; } @@ -284,3 +277,6 @@ export class RemoteConversation { } } } + +// Alias for user-facing API +export const Conversation = RemoteConversation; diff --git a/src/index.ts b/src/index.ts index c0d6c0c..c9220df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ */ // Main conversation and workspace classes -export { RemoteConversation } from './conversation/remote-conversation'; +export { RemoteConversation, Conversation } from './conversation/remote-conversation'; export { ConversationManager } from './conversation/conversation-manager'; export { RemoteWorkspace } from './workspace/remote-workspace'; export { RemoteState } from './conversation/remote-state'; @@ -33,6 +33,7 @@ export type { TextContent, ImageContent, AgentBase, + Agent, LLM, ServerInfo, Success, @@ -75,7 +76,7 @@ export type { RemoteConversationOptions } from './conversation/remote-conversati export type { ConversationManagerOptions } from './conversation/conversation-manager'; // Re-import for default export -import { RemoteConversation } from './conversation/remote-conversation'; +import { RemoteConversation, Conversation } from './conversation/remote-conversation'; import { ConversationManager } from './conversation/conversation-manager'; import { RemoteWorkspace } from './workspace/remote-workspace'; import { RemoteState } from './conversation/remote-state'; @@ -87,6 +88,7 @@ import { EventSortOrder, AgentExecutionStatus } from './types/base'; // Default export for convenience export default { RemoteConversation, + Conversation, ConversationManager, RemoteWorkspace, RemoteState, diff --git a/src/types/base.ts b/src/types/base.ts index 3b70cbb..bd4c9e1 100644 --- a/src/types/base.ts +++ b/src/types/base.ts @@ -79,6 +79,9 @@ export interface AgentBase { [key: string]: any; } +// Alias for user-facing API +export type Agent = AgentBase; + export interface LLM { model: string; api_key?: string; From f077c82a7d27b0597b5cd9e49908a0ff7f3dcdaf Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 30 Oct 2025 17:10:16 +0000 Subject: [PATCH 2/5] Fix factory classes to properly extend base classes - Convert Conversation and Workspace from factory functions to proper classes that extend RemoteConversation and RemoteWorkspace - Update ConversationManager to use new constructor-based API - Update serverStatus.ts to use new constructor pattern - All builds now pass successfully Co-authored-by: openhands --- README.md | 51 +++--- .../src/components/ConversationManager.tsx | 47 +++--- example/src/utils/serverStatus.ts | 10 +- examples/basic-usage.ts | 50 +++--- src/conversation/conversation-manager.ts | 20 ++- src/conversation/conversation.ts | 30 ++++ src/conversation/remote-conversation.ts | 153 ++++++------------ src/index.ts | 9 +- src/workspace/workspace.ts | 19 +++ 9 files changed, 198 insertions(+), 191 deletions(-) create mode 100644 src/conversation/conversation.ts create mode 100644 src/workspace/workspace.ts diff --git a/README.md b/README.md index 029e8e6..314b6e6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ npm install @openhands/agent-server-typescript-client ### Creating a Conversation ```typescript -import { Conversation, Agent, RemoteWorkspace } from '@openhands/agent-server-typescript-client'; +import { Conversation, Agent, Workspace } from '@openhands/agent-server-typescript-client'; const agent: Agent = { kind: 'CodeActAgent', @@ -32,24 +32,22 @@ const agent: Agent = { }; // Create a remote workspace -const workspace = new RemoteWorkspace({ +const workspace = new Workspace({ host: 'http://localhost:3000', workingDir: '/tmp', apiKey: 'your-session-api-key' }); -const conversation = await Conversation.create( - 'http://localhost:3000', // Agent server URL - agent, - workspace, - { - apiKey: 'your-session-api-key', - initialMessage: 'Hello, can you help me write some code?', - callback: (event) => { - console.log('Received event:', event); - } +const conversation = new Conversation(agent, workspace, { + callback: (event) => { + console.log('Received event:', event); } -); +}); + +// Start the conversation with an initial message +await conversation.start({ + initialMessage: 'Hello, can you help me write some code?' +}); // Start WebSocket for real-time events await conversation.startWebSocketClient(); @@ -63,20 +61,18 @@ await conversation.run(); ```typescript // Create a remote workspace for the existing conversation -const workspace = new RemoteWorkspace({ +const workspace = new Workspace({ host: 'http://localhost:3000', workingDir: '/tmp', apiKey: 'your-session-api-key' }); -const conversation = await Conversation.load( - 'http://localhost:3000', - 'conversation-id-here', - workspace, - { - apiKey: 'your-session-api-key' - } -); +const conversation = new Conversation(agent, workspace, { + conversationId: 'conversation-id-here' +}); + +// Connect to the existing conversation +await conversation.start(); ``` ### Using the Workspace @@ -136,17 +132,18 @@ await conversation.updateSecrets({ ## API Reference -### RemoteConversation +### Conversation -The main class for managing conversations with OpenHands agents. +Factory function that creates conversations with OpenHands agents. -#### Static Methods +#### Constructor -- `RemoteConversation.create(host, agent, options)` - Create a new conversation -- `RemoteConversation.load(host, conversationId, options)` - Load an existing conversation +- `new Conversation(agent, workspace, options?)` - Create a new conversation instance #### Instance Methods +- `start(options?)` - Start the conversation (creates new or connects to existing) + - `sendMessage(message)` - Send a message to the agent - `run()` - Start agent execution - `pause()` - Pause agent execution diff --git a/example/src/components/ConversationManager.tsx b/example/src/components/ConversationManager.tsx index 800da4c..3b8a00e 100644 --- a/example/src/components/ConversationManager.tsx +++ b/example/src/components/ConversationManager.tsx @@ -5,7 +5,7 @@ import { Conversation, RemoteConversation, Agent, - RemoteWorkspace, + Workspace, Event } from '@openhands/agent-server-typescript-client'; import { useSettings } from '../contexts/SettingsContext'; @@ -201,22 +201,16 @@ export const ConversationManager: React.FC = () => { }; // Create a remote workspace - const workspace = new RemoteWorkspace({ + const workspace = new Workspace({ host: manager.host, workingDir: '/tmp', apiKey: manager.apiKey }); - const conversation = await Conversation.create( - manager.host, - agent, - workspace, - { - apiKey: manager.apiKey, - initialMessage: 'Hello! I\'m ready to help you with your tasks.', - maxIterations: 50, - callback: (event: Event) => { - console.log('Received WebSocket event for new conversation:', event); + const conversation = new Conversation(agent, workspace, { + maxIterations: 50, + callback: (event: Event) => { + console.log('Received WebSocket event for new conversation:', event); // Update the conversation's events in real-time setConversations(prev => prev.map(conv => { @@ -231,6 +225,11 @@ export const ConversationManager: React.FC = () => { ); console.log('Created conversation:', conversation); + + // Start the conversation with initial message + await conversation.start({ + initialMessage: 'Hello! I\'m ready to help you with your tasks.' + }); // Start WebSocket client for real-time updates try { @@ -299,6 +298,12 @@ export const ConversationManager: React.FC = () => { // Load conversation details try { + // Get the conversation info to extract the agent + const conversationInfo = conversations.find(c => c.id === conversationId); + if (!conversationInfo) { + throw new Error('Conversation not found'); + } + // Create a callback to handle real-time events const eventCallback = (event: Event) => { console.log('Received WebSocket event:', event); @@ -331,22 +336,20 @@ export const ConversationManager: React.FC = () => { }; // Create a remote workspace for the existing conversation - const workspace = new RemoteWorkspace({ + const workspace = new Workspace({ host: manager.host, workingDir: '/tmp', apiKey: manager.apiKey }); // Load conversation with callback - const remoteConversation = await Conversation.load( - manager.host, - conversationId, - workspace, - { - apiKey: manager.apiKey, - callback: eventCallback, - } - ); + const remoteConversation = new Conversation(conversationInfo.agent, workspace, { + conversationId: conversationId, + callback: eventCallback, + }); + + // Connect to the existing conversation + await remoteConversation.start(); console.log('Loaded remote conversation:', remoteConversation); // Start WebSocket client for real-time updates diff --git a/example/src/utils/serverStatus.ts b/example/src/utils/serverStatus.ts index 3576196..4c89ccb 100644 --- a/example/src/utils/serverStatus.ts +++ b/example/src/utils/serverStatus.ts @@ -59,8 +59,7 @@ export const testLLMConfiguration = async (settings: Settings): Promise<{ succes }); // Create a test conversation using the SDK - const conversation = await RemoteConversation.create( - settings.agentServerUrl, + const conversation = new RemoteConversation( { kind: 'Agent', llm: { @@ -68,12 +67,11 @@ export const testLLMConfiguration = async (settings: Settings): Promise<{ succes api_key: settings.apiKey, } }, - workspace, - { - apiKey: settings.agentServerApiKey, - } + workspace ); + await conversation.start(); + try { // Send a simple test message to validate LLM configuration await conversation.sendMessage({ diff --git a/examples/basic-usage.ts b/examples/basic-usage.ts index 1a3a9b5..f490564 100644 --- a/examples/basic-usage.ts +++ b/examples/basic-usage.ts @@ -2,7 +2,7 @@ * Basic usage example for the OpenHands Agent Server TypeScript Client */ -import { Conversation, Agent, RemoteWorkspace, AgentExecutionStatus } from '../src/index.js'; +import { Conversation, Agent, Workspace, AgentExecutionStatus } from '../src/index.js'; async function main() { // Define the agent configuration @@ -16,7 +16,7 @@ async function main() { try { // Create a remote workspace - const workspace = new RemoteWorkspace({ + const workspace = new Workspace({ host: 'http://localhost:3000', workingDir: '/tmp', apiKey: process.env.SESSION_API_KEY || 'your-session-api-key' @@ -24,18 +24,16 @@ async function main() { // Create a new conversation console.log('Creating conversation...'); - const conversation = await Conversation.create( - 'http://localhost:3000', // Replace with your agent server URL - agent, - workspace, - { - apiKey: process.env.SESSION_API_KEY || 'your-session-api-key', - initialMessage: 'Hello! Can you help me write a simple Python script?', - callback: (event) => { - console.log(`Event received: ${event.kind} at ${event.timestamp}`); - }, - } - ); + const conversation = new Conversation(agent, workspace, { + callback: (event) => { + console.log(`Event received: ${event.kind} at ${event.timestamp}`); + }, + }); + + // Start the conversation with an initial message + await conversation.start({ + initialMessage: 'Hello! Can you help me write a simple Python script?' + }); console.log(`Conversation created with ID: ${conversation.id}`); @@ -87,22 +85,28 @@ async function main() { // Example of loading an existing conversation async function loadExistingConversation() { + const agent: Agent = { + kind: 'CodeActAgent', + llm: { + model: 'gpt-4', + api_key: process.env.OPENAI_API_KEY || 'your-openai-api-key', + }, + }; + try { // Create a remote workspace for the existing conversation - const workspace = new RemoteWorkspace({ + const workspace = new Workspace({ host: 'http://localhost:3000', workingDir: '/tmp', apiKey: process.env.SESSION_API_KEY || 'your-session-api-key' }); - const conversation = await Conversation.load( - 'http://localhost:3000', - 'existing-conversation-id', - workspace, - { - apiKey: process.env.SESSION_API_KEY || 'your-session-api-key', - } - ); + const conversation = new Conversation(agent, workspace, { + conversationId: 'existing-conversation-id' + }); + + // Connect to the existing conversation + await conversation.start(); console.log(`Loaded conversation: ${conversation.id}`); diff --git a/src/conversation/conversation-manager.ts b/src/conversation/conversation-manager.ts index 38f4805..4ae1643 100644 --- a/src/conversation/conversation-manager.ts +++ b/src/conversation/conversation-manager.ts @@ -97,18 +97,25 @@ export class ConversationManager { apiKey: this.apiKey, }); - return RemoteConversation.create(this.host, agent, workspace, { - apiKey: this.apiKey, - initialMessage: options.initialMessage, + const conversation = new RemoteConversation(agent, workspace, { maxIterations: options.maxIterations, stuckDetection: options.stuckDetection, }); + + await conversation.start({ + initialMessage: options.initialMessage, + }); + + return conversation; } /** * Load an existing conversation */ async loadConversation(conversationId: ConversationID, workingDir: string = '/tmp'): Promise { + // Get conversation info to extract the agent + const conversationInfo = await this.getConversation(conversationId); + // Create a workspace for the existing conversation const workspace = new RemoteWorkspace({ host: this.host, @@ -116,9 +123,12 @@ export class ConversationManager { apiKey: this.apiKey, }); - return RemoteConversation.load(this.host, conversationId, workspace, { - apiKey: this.apiKey, + const conversation = new RemoteConversation(conversationInfo.agent, workspace, { + conversationId: conversationId, }); + + await conversation.start(); + return conversation; } /** diff --git a/src/conversation/conversation.ts b/src/conversation/conversation.ts new file mode 100644 index 0000000..b7d0ada --- /dev/null +++ b/src/conversation/conversation.ts @@ -0,0 +1,30 @@ +/** + * Conversation factory function that returns RemoteConversation + * Matches the Python SDK pattern: Conversation(agent, workspace) + */ + +import { AgentBase } from '../types/base'; +import { RemoteWorkspace } from '../workspace/remote-workspace'; +import { RemoteConversation, RemoteConversationOptions } from './remote-conversation'; + +/** + * Conversation class that extends RemoteConversation. + * Provides a cleaner API that matches the Python SDK naming. + * + * Usage: + * const conversation = new Conversation(agent, workspace); + * await conversation.start(); + * + * For existing conversations: + * const conversation = new Conversation(agent, workspace, { conversationId: 'existing-id' }); + * await conversation.start(); + */ +export class Conversation extends RemoteConversation { + constructor( + agent: AgentBase, + workspace: RemoteWorkspace, + options?: RemoteConversationOptions + ) { + super(agent, workspace, options); + } +} \ No newline at end of file diff --git a/src/conversation/remote-conversation.ts b/src/conversation/remote-conversation.ts index 0a47fe0..a315a46 100644 --- a/src/conversation/remote-conversation.ts +++ b/src/conversation/remote-conversation.ts @@ -27,38 +27,42 @@ import { } from '../models/conversation'; export interface RemoteConversationOptions { - host: string; conversationId?: string; - apiKey?: string; callback?: ConversationCallbackType; + initialMessage?: string; + maxIterations?: number; + stuckDetection?: boolean; } export class RemoteConversation { - public readonly host: string; - public readonly apiKey?: string; + public readonly agent: AgentBase; + public readonly workspace: RemoteWorkspace; private _conversationId?: string; private _state?: RemoteState; - private _workspace?: RemoteWorkspace; private client: HttpClient; private wsClient?: WebSocketCallbackClient; private callback?: ConversationCallbackType; - constructor(options: RemoteConversationOptions) { - this.host = options.host.replace(/\/$/, ''); - this.apiKey = options.apiKey; - this._conversationId = options.conversationId; + constructor( + agent: AgentBase, + workspace: RemoteWorkspace, + options: RemoteConversationOptions = {} + ) { + this.agent = agent; + this.workspace = workspace; this.callback = options.callback; + this._conversationId = options.conversationId; this.client = new HttpClient({ - baseUrl: this.host, - apiKey: this.apiKey, + baseUrl: workspace.host, + apiKey: workspace.apiKey, timeout: 60000, }); } get id(): ConversationID { if (!this._conversationId) { - throw new Error('Conversation ID not set. Create or load a conversation first.'); + throw new Error('Conversation ID not set. Call start() to initialize the conversation.'); } return this._conversationId; } @@ -66,18 +70,40 @@ export class RemoteConversation { get state(): RemoteState { if (!this._state) { if (!this._conversationId) { - throw new Error('Conversation not initialized. Create or load a conversation first.'); + throw new Error('Conversation not initialized. Call start() to initialize the conversation.'); } this._state = new RemoteState(this.client, this._conversationId); } return this._state; } - get workspace(): RemoteWorkspace { - if (!this._workspace) { - throw new Error('Workspace not initialized. Create or load a conversation first.'); + async start(options: { initialMessage?: string; maxIterations?: number; stuckDetection?: boolean } = {}): Promise { + if (this._conversationId) { + // Existing conversation - verify it exists + await this.client.get(`/api/conversations/${this._conversationId}`); + return; } - return this._workspace; + + // Create new conversation + let initialMessage: Message | undefined; + if (options.initialMessage) { + initialMessage = { + role: 'user', + content: [{ type: 'text', text: options.initialMessage }], + }; + } + + const request: CreateConversationRequest = { + agent: this.agent, + initial_message: initialMessage, + max_iterations: options.maxIterations || 50, + stuck_detection: options.stuckDetection ?? true, + workspace: { type: 'local', working_dir: this.workspace.workingDir }, + }; + + const response = await this.client.post('/api/conversations', request); + const conversationInfo = response.data; + this._conversationId = conversationInfo.id; } async conversationStats(): Promise { @@ -169,10 +195,10 @@ export class RemoteConversation { }; this.wsClient = new WebSocketCallbackClient({ - host: this.host, + host: this.workspace.host, conversationId: this.id, callback: combinedCallback, - apiKey: this.apiKey, + apiKey: this.workspace.apiKey, }); this.wsClient.start(); @@ -185,98 +211,13 @@ export class RemoteConversation { } } - // Static factory methods - static async create( - host: string, - agent: AgentBase, - workspace: RemoteWorkspace, - options: { - apiKey?: string; - initialMessage?: string; - maxIterations?: number; - stuckDetection?: boolean; - callback?: ConversationCallbackType; - } = {} - ): Promise { - const client = new HttpClient({ - baseUrl: host.replace(/\/$/, ''), - apiKey: options.apiKey, - timeout: 60000, - }); - - // Convert string initialMessage to Message object if provided - let initialMessage: Message | undefined; - if (options.initialMessage) { - initialMessage = { - role: 'user', - content: [{ type: 'text', text: options.initialMessage }], - }; - } - - console.log('Agent object before request:', JSON.stringify(agent, null, 2)); - - const request: CreateConversationRequest = { - agent, - initial_message: initialMessage, - max_iterations: options.maxIterations || 50, - stuck_detection: options.stuckDetection ?? true, - workspace: { type: 'local', working_dir: workspace.workingDir }, - }; - - console.log('Full request object:', JSON.stringify(request, null, 2)); - - const response = await client.post('/api/conversations', request); - const conversationInfo = response.data; - - const conversation = new RemoteConversation({ - host, - conversationId: conversationInfo.id, - apiKey: options.apiKey, - callback: options.callback, - }); - - // Use the provided workspace - conversation._workspace = workspace; - - return conversation; - } - - static async load( - host: string, - conversationId: string, - workspace: RemoteWorkspace, - options: { - apiKey?: string; - callback?: ConversationCallbackType; - } = {} - ): Promise { - const conversation = new RemoteConversation({ - host, - conversationId, - apiKey: options.apiKey, - callback: options.callback, - }); - - // Verify conversation exists and get workspace info - const response = await conversation.client.get( - `/api/conversations/${conversationId}` - ); - const conversationInfo = response.data; - // Use the provided workspace - conversation._workspace = workspace; - - return conversation; - } async close(): Promise { await this.stopWebSocketClient(); this.client.close(); - if (this._workspace) { - this._workspace.close(); - } + this.workspace.close(); } } -// Alias for user-facing API -export const Conversation = RemoteConversation; + diff --git a/src/index.ts b/src/index.ts index c9220df..5b85bcd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,9 +6,11 @@ */ // Main conversation and workspace classes -export { RemoteConversation, Conversation } from './conversation/remote-conversation'; +export { RemoteConversation } from './conversation/remote-conversation'; +export { Conversation } from './conversation/conversation'; export { ConversationManager } from './conversation/conversation-manager'; export { RemoteWorkspace } from './workspace/remote-workspace'; +export { Workspace } from './workspace/workspace'; export { RemoteState } from './conversation/remote-state'; export { RemoteEventsList } from './events/remote-events-list'; @@ -76,9 +78,11 @@ export type { RemoteConversationOptions } from './conversation/remote-conversati export type { ConversationManagerOptions } from './conversation/conversation-manager'; // Re-import for default export -import { RemoteConversation, Conversation } from './conversation/remote-conversation'; +import { RemoteConversation } from './conversation/remote-conversation'; +import { Conversation } from './conversation/conversation'; import { ConversationManager } from './conversation/conversation-manager'; import { RemoteWorkspace } from './workspace/remote-workspace'; +import { Workspace } from './workspace/workspace'; import { RemoteState } from './conversation/remote-state'; import { RemoteEventsList } from './events/remote-events-list'; import { WebSocketCallbackClient } from './events/websocket-client'; @@ -91,6 +95,7 @@ export default { Conversation, ConversationManager, RemoteWorkspace, + Workspace, RemoteState, RemoteEventsList, WebSocketCallbackClient, diff --git a/src/workspace/workspace.ts b/src/workspace/workspace.ts new file mode 100644 index 0000000..5bf911e --- /dev/null +++ b/src/workspace/workspace.ts @@ -0,0 +1,19 @@ +/** + * Workspace factory function that returns RemoteWorkspace + * Matches the Python SDK pattern: Workspace(host="http://...") + */ + +import { RemoteWorkspace, RemoteWorkspaceOptions } from './remote-workspace'; + +/** + * Workspace class that extends RemoteWorkspace. + * Provides a cleaner API that matches the Python SDK naming. + * + * Usage: + * const workspace = new Workspace({ host: 'http://localhost:8000', apiKey: 'key' }); + */ +export class Workspace extends RemoteWorkspace { + constructor(options: RemoteWorkspaceOptions) { + super(options); + } +} \ No newline at end of file From bfdb3efcb9765ef812b75568a483c43453f7e8ad Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 30 Oct 2025 17:16:07 +0000 Subject: [PATCH 3/5] Add Agent constructor class and fix API response handling - Create Agent class with constructor-based API for consistency - Update README and examples to use new Agent() constructor - Fix RemoteState.getConversationInfo() to handle full_state wrapper - Export Agent class and AgentOptions type from main index - Update React example to use new Agent constructor pattern Co-authored-by: openhands --- README.md | 4 +- .../src/components/ConversationManager.tsx | 4 +- src/agent/agent.ts | 45 +++++++++++++++++++ src/agent/index.ts | 1 + src/conversation/remote-state.ts | 16 +++++-- src/index.ts | 8 +++- 6 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 src/agent/agent.ts create mode 100644 src/agent/index.ts diff --git a/README.md b/README.md index 314b6e6..ae0a418 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ npm install @openhands/agent-server-typescript-client ```typescript import { Conversation, Agent, Workspace } from '@openhands/agent-server-typescript-client'; -const agent: Agent = { +const agent = new Agent({ kind: 'CodeActAgent', llm: { model: 'gpt-4', api_key: 'your-openai-api-key' } -}; +}); // Create a remote workspace const workspace = new Workspace({ diff --git a/example/src/components/ConversationManager.tsx b/example/src/components/ConversationManager.tsx index 3b8a00e..2748fed 100644 --- a/example/src/components/ConversationManager.tsx +++ b/example/src/components/ConversationManager.tsx @@ -192,13 +192,13 @@ export const ConversationManager: React.FC = () => { setError(null); try { // Create a simple agent configuration - const agent: Agent = { + const agent = new Agent({ kind: 'Agent', llm: { model: settings.modelName, api_key: settings.apiKey || '' } - }; + }); // Create a remote workspace const workspace = new Workspace({ diff --git a/src/agent/agent.ts b/src/agent/agent.ts new file mode 100644 index 0000000..53b8753 --- /dev/null +++ b/src/agent/agent.ts @@ -0,0 +1,45 @@ +/** + * Agent class that provides a constructor-based API for creating agents. + * Provides a cleaner API that matches the Python SDK naming. + */ + +import { AgentBase, LLM } from '../types/base'; + +export interface AgentOptions { + llm: LLM; + kind?: string; + name?: string; + [key: string]: any; +} + +/** + * Agent class that implements AgentBase interface. + * Provides a constructor-based API for creating agents. + * + * Usage: + * const agent = new Agent({ + * llm: { + * model: 'gpt-4', + * api_key: 'your-key' + * } + * }); + */ +export class Agent implements AgentBase { + kind: string; + llm: LLM; + name?: string; + [key: string]: any; + + constructor(options: AgentOptions) { + this.kind = options.kind || 'Agent'; + this.llm = options.llm; + this.name = options.name; + + // Copy any additional properties + Object.keys(options).forEach(key => { + if (key !== 'kind' && key !== 'llm' && key !== 'name') { + this[key] = options[key]; + } + }); + } +} \ No newline at end of file diff --git a/src/agent/index.ts b/src/agent/index.ts new file mode 100644 index 0000000..297341a --- /dev/null +++ b/src/agent/index.ts @@ -0,0 +1 @@ +export { Agent, AgentOptions } from './agent'; \ No newline at end of file diff --git a/src/conversation/remote-state.ts b/src/conversation/remote-state.ts index 95e338d..e4a9a26 100644 --- a/src/conversation/remote-state.ts +++ b/src/conversation/remote-state.ts @@ -44,12 +44,20 @@ export class RemoteState { } // Fallback to REST API if no cached state - const response = await this.client.get( + const response = await this.client.get( `/api/conversations/${this.conversationId}` ); - const state = response.data; - this.cachedState = state; - return state; + + // Handle the case where the API returns a full_state wrapper + let conversationInfo: ConversationInfo; + if (response.data.full_state) { + conversationInfo = response.data.full_state as ConversationInfo; + } else { + conversationInfo = response.data as ConversationInfo; + } + + this.cachedState = conversationInfo; + return conversationInfo; }); } diff --git a/src/index.ts b/src/index.ts index 5b85bcd..84d52a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,9 @@ export { Workspace } from './workspace/workspace'; export { RemoteState } from './conversation/remote-state'; export { RemoteEventsList } from './events/remote-events-list'; +// Agent classes +export { Agent } from './agent/agent'; + // WebSocket client for real-time events export { WebSocketCallbackClient } from './events/websocket-client'; @@ -35,7 +38,6 @@ export type { TextContent, ImageContent, AgentBase, - Agent, LLM, ServerInfo, Success, @@ -48,6 +50,8 @@ export type { AlwaysConfirm, } from './types/base'; +export type { AgentOptions } from './agent/agent'; + export { EventSortOrder, AgentExecutionStatus } from './types/base'; // Workspace models @@ -88,6 +92,7 @@ import { RemoteEventsList } from './events/remote-events-list'; import { WebSocketCallbackClient } from './events/websocket-client'; import { HttpClient, HttpError } from './client/http-client'; import { EventSortOrder, AgentExecutionStatus } from './types/base'; +import { Agent } from './agent/agent'; // Default export for convenience export default { @@ -103,4 +108,5 @@ export default { HttpError, EventSortOrder, AgentExecutionStatus, + Agent, }; From 704a0eb347430cf8f1c2febd7a909d944d43bb04 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 30 Oct 2025 17:22:30 +0000 Subject: [PATCH 4/5] Remove kind parameter from Agent examples to match Python SDK - Remove explicit kind: 'Agent' from all examples and README - Agent constructor defaults kind to 'Agent' automatically - Matches Python SDK pattern where Agent() doesn't require kind parameter - All builds still passing --- README.md | 1 - example/src/components/ConversationManager.tsx | 1 - examples/basic-usage.ts | 10 ++++------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ae0a418..c73e19b 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ npm install @openhands/agent-server-typescript-client import { Conversation, Agent, Workspace } from '@openhands/agent-server-typescript-client'; const agent = new Agent({ - kind: 'CodeActAgent', llm: { model: 'gpt-4', api_key: 'your-openai-api-key' diff --git a/example/src/components/ConversationManager.tsx b/example/src/components/ConversationManager.tsx index 2748fed..cc3f9f1 100644 --- a/example/src/components/ConversationManager.tsx +++ b/example/src/components/ConversationManager.tsx @@ -193,7 +193,6 @@ export const ConversationManager: React.FC = () => { try { // Create a simple agent configuration const agent = new Agent({ - kind: 'Agent', llm: { model: settings.modelName, api_key: settings.apiKey || '' diff --git a/examples/basic-usage.ts b/examples/basic-usage.ts index f490564..8026b44 100644 --- a/examples/basic-usage.ts +++ b/examples/basic-usage.ts @@ -6,13 +6,12 @@ import { Conversation, Agent, Workspace, AgentExecutionStatus } from '../src/ind async function main() { // Define the agent configuration - const agent: Agent = { - kind: 'CodeActAgent', + const agent = new Agent({ llm: { model: 'gpt-4', api_key: process.env.OPENAI_API_KEY || 'your-openai-api-key', }, - }; + }); try { // Create a remote workspace @@ -85,13 +84,12 @@ async function main() { // Example of loading an existing conversation async function loadExistingConversation() { - const agent: Agent = { - kind: 'CodeActAgent', + const agent = new Agent({ llm: { model: 'gpt-4', api_key: process.env.OPENAI_API_KEY || 'your-openai-api-key', }, - }; + }); try { // Create a remote workspace for the existing conversation From 47a1877667af30ab5f14d9ef4a52398df5704c85 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 30 Oct 2025 17:25:35 +0000 Subject: [PATCH 5/5] Fix tests and formatting for new API - Update tests to use new Agent, Workspace, and Conversation constructors - Test both new API and backwards compatibility exports - Fix code formatting with prettier - All CI steps now passing: build, lint, test, format:check --- examples/basic-usage.ts | 8 +-- src/__tests__/index.test.ts | 81 ++++++++++++++++++------ src/agent/agent.ts | 8 +-- src/agent/index.ts | 2 +- src/conversation/conversation-manager.ts | 5 +- src/conversation/conversation.ts | 12 ++-- src/conversation/remote-conversation.ts | 12 ++-- src/conversation/remote-state.ts | 8 +-- src/workspace/workspace.ts | 4 +- 9 files changed, 88 insertions(+), 52 deletions(-) diff --git a/examples/basic-usage.ts b/examples/basic-usage.ts index 8026b44..78309d1 100644 --- a/examples/basic-usage.ts +++ b/examples/basic-usage.ts @@ -18,7 +18,7 @@ async function main() { const workspace = new Workspace({ host: 'http://localhost:3000', workingDir: '/tmp', - apiKey: process.env.SESSION_API_KEY || 'your-session-api-key' + apiKey: process.env.SESSION_API_KEY || 'your-session-api-key', }); // Create a new conversation @@ -31,7 +31,7 @@ async function main() { // Start the conversation with an initial message await conversation.start({ - initialMessage: 'Hello! Can you help me write a simple Python script?' + initialMessage: 'Hello! Can you help me write a simple Python script?', }); console.log(`Conversation created with ID: ${conversation.id}`); @@ -96,11 +96,11 @@ async function loadExistingConversation() { const workspace = new Workspace({ host: 'http://localhost:3000', workingDir: '/tmp', - apiKey: process.env.SESSION_API_KEY || 'your-session-api-key' + apiKey: process.env.SESSION_API_KEY || 'your-session-api-key', }); const conversation = new Conversation(agent, workspace, { - conversationId: 'existing-conversation-id' + conversationId: 'existing-conversation-id', }); // Connect to the existing conversation diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 91d0a86..10916dc 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -1,51 +1,90 @@ -import { RemoteConversation, RemoteWorkspace } from '../index'; +import { Conversation, Agent, Workspace, RemoteConversation, RemoteWorkspace } from '../index'; describe('OpenHands Agent Server TypeScript Client', () => { describe('Exports', () => { - it('should export RemoteConversation', () => { + it('should export Conversation', () => { + expect(Conversation).toBeDefined(); + expect(typeof Conversation).toBe('function'); + }); + + it('should export Agent', () => { + expect(Agent).toBeDefined(); + expect(typeof Agent).toBe('function'); + }); + + it('should export Workspace', () => { + expect(Workspace).toBeDefined(); + expect(typeof Workspace).toBe('function'); + }); + + it('should export RemoteConversation for backwards compatibility', () => { expect(RemoteConversation).toBeDefined(); expect(typeof RemoteConversation).toBe('function'); }); - it('should export RemoteWorkspace', () => { + it('should export RemoteWorkspace for backwards compatibility', () => { expect(RemoteWorkspace).toBeDefined(); expect(typeof RemoteWorkspace).toBe('function'); }); }); - describe('RemoteConversation', () => { - it('should create instance with config', () => { - const config = { + describe('New API - Conversation', () => { + it('should create instance with agent and workspace', () => { + const agent = new Agent({ + llm: { + model: 'gpt-4', + api_key: 'test-key', + }, + }); + + const workspace = new Workspace({ host: 'http://localhost:8000', + workingDir: '/tmp', apiKey: 'test-key', - }; + }); - const conversation = new RemoteConversation(config); + const conversation = new Conversation(agent, workspace); expect(conversation).toBeInstanceOf(RemoteConversation); + expect(conversation.workspace).toBe(workspace); }); + }); - it('should throw error when accessing workspace before initialization', () => { - const config = { - host: 'http://localhost:8000', - apiKey: 'test-key', - }; + describe('Agent', () => { + it('should create instance with LLM config', () => { + const agent = new Agent({ + llm: { + model: 'gpt-4', + api_key: 'test-key', + }, + }); + + expect(agent).toBeInstanceOf(Agent); + expect(agent.kind).toBe('Agent'); + expect(agent.llm.model).toBe('gpt-4'); + expect(agent.llm.api_key).toBe('test-key'); + }); + + it('should allow custom kind', () => { + const agent = new Agent({ + kind: 'CustomAgent', + llm: { + model: 'gpt-4', + api_key: 'test-key', + }, + }); - const conversation = new RemoteConversation(config); - expect(() => conversation.workspace).toThrow( - 'Workspace not initialized. Create or load a conversation first.' - ); + expect(agent.kind).toBe('CustomAgent'); }); }); - describe('RemoteWorkspace', () => { + describe('Workspace', () => { it('should create instance with options', () => { - const options = { + const workspace = new Workspace({ host: 'http://localhost:8000', workingDir: '/tmp', apiKey: 'test-key', - }; + }); - const workspace = new RemoteWorkspace(options); expect(workspace).toBeInstanceOf(RemoteWorkspace); expect(workspace.host).toBe('http://localhost:8000'); expect(workspace.workingDir).toBe('/tmp'); diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 53b8753..6e6d387 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -15,7 +15,7 @@ export interface AgentOptions { /** * Agent class that implements AgentBase interface. * Provides a constructor-based API for creating agents. - * + * * Usage: * const agent = new Agent({ * llm: { @@ -34,12 +34,12 @@ export class Agent implements AgentBase { this.kind = options.kind || 'Agent'; this.llm = options.llm; this.name = options.name; - + // Copy any additional properties - Object.keys(options).forEach(key => { + Object.keys(options).forEach((key) => { if (key !== 'kind' && key !== 'llm' && key !== 'name') { this[key] = options[key]; } }); } -} \ No newline at end of file +} diff --git a/src/agent/index.ts b/src/agent/index.ts index 297341a..678d6d7 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -1 +1 @@ -export { Agent, AgentOptions } from './agent'; \ No newline at end of file +export { Agent, AgentOptions } from './agent'; diff --git a/src/conversation/conversation-manager.ts b/src/conversation/conversation-manager.ts index 4ae1643..043a289 100644 --- a/src/conversation/conversation-manager.ts +++ b/src/conversation/conversation-manager.ts @@ -112,7 +112,10 @@ export class ConversationManager { /** * Load an existing conversation */ - async loadConversation(conversationId: ConversationID, workingDir: string = '/tmp'): Promise { + async loadConversation( + conversationId: ConversationID, + workingDir: string = '/tmp' + ): Promise { // Get conversation info to extract the agent const conversationInfo = await this.getConversation(conversationId); diff --git a/src/conversation/conversation.ts b/src/conversation/conversation.ts index b7d0ada..ab54dee 100644 --- a/src/conversation/conversation.ts +++ b/src/conversation/conversation.ts @@ -10,21 +10,17 @@ import { RemoteConversation, RemoteConversationOptions } from './remote-conversa /** * Conversation class that extends RemoteConversation. * Provides a cleaner API that matches the Python SDK naming. - * + * * Usage: * const conversation = new Conversation(agent, workspace); * await conversation.start(); - * + * * For existing conversations: * const conversation = new Conversation(agent, workspace, { conversationId: 'existing-id' }); * await conversation.start(); */ export class Conversation extends RemoteConversation { - constructor( - agent: AgentBase, - workspace: RemoteWorkspace, - options?: RemoteConversationOptions - ) { + constructor(agent: AgentBase, workspace: RemoteWorkspace, options?: RemoteConversationOptions) { super(agent, workspace, options); } -} \ No newline at end of file +} diff --git a/src/conversation/remote-conversation.ts b/src/conversation/remote-conversation.ts index a315a46..784952e 100644 --- a/src/conversation/remote-conversation.ts +++ b/src/conversation/remote-conversation.ts @@ -70,14 +70,18 @@ export class RemoteConversation { get state(): RemoteState { if (!this._state) { if (!this._conversationId) { - throw new Error('Conversation not initialized. Call start() to initialize the conversation.'); + throw new Error( + 'Conversation not initialized. Call start() to initialize the conversation.' + ); } this._state = new RemoteState(this.client, this._conversationId); } return this._state; } - async start(options: { initialMessage?: string; maxIterations?: number; stuckDetection?: boolean } = {}): Promise { + async start( + options: { initialMessage?: string; maxIterations?: number; stuckDetection?: boolean } = {} + ): Promise { if (this._conversationId) { // Existing conversation - verify it exists await this.client.get(`/api/conversations/${this._conversationId}`); @@ -211,13 +215,9 @@ export class RemoteConversation { } } - - async close(): Promise { await this.stopWebSocketClient(); this.client.close(); this.workspace.close(); } } - - diff --git a/src/conversation/remote-state.ts b/src/conversation/remote-state.ts index e4a9a26..8d51632 100644 --- a/src/conversation/remote-state.ts +++ b/src/conversation/remote-state.ts @@ -44,10 +44,8 @@ export class RemoteState { } // Fallback to REST API if no cached state - const response = await this.client.get( - `/api/conversations/${this.conversationId}` - ); - + const response = await this.client.get(`/api/conversations/${this.conversationId}`); + // Handle the case where the API returns a full_state wrapper let conversationInfo: ConversationInfo; if (response.data.full_state) { @@ -55,7 +53,7 @@ export class RemoteState { } else { conversationInfo = response.data as ConversationInfo; } - + this.cachedState = conversationInfo; return conversationInfo; }); diff --git a/src/workspace/workspace.ts b/src/workspace/workspace.ts index 5bf911e..45e10ec 100644 --- a/src/workspace/workspace.ts +++ b/src/workspace/workspace.ts @@ -8,7 +8,7 @@ import { RemoteWorkspace, RemoteWorkspaceOptions } from './remote-workspace'; /** * Workspace class that extends RemoteWorkspace. * Provides a cleaner API that matches the Python SDK naming. - * + * * Usage: * const workspace = new Workspace({ host: 'http://localhost:8000', apiKey: 'key' }); */ @@ -16,4 +16,4 @@ export class Workspace extends RemoteWorkspace { constructor(options: RemoteWorkspaceOptions) { super(options); } -} \ No newline at end of file +}