From c987590c5f5d9dad968399910a86bb8482a82c18 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Tue, 15 Sep 2026 20:33:30 -0400 Subject: [PATCH] refactor(protocol): mark stateless generation experimental --- V2_HTTP_API_AUDIT.md | 28 +++++++++---------- .../client/src/promise/generated/client.ts | 2 +- packages/client/test/promise.test.ts | 2 +- packages/protocol/openapi.json | 4 +-- packages/protocol/src/groups/generate.ts | 4 +-- packages/server/test/generate.test.ts | 4 +-- services/www/openapi.json | 4 +-- services/www/public/openapi.json | 4 +-- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/V2_HTTP_API_AUDIT.md b/V2_HTTP_API_AUDIT.md index 2a6f0e70aa6..8c2165b8834 100644 --- a/V2_HTTP_API_AUDIT.md +++ b/V2_HTTP_API_AUDIT.md @@ -100,7 +100,7 @@ Review endpoints in document order. For each endpoint, select one disposition an | [x] 021 | `GET` | `/api/config` | `config.get` | Keep | Compatibility entries removed; response now contains only documents and OpenCode directories. | | [x] 022 | `GET` | `/api/config/preferences` | `config.preferences` | Remove | Redundant special projection of global config. | | [x] 023 | `PATCH` | `/api/config/preferences` | `config.updatePreferences` | Remove | Redundant field-specific config mutation API. | -| [ ] 024 | `GET` | `/api/config/shell` | `config.shells` | | | +| [x] 024 | `GET` | `/api/config/shell` | `config.shells` | Keep | Required by the server Terminal shell setting. | | [x] 024a | `PATCH` | `/api/experimental/config` | `experimental.config.update` | Change | Experimental global config mutation; initially accepts only `shell`. | ## Group 3: Credentials, integrations, MCP, and web search @@ -112,14 +112,14 @@ Review endpoints in document order. For each endpoint, select one disposition an | [x] 025 | `GET` | `/api/integration` | `integration.list` | Keep | Full integration inventory is consumed by authentication and integration-selection clients. | | [x] 026 | `GET` | `/api/integration/{integrationID}` | `integration.get` | Change | Missing integration now returns typed `404` instead of optional data. | | [x] 027 | `POST` | `/api/experimental/integration/wellknown` | `experimental.integration.wellknown.add` | Experimental-only | Retained outside the stable API commitment. | -| [ ] 028 | `POST` | `/api/integration/{integrationID}/connect/key` | `integration.connect.key` | | | -| [ ] 029 | `POST` | `/api/integration/{integrationID}/connect/oauth` | `integration.oauth.connect` | | | -| [ ] 030 | `GET` | `/api/integration/{integrationID}/connect/oauth/{attemptID}` | `integration.oauth.status` | | | -| [ ] 031 | `DELETE` | `/api/integration/{integrationID}/connect/oauth/{attemptID}` | `integration.oauth.cancel` | | | -| [ ] 032 | `POST` | `/api/integration/{integrationID}/connect/oauth/{attemptID}/complete` | `integration.oauth.complete` | | | -| [ ] 033 | `POST` | `/api/integration/{integrationID}/connect/command` | `integration.command.connect` | | | -| [ ] 034 | `GET` | `/api/integration/{integrationID}/connect/command/{attemptID}` | `integration.command.status` | | | -| [ ] 035 | `DELETE` | `/api/integration/{integrationID}/connect/command/{attemptID}` | `integration.command.cancel` | | | +| [x] 028 | `POST` | `/api/integration/{integrationID}/connect/key` | `integration.connect.key` | Change | Missing integration returns typed `404`; key form answers retained. | +| [x] 029 | `POST` | `/api/integration/{integrationID}/connect/oauth` | `integration.oauth.connect` | Keep | OAuth connection start contract retained. | +| [x] 030 | `GET` | `/api/integration/{integrationID}/connect/oauth/{attemptID}` | `integration.oauth.status` | Change | Missing integration or OAuth attempt returns typed `404`. | +| [x] 031 | `DELETE` | `/api/integration/{integrationID}/connect/oauth/{attemptID}` | `integration.oauth.cancel` | Keep | Idempotent cancellation remains a no-op for unavailable or terminal attempts. | +| [x] 032 | `POST` | `/api/integration/{integrationID}/connect/oauth/{attemptID}/complete` | `integration.oauth.complete` | Change | Missing integration or OAuth attempt returns typed `404`; code remains mode-dependent. | +| [x] 033 | `POST` | `/api/integration/{integrationID}/connect/command` | `integration.command.connect` | Change | Missing integration or command method returns typed `404`. | +| [x] 034 | `GET` | `/api/integration/{integrationID}/connect/command/{attemptID}` | `integration.command.status` | Change | Missing integration or command attempt returns typed `404`. | +| [x] 035 | `DELETE` | `/api/integration/{integrationID}/connect/command/{attemptID}` | `integration.command.cancel` | Keep | Idempotent cancellation remains a no-op for unavailable or terminal attempts. | | [x] 036 | `GET` | `/api/mcp` | `mcp.list` | Keep | MCP inventory and connection status retained. | | [x] 037 | `PUT` | `/api/experimental/mcp/{server}` | `experimental.mcp.add` | Experimental-only | Runtime-only MCP override; does not persist configuration. | | [x] 038 | `DELETE` | `/api/experimental/mcp/{server}` | `experimental.mcp.remove` | Experimental-only | Runtime removal override; missing server returns `404`. | @@ -271,9 +271,9 @@ Review endpoints in document order. For each endpoint, select one disposition an | Done | Method | Path | Operation ID | Decision | Notes | |---|---|---|---|---|---| -| [ ] 138 | `POST` | `/api/generate` | `generate.text` | | | -| [ ] 139 | `POST` | `/api/rpc/{rpcID}/{method}` | `rpc.call` | | | -| [ ] 140 | `GET` | `/api/event` | `event.subscribe` | | | -| [ ] 141 | `GET` | `/api/debug/location` | `debug.location.list` | | | -| [ ] 142 | `DELETE` | `/api/debug/location` | `debug.location.evict` | | | +| [x] 138 | `POST` | `/api/experimental/generate` | `experimental.generate.text` | Experimental-only | Stateless generation retained alongside session generation. | +| [x] 139 | `POST` | `/api/rpc/{rpcID}/{method}` | `rpc.call` | Keep | Generic typed-error plugin RPC transport retained. | +| [x] 140 | `GET` | `/api/event` | `event.subscribe` | Keep | Unified native and dynamic plugin event stream retained. | +| [x] 141 | `GET` | `/api/debug/location` | `debug.location.list` | Keep | Loaded-location debug inventory retained. | +| [x] 142 | `DELETE` | `/api/debug/location` | `debug.location.evict` | Keep | Idempotent loaded-location eviction retained. | | [x] 143 | `GET` | `/api/experimental/migration/v1` | `experimental.migration.v1.status` | Experimental-only | Retained outside the stable API commitment. | diff --git a/packages/client/src/promise/generated/client.ts b/packages/client/src/promise/generated/client.ts index 999bb3c4839..de79e8e6612 100644 --- a/packages/client/src/promise/generated/client.ts +++ b/packages/client/src/promise/generated/client.ts @@ -1088,7 +1088,7 @@ export function make(options: ClientOptions) { request<{ readonly data: GenerateTextOutput }>( { method: "POST", - path: `/api/generate`, + path: `/api/experimental/generate`, body: { prompt: input["prompt"], model: input["model"] }, successStatus: 200, declaredStatuses: [400, 401, 503], diff --git a/packages/client/test/promise.test.ts b/packages/client/test/promise.test.ts index ba601a76b26..a2d417d2f69 100644 --- a/packages/client/test/promise.test.ts +++ b/packages/client/test/promise.test.ts @@ -157,7 +157,7 @@ test("generate.text uses the locationless public contract", async () => { }) expect(await client.generate.text({ prompt: "ping" })).toEqual({ text: "pong" }) - expect(request?.url).toBe("http://localhost:3000/api/generate") + expect(request?.url).toBe("http://localhost:3000/api/experimental/generate") expect(await request?.json()).toEqual({ prompt: "ping" }) }) diff --git a/packages/protocol/openapi.json b/packages/protocol/openapi.json index b2f39efcc66..77a3c46e502 100644 --- a/packages/protocol/openapi.json +++ b/packages/protocol/openapi.json @@ -5114,10 +5114,10 @@ "summary": "Get default model" } }, - "/api/generate": { + "/api/experimental/generate": { "post": { "tags": ["generate"], - "operationId": "generate.text", + "operationId": "experimental.generate.text", "parameters": [], "security": [], "responses": { diff --git a/packages/protocol/src/groups/generate.ts b/packages/protocol/src/groups/generate.ts index 70428209560..6bef53bac02 100644 --- a/packages/protocol/src/groups/generate.ts +++ b/packages/protocol/src/groups/generate.ts @@ -5,7 +5,7 @@ import { InvalidRequestError, ServiceUnavailableError } from "../errors.js" export const GenerateGroup = HttpApiGroup.make("server.generate") .add( - HttpApiEndpoint.post("generate.text", "/api/generate", { + HttpApiEndpoint.post("generate.text", "/api/experimental/generate", { payload: Schema.Struct({ prompt: Schema.String, model: Model.Ref.pipe(Schema.optional), @@ -16,7 +16,7 @@ export const GenerateGroup = HttpApiGroup.make("server.generate") error: [InvalidRequestError, ServiceUnavailableError], }).annotateMerge( OpenApi.annotations({ - identifier: "generate.text", + identifier: "experimental.generate.text", summary: "Generate text", description: "Run one stateless model generation using the server's base configuration and return the assistant text. Uses the base configuration's default model when none is specified.", diff --git a/packages/server/test/generate.test.ts b/packages/server/test/generate.test.ts index c0983e8353b..7628e652e97 100644 --- a/packages/server/test/generate.test.ts +++ b/packages/server/test/generate.test.ts @@ -53,11 +53,11 @@ it.live("uses base configuration without depending on process.cwd()", () => ) expect(global).not.toBe(process.cwd()) - expect(yield* request(handler, new URL("http://opencode.local/api/generate"))).toEqual({ + expect(yield* request(handler, new URL("http://opencode.local/api/experimental/generate"))).toEqual({ model: { providerID: "base", model: "default" }, }) - const legacy = new URL("http://opencode.local/api/generate") + const legacy = new URL("http://opencode.local/api/experimental/generate") legacy.searchParams.set("location[directory]", project) expect(yield* request(handler, legacy)).toEqual({ model: { providerID: "base", model: "default" }, diff --git a/services/www/openapi.json b/services/www/openapi.json index b2f39efcc66..77a3c46e502 100644 --- a/services/www/openapi.json +++ b/services/www/openapi.json @@ -5114,10 +5114,10 @@ "summary": "Get default model" } }, - "/api/generate": { + "/api/experimental/generate": { "post": { "tags": ["generate"], - "operationId": "generate.text", + "operationId": "experimental.generate.text", "parameters": [], "security": [], "responses": { diff --git a/services/www/public/openapi.json b/services/www/public/openapi.json index b2f39efcc66..77a3c46e502 100644 --- a/services/www/public/openapi.json +++ b/services/www/public/openapi.json @@ -5114,10 +5114,10 @@ "summary": "Get default model" } }, - "/api/generate": { + "/api/experimental/generate": { "post": { "tags": ["generate"], - "operationId": "generate.text", + "operationId": "experimental.generate.text", "parameters": [], "security": [], "responses": {