From 2ba630c1809714e3802b98699a7804b8fd38d11b Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Mon, 9 Feb 2026 18:53:00 -0800 Subject: [PATCH] acp spec --- .github/workflows/ci.yaml | 10 + CLAUDE.md | 186 +- Cargo.toml | 12 +- docs/advanced/acp-http-client.mdx | 67 + docs/agent-sessions.mdx | 70 +- docs/attachments.mdx | 14 +- docs/conversion.mdx | 113 - docs/credentials.mdx | 159 +- docs/custom-tools.mdx | 14 +- docs/deploy/cloudflare.mdx | 11 +- docs/deploy/daytona.mdx | 4 +- docs/deploy/docker.mdx | 4 +- docs/deploy/e2b.mdx | 4 +- docs/deploy/vercel.mdx | 4 +- docs/docs.json | 4 + docs/file-system.mdx | 93 +- docs/manage-sessions.mdx | 10 +- docs/mcp-config.mdx | 7 +- docs/openapi.json | 2189 +- docs/opencode-compatibility.mdx | 153 +- docs/quickstart.mdx | 10 +- docs/sdks/typescript.mdx | 221 +- docs/skills-config.mdx | 7 +- examples/CLAUDE.md | 19 +- frontend/CLAUDE.md | 162 +- frontend/packages/inspector/index.html | 20 - .../src/components/SessionCreateMenu.tsx | 5 +- .../agents/FeatureCoverageBadges.tsx | 2 +- .../src/components/chat/ChatMessages.tsx | 4 +- .../src/components/chat/ChatPanel.tsx | 14 +- .../src/components/chat/messageUtils.ts | 2 +- .../src/components/chat/renderContentPart.tsx | 2 +- .../inspector/src/components/chat/types.ts | 2 +- .../src/components/debug/AgentsTab.tsx | 2 +- .../src/components/debug/ApprovalsTab.tsx | 2 +- .../src/components/debug/DebugPanel.tsx | 2 +- .../src/components/debug/EventsTab.tsx | 2 +- .../src/components/debug/RequestLogTab.tsx | 114 +- .../src/components/debug/eventUtils.ts | 45 +- .../inspector/src/lib/legacyClient.ts | 790 + .../packages/inspector/src/types/agents.ts | 11 +- .../packages/inspector/src/types/legacyApi.ts | 145 + .../inspector/src/types/requestLog.ts | 2 + frontend/packages/inspector/vite.config.ts | 4 + .../packages/website/src/components/FAQ.tsx | 2 +- pnpm-lock.yaml | 316 +- pnpm-workspace.yaml | 1 - research/acp/00-delete-first.md | 53 + research/acp/README.md | 41 + research/acp/acp-notes.md | 66 + research/acp/acp-over-http-findings.md | 99 + research/acp/extensibility-status.md | 126 + research/acp/friction.md | 229 + research/acp/inspector-unimplemented.md | 14 + research/acp/merge-acp.md | 242 + research/acp/migration-steps.md | 174 + .../acp/missing-features-spec/01-questions.md | 205 + .../04-filesystem-api.md | 387 + .../05-health-endpoint.md | 90 + .../missing-features-spec/06-server-status.md | 144 + .../07-session-termination.md | 123 + .../08-model-variants.md | 130 + .../missing-features-spec/10-include-raw.md | 98 + .../missing-features-spec/12-agent-listing.md | 222 + .../13-models-modes-listing.md | 67 + .../14-message-attachments.md | 132 + .../15-session-creation-richness.md | 107 + .../missing-features-spec/16-session-info.md | 170 + .../17-error-termination-metadata.md | 191 + .../missing-features-spec/feature-index.md | 30 + research/acp/missing-features-spec/plan.md | 132 + research/acp/old-rest-openapi-list.md | 437 + research/acp/rfds-vs-extensions.md | 21 + research/acp/spec.md | 366 + research/acp/todo.md | 169 + research/acp/ts-client.md | 221 + research/acp/v1-schema-to-acp-mapping.md | 157 + research/agents/claude.md | 15 + research/agents/codex.md | 35 + resources/agent-schemas/.gitignore | 3 - .../artifacts/json-schema/amp.json | 196 - .../artifacts/json-schema/claude.json | 182 - .../artifacts/json-schema/codex.json | 17956 ---------------- .../artifacts/json-schema/opencode.json | 5916 ----- .../artifacts/openapi/opencode.json | 10933 ---------- resources/agent-schemas/deno.lock | 695 - resources/agent-schemas/package.json | 31 - resources/agent-schemas/src/amp.ts | 286 - resources/agent-schemas/src/cache.ts | 101 - .../src/claude-event-types-cli.ts | 11 - .../src/claude-event-types-docs.ts | 8 - .../src/claude-event-types-sdk.ts | 4 - .../agent-schemas/src/claude-event-types.ts | 338 - resources/agent-schemas/src/claude.ts | 137 - resources/agent-schemas/src/codex.ts | 181 - resources/agent-schemas/src/index.ts | 112 - resources/agent-schemas/src/normalize.ts | 129 - resources/agent-schemas/src/opencode.ts | 83 - resources/agent-schemas/tsconfig.json | 16 - sdks/acp-http-client/package.json | 37 + sdks/acp-http-client/src/index.ts | 574 + sdks/acp-http-client/tests/smoke.test.ts | 135 + sdks/acp-http-client/tsconfig.json | 16 + sdks/acp-http-client/tsup.config.ts | 9 + sdks/acp-http-client/vitest.config.ts | 8 + sdks/cli-shared/package.json | 2 +- sdks/cli/package.json | 2 +- sdks/cli/platforms/darwin-arm64/package.json | 2 +- sdks/cli/platforms/darwin-x64/package.json | 2 +- sdks/cli/platforms/linux-arm64/package.json | 2 +- sdks/cli/platforms/linux-x64/package.json | 2 +- sdks/cli/platforms/win32-x64/package.json | 2 +- sdks/gigacode/package.json | 2 +- .../platforms/darwin-arm64/package.json | 2 +- .../platforms/darwin-x64/package.json | 2 +- .../platforms/linux-arm64/package.json | 2 +- .../gigacode/platforms/linux-x64/package.json | 2 +- .../gigacode/platforms/win32-x64/package.json | 2 +- sdks/typescript/package.json | 9 +- sdks/typescript/src/client.ts | 960 +- sdks/typescript/src/generated/openapi.ts | 1009 +- sdks/typescript/src/index.ts | 102 +- sdks/typescript/src/spawn.ts | 2 +- sdks/typescript/src/types.ts | 346 +- sdks/typescript/tests/client.test.ts | 322 - sdks/typescript/tests/integration.test.ts | 386 +- sdks/typescript/tests/sse-parser.test.ts | 208 - server/CLAUDE.md | 142 +- server/packages/agent-management/Cargo.toml | 1 - server/packages/error/src/lib.rs | 36 + .../extracted-agent-schemas/Cargo.toml | 21 - .../packages/extracted-agent-schemas/build.rs | 70 - .../extracted-agent-schemas/src/lib.rs | 33 - .../tests/schema_roundtrip.rs | 104 - server/packages/sandbox-agent/Cargo.toml | 4 +- server/packages/sandbox-agent/build.rs | 3 + .../sandbox-agent/src/acp_runtime/backend.rs | 330 + .../sandbox-agent/src/acp_runtime/ext_meta.rs | 123 + .../src/acp_runtime/ext_methods.rs | 1386 ++ .../sandbox-agent/src/acp_runtime/helpers.rs | 573 + .../sandbox-agent/src/acp_runtime/mock.rs | 425 + .../sandbox-agent/src/acp_runtime/mod.rs | 1763 ++ .../src/agent_server_logs/mod.rs | 8 + .../src/agent_server_logs/unix.rs | 3 +- .../src/agent_server_logs/windows.rs | 3 +- server/packages/sandbox-agent/src/cli.rs | 1262 +- .../packages/sandbox-agent/src/credentials.rs | 1 - server/packages/sandbox-agent/src/daemon.rs | 4 +- server/packages/sandbox-agent/src/lib.rs | 4 +- .../src/opencode_session_manager.rs | 1090 + .../sandbox-agent/src/router/support.rs | 626 + .../sandbox-agent/src/router/types.rs | 346 + .../src/universal_events.rs} | 0 .../tests/agent-flows/agent_basic_reply.rs | 45 - .../tests/agent-flows/agent_file_edit_flow.rs | 105 - .../tests/agent-flows/agent_multi_turn.rs | 144 - .../agent-flows/agent_permission_flow.rs | 70 - .../tests/agent-flows/agent_question_flow.rs | 67 - .../tests/agent-flows/agent_termination.rs | 56 - .../tests/agent-flows/agent_tool_flow.rs | 93 - .../sandbox-agent/tests/agent-flows/mod.rs | 8 - .../tests/agent-management/agents.rs | 182 - .../tests/agent-management/mod.rs | 1 - .../sandbox-agent/tests/agent_flows.rs | 2 - .../sandbox-agent/tests/agent_management.rs | 2 - .../sandbox-agent/tests/common/http.rs | 1069 - .../sandbox-agent/tests/common/mod.rs | 339 - .../tests/http/agent_endpoints.rs | 411 - .../sandbox-agent/tests/http/fs_endpoints.rs | 270 - ...points_snapshots@agent_install_claude.snap | 6 - ...dpoints_snapshots@agent_install_codex.snap | 6 - ...ndpoints_snapshots@agent_install_mock.snap | 6 - ...ints_snapshots@agent_install_opencode.snap | 6 - ..._endpoints_snapshots@agent_models_amp.snap | 19 - ...dpoints_snapshots@agent_models_claude.snap | 8 - ...ndpoints_snapshots@agent_models_codex.snap | 8 - ...endpoints_snapshots@agent_models_mock.snap | 12 - ...oints_snapshots@agent_models_opencode.snap | 8 - ...ndpoints_snapshots@agent_modes_claude.snap | 12 - ...endpoints_snapshots@agent_modes_codex.snap | 12 - ..._endpoints_snapshots@agent_modes_mock.snap | 12 - ...points_snapshots@agent_modes_opencode.snap | 14 - ...ndpoints_snapshots@agents_list_global.snap | 10 - ...ent_endpoints_snapshots@health_global.snap | 6 - ...h_snapshots@auth_health_public_global.snap | 8 - ...h_snapshots@auth_invalid_token_global.snap | 13 - ...h_snapshots@auth_missing_token_global.snap | 13 - ...uth_snapshots@auth_valid_token_global.snap | 12 - ...ts__cors_snapshots@cors_actual_global.snap | 12 - ..._cors_snapshots@cors_preflight_global.snap | 11 - ...endpoints_snapshots@agent_install_amp.snap | 6 - ...points_snapshots@agent_install_claude.snap | 5 - ...dpoints_snapshots@agent_install_codex.snap | 6 - ...ndpoints_snapshots@agent_install_mock.snap | 5 - ...ints_snapshots@agent_install_opencode.snap | 5 - ..._endpoints_snapshots@agent_models_amp.snap | 13 - ...dpoints_snapshots@agent_models_claude.snap | 9 - ...ndpoints_snapshots@agent_models_codex.snap | 9 - ...oints_snapshots@agent_models_opencode.snap | 8 - ...t_endpoints_snapshots@agent_modes_amp.snap | 9 - ...ndpoints_snapshots@agent_modes_claude.snap | 11 - ...endpoints_snapshots@agent_modes_codex.snap | 12 - ..._endpoints_snapshots@agent_modes_mock.snap | 11 - ...points_snapshots@agent_modes_opencode.snap | 14 - ...ndpoints_snapshots@agents_list_global.snap | 12 - ...ent_endpoints_snapshots@health_global.snap | 5 - ...h_snapshots@auth_health_public_global.snap | 7 - ...h_snapshots@auth_invalid_token_global.snap | 12 - ...h_snapshots@auth_missing_token_global.snap | 12 - ...uth_snapshots@auth_valid_token_global.snap | 14 - ...ts__cors_snapshots@cors_actual_global.snap | 10 - ..._cors_snapshots@cors_preflight_global.snap | 9 - .../sandbox-agent/tests/http_endpoints.rs | 4 - .../tests/opencode-compat/helpers/spawn.ts | 2 +- .../tests/opencode-compat/session.test.ts | 12 +- .../sandbox-agent/tests/opencode_openapi.rs | 7 + .../server-manager/agent_server_manager.rs | 122 - .../sandbox-agent/tests/server-manager/mod.rs | 2 - .../sandbox-agent/tests/server_manager.rs | 2 - .../packages/sandbox-agent/tests/sessions.rs | 2 - .../sandbox-agent/tests/sessions/mod.rs | 6 - .../tests/sessions/multi_turn.rs | 128 - .../tests/sessions/permissions.rs | 272 - .../sandbox-agent/tests/sessions/questions.rs | 140 - .../sandbox-agent/tests/sessions/reasoning.rs | 53 - .../tests/sessions/session_lifecycle.rs | 263 - ...sert_session_snapshot@multi_turn_mock.snap | 87 - ...ssion_snapshot@permission_events_mock.snap | 31 - ...napshot@permission_reply_missing_mock.snap | 10 - ...ession_snapshot@permission_reply_mock.snap | 5 - ..._snapshot@question_reject_events_mock.snap | 31 - ...snapshot@question_reject_missing_mock.snap | 10 - ...session_snapshot@question_reject_mock.snap | 5 - ...n_snapshot@question_reply_events_mock.snap | 31 - ..._snapshot@question_reply_missing_mock.snap | 10 - ..._session_snapshot@question_reply_mock.snap | 5 - ...sion_snapshot@concurrency_events_mock.snap | 91 - ...ession_snapshot@create_session_mock-2.snap | 6 - ..._session_snapshot@create_session_mock.snap | 6 - ...rt_session_snapshot@send_message_mock.snap | 5 - ...session_snapshot@sessions_list_mock-2.snap | 6 - ...t_session_snapshot@sessions_list_mock.snap | 6 - ...http_events_snapshot@http_events_mock.snap | 47 - ...n_sse_events_snapshot@sse_events_mock.snap | 47 - ...e_events_snapshot@sse_events_mock.snap.new | 41 - .../sandbox-agent/tests/sessions/status.rs | 58 - server/packages/sandbox-agent/tests/ui.rs | 2 - .../sandbox-agent/tests/ui/inspector_ui.rs | 37 - server/packages/sandbox-agent/tests/ui/mod.rs | 1 - .../tests/v2_agent_process_matrix.rs | 628 + server/packages/sandbox-agent/tests/v2_api.rs | 354 + .../tests/v2_api/acp_extensions.rs | 569 + .../tests/v2_api/acp_transport.rs | 623 + .../tests/v2_api/control_plane.rs | 366 + .../universal-agent-schema/Cargo.toml | 16 - .../universal-agent-schema/src/agents/amp.rs | 227 - .../src/agents/claude.rs | 524 - .../src/agents/codex.rs | 528 - .../universal-agent-schema/src/agents/mod.rs | 5 - .../src/agents/opencode.rs | 621 - .../packages/universal-schema-gen/Cargo.toml | 14 - server/packages/universal-schema-gen/build.rs | 26 - .../packages/universal-schema-gen/src/lib.rs | 2 - spec/universal-schema.json | 645 - 264 files changed, 18559 insertions(+), 51021 deletions(-) create mode 100644 docs/advanced/acp-http-client.mdx delete mode 100644 docs/conversion.mdx create mode 100644 frontend/packages/inspector/src/lib/legacyClient.ts create mode 100644 frontend/packages/inspector/src/types/legacyApi.ts create mode 100644 research/acp/00-delete-first.md create mode 100644 research/acp/README.md create mode 100644 research/acp/acp-notes.md create mode 100644 research/acp/acp-over-http-findings.md create mode 100644 research/acp/extensibility-status.md create mode 100644 research/acp/friction.md create mode 100644 research/acp/inspector-unimplemented.md create mode 100644 research/acp/merge-acp.md create mode 100644 research/acp/migration-steps.md create mode 100644 research/acp/missing-features-spec/01-questions.md create mode 100644 research/acp/missing-features-spec/04-filesystem-api.md create mode 100644 research/acp/missing-features-spec/05-health-endpoint.md create mode 100644 research/acp/missing-features-spec/06-server-status.md create mode 100644 research/acp/missing-features-spec/07-session-termination.md create mode 100644 research/acp/missing-features-spec/08-model-variants.md create mode 100644 research/acp/missing-features-spec/10-include-raw.md create mode 100644 research/acp/missing-features-spec/12-agent-listing.md create mode 100644 research/acp/missing-features-spec/13-models-modes-listing.md create mode 100644 research/acp/missing-features-spec/14-message-attachments.md create mode 100644 research/acp/missing-features-spec/15-session-creation-richness.md create mode 100644 research/acp/missing-features-spec/16-session-info.md create mode 100644 research/acp/missing-features-spec/17-error-termination-metadata.md create mode 100644 research/acp/missing-features-spec/feature-index.md create mode 100644 research/acp/missing-features-spec/plan.md create mode 100644 research/acp/old-rest-openapi-list.md create mode 100644 research/acp/rfds-vs-extensions.md create mode 100644 research/acp/spec.md create mode 100644 research/acp/todo.md create mode 100644 research/acp/ts-client.md create mode 100644 research/acp/v1-schema-to-acp-mapping.md delete mode 100644 resources/agent-schemas/.gitignore delete mode 100644 resources/agent-schemas/artifacts/json-schema/amp.json delete mode 100644 resources/agent-schemas/artifacts/json-schema/claude.json delete mode 100644 resources/agent-schemas/artifacts/json-schema/codex.json delete mode 100644 resources/agent-schemas/artifacts/json-schema/opencode.json delete mode 100644 resources/agent-schemas/artifacts/openapi/opencode.json delete mode 100644 resources/agent-schemas/deno.lock delete mode 100644 resources/agent-schemas/package.json delete mode 100644 resources/agent-schemas/src/amp.ts delete mode 100644 resources/agent-schemas/src/cache.ts delete mode 100644 resources/agent-schemas/src/claude-event-types-cli.ts delete mode 100644 resources/agent-schemas/src/claude-event-types-docs.ts delete mode 100644 resources/agent-schemas/src/claude-event-types-sdk.ts delete mode 100644 resources/agent-schemas/src/claude-event-types.ts delete mode 100644 resources/agent-schemas/src/claude.ts delete mode 100644 resources/agent-schemas/src/codex.ts delete mode 100644 resources/agent-schemas/src/index.ts delete mode 100644 resources/agent-schemas/src/normalize.ts delete mode 100644 resources/agent-schemas/src/opencode.ts delete mode 100644 resources/agent-schemas/tsconfig.json create mode 100644 sdks/acp-http-client/package.json create mode 100644 sdks/acp-http-client/src/index.ts create mode 100644 sdks/acp-http-client/tests/smoke.test.ts create mode 100644 sdks/acp-http-client/tsconfig.json create mode 100644 sdks/acp-http-client/tsup.config.ts create mode 100644 sdks/acp-http-client/vitest.config.ts delete mode 100644 sdks/typescript/tests/client.test.ts delete mode 100644 sdks/typescript/tests/sse-parser.test.ts delete mode 100644 server/packages/extracted-agent-schemas/Cargo.toml delete mode 100644 server/packages/extracted-agent-schemas/build.rs delete mode 100644 server/packages/extracted-agent-schemas/src/lib.rs delete mode 100644 server/packages/extracted-agent-schemas/tests/schema_roundtrip.rs create mode 100644 server/packages/sandbox-agent/src/acp_runtime/backend.rs create mode 100644 server/packages/sandbox-agent/src/acp_runtime/ext_meta.rs create mode 100644 server/packages/sandbox-agent/src/acp_runtime/ext_methods.rs create mode 100644 server/packages/sandbox-agent/src/acp_runtime/helpers.rs create mode 100644 server/packages/sandbox-agent/src/acp_runtime/mock.rs create mode 100644 server/packages/sandbox-agent/src/acp_runtime/mod.rs delete mode 100644 server/packages/sandbox-agent/src/credentials.rs create mode 100644 server/packages/sandbox-agent/src/opencode_session_manager.rs create mode 100644 server/packages/sandbox-agent/src/router/support.rs create mode 100644 server/packages/sandbox-agent/src/router/types.rs rename server/packages/{universal-agent-schema/src/lib.rs => sandbox-agent/src/universal_events.rs} (100%) delete mode 100644 server/packages/sandbox-agent/tests/agent-flows/agent_basic_reply.rs delete mode 100644 server/packages/sandbox-agent/tests/agent-flows/agent_file_edit_flow.rs delete mode 100644 server/packages/sandbox-agent/tests/agent-flows/agent_multi_turn.rs delete mode 100644 server/packages/sandbox-agent/tests/agent-flows/agent_permission_flow.rs delete mode 100644 server/packages/sandbox-agent/tests/agent-flows/agent_question_flow.rs delete mode 100644 server/packages/sandbox-agent/tests/agent-flows/agent_termination.rs delete mode 100644 server/packages/sandbox-agent/tests/agent-flows/agent_tool_flow.rs delete mode 100644 server/packages/sandbox-agent/tests/agent-flows/mod.rs delete mode 100644 server/packages/sandbox-agent/tests/agent-management/agents.rs delete mode 100644 server/packages/sandbox-agent/tests/agent-management/mod.rs delete mode 100644 server/packages/sandbox-agent/tests/agent_flows.rs delete mode 100644 server/packages/sandbox-agent/tests/agent_management.rs delete mode 100644 server/packages/sandbox-agent/tests/common/http.rs delete mode 100644 server/packages/sandbox-agent/tests/common/mod.rs delete mode 100644 server/packages/sandbox-agent/tests/http/agent_endpoints.rs delete mode 100644 server/packages/sandbox-agent/tests/http/fs_endpoints.rs delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_install_claude.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_install_codex.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_install_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_install_opencode.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_models_amp.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_models_claude.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_models_codex.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_models_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_models_opencode.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_modes_claude.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_modes_codex.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_modes_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agent_modes_opencode.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@agents_list_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__agent_endpoints_snapshots@health_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__auth_snapshots@auth_health_public_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__auth_snapshots@auth_invalid_token_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__auth_snapshots@auth_missing_token_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__auth_snapshots@auth_valid_token_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__cors_snapshots@cors_actual_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/agent_endpoints__cors_snapshots@cors_preflight_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_amp.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_claude.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_codex.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_opencode.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_models_amp.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_models_claude.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_models_codex.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_models_opencode.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_modes_amp.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_modes_claude.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_modes_codex.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_modes_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_modes_opencode.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agents_list_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@health_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_health_public_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_invalid_token_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_missing_token_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_valid_token_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__cors_snapshots@cors_actual_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__cors_snapshots@cors_preflight_global.snap delete mode 100644 server/packages/sandbox-agent/tests/http_endpoints.rs delete mode 100644 server/packages/sandbox-agent/tests/server-manager/agent_server_manager.rs delete mode 100644 server/packages/sandbox-agent/tests/server-manager/mod.rs delete mode 100644 server/packages/sandbox-agent/tests/server_manager.rs delete mode 100644 server/packages/sandbox-agent/tests/sessions.rs delete mode 100644 server/packages/sandbox-agent/tests/sessions/mod.rs delete mode 100644 server/packages/sandbox-agent/tests/sessions/multi_turn.rs delete mode 100644 server/packages/sandbox-agent/tests/sessions/permissions.rs delete mode 100644 server/packages/sandbox-agent/tests/sessions/questions.rs delete mode 100644 server/packages/sandbox-agent/tests/sessions/reasoning.rs delete mode 100644 server/packages/sandbox-agent/tests/sessions/session_lifecycle.rs delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__multi_turn__assert_session_snapshot@multi_turn_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_events_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_reply_missing_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_reply_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reject_events_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reject_missing_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reject_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_events_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_missing_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@concurrency_events_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@create_session_mock-2.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@create_session_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@send_message_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@sessions_list_mock-2.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@sessions_list_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_http_events_snapshot@http_events_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_sse_events_snapshot@sse_events_mock.snap delete mode 100644 server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_sse_events_snapshot@sse_events_mock.snap.new delete mode 100644 server/packages/sandbox-agent/tests/sessions/status.rs delete mode 100644 server/packages/sandbox-agent/tests/ui.rs delete mode 100644 server/packages/sandbox-agent/tests/ui/inspector_ui.rs delete mode 100644 server/packages/sandbox-agent/tests/ui/mod.rs create mode 100644 server/packages/sandbox-agent/tests/v2_agent_process_matrix.rs create mode 100644 server/packages/sandbox-agent/tests/v2_api.rs create mode 100644 server/packages/sandbox-agent/tests/v2_api/acp_extensions.rs create mode 100644 server/packages/sandbox-agent/tests/v2_api/acp_transport.rs create mode 100644 server/packages/sandbox-agent/tests/v2_api/control_plane.rs delete mode 100644 server/packages/universal-agent-schema/Cargo.toml delete mode 100644 server/packages/universal-agent-schema/src/agents/amp.rs delete mode 100644 server/packages/universal-agent-schema/src/agents/claude.rs delete mode 100644 server/packages/universal-agent-schema/src/agents/codex.rs delete mode 100644 server/packages/universal-agent-schema/src/agents/mod.rs delete mode 100644 server/packages/universal-agent-schema/src/agents/opencode.rs delete mode 100644 server/packages/universal-schema-gen/Cargo.toml delete mode 100644 server/packages/universal-schema-gen/build.rs delete mode 100644 server/packages/universal-schema-gen/src/lib.rs delete mode 100644 spec/universal-schema.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 638ff8e..f574def 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,3 +23,13 @@ jobs: - run: pnpm install - name: Run checks run: ./scripts/release/main.ts --version 0.0.0 --check + - name: Run ACP v2 server tests + run: | + cargo test -p sandbox-agent-agent-management + cargo test -p sandbox-agent --test v2_api + cargo test -p sandbox-agent --test v2_agent_process_matrix + cargo test -p sandbox-agent --lib + - name: Run SDK tests + run: pnpm --dir sdks/typescript test + - name: Run Inspector browser E2E + run: pnpm --filter @sandbox-agent/inspector test:agent-browser diff --git a/CLAUDE.md b/CLAUDE.md index c0dcf17..5f14192 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,136 +1,80 @@ # Instructions -## SDK Modes +## ACP v2 Baseline -There are two ways to work with the SDKs: +- v2 is ACP-native. +- `/v1/*` is removed and returns `410 Gone` (`application/problem+json`). +- `/opencode/*` is disabled during ACP core phases and returns `503`. +- Prompt/session traffic is ACP JSON-RPC over streamable HTTP on `/v2/rpc`: + - `POST /v2/rpc` + - `GET /v2/rpc` (SSE) + - `DELETE /v2/rpc` +- Control-plane endpoints: + - `GET /v2/health` + - `GET /v2/agents` + - `POST /v2/agents/{agent}/install` +- Binary filesystem transfer endpoints (intentionally HTTP, not ACP extension methods): + - `GET /v2/fs/file` + - `PUT /v2/fs/file` + - `POST /v2/fs/upload-batch` +- Sandbox Agent ACP extension method naming: + - Custom ACP methods use `_sandboxagent/...` (not `_sandboxagent/v2/...`). + - Session detach method is `_sandboxagent/session/detach`. -- **Embedded**: Spawns the `sandbox-agent` server as a subprocess on a unique port and communicates with it locally. Useful for local development or when running the SDK and agent in the same environment. -- **Server**: Connects to a remotely running `sandbox-agent` server. The server is typically running inside a sandbox (e.g., Docker, E2B, Daytona, Vercel Sandboxes) and the SDK connects to it over HTTP. +## API Scope -## Agent Schemas +- ACP is the primary protocol for agent/session behavior and all functionality that talks directly to the agent. +- ACP extensions may be used for gaps (for example `skills`, `models`, and related metadata), but the default is that agent-facing behavior is implemented by the agent through ACP. +- Custom HTTP APIs are for non-agent/session platform services (for example filesystem, terminals, and other host/runtime capabilities). +- Filesystem and terminal APIs remain Sandbox Agent-specific HTTP contracts and are not ACP. +- Keep `GET /v2/fs/file`, `PUT /v2/fs/file`, and `POST /v2/fs/upload-batch` on HTTP: + - These are Sandbox Agent host/runtime operations with cross-agent-consistent behavior. + - They may involve very large binary transfers that ACP JSON-RPC envelopes are not suited to stream. + - This is intentionally separate from ACP native `fs/read_text_file` and `fs/write_text_file`. + - ACP extension variants may exist in parallel, but SDK defaults should prefer HTTP for these binary transfer operations. -Agent schemas (Claude Code, Codex, OpenCode, Amp) are available for reference in `resources/agent-schemas/artifacts/json-schema/`. +## Naming and Ownership -Extraction methods: -- **Claude**: Uses `claude --output-format json --json-schema` CLI command -- **Codex**: Uses `codex app-server generate-json-schema` CLI command -- **OpenCode**: Fetches from GitHub OpenAPI spec -- **Amp**: Scrapes from `https://ampcode.com/manual/appendix?preview#message-schema` +- This repository/product is **Sandbox Agent**. +- **Gigacode** is a separate user-facing UI/client, not the server product name. +- Gigacode integrates with Sandbox Agent via the OpenCode-compatible surface (`/opencode/*`) when that compatibility layer is enabled. +- Canonical extension namespace/domain string is `sandboxagent.dev` (no hyphen). +- Canonical custom ACP extension method prefix is `_sandboxagent/...` (no hyphen). -All extractors have fallback schemas for when CLI/URL is unavailable. +## Architecture (Brief) -Research on how different agents operate (CLI flags, streaming formats, HITL patterns, etc.) is in `research/agents/`. When adding or making changes to agent docs, follow the same structure as existing files. +- HTTP contract and problem/error mapping: `server/packages/sandbox-agent/src/router.rs` +- ACP client runtime and agent process bridge: `server/packages/sandbox-agent/src/acp_runtime/mod.rs` +- Agent/native + ACP agent process install and lazy install: `server/packages/agent-management/` +- Inspector UI served at `/ui/` and bound to ACP over HTTP from `frontend/packages/inspector/` -Universal schema guidance: -- The universal schema should cover the full feature set of all agents. -- Conversions must be best-effort overlap without being lossy; preserve raw payloads when needed. -- **The mock agent acts as the reference implementation** for correct event behavior. Real agents should use synthetic events to match the mock agent's event patterns (e.g., emitting both daemon synthetic and agent native `session.started` events, proper `item.started` → `item.delta` → `item.completed` sequences). +## TypeScript SDK Architecture -## Spec Tracking +- TypeScript clients are split into: + - `acp-http-client`: protocol-pure ACP-over-HTTP (`/v2/rpc`) with no Sandbox-specific metadata/extensions. + - `sandbox-agent`: `SandboxAgentClient` wrapper that adds Sandbox metadata/extension helpers and keeps non-ACP HTTP helpers. +- `SandboxAgentClient` constructor is `new SandboxAgentClient(...)`. +- `SandboxAgentClient` auto-connects by default; `autoConnect: false` requires explicit `.connect()`. +- ACP/session methods must throw when disconnected (`NotConnectedError`), and `.connect()` must throw when already connected (`AlreadyConnectedError`). +- A `SandboxAgentClient` instance may have at most one active ACP connection at a time. +- Stable ACP session method names should stay ACP-aligned in the Sandbox wrapper (`newSession`, `loadSession`, `prompt`, `cancel`, `setSessionMode`, `setSessionConfigOption`). +- Sandbox extension methods are first-class wrapper helpers (`listModels`, `setMetadata`, `detachSession`, `terminateSession`). -- Keep CLI subcommands in sync with every HTTP endpoint. -- Update `CLAUDE.md` to keep CLI endpoints in sync with HTTP API changes. -- When adding or modifying CLI commands, update `docs/cli.mdx` to reflect the changes. -- When changing the HTTP API, update the TypeScript SDK and CLI together. -- Do not make breaking changes to API endpoints. -- When changing API routes, ensure the HTTP/SSE test suite has full coverage of every route. -- When agent schema changes, ensure API tests cover the new schema and event shapes end-to-end. -- When the universal schema changes, update mock-agent events to cover the new fields or event types. -- Update `docs/conversion.md` whenever agent-native schema terms, synthetic events, identifier mappings, or conversion logic change. -- Never use synthetic data or mocked responses in tests. -- Never manually write agent types; always use generated types in `resources/agent-schemas/`. If types are broken, fix the generated types. -- The universal schema must provide consistent behavior across providers; avoid requiring frontend/client logic to special-case agents. -- The UI must reflect every field in AgentCapabilities (feature coverage); keep it in sync with `docs/session-transcript-schema.mdx` and `agent_capabilities_for`. -- When parsing agent data, if something is unexpected or does not match the schema, bail out and surface the error rather than trying to continue with partial parsing. -- When defining the universal schema, choose the option most compatible with native agent APIs, and add synthetics to fill gaps for other agents. -- Use `docs/session-transcript-schema.mdx` as the source of truth for schema terminology and keep it updated alongside schema changes. -- On parse failures, emit an `agent.unparsed` event (source=daemon, synthetic=true) and treat it as a test failure. Preserve raw payloads when `include_raw=true`. -- Track subagent support in `docs/conversion.md`. For now, normalize subagent activity into normal message/tool flow, but revisit explicit subagent modeling later. -- Keep the FAQ in `README.md` and `frontend/packages/website/src/components/FAQ.tsx` in sync. When adding or modifying FAQ entries, update both files. -- Update `research/wip-agent-support.md` as agent support changes are implemented. +## Source Documents -### OpenAPI / utoipa requirements +- `~/misc/acp-docs/schema/schema.json` +- `~/misc/acp-docs/schema/meta.json` +- `research/acp/spec.md` +- `research/acp/v1-schema-to-acp-mapping.md` +- `research/acp/friction.md` +- `research/acp/todo.md` -Every `#[utoipa::path(...)]` handler function must have a doc comment where: -- The **first line** becomes the OpenAPI `summary` (short human-readable title, e.g. `"List Agents"`). This is used as the sidebar label and page heading in the docs site. -- The **remaining lines** become the OpenAPI `description` (one-sentence explanation of what the endpoint does). -- Every `responses(...)` entry must have a `description` (no empty descriptions). +## Change Tracking -When adding or modifying endpoints, regenerate `docs/openapi.json` and verify titles render correctly in the docs site. - -### CLI ⇄ HTTP endpoint map (keep in sync) - -- `sandbox-agent api agents list` ↔ `GET /v1/agents` -- `sandbox-agent api agents install` ↔ `POST /v1/agents/{agent}/install` -- `sandbox-agent api agents modes` ↔ `GET /v1/agents/{agent}/modes` -- `sandbox-agent api agents models` ↔ `GET /v1/agents/{agent}/models` -- `sandbox-agent api sessions list` ↔ `GET /v1/sessions` -- `sandbox-agent api sessions create` ↔ `POST /v1/sessions/{sessionId}` -- `sandbox-agent api sessions send-message` ↔ `POST /v1/sessions/{sessionId}/messages` -- `sandbox-agent api sessions send-message-stream` ↔ `POST /v1/sessions/{sessionId}/messages/stream` -- `sandbox-agent api sessions terminate` ↔ `POST /v1/sessions/{sessionId}/terminate` -- `sandbox-agent api sessions events` / `get-messages` ↔ `GET /v1/sessions/{sessionId}/events` -- `sandbox-agent api sessions events-sse` ↔ `GET /v1/sessions/{sessionId}/events/sse` -- `sandbox-agent api sessions reply-question` ↔ `POST /v1/sessions/{sessionId}/questions/{questionId}/reply` -- `sandbox-agent api sessions reject-question` ↔ `POST /v1/sessions/{sessionId}/questions/{questionId}/reject` -- `sandbox-agent api sessions reply-permission` ↔ `POST /v1/sessions/{sessionId}/permissions/{permissionId}/reply` -- `sandbox-agent api fs entries` ↔ `GET /v1/fs/entries` -- `sandbox-agent api fs read` ↔ `GET /v1/fs/file` -- `sandbox-agent api fs write` ↔ `PUT /v1/fs/file` -- `sandbox-agent api fs delete` ↔ `DELETE /v1/fs/entry` -- `sandbox-agent api fs mkdir` ↔ `POST /v1/fs/mkdir` -- `sandbox-agent api fs move` ↔ `POST /v1/fs/move` -- `sandbox-agent api fs stat` ↔ `GET /v1/fs/stat` -- `sandbox-agent api fs upload-batch` ↔ `POST /v1/fs/upload-batch` - -## OpenCode Compatibility Layer - -`sandbox-agent opencode` starts a sandbox-agent server and attaches an OpenCode session (uses `/opencode`). - -### Session ownership - -Sessions are stored **only** in sandbox-agent's v1 `SessionManager` — they are never sent to or stored in the native OpenCode server. The OpenCode TUI reads sessions via `GET /session` which the compat layer serves from the v1 store. The native OpenCode process has no knowledge of sessions. - -### Proxy elimination strategy - -The `/opencode` compat layer (`opencode_compat.rs`) historically proxied many endpoints to the native OpenCode server via `proxy_native_opencode()`. The goal is to **eliminate proxying** by implementing each endpoint natively using the v1 `SessionManager` as the single source of truth. - -**Already de-proxied** (use v1 SessionManager directly): -- `GET /session` — `oc_session_list` reads from `SessionManager::list_sessions()` -- `GET /session/{id}` — `oc_session_get` reads from `SessionManager::get_session_info()` -- `GET /session/status` — `oc_session_status` derives busy/idle from v1 session `ended` flag -- `POST /tui/open-sessions` — returns `true` directly (TUI fetches sessions from `GET /session`) -- `POST /tui/select-session` — emits `tui.session.select` event via the OpenCode event broadcaster - -**Still proxied** (none of these reference session IDs or the session list — all are session-agnostic): -- `GET /command` — command list -- `GET /config`, `PATCH /config` — project config read/write -- `GET /global/config`, `PATCH /global/config` — global config read/write -- `GET /tui/control/next`, `POST /tui/control/response` — TUI control loop -- `POST /tui/append-prompt`, `/tui/submit-prompt`, `/tui/clear-prompt` — prompt management -- `POST /tui/open-help`, `/tui/open-themes`, `/tui/open-models` — TUI navigation -- `POST /tui/execute-command`, `/tui/show-toast`, `/tui/publish` — TUI actions - -When converting a proxied endpoint: add needed fields to `SessionState`/`SessionInfo` in `router.rs`, implement the logic natively in `opencode_compat.rs`, and use `session_info_to_opencode_value()` to format responses. - -## Post-Release Testing - -After cutting a release, verify the release works correctly. Run `/project:post-release-testing` to execute the testing agent. - -## OpenCode Compatibility Tests - -The OpenCode compatibility suite lives at `server/packages/sandbox-agent/tests/opencode-compat` and validates the `@opencode-ai/sdk` against the `/opencode` API. Run it with: - -```bash -SANDBOX_AGENT_SKIP_INSPECTOR=1 pnpm --filter @sandbox-agent/opencode-compat-tests test -``` - -## Naming - -- The product name is "Gigacode" (capital G, lowercase c). The CLI binary/package is `gigacode` (lowercase). - -## Git Commits - -- Do not include any co-authors in commit messages (no `Co-Authored-By` lines) -- Use conventional commits style (e.g., `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`) -- Keep commit messages to a single line +- Keep CLI subcommands and HTTP endpoints in sync. +- Update `docs/cli.mdx` when CLI behavior changes. +- Regenerate `docs/openapi.json` when HTTP contracts change. +- Keep `docs/inspector.mdx` and `docs/sdks/typescript.mdx` aligned with implementation. +- Append blockers/decisions to `research/acp/friction.md` during ACP work. +- TypeScript SDK tests should run against a real running server/runtime over real `/v2` HTTP APIs, typically using the real `mock` agent for deterministic behavior. +- Do not use Vitest fetch/transport mocks to simulate server functionality in TypeScript SDK tests. diff --git a/Cargo.toml b/Cargo.toml index eafe654..6f71204 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["server/packages/*", "gigacode"] [workspace.package] -version = "0.1.12-rc.1" +version = "0.2.0" edition = "2021" authors = [ "Rivet Gaming, LLC " ] license = "Apache-2.0" @@ -12,12 +12,10 @@ description = "Universal API for automatic coding agents in sandboxes. Supports [workspace.dependencies] # Internal crates -sandbox-agent = { version = "0.1.12-rc.1", path = "server/packages/sandbox-agent" } -sandbox-agent-error = { version = "0.1.12-rc.1", path = "server/packages/error" } -sandbox-agent-agent-management = { version = "0.1.12-rc.1", path = "server/packages/agent-management" } -sandbox-agent-agent-credentials = { version = "0.1.12-rc.1", path = "server/packages/agent-credentials" } -sandbox-agent-universal-agent-schema = { version = "0.1.12-rc.1", path = "server/packages/universal-agent-schema" } -sandbox-agent-extracted-agent-schemas = { version = "0.1.12-rc.1", path = "server/packages/extracted-agent-schemas" } +sandbox-agent = { version = "0.2.0", path = "server/packages/sandbox-agent" } +sandbox-agent-error = { version = "0.2.0", path = "server/packages/error" } +sandbox-agent-agent-management = { version = "0.2.0", path = "server/packages/agent-management" } +sandbox-agent-agent-credentials = { version = "0.2.0", path = "server/packages/agent-credentials" } # Serialization serde = { version = "1.0", features = ["derive"] } diff --git a/docs/advanced/acp-http-client.mdx b/docs/advanced/acp-http-client.mdx new file mode 100644 index 0000000..4d93363 --- /dev/null +++ b/docs/advanced/acp-http-client.mdx @@ -0,0 +1,67 @@ +--- +title: "ACP HTTP Client" +description: "Protocol-pure ACP JSON-RPC over streamable HTTP client." +--- + +`acp-http-client` is a standalone, low-level package for ACP over HTTP (`/v2/rpc`). + +Use it when you want strict ACP protocol behavior with no Sandbox-specific metadata or extension adaptation. + +## Install + +```bash +npm install acp-http-client +``` + +## Usage + +```ts +import { AcpHttpClient } from "acp-http-client"; + +const client = new AcpHttpClient({ + baseUrl: "http://127.0.0.1:2468", + token: process.env.SANDBOX_TOKEN, +}); + +await client.initialize({ + _meta: { + "sandboxagent.dev": { + agent: "mock", + }, + }, +}); + +const session = await client.newSession({ + cwd: "/", + mcpServers: [], + _meta: { + "sandboxagent.dev": { + agent: "mock", + }, + }, +}); + +const result = await client.prompt({ + sessionId: session.sessionId, + prompt: [{ type: "text", text: "hello" }], +}); + +console.log(result.stopReason); +await client.disconnect(); +``` + +## Scope + +- Implements ACP HTTP transport and connection lifecycle. +- Supports ACP requests/notifications and session streaming. +- Does not inject `_meta["sandboxagent.dev"]`. +- Does not wrap `_sandboxagent/*` extension methods/events. + +## Transport Contract + +- `POST /v2/rpc` is JSON-only. Send `Content-Type: application/json` and `Accept: application/json`. +- `GET /v2/rpc` is SSE-only. Send `Accept: text/event-stream`. +- Keep one active SSE stream per ACP connection id. +- `x-acp-agent` is removed. Provide agent via `_meta["sandboxagent.dev"].agent` on `initialize` and `session/new`. + +If you want Sandbox Agent metadata/extensions and higher-level helpers, use `sandbox-agent` and `SandboxAgentClient` instead. diff --git a/docs/agent-sessions.mdx b/docs/agent-sessions.mdx index 78390a3..754bf70 100644 --- a/docs/agent-sessions.mdx +++ b/docs/agent-sessions.mdx @@ -24,12 +24,13 @@ Sessions are the unit of interaction with an agent. You create one session per t ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); await client.createSession("build-session", { agent: "codex", @@ -60,12 +61,13 @@ curl -X POST "http://127.0.0.1:2468/v1/sessions/build-session" \ ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); await client.postMessage("build-session", { message: "Summarize the repository structure.", @@ -84,12 +86,13 @@ curl -X POST "http://127.0.0.1:2468/v1/sessions/build-session/messages" \ ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); const response = await client.postMessageStream("build-session", { message: "Explain the main entrypoints.", @@ -118,12 +121,13 @@ curl -N -X POST "http://127.0.0.1:2468/v1/sessions/build-session/messages/stream ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); const events = await client.getEvents("build-session", { offset: 0, @@ -146,12 +150,13 @@ curl -X GET "http://127.0.0.1:2468/v1/sessions/build-session/events?offset=0&lim ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); for await (const event of client.streamEvents("build-session", { offset: 0 })) { console.log(event.type, event.data); @@ -168,12 +173,13 @@ curl -N -X GET "http://127.0.0.1:2468/v1/sessions/build-session/events/sse?offse ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); const sessions = await client.listSessions(); console.log(sessions.sessions); @@ -191,12 +197,13 @@ When the agent asks a question, reply with an array of answers. Each inner array ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); await client.replyQuestion("build-session", "question-1", { answers: [["yes"]], @@ -215,12 +222,13 @@ curl -X POST "http://127.0.0.1:2468/v1/sessions/build-session/questions/question ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); await client.rejectQuestion("build-session", "question-1"); ``` @@ -237,12 +245,13 @@ Use `once`, `always`, or `reject`. ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); await client.replyPermission("build-session", "permission-1", { reply: "once", @@ -261,12 +270,13 @@ curl -X POST "http://127.0.0.1:2468/v1/sessions/build-session/permissions/permis ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); await client.terminateSession("build-session"); ``` diff --git a/docs/attachments.mdx b/docs/attachments.mdx index 2458c52..acc54fe 100644 --- a/docs/attachments.mdx +++ b/docs/attachments.mdx @@ -11,13 +11,14 @@ Use the filesystem API to upload files, then reference them as attachments when ```ts TypeScript - import { SandboxAgent } from "sandbox-agent"; + import { SandboxAgentClient } from "sandbox-agent"; import fs from "node:fs"; - const client = await SandboxAgent.connect({ + const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, - }); + agent: "mock", + }); const buffer = await fs.promises.readFile("./data.csv"); @@ -42,12 +43,13 @@ Use the filesystem API to upload files, then reference them as attachments when ```ts TypeScript - import { SandboxAgent } from "sandbox-agent"; + import { SandboxAgentClient } from "sandbox-agent"; - const client = await SandboxAgent.connect({ + const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, - }); + agent: "mock", + }); await client.postMessage("my-session", { message: "Please analyze the attached CSV.", diff --git a/docs/conversion.mdx b/docs/conversion.mdx deleted file mode 100644 index d7eb505..0000000 --- a/docs/conversion.mdx +++ /dev/null @@ -1,113 +0,0 @@ -# Universal ↔ Agent Term Mapping - -Source of truth: generated agent schemas in `resources/agent-schemas/artifacts/json-schema/`. - -Identifiers - -+----------------------+------------------------+------------------------------------------+-----------------------------+------------------------+----------------------+ -| Universal term | Claude | Codex (app-server) | OpenCode | Amp | Pi (RPC) | -+----------------------+------------------------+------------------------------------------+-----------------------------+------------------------+----------------------+ -| session_id | n/a (daemon-only) | n/a (daemon-only) | n/a (daemon-only) | n/a (daemon-only) | n/a (daemon-only) | -| native_session_id | none | threadId | sessionID | none | sessionId | -| item_id | synthetic | ThreadItem.id | Message.id | StreamJSONMessage.id | messageId/toolCallId | -| native_item_id | none | ThreadItem.id | Message.id | StreamJSONMessage.id | messageId/toolCallId | -+----------------------+------------------------+------------------------------------------+-----------------------------+------------------------+----------------------+ - -Notes: -- When a provider does not supply IDs (Claude), we synthesize item_id values and keep native_item_id null. -- native_session_id is the only provider session identifier. It is intentionally used for thread/session/run ids. -- native_item_id preserves the agent-native item/message id when present. -- source indicates who emitted the event: agent (native) or daemon (synthetic). -- raw is always present on events. When clients do not opt-in to raw payloads, raw is null. -- opt-in via `include_raw=true` on events endpoints (HTTP + SSE). -- If parsing fails, emit agent.unparsed (source=daemon, synthetic=true). Tests must assert zero unparsed events. - -Runtime model by agent - -| Agent | Runtime model | Notes | -|---|---|---| -| Claude | Per-message subprocess streaming | Routed through `AgentManager::spawn_streaming` with Claude stream-json stdin. | -| Amp | Per-message subprocess streaming | Routed through `AgentManager::spawn_streaming` with parsed JSONL output. | -| Codex | Shared app-server (stdio JSON-RPC) | One shared server process, daemon sessions map to Codex thread IDs. | -| OpenCode | Shared HTTP server + SSE | One shared HTTP server, daemon sessions map to OpenCode session IDs. | -| Pi | Dedicated per-session RPC process | Canonical path is router-managed Pi runtime (`pi --mode rpc`), one process per daemon session. | - -Pi runtime contract: -- Session/message lifecycle for Pi must stay on router-managed per-session RPC runtime. -- `AgentManager::spawn(Pi)` is kept for one-shot utility/testing flows. -- `AgentManager::spawn_streaming(Pi)` is intentionally unsupported. - -Events / Message Flow - -+------------------------+------------------------------+--------------------------------------------+-----------------------------------------+----------------------------------+----------------------------+ -| Universal term | Claude | Codex (app-server) | OpenCode | Amp | Pi (RPC) | -+------------------------+------------------------------+--------------------------------------------+-----------------------------------------+----------------------------------+----------------------------+ -| session.started | none | method=thread/started | type=session.created | none | none | -| session.ended | SDKMessage.type=result | no explicit session end (turn/completed) | no explicit session end (session.deleted)| type=done | none (daemon synthetic) | -| turn.started | synthetic on message send | method=turn/started | type=session.status (busy) | synthetic on message send | none (daemon synthetic) | -| turn.ended | synthetic after result | method=turn/completed | type=session.idle | synthetic on done | none (daemon synthetic) | -| message (user) | SDKMessage.type=user | item/completed (ThreadItem.type=userMessage)| message.updated (Message.role=user) | type=message | none (daemon synthetic) | -| message (assistant) | SDKMessage.type=assistant | item/completed (ThreadItem.type=agentMessage)| message.updated (Message.role=assistant)| type=message | message_start/message_end | -| message.delta | stream_event (partial) or synthetic | method=item/agentMessage/delta | type=message.part.updated (text-part delta) | synthetic | message_update (text_delta/thinking_delta) | -| tool call | type=tool_use | method=item/mcpToolCall/progress | message.part.updated (part.type=tool) | type=tool_call | tool_execution_start | -| tool result | user.message.content.tool_result | item/completed (tool result ThreadItem variants) | message.part.updated (part.type=tool, state=completed) | type=tool_result | tool_execution_end | -| permission.requested | control_request.can_use_tool | none | type=permission.asked | none | none | -| permission.resolved | daemon reply to can_use_tool | none | type=permission.replied | none | none | -| question.requested | tool_use (AskUserQuestion) | experimental request_user_input (payload) | type=question.asked | none | none | -| question.resolved | tool_result (AskUserQuestion) | experimental request_user_input (payload) | type=question.replied / question.rejected | none | none | -| error | SDKResultMessage.error | method=error | type=session.error (or message error) | type=error | hook_error (status item) | -+------------------------+------------------------------+--------------------------------------------+-----------------------------------------+----------------------------------+----------------------------+ - -Permission status normalization: -- `permission.requested` uses `status=requested`. -- `permission.resolved` uses `status=accept`, `accept_for_session`, or `reject`. - -Synthetics - -+------------------------------+------------------------+--------------------------+--------------------------------------------------------------+ -| Synthetic element | When it appears | Stored as | Notes | -+------------------------------+------------------------+--------------------------+--------------------------------------------------------------+ -| session.started | When agent emits no explicit start | session.started event | Mark source=daemon | -| session.ended | When agent emits no explicit end | session.ended event | Mark source=daemon; reason may be inferred | -| turn.started | When agent emits no explicit turn start | turn.started event | Mark source=daemon | -| turn.ended | When agent emits no explicit turn end | turn.ended event | Mark source=daemon | -| item_id (Claude) | Claude provides no item IDs | item_id | Maintain provider_item_id map when possible | -| user message (Claude) | Claude emits only assistant output | item.completed | Mark source=daemon; preserve raw input in event metadata | -| question events (Claude) | AskUserQuestion tool usage | question.requested/resolved | Derived from tool_use blocks (source=agent) | -| native_session_id (Codex) | Codex uses threadId | native_session_id | Intentionally merged threadId into native_session_id | -+------------------------------+------------------------+--------------------------+--------------------------------------------------------------+ -| message.delta (Claude) | No native deltas emitted | item.delta | Synthetic delta with full message content; source=daemon | -| message.delta (Amp) | No native deltas | item.delta | Synthetic delta with full message content; source=daemon | -+------------------------------+------------------------+--------------------------+--------------------------------------------------------------+ -| message.delta (OpenCode) | text part delta before message | item.delta | If part arrives first, create item.started stub then delta | -+------------------------------+------------------------+--------------------------+--------------------------------------------------------------+ - -Delta handling - -- Codex emits agent message and other deltas (e.g., item/agentMessage/delta). -- OpenCode emits part deltas via message.part.updated with a delta string. -- Claude can emit stream_event deltas when partial streaming is enabled; Amp does not emit deltas. -- Pi emits message_update deltas and cumulative tool_execution_update partialResult values (we diff to produce deltas). - -Policy: -- Emit item.delta for streamable text content across providers. -- For providers without native deltas, emit a single synthetic delta containing the full content prior to item.completed. -- For Claude when partial streaming is enabled, forward native deltas and skip the synthetic full-content delta. -- For providers with native deltas, forward as-is; also emit item.completed when final content is known. -- For OpenCode reasoning part deltas, emit typed reasoning item updates (item.started/item.completed with content.type=reasoning) instead of item.delta. - -Message normalization notes - -- user vs assistant: normalized via role in the universal item; provider role fields or item types determine role. -- file artifacts: always represented as content parts (type=file_ref) inside message/tool_result items, not a separate item kind. -- reasoning: represented as content parts (type=reasoning) inside message items, with visibility when available. -- subagents: OpenCode subtask parts and Claude Task tool usage are currently normalized into standard message/tool flow (no dedicated subagent fields). -- OpenCode unrolling: message.updated creates/updates the parent message item; tool-related parts emit separate tool item events (item.started/ item.completed) with parent_id pointing to the message item. -- If a message.part.updated arrives before message.updated, we create a stub item.started (source=daemon) so deltas have a parent. -- Tool calls/results are always emitted as separate tool items to keep behavior consistent across agents. -- If Pi message_update events omit messageId, we synthesize a stable message id and emit a synthetic item.started before the first delta so streaming text stays grouped. -- Pi auto_compaction_start/auto_compaction_end and auto_retry_start/auto_retry_end events are mapped to status items (label `pi.*`). -- Pi extension_ui_request/extension_error events are mapped to status items. -- Pi RPC from pi-coding-agent does not include sessionId in events; each daemon session owns a dedicated Pi RPC process, so events are routed by runtime ownership (parallel sessions supported). -- PI `variant` maps directly to PI RPC `set_thinking_level.level` before prompts are sent. -- PI remains source of truth for thinking-level constraints: unsupported levels (including non-reasoning models and model-specific limits such as `xhigh`) are PI-native clamped or rejected. diff --git a/docs/credentials.mdx b/docs/credentials.mdx index ce1ce7b..676df7e 100644 --- a/docs/credentials.mdx +++ b/docs/credentials.mdx @@ -1,144 +1,55 @@ --- title: "Credentials" -description: "How sandbox-agent discovers and uses provider credentials." +description: "How sandbox-agent discovers and exposes provider credentials." icon: "key" --- -Sandbox-agent automatically discovers API credentials from environment variables and agent config files. Credentials are used to authenticate with AI providers (Anthropic, OpenAI) when spawning agents. +`sandbox-agent` can discover provider credentials from environment variables and local agent config files. -## Credential sources +## Supported providers -Credentials are extracted in priority order. The first valid credential found for each provider is used. +- Anthropic +- OpenAI +- Additional provider entries discovered via OpenCode config -### Environment variables (highest priority) - -**API keys** (checked first): +## Common environment variables | Variable | Provider | -|----------|----------| +| --- | --- | | `ANTHROPIC_API_KEY` | Anthropic | -| `CLAUDE_API_KEY` | Anthropic (fallback) | +| `CLAUDE_API_KEY` | Anthropic fallback | | `OPENAI_API_KEY` | OpenAI | -| `CODEX_API_KEY` | OpenAI (fallback) | +| `CODEX_API_KEY` | OpenAI fallback | -**OAuth tokens** (checked if no API key found): +## Extract credentials (CLI) -| Variable | Provider | -|----------|----------| -| `CLAUDE_CODE_OAUTH_TOKEN` | Anthropic (OAuth) | -| `ANTHROPIC_AUTH_TOKEN` | Anthropic (OAuth fallback) | - -OAuth tokens from environment variables are only used when `include_oauth` is enabled (the default). - -### Agent config files - -If no environment variable is set, sandbox-agent checks agent-specific config files: - -| Agent | Config path | Provider | -|-------|-------------|----------| -| Amp | `~/.amp/config.json` | Anthropic | -| Claude Code | `~/.claude.json`, `~/.claude/.credentials.json` | Anthropic | -| Codex | `~/.codex/auth.json` | OpenAI | -| OpenCode | `~/.local/share/opencode/auth.json` | Both | - -OAuth tokens are supported for Claude Code, Codex, and OpenCode. Expired tokens are automatically skipped. - -## Provider requirements by agent - -| Agent | Required provider | -|-------|-------------------| -| Claude Code | Anthropic | -| Amp | Anthropic | -| Codex | OpenAI | -| OpenCode | Anthropic or OpenAI | -| Mock | None | - -## Error handling behavior - -Sandbox-agent uses a **best-effort, fail-forward** approach to credentials: - -### Extraction failures are silent - -If a config file is missing, unreadable, or malformed, extraction continues to the next source. No errors are thrown. Missing credentials simply mean the provider is marked as unavailable. - -``` -~/.claude.json missing → try ~/.claude/.credentials.json -~/.claude/.credentials.json missing → try OpenCode config -All sources exhausted → anthropic = None (not an error) -``` - -### Agents spawn without credential validation - -When you send a message to a session, sandbox-agent does **not** pre-validate credentials. The agent process is spawned with whatever credentials were found (or none), and the agent's native error surfaces if authentication fails. - -This design: -- Lets you test agent error handling behavior -- Avoids duplicating provider-specific auth validation -- Ensures sandbox-agent faithfully proxies agent behavior - -For example, sending a message to Claude Code without Anthropic credentials will spawn the agent, which will then emit its own "ANTHROPIC_API_KEY not set" error through the event stream. - -## Checking credential status - -### API endpoint - -The `GET /v1/agents` endpoint includes a `credentialsAvailable` field for each agent: - -```json -{ - "agents": [ - { - "id": "claude", - "installed": true, - "credentialsAvailable": true, - ... - }, - { - "id": "codex", - "installed": true, - "credentialsAvailable": false, - ... - } - ] -} -``` - -### TypeScript SDK - -```typescript -const { agents } = await client.listAgents(); -for (const agent of agents) { - console.log(`${agent.id}: ${agent.credentialsAvailable ? 'authenticated' : 'no credentials'}`); -} -``` - -### OpenCode compatibility - -The `/opencode/provider` endpoint returns a `connected` array listing providers with valid credentials: - -```json -{ - "all": [...], - "connected": ["claude", "mock"] -} -``` - -## Passing credentials explicitly - -You can override auto-discovered credentials by setting environment variables before starting sandbox-agent: +Show discovered credentials (redacted by default): ```bash -export ANTHROPIC_API_KEY=sk-ant-... -export OPENAI_API_KEY=sk-... -sandbox-agent daemon start +sandbox-agent credentials extract ``` -Or when using the SDK in embedded mode: +Reveal raw values: -```typescript -const client = await SandboxAgentClient.spawn({ - env: { - ANTHROPIC_API_KEY: process.env.MY_ANTHROPIC_KEY, - }, -}); +```bash +sandbox-agent credentials extract --reveal ``` + +Filter by agent/provider: + +```bash +sandbox-agent credentials extract --agent codex +sandbox-agent credentials extract --provider openai +``` + +Emit shell exports: + +```bash +sandbox-agent credentials extract-env --export +``` + +## Notes + +- Discovery is best-effort: missing/invalid files do not crash extraction. +- v2 does not expose legacy v1 `credentialsAvailable` agent fields. +- Authentication failures are surfaced by the selected ACP agent process/agent during ACP requests. diff --git a/docs/custom-tools.mdx b/docs/custom-tools.mdx index 4690888..a2e2d1b 100644 --- a/docs/custom-tools.mdx +++ b/docs/custom-tools.mdx @@ -66,13 +66,14 @@ Both approaches execute code inside the sandbox, so your tools have full access ```ts TypeScript - import { SandboxAgent } from "sandbox-agent"; + import { SandboxAgentClient } from "sandbox-agent"; import fs from "node:fs"; - const client = await SandboxAgent.connect({ + const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, - }); + agent: "mock", + }); const content = await fs.promises.readFile("./dist/mcp-server.cjs"); await client.writeFsFile( @@ -175,13 +176,14 @@ Skills are markdown files that instruct the agent how to use a script. Upload th ```ts TypeScript - import { SandboxAgent } from "sandbox-agent"; + import { SandboxAgentClient } from "sandbox-agent"; import fs from "node:fs"; - const client = await SandboxAgent.connect({ + const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, - }); + agent: "mock", + }); const script = await fs.promises.readFile("./dist/random-number.cjs"); await client.writeFsFile( diff --git a/docs/deploy/cloudflare.mdx b/docs/deploy/cloudflare.mdx index 204870b..0228f91 100644 --- a/docs/deploy/cloudflare.mdx +++ b/docs/deploy/cloudflare.mdx @@ -98,7 +98,7 @@ const PORT = 8000; /** Check if sandbox-agent is already running */ async function isServerRunning(sandbox: Sandbox): Promise { try { - const result = await sandbox.exec(`curl -sf http://localhost:${PORT}/v1/health`); + const result = await sandbox.exec(`curl -sf http://localhost:${PORT}/v2/health`); return result.success; } catch { return false; @@ -131,7 +131,7 @@ export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); - // Proxy requests: /sandbox/:name/v1/... + // Proxy requests: /sandbox/:name/v2/... const match = url.pathname.match(/^\/sandbox\/([^/]+)(\/.*)?$/); if (match) { const [, name, path = "/"] = match; @@ -154,11 +154,12 @@ export default { ## Connect from Client ```typescript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; // Connect via the proxy endpoint -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://localhost:8787/sandbox/my-sandbox", + agent: "mock", }); // Wait for server to be ready @@ -230,7 +231,7 @@ First run builds the Docker container (2-3 minutes). Subsequent runs are much fa Test with curl: ```bash -curl http://localhost:8787/sandbox/demo/v1/health +curl http://localhost:8787/sandbox/demo/v2/health ``` diff --git a/docs/deploy/daytona.mdx b/docs/deploy/daytona.mdx index fad980d..14e1347 100644 --- a/docs/deploy/daytona.mdx +++ b/docs/deploy/daytona.mdx @@ -16,7 +16,7 @@ Daytona Tier 3+ is required to access api.anthropic.com and api.openai.com. Tier ```typescript import { Daytona } from "@daytonaio/sdk"; -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; const daytona = new Daytona(); @@ -44,7 +44,7 @@ await new Promise((r) => setTimeout(r, 2000)); const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url; // Connect and use the SDK -const client = await SandboxAgent.connect({ baseUrl }); +const client = new SandboxAgentClient({ baseUrl, agent: "mock" }); await client.createSession("my-session", { agent: "claude", diff --git a/docs/deploy/docker.mdx b/docs/deploy/docker.mdx index 4961b0c..a7520b7 100644 --- a/docs/deploy/docker.mdx +++ b/docs/deploy/docker.mdx @@ -33,7 +33,7 @@ Access the API at `http://localhost:3000`. ```typescript import Docker from "dockerode"; -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; const docker = new Docker(); const PORT = 3000; @@ -62,7 +62,7 @@ await container.start(); // Wait for server and connect const baseUrl = `http://127.0.0.1:${PORT}`; -const client = await SandboxAgent.connect({ baseUrl }); +const client = new SandboxAgentClient({ baseUrl, agent: "mock" }); // Use the client... await client.createSession("my-session", { diff --git a/docs/deploy/e2b.mdx b/docs/deploy/e2b.mdx index cfe1d66..7d06164 100644 --- a/docs/deploy/e2b.mdx +++ b/docs/deploy/e2b.mdx @@ -12,7 +12,7 @@ description: "Deploy the daemon inside an E2B sandbox." ```typescript import { Sandbox } from "@e2b/code-interpreter"; -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; // Pass API keys to the sandbox const envs: Record = {}; @@ -38,7 +38,7 @@ await sandbox.commands.run( // Connect to the server const baseUrl = `https://${sandbox.getHost(3000)}`; -const client = await SandboxAgent.connect({ baseUrl }); +const client = new SandboxAgentClient({ baseUrl, agent: "mock" }); // Wait for server to be ready for (let i = 0; i < 30; i++) { diff --git a/docs/deploy/vercel.mdx b/docs/deploy/vercel.mdx index be2bec0..f32013a 100644 --- a/docs/deploy/vercel.mdx +++ b/docs/deploy/vercel.mdx @@ -12,7 +12,7 @@ description: "Deploy the daemon inside a Vercel Sandbox." ```typescript import { Sandbox } from "@vercel/sandbox"; -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; // Pass API keys to the sandbox const envs: Record = {}; @@ -51,7 +51,7 @@ await sandbox.runCommand({ // Connect to the server const baseUrl = sandbox.domain(3000); -const client = await SandboxAgent.connect({ baseUrl }); +const client = new SandboxAgentClient({ baseUrl, agent: "mock" }); // Wait for server to be ready for (let i = 0; i < 30; i++) { diff --git a/docs/docs.json b/docs/docs.json index 89dd714..d6b62f1 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -85,6 +85,10 @@ "group": "Features", "pages": ["file-system"] }, + { + "group": "Advanced", + "pages": ["advanced/acp-http-client"] + }, { "group": "Reference", "pages": [ diff --git a/docs/file-system.mdx b/docs/file-system.mdx index 3aae00c..5bc57fd 100644 --- a/docs/file-system.mdx +++ b/docs/file-system.mdx @@ -6,6 +6,17 @@ icon: "folder" --- The filesystem API lets you list, read, write, move, and delete files inside the sandbox, plus upload batches of files via tar archives. +Control operations (`list`, `mkdir`, `move`, `stat`, `delete`) are ACP extensions on `/v2/rpc` and require an active ACP connection in the SDK. + +Binary transfer is intentionally a separate HTTP API (not ACP extension methods): + +- `GET /v2/fs/file` +- `PUT /v2/fs/file` +- `POST /v2/fs/upload-batch` + +Reason: these are host/runtime capabilities implemented by Sandbox Agent for cross-agent-consistent behavior, and they may require streaming very large binary payloads that ACP JSON-RPC is not suited to transport efficiently. +This is intentionally separate from ACP native `fs/read_text_file` and `fs/write_text_file`. +ACP extension variants may exist in parallel for compatibility, but SDK defaults should use the HTTP endpoints above for binary transfer. ## Path Resolution @@ -18,14 +29,15 @@ The session working directory is the server process current working directory at ## List Entries +`listFsEntries()` uses ACP extension method `_sandboxagent/fs/list_entries`. + ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ - baseUrl: "http://127.0.0.1:2468", +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock" }); const entries = await client.listFsEntries({ path: "./workspace", @@ -36,23 +48,25 @@ console.log(entries); ``` ```bash cURL -curl -X GET "http://127.0.0.1:2468/v1/fs/entries?path=./workspace&sessionId=my-session" \ - -H "Authorization: Bearer $SANDBOX_TOKEN" +curl -X POST "http://127.0.0.1:2468/v2/rpc" \ + -H "Authorization: Bearer $SANDBOX_TOKEN" \ + -H "x-acp-connection-id: acp_conn_1" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"_sandboxagent/fs/list_entries","params":{"path":"./workspace","sessionId":"my-session"}}' ``` ## Read And Write Files -`PUT /v1/fs/file` writes raw bytes. `GET /v1/fs/file` returns raw bytes. +`PUT /v2/fs/file` writes raw bytes. `GET /v2/fs/file` returns raw bytes. ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ - baseUrl: "http://127.0.0.1:2468", +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock" }); await client.writeFsFile({ path: "./notes.txt", sessionId: "my-session" }, "hello"); @@ -66,11 +80,11 @@ console.log(text); ``` ```bash cURL -curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=./notes.txt&sessionId=my-session" \ +curl -X PUT "http://127.0.0.1:2468/v2/fs/file?path=./notes.txt&sessionId=my-session" \ -H "Authorization: Bearer $SANDBOX_TOKEN" \ --data-binary "hello" -curl -X GET "http://127.0.0.1:2468/v1/fs/file?path=./notes.txt&sessionId=my-session" \ +curl -X GET "http://127.0.0.1:2468/v2/fs/file?path=./notes.txt&sessionId=my-session" \ -H "Authorization: Bearer $SANDBOX_TOKEN" \ --output ./notes.txt ``` @@ -78,14 +92,15 @@ curl -X GET "http://127.0.0.1:2468/v1/fs/file?path=./notes.txt&sessionId=my-sess ## Create Directories +`mkdirFs()` uses ACP extension method `_sandboxagent/fs/mkdir`. + ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ - baseUrl: "http://127.0.0.1:2468", +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock" }); await client.mkdirFs({ path: "./data", @@ -94,21 +109,25 @@ await client.mkdirFs({ ``` ```bash cURL -curl -X POST "http://127.0.0.1:2468/v1/fs/mkdir?path=./data&sessionId=my-session" \ - -H "Authorization: Bearer $SANDBOX_TOKEN" +curl -X POST "http://127.0.0.1:2468/v2/rpc" \ + -H "Authorization: Bearer $SANDBOX_TOKEN" \ + -H "x-acp-connection-id: acp_conn_1" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":2,"method":"_sandboxagent/fs/mkdir","params":{"path":"./data","sessionId":"my-session"}}' ``` ## Move, Delete, And Stat +`moveFs()`, `statFs()`, and `deleteFsEntry()` use ACP extension methods (`_sandboxagent/fs/move`, `_sandboxagent/fs/stat`, `_sandboxagent/fs/delete_entry`). + ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ - baseUrl: "http://127.0.0.1:2468", +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock" }); await client.moveFs( { from: "./notes.txt", to: "./notes-old.txt", overwrite: true }, @@ -129,16 +148,23 @@ console.log(stat); ``` ```bash cURL -curl -X POST "http://127.0.0.1:2468/v1/fs/move?sessionId=my-session" \ +curl -X POST "http://127.0.0.1:2468/v2/rpc" \ -H "Authorization: Bearer $SANDBOX_TOKEN" \ + -H "x-acp-connection-id: acp_conn_1" \ -H "Content-Type: application/json" \ - -d '{"from":"./notes.txt","to":"./notes-old.txt","overwrite":true}' + -d '{"jsonrpc":"2.0","id":3,"method":"_sandboxagent/fs/move","params":{"from":"./notes.txt","to":"./notes-old.txt","overwrite":true,"sessionId":"my-session"}}' -curl -X GET "http://127.0.0.1:2468/v1/fs/stat?path=./notes-old.txt&sessionId=my-session" \ - -H "Authorization: Bearer $SANDBOX_TOKEN" +curl -X POST "http://127.0.0.1:2468/v2/rpc" \ + -H "Authorization: Bearer $SANDBOX_TOKEN" \ + -H "x-acp-connection-id: acp_conn_1" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":4,"method":"_sandboxagent/fs/stat","params":{"path":"./notes-old.txt","sessionId":"my-session"}}' -curl -X DELETE "http://127.0.0.1:2468/v1/fs/entry?path=./notes-old.txt&sessionId=my-session" \ - -H "Authorization: Bearer $SANDBOX_TOKEN" +curl -X POST "http://127.0.0.1:2468/v2/rpc" \ + -H "Authorization: Bearer $SANDBOX_TOKEN" \ + -H "x-acp-connection-id: acp_conn_1" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":5,"method":"_sandboxagent/fs/delete_entry","params":{"path":"./notes-old.txt","sessionId":"my-session"}}' ``` @@ -148,15 +174,14 @@ Batch upload accepts `application/x-tar` only and extracts into the destination ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; import fs from "node:fs"; import path from "node:path"; import tar from "tar"; -const client = await SandboxAgent.connect({ - baseUrl: "http://127.0.0.1:2468", +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock" }); const archivePath = path.join(process.cwd(), "skills.tar"); await tar.c({ @@ -176,7 +201,7 @@ console.log(result); ```bash cURL tar -cf skills.tar -C ./skills . -curl -X POST "http://127.0.0.1:2468/v1/fs/upload-batch?path=./skills&sessionId=my-session" \ +curl -X POST "http://127.0.0.1:2468/v2/fs/upload-batch?path=./skills&sessionId=my-session" \ -H "Authorization: Bearer $SANDBOX_TOKEN" \ -H "Content-Type: application/x-tar" \ --data-binary @skills.tar diff --git a/docs/manage-sessions.mdx b/docs/manage-sessions.mdx index a43c371..7d51538 100644 --- a/docs/manage-sessions.mdx +++ b/docs/manage-sessions.mdx @@ -25,10 +25,11 @@ Two ways to receive events: SSE streaming (recommended) or polling. Use SSE for real-time events with automatic reconnection support. ```typescript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", + agent: "mock", }); // Get offset from last stored event (0 returns all events) @@ -130,7 +131,10 @@ const codingSession = actor({ }, createVars: async (c): Promise => { - const client = await SandboxAgent.connect({ baseUrl: c.state.baseUrl }); + const client = new SandboxAgentClient({ + baseUrl: c.state.baseUrl, + agent: "mock", +}); await client.createSession(c.state.sessionId, { agent: "claude" }); return { client }; }, diff --git a/docs/mcp-config.mdx b/docs/mcp-config.mdx index 668d937..0536868 100644 --- a/docs/mcp-config.mdx +++ b/docs/mcp-config.mdx @@ -14,12 +14,13 @@ The `mcp` field is a map of server name to config. Use `type: "local"` for stdio ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); await client.createSession("claude-mcp", { agent: "claude", diff --git a/docs/openapi.json b/docs/openapi.json index 7cf6231..6fc7ae0 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -10,7 +10,7 @@ "license": { "name": "Apache-2.0" }, - "version": "0.1.12-rc.1" + "version": "0.2.0" }, "servers": [ { @@ -18,240 +18,17 @@ } ], "paths": { - "/v1/agents": { + "/v2/fs/file": { "get": { "tags": [ - "agents" + "v2" ], - "summary": "List Agents", - "description": "Returns all available coding agents and their installation status.", - "operationId": "list_agents", - "responses": { - "200": { - "description": "List of available agents", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AgentListResponse" - } - } - } - } - } - } - }, - "/v1/agents/{agent}/install": { - "post": { - "tags": [ - "agents" - ], - "summary": "Install Agent", - "description": "Installs or updates a coding agent (e.g. claude, codex, opencode, amp).", - "operationId": "install_agent", - "parameters": [ - { - "name": "agent", - "in": "path", - "description": "Agent id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AgentInstallRequest" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "Agent installed" - }, - "400": { - "description": "Invalid request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - }, - "404": { - "description": "Agent not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - }, - "500": { - "description": "Installation failed", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - } - } - } - }, - "/v1/agents/{agent}/models": { - "get": { - "tags": [ - "agents" - ], - "summary": "List Agent Models", - "description": "Returns the available LLM models for an agent.", - "operationId": "get_agent_models", - "parameters": [ - { - "name": "agent", - "in": "path", - "description": "Agent id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Available models", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AgentModelsResponse" - } - } - } - }, - "404": { - "description": "Agent not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - } - } - } - }, - "/v1/agents/{agent}/modes": { - "get": { - "tags": [ - "agents" - ], - "summary": "List Agent Modes", - "description": "Returns the available interaction modes for an agent.", - "operationId": "get_agent_modes", - "parameters": [ - { - "name": "agent", - "in": "path", - "description": "Agent id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Available modes", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AgentModesResponse" - } - } - } - }, - "400": { - "description": "Invalid request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - } - } - } - }, - "/v1/fs/entries": { - "get": { - "tags": [ - "fs" - ], - "summary": "List Directory", - "description": "Lists files and directories at the given path.", - "operationId": "fs_entries", + "operationId": "get_v2_fs_file", "parameters": [ { "name": "path", "in": "query", - "description": "Path to list (relative or absolute)", - "required": false, - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "session_id", - "in": "query", - "description": "Session id for relative paths", - "required": false, - "schema": { - "type": "string", - "nullable": true - } - } - ], - "responses": { - "200": { - "description": "Directory listing", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FsEntry" - } - } - } - } - } - } - } - }, - "/v1/fs/entry": { - "delete": { - "tags": [ - "fs" - ], - "summary": "Delete Entry", - "description": "Deletes a file or directory.", - "operationId": "fs_delete_entry", - "parameters": [ - { - "name": "path", - "in": "query", - "description": "File or directory path", + "description": "File path", "required": true, "schema": { "type": "string" @@ -260,60 +37,7 @@ { "name": "session_id", "in": "query", - "description": "Session id for relative paths", - "required": false, - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "recursive", - "in": "query", - "description": "Delete directories recursively", - "required": false, - "schema": { - "type": "boolean", - "nullable": true - } - } - ], - "responses": { - "200": { - "description": "Delete result", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FsActionResponse" - } - } - } - } - } - } - }, - "/v1/fs/file": { - "get": { - "tags": [ - "fs" - ], - "summary": "Read File", - "description": "Reads the raw bytes of a file.", - "operationId": "fs_read_file", - "parameters": [ - { - "name": "path", - "in": "query", - "description": "File path (relative or absolute)", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "session_id", - "in": "query", - "description": "Session id for relative paths", + "description": "Session id for relative path base", "required": false, "schema": { "type": "string", @@ -323,30 +47,20 @@ ], "responses": { "200": { - "description": "File content", - "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - } - } + "description": "File content" } } }, "put": { "tags": [ - "fs" + "v2" ], - "summary": "Write File", - "description": "Writes raw bytes to a file, creating it if it doesn't exist.", - "operationId": "fs_write_file", + "operationId": "put_v2_fs_file", "parameters": [ { "name": "path", "in": "query", - "description": "File path (relative or absolute)", + "description": "File path", "required": true, "schema": { "type": "string" @@ -355,7 +69,7 @@ { "name": "session_id", "in": "query", - "description": "Session id for relative paths", + "description": "Session id for relative path base", "required": false, "schema": { "type": "string", @@ -364,11 +78,11 @@ } ], "requestBody": { + "description": "Raw file bytes", "content": { - "application/octet-stream": { + "text/plain": { "schema": { - "type": "string", - "format": "binary" + "type": "string" } } }, @@ -388,62 +102,27 @@ } } }, - "/v1/fs/mkdir": { + "/v2/fs/upload-batch": { "post": { "tags": [ - "fs" + "v2" ], - "summary": "Create Directory", - "description": "Creates a directory, including any missing parent directories.", - "operationId": "fs_mkdir", + "operationId": "post_v2_fs_upload_batch", "parameters": [ { "name": "path", "in": "query", - "description": "Directory path to create", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "session_id", - "in": "query", - "description": "Session id for relative paths", + "description": "Destination path", "required": false, "schema": { "type": "string", "nullable": true } - } - ], - "responses": { - "200": { - "description": "Directory created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FsActionResponse" - } - } - } - } - } - } - }, - "/v1/fs/move": { - "post": { - "tags": [ - "fs" - ], - "summary": "Move Entry", - "description": "Moves or renames a file or directory.", - "operationId": "fs_move", - "parameters": [ + }, { "name": "session_id", "in": "query", - "description": "Session id for relative paths", + "description": "Session id for relative path base", "required": false, "schema": { "type": "string", @@ -452,10 +131,11 @@ } ], "requestBody": { + "description": "tar archive body", "content": { - "application/json": { + "text/plain": { "schema": { - "$ref": "#/components/schemas/FsMoveRequest" + "type": "string" } } }, @@ -463,105 +143,7 @@ }, "responses": { "200": { - "description": "Move result", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FsMoveResponse" - } - } - } - } - } - } - }, - "/v1/fs/stat": { - "get": { - "tags": [ - "fs" - ], - "summary": "Get File Info", - "description": "Returns metadata (size, timestamps, type) for a path.", - "operationId": "fs_stat", - "parameters": [ - { - "name": "path", - "in": "query", - "description": "Path to stat", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "session_id", - "in": "query", - "description": "Session id for relative paths", - "required": false, - "schema": { - "type": "string", - "nullable": true - } - } - ], - "responses": { - "200": { - "description": "File metadata", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FsStat" - } - } - } - } - } - } - }, - "/v1/fs/upload-batch": { - "post": { - "tags": [ - "fs" - ], - "summary": "Upload Files", - "description": "Uploads a tar.gz archive and extracts it to the destination directory.", - "operationId": "fs_upload_batch", - "parameters": [ - { - "name": "path", - "in": "query", - "description": "Destination directory for extraction", - "required": false, - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "session_id", - "in": "query", - "description": "Session id for relative paths", - "required": false, - "schema": { - "type": "string", - "nullable": true - } - } - ], - "requestBody": { - "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Upload result", + "description": "Upload/extract result", "content": { "application/json": { "schema": { @@ -573,17 +155,17 @@ } } }, - "/v1/health": { + "/v2/health": { "get": { "tags": [ - "meta" + "v2" ], - "summary": "Health Check", - "description": "Returns the server health status.", - "operationId": "get_health", + "summary": "v2 Health", + "description": "Returns server health for the v2 ACP surface.", + "operationId": "get_v2_health", "responses": { "200": { - "description": "Server is healthy", + "description": "Service health response", "content": { "application/json": { "schema": { @@ -595,67 +177,17 @@ } } }, - "/v1/sessions": { + "/v2/rpc": { "get": { "tags": [ - "sessions" + "v2" ], - "summary": "List Sessions", - "description": "Returns all active sessions.", - "operationId": "list_sessions", + "summary": "ACP SSE", + "description": "Streams ACP JSON-RPC envelopes for an ACP client over SSE.", + "operationId": "get_v2_acp", "responses": { "200": { - "description": "List of active sessions", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SessionListResponse" - } - } - } - } - } - } - }, - "/v1/sessions/{session_id}": { - "post": { - "tags": [ - "sessions" - ], - "summary": "Create Session", - "description": "Creates a new agent session with the given configuration.", - "operationId": "create_session", - "parameters": [ - { - "name": "session_id", - "in": "path", - "description": "Client session id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateSessionRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Session created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateSessionResponse" - } - } - } + "description": "SSE stream of ACP envelopes" }, "400": { "description": "Invalid request", @@ -667,8 +199,28 @@ } } }, + "404": { + "description": "Unknown ACP client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "406": { + "description": "Client does not accept SSE responses", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, "409": { - "description": "Session already exists", + "description": "ACP client already has an active SSE stream", "content": { "application/json": { "schema": { @@ -678,213 +230,19 @@ } } } - } - }, - "/v1/sessions/{session_id}/events": { - "get": { - "tags": [ - "sessions" - ], - "summary": "Get Events", - "description": "Returns session events with optional offset-based pagination.", - "operationId": "get_events", - "parameters": [ - { - "name": "session_id", - "in": "path", - "description": "Session id", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "offset", - "in": "query", - "description": "Last seen event sequence (exclusive)", - "required": false, - "schema": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - } - }, - { - "name": "limit", - "in": "query", - "description": "Max events to return", - "required": false, - "schema": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - } - }, - { - "name": "include_raw", - "in": "query", - "description": "Include raw provider payloads", - "required": false, - "schema": { - "type": "boolean", - "nullable": true - } - } - ], - "responses": { - "200": { - "description": "Session events", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventsResponse" - } - } - } - }, - "404": { - "description": "Session not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - } - } - } - }, - "/v1/sessions/{session_id}/events/sse": { - "get": { - "tags": [ - "sessions" - ], - "summary": "Subscribe to Events (SSE)", - "description": "Opens an SSE stream for real-time session events.", - "operationId": "get_events_sse", - "parameters": [ - { - "name": "session_id", - "in": "path", - "description": "Session id", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "offset", - "in": "query", - "description": "Last seen event sequence (exclusive)", - "required": false, - "schema": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - } - }, - { - "name": "include_raw", - "in": "query", - "description": "Include raw provider payloads", - "required": false, - "schema": { - "type": "boolean", - "nullable": true - } - } - ], - "responses": { - "200": { - "description": "SSE event stream" - } - } - } - }, - "/v1/sessions/{session_id}/messages": { + }, "post": { "tags": [ - "sessions" - ], - "summary": "Send Message", - "description": "Sends a message to a session and returns immediately.", - "operationId": "post_message", - "parameters": [ - { - "name": "session_id", - "in": "path", - "description": "Session id", - "required": true, - "schema": { - "type": "string" - } - } + "v2" ], + "summary": "ACP POST", + "description": "Sends ACP JSON-RPC envelopes to an ACP client and returns request responses.", + "operationId": "post_v2_acp", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MessageRequest" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "Message accepted" - }, - "404": { - "description": "Session not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - } - } - } - }, - "/v1/sessions/{session_id}/messages/stream": { - "post": { - "tags": [ - "sessions" - ], - "summary": "Send Message (Streaming)", - "description": "Sends a message and returns an SSE event stream of the agent's response.", - "operationId": "post_message_stream", - "parameters": [ - { - "name": "session_id", - "in": "path", - "description": "Session id", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "include_raw", - "in": "query", - "description": "Include raw provider payloads", - "required": false, - "schema": { - "type": "boolean", - "nullable": true - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MessageRequest" + "$ref": "#/components/schemas/AcpEnvelope" } } }, @@ -892,10 +250,60 @@ }, "responses": { "200": { - "description": "SSE event stream" + "description": "JSON-RPC response envelope", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AcpEnvelope" + } + } + } + }, + "202": { + "description": "JSON-RPC notification accepted" + }, + "400": { + "description": "Invalid ACP envelope", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } }, "404": { - "description": "Session not found", + "description": "Unknown ACP client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "406": { + "description": "Client does not accept JSON responses", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "415": { + "description": "Unsupported media type", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "504": { + "description": "ACP agent process response timeout", "content": { "application/json": { "schema": { @@ -905,52 +313,20 @@ } } } - } - }, - "/v1/sessions/{session_id}/permissions/{permission_id}/reply": { - "post": { + }, + "delete": { "tags": [ - "sessions" + "v2" ], - "summary": "Reply to Permission", - "description": "Approves or denies a permission request from the agent.", - "operationId": "reply_permission", - "parameters": [ - { - "name": "session_id", - "in": "path", - "description": "Session id", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "permission_id", - "in": "path", - "description": "Permission id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PermissionReplyRequest" - } - } - }, - "required": true - }, + "summary": "ACP Close", + "description": "Closes an ACP client and releases agent process resources.", + "operationId": "delete_v2_acp", "responses": { "204": { - "description": "Permission reply accepted" + "description": "ACP client closed" }, - "404": { - "description": "Session or permission not found", + "400": { + "description": "Invalid request", "content": { "application/json": { "schema": { @@ -958,135 +334,9 @@ } } } - } - } - } - }, - "/v1/sessions/{session_id}/questions/{question_id}/reject": { - "post": { - "tags": [ - "sessions" - ], - "summary": "Reject Question", - "description": "Rejects a human-in-the-loop question from the agent.", - "operationId": "reject_question", - "parameters": [ - { - "name": "session_id", - "in": "path", - "description": "Session id", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "question_id", - "in": "path", - "description": "Question id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "Question rejected" }, "404": { - "description": "Session or question not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - } - } - } - }, - "/v1/sessions/{session_id}/questions/{question_id}/reply": { - "post": { - "tags": [ - "sessions" - ], - "summary": "Reply to Question", - "description": "Replies to a human-in-the-loop question from the agent.", - "operationId": "reply_question", - "parameters": [ - { - "name": "session_id", - "in": "path", - "description": "Session id", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "question_id", - "in": "path", - "description": "Question id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/QuestionReplyRequest" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "Question answered" - }, - "404": { - "description": "Session or question not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - } - } - } - }, - "/v1/sessions/{session_id}/terminate": { - "post": { - "tags": [ - "sessions" - ], - "summary": "Terminate Session", - "description": "Terminates a running session and cleans up resources.", - "operationId": "terminate_session", - "parameters": [ - { - "name": "session_id", - "in": "path", - "description": "Session id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "Session terminated" - }, - "404": { - "description": "Session not found", + "description": "Unknown ACP client", "content": { "application/json": { "schema": { @@ -1101,6 +351,33 @@ }, "components": { "schemas": { + "AcpEnvelope": { + "type": "object", + "required": [ + "jsonrpc" + ], + "properties": { + "error": { + "nullable": true + }, + "id": { + "nullable": true + }, + "jsonrpc": { + "type": "string" + }, + "method": { + "type": "string", + "nullable": true + }, + "params": { + "nullable": true + }, + "result": { + "nullable": true + } + } + }, "AgentCapabilities": { "type": "object", "required": [ @@ -1161,8 +438,7 @@ "type": "boolean" }, "sharedProcess": { - "type": "boolean", - "description": "Whether this agent uses a shared long-running server process (vs per-turn subprocess)" + "type": "boolean" }, "status": { "type": "boolean" @@ -1181,32 +457,6 @@ } } }, - "AgentError": { - "type": "object", - "required": [ - "type", - "message" - ], - "properties": { - "agent": { - "type": "string", - "nullable": true - }, - "details": { - "nullable": true - }, - "message": { - "type": "string" - }, - "session_id": { - "type": "string", - "nullable": true - }, - "type": { - "$ref": "#/components/schemas/ErrorType" - } - } - }, "AgentInfo": { "type": "object", "required": [ @@ -1220,8 +470,11 @@ "$ref": "#/components/schemas/AgentCapabilities" }, "credentialsAvailable": { - "type": "boolean", - "description": "Whether the agent's required provider credentials are available" + "type": "boolean" + }, + "defaultModel": { + "type": "string", + "nullable": true }, "id": { "type": "string" @@ -1229,6 +482,20 @@ "installed": { "type": "boolean" }, + "models": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentModelInfo" + }, + "nullable": true + }, + "modes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentModeInfo" + }, + "nullable": true + }, "path": { "type": "string", "nullable": true @@ -1247,15 +514,64 @@ } } }, + "AgentInstallArtifact": { + "type": "object", + "required": [ + "kind", + "path", + "source" + ], + "properties": { + "kind": { + "type": "string" + }, + "path": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string", + "nullable": true + } + } + }, "AgentInstallRequest": { "type": "object", "properties": { + "agentProcessVersion": { + "type": "string", + "nullable": true + }, + "agentVersion": { + "type": "string", + "nullable": true + }, "reinstall": { "type": "boolean", "nullable": true } } }, + "AgentInstallResponse": { + "type": "object", + "required": [ + "already_installed", + "artifacts" + ], + "properties": { + "already_installed": { + "type": "boolean" + }, + "artifacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentInstallArtifact" + } + } + } + }, "AgentListResponse": { "type": "object", "required": [ @@ -1315,343 +631,19 @@ } } }, - "AgentModelsResponse": { - "type": "object", - "required": [ - "models" - ], - "properties": { - "defaultModel": { - "type": "string", - "nullable": true - }, - "models": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AgentModelInfo" - } - } - } - }, - "AgentModesResponse": { - "type": "object", - "required": [ - "modes" - ], - "properties": { - "modes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AgentModeInfo" - } - } - } - }, - "AgentUnparsedData": { - "type": "object", - "required": [ - "error", - "location" - ], - "properties": { - "error": { - "type": "string" - }, - "location": { - "type": "string" - }, - "raw_hash": { - "type": "string", - "nullable": true - } - } - }, - "ContentPart": { - "oneOf": [ - { - "type": "object", - "required": [ - "text", - "type" - ], - "properties": { - "text": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "text" - ] - } - } - }, - { - "type": "object", - "required": [ - "json", - "type" - ], - "properties": { - "json": {}, - "type": { - "type": "string", - "enum": [ - "json" - ] - } - } - }, - { - "type": "object", - "required": [ - "name", - "arguments", - "call_id", - "type" - ], - "properties": { - "arguments": { - "type": "string" - }, - "call_id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "tool_call" - ] - } - } - }, - { - "type": "object", - "required": [ - "call_id", - "output", - "type" - ], - "properties": { - "call_id": { - "type": "string" - }, - "output": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "tool_result" - ] - } - } - }, - { - "type": "object", - "required": [ - "path", - "action", - "type" - ], - "properties": { - "action": { - "$ref": "#/components/schemas/FileAction" - }, - "diff": { - "type": "string", - "nullable": true - }, - "path": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "file_ref" - ] - } - } - }, - { - "type": "object", - "required": [ - "text", - "visibility", - "type" - ], - "properties": { - "text": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "reasoning" - ] - }, - "visibility": { - "$ref": "#/components/schemas/ReasoningVisibility" - } - } - }, - { - "type": "object", - "required": [ - "path", - "type" - ], - "properties": { - "mime": { - "type": "string", - "nullable": true - }, - "path": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "image" - ] - } - } - }, - { - "type": "object", - "required": [ - "label", - "type" - ], - "properties": { - "detail": { - "type": "string", - "nullable": true - }, - "label": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "status" - ] - } - } - } - ], - "discriminator": { - "propertyName": "type" - } - }, - "CreateSessionRequest": { - "type": "object", - "required": [ - "agent" - ], - "properties": { - "agent": { - "type": "string" - }, - "agentMode": { - "type": "string", - "nullable": true - }, - "agentVersion": { - "type": "string", - "nullable": true - }, - "directory": { - "type": "string", - "nullable": true - }, - "mcp": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/McpServerConfig" - }, - "nullable": true - }, - "model": { - "type": "string", - "nullable": true - }, - "permissionMode": { - "type": "string", - "nullable": true - }, - "skills": { - "allOf": [ - { - "$ref": "#/components/schemas/SkillsConfig" - } - ], - "nullable": true - }, - "title": { - "type": "string", - "nullable": true - }, - "variant": { - "type": "string", - "nullable": true - } - } - }, - "CreateSessionResponse": { - "type": "object", - "required": [ - "healthy" - ], - "properties": { - "error": { - "allOf": [ - { - "$ref": "#/components/schemas/AgentError" - } - ], - "nullable": true - }, - "healthy": { - "type": "boolean" - }, - "nativeSessionId": { - "type": "string", - "nullable": true - } - } - }, - "ErrorData": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "code": { - "type": "string", - "nullable": true - }, - "details": { - "nullable": true - }, - "message": { - "type": "string" - } - } - }, "ErrorType": { "type": "string", "enum": [ "invalid_request", + "conflict", "unsupported_agent", "agent_not_installed", "install_failed", "agent_process_exited", "token_invalid", "permission_denied", + "not_acceptable", + "unsupported_media_type", "session_not_found", "session_already_exists", "mode_not_supported", @@ -1659,60 +651,6 @@ "timeout" ] }, - "EventSource": { - "type": "string", - "enum": [ - "agent", - "daemon" - ] - }, - "EventsQuery": { - "type": "object", - "properties": { - "includeRaw": { - "type": "boolean", - "nullable": true - }, - "limit": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "offset": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - } - } - }, - "EventsResponse": { - "type": "object", - "required": [ - "events", - "hasMore" - ], - "properties": { - "events": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UniversalEvent" - } - }, - "hasMore": { - "type": "boolean" - } - } - }, - "FileAction": { - "type": "string", - "enum": [ - "read", - "write", - "patch" - ] - }, "FsActionResponse": { "type": "object", "required": [ @@ -1934,311 +872,6 @@ } } }, - "ItemDeltaData": { - "type": "object", - "required": [ - "item_id", - "delta" - ], - "properties": { - "delta": { - "type": "string" - }, - "item_id": { - "type": "string" - }, - "native_item_id": { - "type": "string", - "nullable": true - } - } - }, - "ItemEventData": { - "type": "object", - "required": [ - "item" - ], - "properties": { - "item": { - "$ref": "#/components/schemas/UniversalItem" - } - } - }, - "ItemKind": { - "type": "string", - "enum": [ - "message", - "tool_call", - "tool_result", - "system", - "status", - "unknown" - ] - }, - "ItemRole": { - "type": "string", - "enum": [ - "user", - "assistant", - "system", - "tool" - ] - }, - "ItemStatus": { - "type": "string", - "enum": [ - "in_progress", - "completed", - "failed" - ] - }, - "McpCommand": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "McpOAuthConfig": { - "type": "object", - "properties": { - "clientId": { - "type": "string", - "nullable": true - }, - "clientSecret": { - "type": "string", - "nullable": true - }, - "scope": { - "type": "string", - "nullable": true - } - } - }, - "McpOAuthConfigOrDisabled": { - "oneOf": [ - { - "$ref": "#/components/schemas/McpOAuthConfig" - }, - { - "type": "boolean" - } - ] - }, - "McpRemoteTransport": { - "type": "string", - "enum": [ - "http", - "sse" - ] - }, - "McpServerConfig": { - "oneOf": [ - { - "type": "object", - "required": [ - "command", - "type" - ], - "properties": { - "args": { - "type": "array", - "items": { - "type": "string" - } - }, - "command": { - "$ref": "#/components/schemas/McpCommand" - }, - "cwd": { - "type": "string", - "nullable": true - }, - "enabled": { - "type": "boolean", - "nullable": true - }, - "env": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "nullable": true - }, - "timeoutMs": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "type": { - "type": "string", - "enum": [ - "local" - ] - } - } - }, - { - "type": "object", - "required": [ - "url", - "type" - ], - "properties": { - "bearerTokenEnvVar": { - "type": "string", - "nullable": true - }, - "enabled": { - "type": "boolean", - "nullable": true - }, - "envHeaders": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "nullable": true - }, - "headers": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "nullable": true - }, - "oauth": { - "allOf": [ - { - "$ref": "#/components/schemas/McpOAuthConfigOrDisabled" - } - ], - "nullable": true - }, - "timeoutMs": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "transport": { - "allOf": [ - { - "$ref": "#/components/schemas/McpRemoteTransport" - } - ], - "nullable": true - }, - "type": { - "type": "string", - "enum": [ - "remote" - ] - }, - "url": { - "type": "string" - } - } - } - ], - "discriminator": { - "propertyName": "type" - } - }, - "MessageAttachment": { - "type": "object", - "required": [ - "path" - ], - "properties": { - "filename": { - "type": "string", - "nullable": true - }, - "mime": { - "type": "string", - "nullable": true - }, - "path": { - "type": "string" - } - } - }, - "MessageRequest": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "attachments": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MessageAttachment" - } - }, - "message": { - "type": "string" - } - } - }, - "PermissionEventData": { - "type": "object", - "required": [ - "permission_id", - "action", - "status" - ], - "properties": { - "action": { - "type": "string" - }, - "metadata": { - "nullable": true - }, - "permission_id": { - "type": "string" - }, - "status": { - "$ref": "#/components/schemas/PermissionStatus" - } - } - }, - "PermissionReply": { - "type": "string", - "enum": [ - "once", - "always", - "reject" - ] - }, - "PermissionReplyRequest": { - "type": "object", - "required": [ - "reply" - ], - "properties": { - "reply": { - "$ref": "#/components/schemas/PermissionReply" - } - } - }, - "PermissionStatus": { - "type": "string", - "enum": [ - "requested", - "accept", - "accept_for_session", - "reject" - ] - }, "ProblemDetails": { "type": "object", "required": [ @@ -2269,71 +902,8 @@ }, "additionalProperties": {} }, - "QuestionEventData": { - "type": "object", - "required": [ - "question_id", - "prompt", - "options", - "status" - ], - "properties": { - "options": { - "type": "array", - "items": { - "type": "string" - } - }, - "prompt": { - "type": "string" - }, - "question_id": { - "type": "string" - }, - "response": { - "type": "string", - "nullable": true - }, - "status": { - "$ref": "#/components/schemas/QuestionStatus" - } - } - }, - "QuestionReplyRequest": { - "type": "object", - "required": [ - "answers" - ], - "properties": { - "answers": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "QuestionStatus": { - "type": "string", - "enum": [ - "requested", - "answered", - "rejected" - ] - }, - "ReasoningVisibility": { - "type": "string", - "enum": [ - "public", - "private" - ] - }, "ServerStatus": { "type": "string", - "description": "Status of a shared server process for an agent", "enum": [ "running", "stopped", @@ -2371,48 +941,6 @@ } } }, - "SessionEndReason": { - "type": "string", - "enum": [ - "completed", - "error", - "terminated" - ] - }, - "SessionEndedData": { - "type": "object", - "required": [ - "reason", - "terminated_by" - ], - "properties": { - "exit_code": { - "type": "integer", - "format": "int32", - "description": "Process exit code when reason is Error", - "nullable": true - }, - "message": { - "type": "string", - "description": "Error message when reason is Error", - "nullable": true - }, - "reason": { - "$ref": "#/components/schemas/SessionEndReason" - }, - "stderr": { - "allOf": [ - { - "$ref": "#/components/schemas/StderrOutput" - } - ], - "nullable": true - }, - "terminated_by": { - "$ref": "#/components/schemas/TerminatedBy" - } - } - }, "SessionInfo": { "type": "object", "required": [ @@ -2448,13 +976,6 @@ "format": "int64", "minimum": 0 }, - "mcp": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/McpServerConfig" - }, - "nullable": true - }, "model": { "type": "string", "nullable": true @@ -2469,10 +990,10 @@ "sessionId": { "type": "string" }, - "skills": { + "terminationInfo": { "allOf": [ { - "$ref": "#/components/schemas/SkillsConfig" + "$ref": "#/components/schemas/TerminationInfo" } ], "nullable": true @@ -2484,10 +1005,6 @@ "updatedAt": { "type": "integer", "format": "int64" - }, - "variant": { - "type": "string", - "nullable": true } } }, @@ -2505,58 +1022,6 @@ } } }, - "SessionStartedData": { - "type": "object", - "properties": { - "metadata": { - "nullable": true - } - } - }, - "SkillSource": { - "type": "object", - "required": [ - "type", - "source" - ], - "properties": { - "ref": { - "type": "string", - "nullable": true - }, - "skills": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "source": { - "type": "string" - }, - "subpath": { - "type": "string", - "nullable": true - }, - "type": { - "type": "string" - } - } - }, - "SkillsConfig": { - "type": "object", - "required": [ - "sources" - ], - "properties": { - "sources": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SkillSource" - } - } - } - }, "StderrOutput": { "type": "object", "required": [ @@ -2565,203 +1030,51 @@ "properties": { "head": { "type": "string", - "description": "First N lines of stderr (if truncated) or full stderr (if not truncated)", "nullable": true }, "tail": { "type": "string", - "description": "Last N lines of stderr (only present if truncated)", "nullable": true }, - "total_lines": { + "totalLines": { "type": "integer", - "description": "Total number of lines in stderr", "nullable": true, "minimum": 0 }, "truncated": { - "type": "boolean", - "description": "Whether the output was truncated" - } - } - }, - "TerminatedBy": { - "type": "string", - "enum": [ - "agent", - "daemon" - ] - }, - "TurnEventData": { - "type": "object", - "required": [ - "phase" - ], - "properties": { - "metadata": { - "nullable": true - }, - "phase": { - "$ref": "#/components/schemas/TurnPhase" - }, - "turn_id": { - "type": "string", - "nullable": true - } - } - }, - "TurnPhase": { - "type": "string", - "enum": [ - "started", - "ended" - ] - }, - "TurnStreamQuery": { - "type": "object", - "properties": { - "includeRaw": { - "type": "boolean", - "nullable": true - } - } - }, - "UniversalEvent": { - "type": "object", - "required": [ - "event_id", - "sequence", - "time", - "session_id", - "synthetic", - "source", - "type", - "data" - ], - "properties": { - "data": { - "$ref": "#/components/schemas/UniversalEventData" - }, - "event_id": { - "type": "string" - }, - "native_session_id": { - "type": "string", - "nullable": true - }, - "raw": { - "nullable": true - }, - "sequence": { - "type": "integer", - "format": "int64", - "minimum": 0 - }, - "session_id": { - "type": "string" - }, - "source": { - "$ref": "#/components/schemas/EventSource" - }, - "synthetic": { "type": "boolean" - }, - "time": { - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/UniversalEventType" } } }, - "UniversalEventData": { - "oneOf": [ - { - "$ref": "#/components/schemas/TurnEventData" - }, - { - "$ref": "#/components/schemas/SessionStartedData" - }, - { - "$ref": "#/components/schemas/SessionEndedData" - }, - { - "$ref": "#/components/schemas/ItemEventData" - }, - { - "$ref": "#/components/schemas/ItemDeltaData" - }, - { - "$ref": "#/components/schemas/ErrorData" - }, - { - "$ref": "#/components/schemas/PermissionEventData" - }, - { - "$ref": "#/components/schemas/QuestionEventData" - }, - { - "$ref": "#/components/schemas/AgentUnparsedData" - } - ] - }, - "UniversalEventType": { - "type": "string", - "enum": [ - "session.started", - "session.ended", - "turn.started", - "turn.ended", - "item.started", - "item.delta", - "item.completed", - "error", - "permission.requested", - "permission.resolved", - "question.requested", - "question.resolved", - "agent.unparsed" - ] - }, - "UniversalItem": { + "TerminationInfo": { "type": "object", "required": [ - "item_id", - "kind", - "content", - "status" + "reason", + "terminatedBy" ], "properties": { - "content": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ContentPart" - } + "exitCode": { + "type": "integer", + "format": "int32", + "nullable": true }, - "item_id": { + "message": { + "type": "string", + "nullable": true + }, + "reason": { "type": "string" }, - "kind": { - "$ref": "#/components/schemas/ItemKind" - }, - "native_item_id": { - "type": "string", - "nullable": true - }, - "parent_id": { - "type": "string", - "nullable": true - }, - "role": { + "stderr": { "allOf": [ { - "$ref": "#/components/schemas/ItemRole" + "$ref": "#/components/schemas/StderrOutput" } ], "nullable": true }, - "status": { - "$ref": "#/components/schemas/ItemStatus" + "terminatedBy": { + "type": "string" } } } @@ -2769,20 +1082,8 @@ }, "tags": [ { - "name": "meta", - "description": "Service metadata" - }, - { - "name": "agents", - "description": "Agent management" - }, - { - "name": "sessions", - "description": "Session management" - }, - { - "name": "fs", - "description": "Filesystem operations" + "name": "v2", + "description": "ACP-native v2 API" } ] } \ No newline at end of file diff --git a/docs/opencode-compatibility.mdx b/docs/opencode-compatibility.mdx index 559c16e..7242639 100644 --- a/docs/opencode-compatibility.mdx +++ b/docs/opencode-compatibility.mdx @@ -1,149 +1,26 @@ --- title: "OpenCode Compatibility" -description: "Connect OpenCode clients, SDKs, and web UI to Sandbox Agent." +description: "Status of the OpenCode bridge during ACP v2 migration." --- - - **Experimental**: OpenCode SDK & UI support is experimental and may change without notice. - +OpenCode compatibility is intentionally deferred during ACP core migration. -Sandbox Agent exposes an OpenCode-compatible API, allowing you to connect any OpenCode client, SDK, or web UI to control coding agents running inside sandboxes. +## Current status (v2 core phases) -## Why Use OpenCode Clients with Sandbox Agent? +- `/opencode/*` routes are disabled. +- `sandbox-agent opencode` returns an explicit disabled error. +- This is expected while ACP runtime, SDK, and inspector migration is completed. -OpenCode provides a rich ecosystem of clients: +## Planned re-enable step -- **OpenCode CLI** (`opencode attach`): Terminal-based interface -- **OpenCode Web UI**: Browser-based chat interface -- **OpenCode SDK** (`@opencode-ai/sdk`): Rich TypeScript SDK +OpenCode support is restored in a dedicated phase after ACP core is stable: -## Quick Start +1. Reintroduce `/opencode/*` routing on top of ACP internals. +2. Add dedicated OpenCode ↔ ACP integration tests. +3. Re-enable OpenCode docs and operational guidance. -### Using OpenCode CLI & TUI +Track details in: -Sandbox Agent provides an all-in-one command to setup Sandbox Agent and connect an OpenCode session, great for local development: - -```bash -sandbox-agent opencode --port 2468 --no-token -``` - -Or, start the server and attach separately: - -```bash -# Start sandbox-agent -sandbox-agent server --no-token --host 127.0.0.1 --port 2468 - -# Attach OpenCode CLI -opencode attach http://localhost:2468/opencode -``` - -With authentication enabled: - -```bash -# Start with token -sandbox-agent server --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 2468 - -# Attach with password -opencode attach http://localhost:2468/opencode --password "$SANDBOX_TOKEN" -``` - -### Using the OpenCode Web UI - -The OpenCode web UI can connect to Sandbox Agent for a full browser-based experience. - - - - ```bash - sandbox-agent server --no-token --host 127.0.0.1 --port 2468 --cors-allow-origin http://127.0.0.1:5173 - ``` - - - ```bash - git clone https://github.com/anomalyco/opencode - cd opencode/packages/app - export VITE_OPENCODE_SERVER_HOST=127.0.0.1 - export VITE_OPENCODE_SERVER_PORT=2468 - bun install - bun run dev -- --host 127.0.0.1 --port 5173 - ``` - - - Navigate to `http://127.0.0.1:5173/` in your browser. - - - - - If you see `Error: Could not connect to server`, check that: - - The sandbox-agent server is running - - `--cors-allow-origin` matches the **exact** browser origin (`localhost` and `127.0.0.1` are different origins) - - -### Using OpenCode SDK - -```typescript -import { createOpencodeClient } from "@opencode-ai/sdk"; - -const client = createOpencodeClient({ - baseUrl: "http://localhost:2468/opencode", - headers: { Authorization: "Bearer YOUR_TOKEN" }, // if using auth -}); - -// Create a session -const session = await client.session.create(); - -// Send a prompt -await client.session.promptAsync({ - path: { id: session.data.id }, - body: { - parts: [{ type: "text", text: "Hello, write a hello world script" }], - }, -}); - -// Subscribe to events -const events = await client.event.subscribe({}); -for await (const event of events.stream) { - console.log(event); -} -``` - -## Notes - -- **API Routing**: The OpenCode API is available at the `/opencode` base path -- **Authentication**: If sandbox-agent is started with `--token`, include `Authorization: Bearer ` header or use `--password` flag with CLI -- **CORS**: When using the web UI from a different origin, configure `--cors-allow-origin` -- **Provider Selection**: Use the provider/model selector in the UI to choose which backing agent to use (claude, codex, opencode, amp) -- **Models & Variants**: Providers are grouped by backing agent (e.g. Claude Code, Codex, Amp). OpenCode models are grouped by `OpenCode ()` to preserve their native provider grouping. Each model keeps its real model ID, and variants are exposed when available (Codex/OpenCode/Amp). -- **Optional Native Proxy for TUI/Config Endpoints**: Set `OPENCODE_COMPAT_PROXY_URL` (for example `http://127.0.0.1:4096`) to proxy select OpenCode-native endpoints to a real OpenCode server. This currently applies to `/command`, `/config`, `/global/config`, and `/tui/*`. If not set, sandbox-agent uses its built-in compatibility handlers. - -## Endpoint Coverage - -See the full endpoint compatibility table below. Most endpoints are functional for session management, messaging, and event streaming. Some endpoints return stub responses for features not yet implemented. - - - -| Endpoint | Status | Notes | -|---|---|---| -| `GET /event` | ✓ | Emits events for session/message updates (SSE) | -| `GET /global/event` | ✓ | Wraps events in GlobalEvent format (SSE) | -| `GET /session` | ✓ | In-memory session store | -| `POST /session` | ✓ | Create new sessions | -| `GET /session/{id}` | ✓ | Get session details | -| `POST /session/{id}/message` | ✓ | Send messages to session | -| `GET /session/{id}/message` | ✓ | Get session messages | -| `GET /permission` | ✓ | List pending permissions | -| `POST /permission/{id}/reply` | ✓ | Respond to permission requests | -| `GET /question` | ✓ | List pending questions | -| `POST /question/{id}/reply` | ✓ | Answer agent questions | -| `GET /provider` | ✓ | Returns provider metadata | -| `GET /command` | ↔ | Proxied to native OpenCode when `OPENCODE_COMPAT_PROXY_URL` is set; otherwise stub response | -| `GET /config` | ↔ | Proxied to native OpenCode when `OPENCODE_COMPAT_PROXY_URL` is set; otherwise stub response | -| `PATCH /config` | ↔ | Proxied to native OpenCode when `OPENCODE_COMPAT_PROXY_URL` is set; otherwise local compatibility behavior | -| `GET /global/config` | ↔ | Proxied to native OpenCode when `OPENCODE_COMPAT_PROXY_URL` is set; otherwise stub response | -| `PATCH /global/config` | ↔ | Proxied to native OpenCode when `OPENCODE_COMPAT_PROXY_URL` is set; otherwise local compatibility behavior | -| `/tui/*` | ↔ | Proxied to native OpenCode when `OPENCODE_COMPAT_PROXY_URL` is set; otherwise local compatibility behavior | -| `GET /agent` | − | Returns agent list | -| *other endpoints* | − | Return empty/stub responses | - -✓ Functional    ↔ Proxied (optional)    − Stubbed - - +- `research/acp/spec.md` +- `research/acp/migration-steps.md` +- `research/acp/todo.md` diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index f2a1f1b..64cb467 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -184,9 +184,12 @@ icon: "rocket" ```typescript - const client = await SandboxAgent.connect({ + import { SandboxAgentClient } from "sandbox-agent"; + + const client = new SandboxAgentClient({ baseUrl: "http://your-server:2468", token: process.env.SANDBOX_TOKEN, + agent: "mock", }); ``` @@ -230,10 +233,11 @@ icon: "rocket" ```typescript - import { SandboxAgent } from "sandbox-agent"; + import { SandboxAgentClient } from "sandbox-agent"; - const client = await SandboxAgent.connect({ + const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", + agent: "claude", }); await client.createSession("my-session", { diff --git a/docs/sdks/typescript.mdx b/docs/sdks/typescript.mdx index bf338a7..9c74214 100644 --- a/docs/sdks/typescript.mdx +++ b/docs/sdks/typescript.mdx @@ -1,11 +1,10 @@ --- title: "TypeScript" -description: "Use the generated client to manage sessions and stream events." +description: "Use the TypeScript SDK to manage ACP sessions and Sandbox Agent HTTP APIs." icon: "js" --- -The TypeScript SDK is generated from the OpenAPI spec that ships with the server. It provides a typed -client for sessions, events, and agent operations. +The TypeScript SDK is centered on `sandbox-agent` and its `SandboxAgentClient`, which provides a Sandbox-facing API for session flows, ACP extensions, and binary HTTP filesystem helpers. ## Install @@ -27,14 +26,17 @@ client for sessions, events, and agent operations. ## Create a client ```ts -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, + agent: "mock", }); ``` +`SandboxAgentClient` is the canonical API. By default it auto-connects (`autoConnect: true`), so provide `agent` in the constructor. Use the instance method `client.connect()` only when you explicitly set `autoConnect: false`. + ## Autospawn (Node only) If you run locally, the SDK can launch the server for you. @@ -42,7 +44,9 @@ If you run locally, the SDK can launch the server for you. ```ts import { SandboxAgent } from "sandbox-agent"; -const client = await SandboxAgent.start(); +const client = await SandboxAgent.start({ + agent: "mock", +}); await client.dispose(); ``` @@ -50,69 +54,162 @@ await client.dispose(); Autospawn uses the local `sandbox-agent` binary. Install `@sandbox-agent/cli` (recommended) or set `SANDBOX_AGENT_BIN` to a custom path. -## Sessions and messages +## Connect lifecycle + +Use manual mode when you want explicit ACP session lifecycle control. ```ts -await client.createSession("demo-session", { - agent: "codex", - agentMode: "default", - permissionMode: "plan", +import { + AlreadyConnectedError, + NotConnectedError, + SandboxAgentClient, +} from "sandbox-agent"; + +const client = new SandboxAgentClient({ + baseUrl: "http://127.0.0.1:2468", + token: process.env.SANDBOX_TOKEN, + agent: "mock", + autoConnect: false, }); -await client.postMessage("demo-session", { message: "Hello" }); +await client.connect(); + +try { + await client.connect(); +} catch (error) { + if (error instanceof AlreadyConnectedError) { + console.error("already connected"); + } +} + +await client.disconnect(); + +try { + await client.prompt({ sessionId: "s", prompt: [{ type: "text", text: "hi" }] }); +} catch (error) { + if (error instanceof NotConnectedError) { + console.error("connect first"); + } +} ``` -List agents and inspect feature coverage (available on `capabilities`): +## Session flow ```ts +const session = await client.newSession({ + cwd: "/", + mcpServers: [], + metadata: { + agent: "mock", + title: "Demo Session", + variant: "high", + permissionMode: "ask", + }, +}); + +const result = await client.prompt({ + sessionId: session.sessionId, + prompt: [{ type: "text", text: "Summarize this repository." }], +}); + +console.log(result.stopReason); +``` + +Load, cancel, and runtime settings use ACP-aligned method names: + +```ts +await client.loadSession({ sessionId: session.sessionId, cwd: "/", mcpServers: [] }); +await client.cancel({ sessionId: session.sessionId }); +await client.setSessionMode({ sessionId: session.sessionId, modeId: "default" }); +await client.setSessionConfigOption({ + sessionId: session.sessionId, + configId: "config-id-from-session", + value: "config-value-id", +}); +``` + +## Extension helpers + +Sandbox extensions are exposed as first-class methods: + +```ts +const models = await client.listModels({ sessionId: session.sessionId }); +console.log(models.currentModelId, models.availableModels.length); + +await client.setMetadata(session.sessionId, { + title: "Renamed Session", + model: "mock", + permissionMode: "ask", +}); + +await client.detachSession(session.sessionId); +await client.terminateSession(session.sessionId); +``` + +## Event handling + +Use `onEvent` to consume converted SDK events. + +```ts +import { SandboxAgentClient, type AgentEvent } from "sandbox-agent"; + +const events: AgentEvent[] = []; + +const client = new SandboxAgentClient({ + baseUrl: "http://127.0.0.1:2468", + token: process.env.SANDBOX_TOKEN, + agent: "mock", + onEvent: (event) => { + events.push(event); + + if (event.type === "sessionEnded") { + console.log("ended", event.notification.params.sessionId ?? event.notification.params.session_id); + } + + if (event.type === "agentUnparsed") { + console.warn("unparsed", event.notification.params); + } + }, +}); +``` + +You can also handle raw session update notifications directly: + +```ts +const client = new SandboxAgentClient({ + baseUrl: "http://127.0.0.1:2468", + token: process.env.SANDBOX_TOKEN, + agent: "mock", + onSessionUpdate: (notification) => { + console.log(notification.update.sessionUpdate); + }, +}); +``` + +## Control + HTTP helpers + +Agent/session and non-binary filesystem control helpers use ACP extension methods over `/v2/rpc`: + +```ts +const health = await client.getHealth(); const agents = await client.listAgents(); -const codex = agents.agents.find((agent) => agent.id === "codex"); -console.log(codex?.capabilities); +await client.installAgent("codex", { reinstall: true }); + +const sessions = await client.listSessions(); +const sessionInfo = await client.getSession(sessions.sessions[0].session_id); ``` -## Poll events +These methods require an active ACP connection and throw `NotConnectedError` when disconnected. -```ts -const events = await client.getEvents("demo-session", { - offset: 0, - limit: 200, - includeRaw: false, -}); +Binary filesystem transfer intentionally remains HTTP: -for (const event of events.events) { - console.log(event.type, event.data); -} -``` +- `readFsFile` -> `GET /v2/fs/file` +- `writeFsFile` -> `PUT /v2/fs/file` +- `uploadFsBatch` -> `POST /v2/fs/upload-batch` -## Stream events (SSE) +Reason: these are Sandbox Agent host/runtime filesystem operations (not agent-specific ACP behavior), intentionally separate from ACP native `fs/read_text_file` / `fs/write_text_file`, and they may require streaming very large binary payloads that ACP JSON-RPC is not suited to transport efficiently. -```ts -for await (const event of client.streamEvents("demo-session", { - offset: 0, - includeRaw: false, -})) { - console.log(event.type, event.data); -} -``` - -The SDK parses `text/event-stream` into `UniversalEvent` objects. If you want full control, use -`getEventsSse()` and parse the stream yourself. - -## Stream a single turn - -```ts -for await (const event of client.streamTurn("demo-session", { message: "Hello" })) { - console.log(event.type, event.data); -} -``` - -This method posts the message and streams only the next turn. For manual control, call -`postMessageStream()` and parse the SSE response yourself. - -## Optional raw payloads - -Set `includeRaw: true` on `getEvents`, `streamEvents`, or `streamTurn` to include the raw provider -payload in `event.raw`. This is useful for debugging and conversion analysis. +ACP extension variants can exist in parallel for compatibility, but `SandboxAgentClient` should prefer the HTTP endpoints above by default. ## Error handling @@ -122,7 +219,7 @@ All HTTP errors throw `SandboxAgentError`: import { SandboxAgentError } from "sandbox-agent"; try { - await client.postMessage("missing-session", { message: "Hi" }); + await client.listAgents(); } catch (error) { if (error instanceof SandboxAgentError) { console.error(error.status, error.problem); @@ -142,6 +239,7 @@ const url = buildInspectorUrl({ token: "optional-bearer-token", headers: { "X-Custom-Header": "value" }, }); + console.log(url); // https://your-sandbox-agent.example.com/ui/?token=...&headers=... ``` @@ -153,10 +251,17 @@ Parameters: ## Types -The SDK exports OpenAPI-derived types for events, items, and feature coverage: +The SDK exports typed events and responses for the Sandbox layer: ```ts -import type { UniversalEvent, UniversalItem, AgentCapabilities } from "sandbox-agent"; +import type { + AgentEvent, + AgentInfo, + HealthResponse, + SessionInfo, + SessionListResponse, + SessionTerminateResponse, +} from "sandbox-agent"; ``` -See the [API Reference](/api) for schema details. +For low-level protocol transport details, see [ACP HTTP Client](/advanced/acp-http-client). diff --git a/docs/skills-config.mdx b/docs/skills-config.mdx index 5f35866..ae87142 100644 --- a/docs/skills-config.mdx +++ b/docs/skills-config.mdx @@ -14,12 +14,13 @@ Pass `skills.sources` when creating a session to load skills from GitHub repos, ```ts TypeScript -import { SandboxAgent } from "sandbox-agent"; +import { SandboxAgentClient } from "sandbox-agent"; -const client = await SandboxAgent.connect({ +const client = new SandboxAgentClient({ baseUrl: "http://127.0.0.1:2468", token: process.env.SANDBOX_TOKEN, -}); + agent: "mock", + }); await client.createSession("claude-skills", { agent: "claude", diff --git a/examples/CLAUDE.md b/examples/CLAUDE.md index 57e13cd..048312f 100644 --- a/examples/CLAUDE.md +++ b/examples/CLAUDE.md @@ -6,12 +6,17 @@ - Do not bind mount host files or host directories into Docker example containers. - If an example needs tools, skills, or MCP servers, install them inside the container during setup. -## Testing Examples +## Testing Examples (ACP v2) -Examples can be tested by starting them in the background and communicating directly with the sandbox-agent API: +Examples should be validated against v2 endpoints: -1. Start the example: `SANDBOX_AGENT_DEV=1 pnpm start &` -2. Note the base URL and session ID from the output. -3. Send messages: `curl -X POST http://127.0.0.1:/v1/sessions//messages -H "Content-Type: application/json" -d '{"message":"..."}'` -4. Poll events: `curl http://127.0.0.1:/v1/sessions//events` -5. Approve permissions: `curl -X POST http://127.0.0.1:/v1/sessions//permissions//reply -H "Content-Type: application/json" -d '{"reply":"once"}'` +1. Start the example: `SANDBOX_AGENT_DEV=1 pnpm start` +2. Create an ACP client by POSTing `initialize` to `/v2/rpc` with `x-acp-agent: mock` (or another installed agent). +3. Capture `x-acp-connection-id` from the response headers. +4. Open SSE stream: `GET /v2/rpc` with `x-acp-connection-id`. +5. Send `session/new` then `session/prompt` via `POST /v2/rpc` with the same connection id. +6. Close connection via `DELETE /v2/rpc` with `x-acp-connection-id`. + +v1 reminder: + +- `/v1/*` is removed and returns `410 Gone`. diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 511b8e5..4ae817d 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -1,150 +1,26 @@ -## Frontend Style Guide +# Frontend Instructions -Examples should follow these design conventions: +## Inspector Architecture -**Color Palette (Dark Theme)** -- Primary accent: `#ff4f00` (orange) for interactive elements and highlights -- Background: `#000000` (main), `#1c1c1e` (cards/containers) -- Borders: `#2c2c2e` -- Input backgrounds: `#2c2c2e` with border `#3a3a3c` -- Text: `#ffffff` (primary), `#8e8e93` (secondary/muted) -- Success: `#30d158` (green) -- Warning: `#ff4f00` (orange) -- Danger: `#ff3b30` (red) -- Purple: `#bf5af2` (for special states like rollback) +- Inspector source is `frontend/packages/inspector/`. +- `/ui/` must use ACP over HTTP (`/v2/rpc`) for session/prompt traffic. +- Primary flow: + - `initialize` + - `session/new` + - `session/prompt` + - `session/update` over SSE +- Keep backend/protocol changes in client bindings; avoid unnecessary full UI rewrites. -**Typography** -- UI: System fonts (`-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, sans-serif`) -- Code: `ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace` -- Sizes: 14-16px body, 12-13px labels, large numbers 48-72px +## Testing -**Sizing & Spacing** -- Border radius: 8px (cards/containers/buttons), 6px (inputs/badges) -- Section padding: 20-24px -- Gap between items: 12px -- Transitions: 200ms ease for all interactive states +Run inspector checks after transport or chat-flow changes: +```bash +pnpm --filter @sandbox-agent/inspector test +pnpm --filter @sandbox-agent/inspector test:agent-browser +``` -**Button Styles** -- Padding: 12px 20px -- Border: none -- Border radius: 8px -- Font size: 14px, weight 600 -- Hover: none (no hover state) -- Disabled: 50% opacity, `cursor: not-allowed` - -**CSS Approach** -- Plain CSS in `