Repository navigation
refactor(client): group experimental SDK methods under client.experimental - #59072
kitlangton wants to merge 19 commits into
Conversation
There was a problem hiding this comment.
The change looks sound. check:generated is clean, the client and httpapi-codegen tests pass (235 and 100), and so do the caller tests in cli, tui, sdk and server. A search found no callers still using the old method paths. Running the CLI from source failed on the base and the PR alike (missing react/jsx-dev-runtime in this environment), so I did no end-to-end CLI or TUI run.
Public API impact (promise and Effect clients; not backwards compatible for experimental methods): every method whose route is under /api/experimental/ moves from client.<group>.<method> to client.experimental.<group>.<method>:
session.{stats, import, export, skill, wait, instructions, log}→experimental.session.*generate.text→experimental.generate.textvoice.{models, session}→experimental.voice.*integration.wellknown.add→experimental.integration.wellknown.addmcp.{add, remove, connect, disconnect, client.token}→experimental.mcp.*file.write→experimental.file.writemigration.v1.status→experimental.migration.v1.statususage.limits→experimental.usage.limitsconfig.update→experimental.config.update
The top-level generate, voice, migration and usage groups are removed. Exported types are renamed with an Experimental prefix, e.g. GenerateApi → ExperimentalGenerateApi, and ExperimentalSessionApi is new. There are no aliases, so external SDK callers must add .experimental. HTTP routes, wire formats and the plugin context (ctx.generate, ctx.session.wait) are unchanged.
The description's list of moved methods leaves out session.skill, session.instructions, mcp.remove, mcp.client.token and file.write. Worth adding them to the release notes so SDK users aren't surprised.
… update docs for experimental namespace
There was a problem hiding this comment.
The new commits look fine.
- Codegen fix: projected Effect clients now read each group from the raw client by its client name, so nested groups such as
experimental.sessionresolve. The in-repo build doesn't use projections, so the generated output is unchanged (check:generatedis clean). The new test covers the case. - Plugin types:
IntegrationDomainno longer needs to omitwellknown, since that group has moved toclient.experimental.integration. - Docs: the client and plugin pages now use the new names.
Checks: codegen tests (100) and client tests (235) pass, and plugin type-checks cleanly with tsc.
The public API impact is the same as in the earlier review: experimental client methods and their types move under client.experimental / the Experimental prefix. The plugin IntegrationDomain type change doesn't change what plugins can call.
There was a problem hiding this comment.
The new commit only clears lint warnings in packages/app, and behaviour is unchanged. The MCP toggle gets the same server name, location, pending check and enabled state as before for both forms of input. packages/app type-checks cleanly with tsc. Nothing new to raise.
The public API impact is unchanged from the earlier review: experimental client methods and their types move under client.experimental / the Experimental prefix.
…amespace # ------------------------ >8 ------------------------ # Do not modify or remove the line above. # Everything below it will be ignored. # # Conflicts: # packages/app/src/settings/general/controllers.ts # packages/client/src/promise/generated/client.ts # packages/client/src/promise/generated/types.ts # packages/client/test/promise.test.ts # packages/plugin/src/effect/session.ts # packages/plugin/src/promise/session.ts # services/www/src/docs/content/build/plugins/effect.mdx
There was a problem hiding this comment.
The new commits look fine. Callers that arrived from dev now use the experimental paths: app voice (experimental.voice.*, experimental.session.wait), file watching (experimental.file.watch/unwatch) and the server media test (experimental.session.import/export). The plugin SessionDomain now takes instructions as well as wait from the experimental session API, so ctx.session.instructions keeps its name. A search found no callers left on the old paths.
Checks: client tests (239) and codegen tests (101) pass, check:generated is clean, media.test.ts passes, and app, plugin and gui-extensions type-check cleanly with tsc.
The public API impact is the same as in the earlier review, now also covering file.watch/file.unwatch → experimental.file.watch/unwatch. All experimental methods move under client.experimental, with no aliases.
There was a problem hiding this comment.
The new commit only clears lint warnings in packages/app and doesn't change behaviour. VoiceTransport.send() now takes a typed VoiceClientEvent union instead of object, and the rest is blank lines. packages/app type-checks cleanly with tsc, so all existing send() calls fit the union, and the voice tests (29) pass. Nothing new to raise.
The public API impact is unchanged from the earlier review: experimental client methods and their types move under client.experimental / the Experimental prefix.
There was a problem hiding this comment.
The new commit looks fine. Endpoint names are unchanged, so server handlers, the codegen skip lists and the generated SDK method names are unaffected: check:generated in packages/client is clean. openapi.json is in sync with its generator. The new protocol test, and the server, CLI api and codemode tests, pass.
New public API change: OpenAPI operation ids persistentPty.{read, list, create, shutdown, handoff, get, update, snapshot, remove, connectToken, connect} → experimental.persistentPty.*, and session.log → experimental.session.log. URLs, payloads and client method names are unchanged. Anything that looks up operations by id must switch to the new names: opencode api <operationId> (e.g. opencode api session.log → opencode api experimental.session.log), the docs' API reference anchors, and SDKs generated from openapi.json in other languages. These endpoints are all experimental, so this isn't blocking, but it may be worth a line in the description's behaviour-change note.
The SDK-level impact is the same as in the earlier reviews: experimental client methods and their types move under client.experimental / the Experimental prefix, with no aliases.
…amespace # ------------------------ >8 ------------------------ # Do not modify or remove the line above. # Everything below it will be ignored. # # Conflicts: # packages/client/script/build.ts # packages/client/src/promise/generated/types.ts
…imental
ctx.generate, ctx.session.wait and ctx.session.instructions move to ctx.experimental.{generate,session.wait,session.instructions} in both the Effect and Promise plugin Context, mirroring client.experimental. No aliases. A core test derives the rule from the Promise adapter's endpoint mapping.
There was a problem hiding this comment.
The plugin commit looks right. The three groups of plugin methods move under ctx.experimental in both plugin contexts (Effect and Promise), core's plugin host and the Promise adapter. The instruction-entry methods keep their session and key checks unchanged. A search found no plugin code left on the old paths.
Checks: the new experimental-namespace and session-instructions tests in packages/core pass, as do the packages/plugin tests. check:generated is clean in packages/client and packages/protocol. packages/plugin, packages/core and packages/app type-check with no new errors.
New public API change (plugin API, breaking, no aliases): ctx.generate.text → ctx.experimental.generate.text, ctx.session.wait → ctx.experimental.session.wait, ctx.session.instructions.entry.{list,put,remove} → ctx.experimental.session.instructions.entry.*. This applies to both plugin contexts (Effect and Promise). Existing plugins that call these must add .experimental. The SDK and OpenAPI operation-id changes are as in the earlier reviews.
One small point inline: adaptedEndpoints is test-only state that the plugin package now exports publicly.
…liases Old client and plugin paths forward to client.experimental / ctx.experimental until the next minor. Adds .github/RELEASE_REMINDERS.md and the AGENTS.md rule for deprecating shipped SDK and plugin API.
There was a problem hiding this comment.
The deprecated aliases work as described. The old SDK method paths point to the same functions as their client.experimental.* homes, in both the Promise and Effect clients. The old Promise and Effect type names are kept as @deprecated aliases. Core's plugin host and the Promise adapter expose the same experimental methods at ctx.experimental.* and at the deprecated ctx.generate, ctx.session.wait and ctx.session.instructions, with unchanged bodies.
Checks: client tests (240) pass and check:generated is clean. The core plugin experimental-namespace and session-instructions tests pass, as do the plugin tests. Client, plugin, core and app type-check with no new errors.
The earlier point about adaptedEndpoints being exported from @opencode/plugin/promise/adapter is not addressed yet.
Public API: the SDK and plugin moves are now backwards compatible through @deprecated aliases until the next minor. Additions: client.experimental.*, ctx.experimental.{generate, session.wait, session.instructions}, and the Experimental* type names. Unchanged from before: the OpenAPI operation-id renames (persistentPty.* and session.log → experimental.*) still have no alias for id-based callers such as opencode api <operationId>.
One gap: in the Effect client, the old paths exist on the client object (OpenCodeClient) but not on the generated group interfaces. AppApi has no generate, voice, usage or migration; SessionApi has no stats or wait; and McpApi, ConfigApi, IntegrationApi and FileApi are missing their moved methods. Effect code that names those types (e.g. SessionApi<E>["wait"] or AppApi<E>["generate"]) won't compile even though the call works at runtime. The Promise group types are built from the client object, so they're unaffected. If the goal is no type-level breaks either, the deprecated members could be added to those Effect interfaces too.
There was a problem hiding this comment.
The commit closes the gap from my last review. The Effect AppApi, SessionApi, IntegrationApi, McpApi, FileApi and ConfigApi interfaces now carry the old members as @deprecated entries, typed from their Experimental* replacements. Effect code typed against them keeps compiling. Nothing in the repo implements these interfaces in a way the extra members would break.
Checks: client, plugin, core and app type-check with no new errors; sdk, server and cli show the same errors as the base; client tests (240) pass.
The SDK and plugin moves are now backwards compatible through deprecated aliases until the next minor, at both runtime and type level. The OpenAPI operation-id renames are unchanged from before. The earlier point about adaptedEndpoints being exported is still open.
compile(..., { aliases: true }) keeps each pre-experimental client path and type name as a @deprecated alias of its client.experimental replacement, removed in the next minor. Replaces the hand-written alias files and reverts the plugin context to its dev names.
There was a problem hiding this comment.
The new commit fixes my open thread: adaptedEndpoints and the test that needed it are gone, so the plugin package no longer exports test-only state.
The deprecated aliases are now generated by the codegen (aliases: true) instead of hand-written. Checks: the new deprecated-aliases test confirms every old path on the real Promise and Effect clients is the same function as its client.experimental path. Client tests (241) and codegen tests (103) pass, check:generated is clean, and client, plugin, core and app type-check with no new errors.
Public API: the old SDK method paths, type names and Effect group members stay as @deprecated aliases until the next minor, so the SDK change is backwards compatible. The plugin context API is unchanged (ctx.generate, ctx.session.wait and ctx.session.instructions stay put; only their types point at Experimental*). The only breaking change left is the renamed OpenAPI operation ids for 12 endpoints (persistentPty.* and session.log → experimental.*), which affects anything that looks operations up by id, such as opencode api <operationId>.
…amespace # ------------------------ >8 ------------------------ # Do not modify or remove the line above. # Everything below it will be ignored. # # Conflicts: # packages/client/src/promise/generated/types.ts
There was a problem hiding this comment.
This update only merges dev; the pull request's own changes are as I last reviewed them. The regenerated openapi.json and Promise types drop IntegrationInfo.configured, from the revert of "fix(tui): identify config-defined providers" (#59439) on dev, not from this pull request. check:generated is clean in packages/client and packages/protocol, and the client tests (241) pass.
The earlier point about the exported adaptedEndpoints is fixed, since it is no longer in the plugin package. Nothing new to raise; the public API impact is the same as in my previous review.
…amespace # ------------------------ >8 ------------------------ # Do not modify or remove the line above. # Everything below it will be ignored. # # Conflicts: # services/www/src/docs/content/build/plugins/index.mdx
There was a problem hiding this comment.
This update only merges dev, and the pull request's own changes still look right after it. The renamed gui-plugins server panel and the moved build/plugins/server.mdx docs keep the experimental paths and types. dev's new stable GET /api/plugin/gui was generated as client.plugin.gui, outside client.experimental.
Checks: check:generated is clean in packages/client and packages/protocol. The protocol tests (10) and client tests (241, including the placement test) pass. app, gui-plugins and plugin type-check cleanly with tsc.
Nothing new to raise; the public API impact is the same as in my previous review.
…amespace # ------------------------ >8 ------------------------ # Do not modify or remove the line above. # Everything below it will be ignored. # # Conflicts: # packages/client/src/effect/api/api.ts # packages/client/src/effect/generated/client.ts # packages/client/src/promise/generated/client.ts # packages/client/src/promise/generated/types.ts # packages/client/test/promise.test.ts # packages/plugin/src/effect/plugin.ts # packages/plugin/src/promise/plugin.ts # packages/protocol/src/client.ts # services/www/src/docs/content/build/plugins/effect.mdx
In the SDK, experimental methods now live under
client.experimental, so you can tell them apart from stable ones.Today
client.session.stats()(experimental) sits right next toclient.session.get()(stable). URLs and wire formats don't change, so shipped apps and older CLIs keep working. Ships as a minor bump with deprecations, not removals: every old SDK method and type name still works as a@deprecatedalias of its new home until the next minor.❌ Before
/api/experimental/was generated into its normal group:client.session.stats(),client.voice.models(),client.mcp.connect().✅ After
/api/experimental/intoclient.experimental:client.experimental.session.stats(),client.experimental.voice.models(),client.experimental.mcp.connect().Experimentalprefix (ExperimentalSessionStatsOutput).experimental., matching the path (12 didn't:session.logand 11persistentPty.*).🕰️ Deprecated aliases, generated (removed in the next minor): a new codegen option,
compile(api, { aliases: true }), keeps every old path and type name as a@deprecatedalias of its new home. The alias is the same function object, and its JSDoc names the replacement and says "Removed in the next minor".packages/client/script/generate.tsturns it on; nothing is hand-written.client.session.{stats,import,export,skill,wait,log},client.session.instructions.entry.*,client.generate.text,client.voice.{models,session},client.migration.v1.status,client.usage.limits,client.integration.wellknown.add,client.mcp.{add,remove,connect,disconnect},client.mcp.client.token,client.file.{watch,unwatch},client.config.update, andclient.file.write(Promise client only).SessionWaitInput = ExperimentalSessionWaitInput). The Effect group interfaces (SessionApi,IntegrationApi,McpApi,FileApi,ConfigApi,AppApi) keep the old members, so code typed against them still compiles, and whole moved groups keep their names (GenerateApi,VoiceApi,MigrationApi,UsageApi).aliases: trueand regenerate.🧩 Plugin SDK: context names are unchanged (
ctx.generate,ctx.session.wait,ctx.session.instructionsstay where they are). Its types now point at theExperimental*SDK types, so plugins don't touch anything deprecated.🧹 Remove at next minor: the generated aliases and their tests.
client.session.{stats,import,export,skill,wait,log},client.session.instructions.entry.{list,put,remove},client.generate.{text,image,decisions},client.voice.{models,session},client.migration.v1.status,client.usage.limits,client.integration.wellknown.add,client.mcp.{add,remove,connect,disconnect},client.mcp.client.token,client.file.{watch,unwatch},client.config.update, andclient.file.write(Promise only). Each old path is nowclient.experimental.<same path>.SessionWaitInput, newExperimentalSessionWaitInput), plus the EffectGenerateApi,VoiceApi,MigrationApi,UsageApiand the PromiseGenerateApiinpackages/client/src/promise/api.ts.aliases: truefrom bothcompilecalls inpackages/client/script/generate.tsand runbun run generateinpackages/client; (2) deleteGenerateApifrompackages/client/src/promise/api.ts; (3) deletepackages/client/test/deprecated-aliases.test.tsand the alias entries inpackages/client/test/promise.test.ts; (4) drop theOmit<IntegrationApi, "wellknown">inpackages/plugin/src/{effect,promise}/integration.ts.🚢 Release: ship as a minor. The repo has no changesets; dispatch
release.ymlondevwithbump: minor.🟰 Behavior change: none on the wire, and no breaking change for SDK or plugin callers: old paths keep working and show as deprecated in editors. Only OpenAPI operation ids change for the 12 renamed endpoints; their generated SDK methods don't. All in-repo callers use the new paths (app, TUI, desktop, CLI, GUI extensions, server and SDK tests), including ones added on
devsince the PR opened (voice, file watch, media, instruction entries).📊 Proof: the tests are derived from real data, not a hand-kept list:
client.experimental⇔ experimental route; every other path to one is exactly the 24 known deprecated aliasesdeprecated-aliasestoBe) as itsclient.experimentalpath, Promise and Effect@deprecateddocs, call the experimental URL, off by default; collisions fail generationexperimental-identifiers/api/experimental⇔ id starts withexperimental.👀 Read:
packages/protocol/src/client.ts(clientNamespace) and, inpackages/httpapi-codegen/src/index.ts, the tree rendering plusaliases(renderAliasedClient,renderPromiseTypes,renderEffectShape). The rule goes by URL, not operation id, because the URL is what callers depend on. Name collisions anywhere in the tree, aliases included, fail generation. Some of these endpoints already back our own UI and may be worth promoting to stable later (session.stats/wait/export/import/log,usage.limits,voice.*,mcp.connect/disconnect/add,config.update,migration.v1.status,integration.wellknown.add); none are promoted here. The rest is generated code and caller renames.No user reports · brought up to date by merging
dev(the branch already used merges); generated files rebuilt with the repo's generatorsChecks run
bun run generateinpackages/protocolandpackages/client;bun run check:generatedin both: cleanbun test(packages/client): 241 passbun test(packages/httpapi-codegen): 103 passbun test test/plugin(packages/core): 439 pass (6 DigitalOcean provider tests fail locally on a missing localhost auth server; file unchanged fromdev, green in CI)bun turbo typecheck: 35/35 passAdded by the opencode bot (AI-generated).
Public API: adds client.experimental.* with generated @deprecated aliases at the old SDK paths and type names (backwards compatible until the next minor); plugin context unchanged; renames 12 experimental OpenAPI operationIds to experimental.*