diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 1525f8f..4942d0a 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -16,7 +16,7 @@ env: AGENT_SERVER_PORT: 8010 HOST_WORKSPACE_DIR: /tmp/agent-workspace AGENT_WORKSPACE_DIR: /workspace - AGENT_SERVER_IMAGE: ghcr.io/openhands/agent-server:7c37803-python # software-agent-sdk v1.18.1 + AGENT_SERVER_IMAGE: ghcr.io/openhands/agent-server:b1235d0-python # software-agent-sdk v1.23.0 jobs: integration-test: diff --git a/AGENTS.md b/AGENTS.md index 011018a..407a38d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -304,10 +304,10 @@ Integration tests are in `src/__tests__/integration/` and require a running agen export LLM_API_KEY="your-api-key" export LLM_MODEL="anthropic/claude-sonnet-4-5-20250929" -# Start agent-server in Docker (software-agent-sdk v1.18.1) +# Start agent-server in Docker (software-agent-sdk v1.23.0) docker run -d --name agent-server -p 8010:8000 \ -v /tmp/agent-workspace:/workspace \ - ghcr.io/openhands/agent-server:7c37803-python + ghcr.io/openhands/agent-server:b1235d0-python # Run integration tests npm run test:integration @@ -336,7 +336,7 @@ Required GitHub secrets: ### CI Image Version -- The integration workflow pins `ghcr.io/openhands/agent-server:7c37803-python`, which corresponds to the `software-agent-sdk` release `v1.18.1`. +- The integration workflow pins `ghcr.io/openhands/agent-server:b1235d0-python`, which corresponds to the `software-agent-sdk` release `v1.23.0`. - Keep the TypeScript client tests strict against that released server image rather than adding compatibility fallbacks for older prerelease builds. ## Agent Behavior Guidelines diff --git a/src/__tests__/api-clients.test.ts b/src/__tests__/api-clients.test.ts index 7b040ef..edf5efb 100644 --- a/src/__tests__/api-clients.test.ts +++ b/src/__tests__/api-clients.test.ts @@ -760,6 +760,7 @@ describe('Auxiliary API clients', () => { const client = new ConversationClient({ host: 'http://example.com' }); await client.sendEvent('c1', { role: 'user', content: [] }, { run: true }); await client.pauseConversation('c1'); + await client.interruptConversation('c1'); await client.runConversation('c1'); await client.askAgent('c1', 'status?'); await client.respondToConfirmation('c1', { accept: true }); @@ -775,17 +776,22 @@ describe('Auxiliary API clients', () => { }) ); expect(global.fetch).toHaveBeenNthCalledWith( - 4, + 3, + 'http://example.com/api/conversations/c1/interrupt', + expect.objectContaining({ method: 'POST', body: JSON.stringify({}) }) + ); + expect(global.fetch).toHaveBeenNthCalledWith( + 5, 'http://example.com/api/conversations/c1/ask_agent', expect.objectContaining({ method: 'POST', body: JSON.stringify({ question: 'status?' }) }) ); expect(global.fetch).toHaveBeenNthCalledWith( - 5, + 6, 'http://example.com/api/conversations/c1/events/respond_to_confirmation', expect.objectContaining({ method: 'POST', body: JSON.stringify({ accept: true }) }) ); expect(global.fetch).toHaveBeenNthCalledWith( - 7, + 8, 'http://example.com/api/conversations/c1', expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ title: 'New title' }) }) ); diff --git a/src/__tests__/integration/deterministic-api.integration.test.ts b/src/__tests__/integration/deterministic-api.integration.test.ts index 315dcf4..c15a5cf 100644 --- a/src/__tests__/integration/deterministic-api.integration.test.ts +++ b/src/__tests__/integration/deterministic-api.integration.test.ts @@ -58,7 +58,7 @@ describe('Deterministic API Integration Tests', () => { expect(root).toBeDefined(); expect(alive.status).toBe('ok'); - expect(health).toBe('OK'); + expect(health.status).toBe('ok'); expect(['ready', 'initializing']).toContain(ready.status); expect(info.version).toBeDefined(); expect(Array.isArray(providers)).toBe(true); @@ -114,14 +114,13 @@ describe('Deterministic API Integration Tests', () => { const finalResponse = await conversation.getAgentFinalResponse(); expect(typeof finalResponse).toBe('string'); - const trajectoryFile = `/workspace/conversations/${conversation.id.replace(/-/g, '')}.zip`; - await workspace.executeCommand( - `mkdir -p /workspace/conversations && printf 'trajectory-data' > ${trajectoryFile}` - ); const trajectory = await conversation.downloadTrajectory(); expect(trajectory).toBeInstanceOf(Blob); - expect(await trajectory.text()).toContain('trajectory-data'); - await workspace.executeCommand(`rm -f ${trajectoryFile}`); + expect(trajectory.size).toBeGreaterThan(0); + const trajectoryBytes = new Uint8Array(await trajectory.arrayBuffer()); + // Verify it's a valid ZIP file (PK\x03\x04 magic bytes) + expect(trajectoryBytes[0]).toBe(0x50); // P + expect(trajectoryBytes[1]).toBe(0x4b); // K const forkedConversation = await conversation.fork({ title: 'Forked from deterministic test', diff --git a/src/client/conversation-client.ts b/src/client/conversation-client.ts index 98ebfcb..e3ea0a7 100644 --- a/src/client/conversation-client.ts +++ b/src/client/conversation-client.ts @@ -90,6 +90,14 @@ export class ConversationClient { return response.data; } + async interruptConversation(conversationId: string): Promise { + const response = await this.client.post( + `/api/conversations/${conversationId}/interrupt`, + {} + ); + return response.data; + } + async runConversation(conversationId: string): Promise { const response = await this.client.post( `/api/conversations/${conversationId}/run`, diff --git a/src/client/server-client.ts b/src/client/server-client.ts index f2f202c..1fc8fdb 100644 --- a/src/client/server-client.ts +++ b/src/client/server-client.ts @@ -1,5 +1,5 @@ import { HttpClient } from './http-client'; -import { AliveStatus, ReadyStatus } from '../models/api'; +import { AliveStatus, HealthStatus, ReadyStatus } from '../models/api'; import { ServerInfo } from '../types/base'; export interface ServerClientOptions { @@ -33,8 +33,8 @@ export class ServerClient { return response.data; } - async getHealth(): Promise { - const response = await this.client.get('/health'); + async getHealth(): Promise { + const response = await this.client.get('/health'); return response.data; } diff --git a/src/index.ts b/src/index.ts index cbb896c..a7a91f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -236,6 +236,7 @@ export type { HttpClientOptions, RequestOptions, HttpResponse } from './client/h export type { AliveStatus, + HealthStatus, ReadyStatus, ProvidersResponse, ModelsResponse, diff --git a/src/models/api.ts b/src/models/api.ts index 80331ab..7777fbf 100644 --- a/src/models/api.ts +++ b/src/models/api.ts @@ -8,6 +8,10 @@ export interface AliveStatus { status: string; } +export interface HealthStatus { + status: string; +} + export interface ReadyStatus { status: string; message?: string;