opencode-anthropic-fix 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +53 -0
- package/README.md +72 -4
- package/docs/opencode-v2-architecture.md +148 -0
- package/docs/opencode-v2.md +198 -0
- package/index.mjs +13 -6608
- package/lib/accounts.mjs +46 -0
- package/lib/debug-dump.mjs +87 -0
- package/lib/host/command-args.mjs +18 -0
- package/lib/host/command-rpc.mjs +192 -0
- package/lib/host/runtime.mjs +6902 -0
- package/lib/host/tool-names.mjs +62 -0
- package/lib/host/v2-context.mjs +45 -0
- package/lib/host/v2-language.mjs +58 -0
- package/lib/host/v2-sdk.mjs +4 -0
- package/lib/host/v2-transport.mjs +374 -0
- package/lib/host/v2.mjs +305 -0
- package/lib/mimicry/request-body.mjs +31 -21
- package/lib/mimicry/request-helpers.mjs +6 -5
- package/lib/mimicry/response-stream.mjs +27 -22
- package/lib/oauth.mjs +29 -9
- package/lib/refresh-lock.mjs +18 -4
- package/lib/session-metrics.mjs +5 -8
- package/lib/unicode-text.mjs +9 -13
- package/licenses/Apache-2.0.txt +201 -0
- package/package.json +21 -1
- package/server.mjs +15 -0
- package/tui.mjs +93 -0
package/NOTICE
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
opencode-anthropic-fix
|
|
2
|
+
Copyright (C) 2025-2026 Marco Jardim and contributors.
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
7
|
+
version. The full text is in LICENSE.
|
|
8
|
+
|
|
9
|
+
This project is a fork of the original OpenCode Anthropic auth plugin by
|
|
10
|
+
AnomalyCo (https://github.com/anomalyco/opencode-anthropic-auth) and contains
|
|
11
|
+
modifications by the maintainers of this repository. GPL notices in this file,
|
|
12
|
+
in LICENSE, and in source headers must never be removed, including during a
|
|
13
|
+
dependency rollback.
|
|
14
|
+
|
|
15
|
+
Third-party dependencies
|
|
16
|
+
========================
|
|
17
|
+
|
|
18
|
+
@tormentalabs/claude-code-wire-compat
|
|
19
|
+
Runtime-neutral Claude Code Messages wire compatibility primitives.
|
|
20
|
+
License: GPL-3.0-or-later
|
|
21
|
+
Copyright (C) claude-code-wire-compat contributors.
|
|
22
|
+
Upstream: https://github.com/marco-jardim/claude-code-wire-compat
|
|
23
|
+
Distributed with its own LICENSE and NOTICE inside the published artifact.
|
|
24
|
+
|
|
25
|
+
This package builds the outgoing Anthropic Messages request. Its npm `latest`
|
|
26
|
+
specifier is resolved to an exact version and integrity hash by package-lock.json;
|
|
27
|
+
the protocol profile is explicitly pinned in lib/mimicry/wire-compat.mjs.
|
|
28
|
+
Provenance and rollback instructions are in docs/shared-package-provenance.md.
|
|
29
|
+
|
|
30
|
+
@ai-sdk/anthropic (3.0.111)
|
|
31
|
+
@ai-sdk/provider
|
|
32
|
+
@ai-sdk/provider-utils
|
|
33
|
+
AI SDK Anthropic provider and supporting interfaces/utilities.
|
|
34
|
+
License: Apache-2.0
|
|
35
|
+
Upstream: https://github.com/vercel/ai
|
|
36
|
+
Copyright 2023 Vercel, Inc. (from the provider packages' notices).
|
|
37
|
+
The complete Apache-2.0 text is supplied in licenses/Apache-2.0.txt.
|
|
38
|
+
Transitive versions are recorded in package-lock.json. The provider-utils
|
|
39
|
+
package also contains an ISC-licensed zod3-to-json-schema implementation;
|
|
40
|
+
its vendored copyright notice and license are included in standalone builds.
|
|
41
|
+
The Anthropic provider is used by the OpenCode v2 compatibility adapter.
|
|
42
|
+
|
|
43
|
+
The npm source package publishes its runtime entrypoints, lib/**/*.mjs, user
|
|
44
|
+
documentation, licenses/, LICENSE and this NOTICE. Its dependencies retain their own
|
|
45
|
+
license files in node_modules; development-only dependencies are not included
|
|
46
|
+
by the package's files allowlist.
|
|
47
|
+
|
|
48
|
+
Standalone builds inline runtime dependencies. scripts/build.mjs collects
|
|
49
|
+
the dependency packages actually present in esbuild's input metadata and
|
|
50
|
+
includes their available LICENSE/NOTICE files and the complete Apache-2.0
|
|
51
|
+
text in THIRD_PARTY_NOTICES. That
|
|
52
|
+
document ships beside the dual-host bundle and is also embedded as comments
|
|
53
|
+
in each standalone JavaScript file so legacy single-file copies retain it.
|
package/README.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Use your Claude Pro or Max subscription with [OpenCode](https://github.com/anomalyco/opencode). Supports multiple accounts with automatic rotation when you hit rate limits.
|
|
4
4
|
|
|
5
|
+
## Supported Hosts
|
|
6
|
+
|
|
7
|
+
The plugin supports **both OpenCode v1 (1.x) and OpenCode v2 (2.x)** through separate host entries sharing the same OAuth, account rotation, and request runtime.
|
|
8
|
+
|
|
9
|
+
| Host | Supported entry and validated versions |
|
|
10
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| OpenCode v1 (1.x) | Legacy `index.mjs` factory and the recent-v1 `server()` entry. Host loading and registration tested on **1.2.27, 1.18.29, and 1.18.34**. |
|
|
12
|
+
| OpenCode v2 (2.x) | `setup()` server adapter and `./tui` entry. The compatibility target starts at **2.0.21**, with **2.0.21 and 2.0.22** tested. |
|
|
13
|
+
|
|
14
|
+
These are validated versions, not a claim that every intervening or newer host release has been tested. The v2 smoke tests use a local Anthropic simulator; real OAuth login, real Anthropic inference, and interactive TUI rendering still require manual validation. See the [v2 compatibility guide](docs/opencode-v2.md) for the exact scope and limitations.
|
|
15
|
+
|
|
5
16
|
## Background
|
|
6
17
|
|
|
7
18
|
[AnomalyCo](https://github.com/anomalyco/opencode-anthropic-auth) originally published this as a built-in plugin shipped with OpenCode itself. The repo was archived following a legal request from Anthropic. Before it was archived, [rmk40](https://github.com/rmk40/opencode-anthropic-auth) — a frequent contributor unrelated to AnomalyCo — had developed multi-account support that never made it upstream.
|
|
@@ -32,6 +43,9 @@ If a document or comment claims this plugin branches on the provider, that claim
|
|
|
32
43
|
|
|
33
44
|
**Prerequisites:** [OpenCode](https://github.com/anomalyco/opencode) installed, a Claude Pro or Max subscription, Node.js 18+.
|
|
34
45
|
|
|
46
|
+
The commands below install the **OpenCode v1** entry. For **OpenCode v2**, use the
|
|
47
|
+
[v2 package installation](#opencode-v2-package-installation) below instead.
|
|
48
|
+
|
|
35
49
|
```bash
|
|
36
50
|
# 1. Clone and install
|
|
37
51
|
git clone https://github.com/marco-jardim/opencode-anthropic-fix.git
|
|
@@ -88,7 +102,15 @@ The [original plugin](https://github.com/anomalyco/opencode-anthropic-auth) prov
|
|
|
88
102
|
|
|
89
103
|
### From npm (recommended)
|
|
90
104
|
|
|
91
|
-
|
|
105
|
+
For OpenCode v1, add to your `opencode.json`:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"plugin": ["opencode-anthropic-fix@latest"]
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
For OpenCode v2 (tested on 2.0.21 and 2.0.22), use the `plugins` key:
|
|
92
114
|
|
|
93
115
|
```json
|
|
94
116
|
{
|
|
@@ -98,7 +120,53 @@ Add to your `opencode.json`:
|
|
|
98
120
|
|
|
99
121
|
OpenCode will install and load the plugin automatically on next start.
|
|
100
122
|
|
|
101
|
-
|
|
123
|
+
The `./server` and `./tui` exports let modern hosts select their entrypoints;
|
|
124
|
+
older v1 loaders retain `index.mjs`. OpenCode v2 loads the package's `./tui` entry
|
|
125
|
+
alongside its server entry; do not install `tui.mjs` as a legacy standalone plugin.
|
|
126
|
+
The v2 `/anthropic` command calls the server through RPC and shows results in
|
|
127
|
+
a dialog and does not add administrative output or OAuth codes to model history.
|
|
128
|
+
See [the v2 guide](docs/opencode-v2.md) for the explicit package installer,
|
|
129
|
+
programmatic RPC interface, cancellation behavior and current validation limits.
|
|
130
|
+
|
|
131
|
+
The explicit package installer (`node scripts/install.mjs link --host=v2`,
|
|
132
|
+
`copy --host=v2`, or `uninstall --host=v2`) only replaces or removes CLI entries
|
|
133
|
+
it owns: links into its installed package or wrappers with its installer marker.
|
|
134
|
+
It refuses unrelated CLI files and symlinks before changing the package. Add
|
|
135
|
+
`--force` to deliberately replace/remove a foreign CLI entry, for example:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
node scripts/install.mjs copy --host=v2 --force
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`--force` never deletes CLI directories or unmanaged package directories and is
|
|
142
|
+
only supported with `--host=v2`.
|
|
143
|
+
|
|
144
|
+
### OpenCode v2 Package Installation
|
|
145
|
+
|
|
146
|
+
From a checkout with dependencies installed, choose a development link or a standalone copy:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Development: link the dual-host package (directory junction on Windows)
|
|
150
|
+
node scripts/install.mjs link --host=v2
|
|
151
|
+
|
|
152
|
+
# Or: build and copy the standalone package, including server and TUI entries
|
|
153
|
+
npm run build
|
|
154
|
+
node scripts/install.mjs copy --host=v2
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Both modes install under `$XDG_CONFIG_HOME/opencode/node_modules/opencode-anthropic-fix`,
|
|
158
|
+
falling back to `~/.config/opencode/node_modules/opencode-anthropic-fix`, rather than
|
|
159
|
+
the legacy `plugin/` discovery directory. The copy needs no dependencies installed
|
|
160
|
+
at its destination. The installer prints the exact `plugins` configuration to add
|
|
161
|
+
to `opencode.json` and does not change your configuration. Remove old standalone
|
|
162
|
+
plugin entries it reports before enabling the package to avoid duplicate loading.
|
|
163
|
+
Restart OpenCode after installing or changing the plugin.
|
|
164
|
+
|
|
165
|
+
To uninstall the managed v2 package, run `node scripts/install.mjs uninstall --host=v2`
|
|
166
|
+
and remove its `plugins` configuration entry. See [the v2 guide](docs/opencode-v2.md)
|
|
167
|
+
for RPC commands, transport details, and validation limits.
|
|
168
|
+
|
|
169
|
+
### OpenCode v1 Development (symlink)
|
|
102
170
|
|
|
103
171
|
Best for active development. Edits to source files take effect immediately.
|
|
104
172
|
|
|
@@ -111,7 +179,7 @@ This creates:
|
|
|
111
179
|
- **Plugin:** `~/.config/opencode/plugin/opencode-anthropic-auth-plugin.js` → `./index.mjs`
|
|
112
180
|
- **CLI:** `~/.local/bin/opencode-anthropic-auth` → `./cli.mjs`
|
|
113
181
|
|
|
114
|
-
### Stable (copy)
|
|
182
|
+
### OpenCode v1 Stable (copy)
|
|
115
183
|
|
|
116
184
|
Bundles the plugin and CLI into self-contained single files (via esbuild) and copies them. No symlinks, no `node_modules` needed at the destination.
|
|
117
185
|
|
|
@@ -124,7 +192,7 @@ This creates:
|
|
|
124
192
|
- **Plugin:** `~/.config/opencode/plugin/opencode-anthropic-auth-plugin.js` (standalone, ~50KB)
|
|
125
193
|
- **CLI:** `~/.local/bin/opencode-anthropic-auth` (standalone, ~35KB)
|
|
126
194
|
|
|
127
|
-
### Uninstall
|
|
195
|
+
### OpenCode v1 Uninstall
|
|
128
196
|
|
|
129
197
|
```bash
|
|
130
198
|
npm run uninstall
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Decision: dual host adapters with one OAuth executor
|
|
2
|
+
|
|
3
|
+
## Context and decision
|
|
4
|
+
|
|
5
|
+
OpenCode 2.0.21 uses a definition object, registration APIs, a separate TUI
|
|
6
|
+
plugin, and different provider/message schemas. The existing v1 factory cannot
|
|
7
|
+
be loaded unchanged by that host. Preserve `index.mjs` as the legacy factory;
|
|
8
|
+
expose `server.mjs` with v1 `server` and v2 `setup`, and a separate `tui.mjs`.
|
|
9
|
+
|
|
10
|
+
Move the existing implementation into `createAnthropicRuntime()` rather than
|
|
11
|
+
maintaining two OAuth pools or wire implementations. Mutable metrics, config,
|
|
12
|
+
retry state, refresh flights and telemetry belong to each runtime. Disposal
|
|
13
|
+
aborts requests, refresh waits and backoff, and flushes pending account writes.
|
|
14
|
+
Disk account format and the cross-process refresh lock remain shared.
|
|
15
|
+
|
|
16
|
+
## Transport options
|
|
17
|
+
|
|
18
|
+
The native HTTP hooks do not expose a continuation that can own the existing
|
|
19
|
+
multi-account retry loop. Directly assigning a custom fetch to provider or model
|
|
20
|
+
settings was also rejected by the real 2.0.21 host: its overlay decoder requires
|
|
21
|
+
JSON values and fails on `settings.fetch`. A mocked hook test cannot detect this
|
|
22
|
+
constraint, which is why the binary smoke is part of validation.
|
|
23
|
+
|
|
24
|
+
Use the package spec `aisdk:@ai-sdk/anthropic@3.0.111` with a process-local HTTP
|
|
25
|
+
bridge. The versioned spec prevents the host from rewriting the unversioned
|
|
26
|
+
Anthropic SDK to its native provider, while remaining resolvable by the host's
|
|
27
|
+
built-in dynamic package loader. The
|
|
28
|
+
public `http.request` hook associates a request with its session and redirects
|
|
29
|
+
it to the bridge. A single-use random ticket identifies the original request;
|
|
30
|
+
the bridge invokes the existing executor and streams its response to the host.
|
|
31
|
+
No function is stored in provider/model settings. The SDK hook retains the
|
|
32
|
+
host's middleware fetch, and the executor remains the only retry owner for
|
|
33
|
+
managed OAuth requests. Other providers and explicitly selected foreign
|
|
34
|
+
connections retain their own settings.
|
|
35
|
+
|
|
36
|
+
The pin also makes Core's option/metadata key `anthropic@3.0.111`, whereas the
|
|
37
|
+
SDK reads and emits `anthropic`. A language-model boundary remaps incoming
|
|
38
|
+
provider options (including replay parts) and outgoing stream/non-stream metadata
|
|
39
|
+
(including reasoning signatures and finish metadata). An unversioned specifier
|
|
40
|
+
is not safe: `aisdk-native.ts:resolve` rewrites even the explicit `aisdk:` route
|
|
41
|
+
to the native provider. The host's dynamic SDK hook may install the pinned
|
|
42
|
+
package first; our later hook replaces that SDK with the plugin's pinned copy.
|
|
43
|
+
|
|
44
|
+
The bridge binds only to IPv4 loopback on an ephemeral port. It accepts only
|
|
45
|
+
issued request tickets, never accepts a caller-supplied destination, and strips
|
|
46
|
+
its private correlation data before the executor. Plugin unload closes it.
|
|
47
|
+
The public session-interruption event cancels pending requests because the
|
|
48
|
+
2.0.21 AI SDK call does not itself receive the host's abort signal. Losing that
|
|
49
|
+
event subscription aborts active requests and rejects new requests.
|
|
50
|
+
|
|
51
|
+
This is immediate cancellation for primary execution events, not a guarantee
|
|
52
|
+
for auxiliary fiber interruption. 2.0.22 and inspected HEAD `f74512b` still omit
|
|
53
|
+
the interruption signal from `doStream`. Supplied SDK signals and loopback
|
|
54
|
+
disconnects propagate upstream. Otherwise `generate` and `title` tickets have a
|
|
55
|
+
10-minute total deadline, including pre-header waits and streaming, to bound
|
|
56
|
+
orphaned requests. Slow primary/compaction streams are exempt. Upstream must pass
|
|
57
|
+
the Effect interruption signal to `doStream` as `abortSignal` for immediate
|
|
58
|
+
auxiliary cancellation; no plugin hook currently exposes that fiber lifetime.
|
|
59
|
+
|
|
60
|
+
Managed provider settings disable `timeout`, `headerTimeout` and `chunkTimeout`.
|
|
61
|
+
2.0.22 already has HEAD's 300-second default header/chunk limits. These conflict
|
|
62
|
+
with an executor that bounds attempts but not total wall time (uncapped
|
|
63
|
+
Retry-After waits and repeated transient 429s). The auxiliary deadline remains
|
|
64
|
+
independent of those host defaults. 2.0.21 ignores the new header option and
|
|
65
|
+
uses `chunkTimeout: 0`, because its public schema rejects `false` for that field
|
|
66
|
+
(its fetch wrapper activates the timer only for positive numbers).
|
|
67
|
+
|
|
68
|
+
## Credentials, commands and history
|
|
69
|
+
|
|
70
|
+
The plugin registers its own OAuth method ID. Host credential values are copies;
|
|
71
|
+
the pool on disk is authoritative for rotation, removal and refresh. A foreign
|
|
72
|
+
OAuth method or an explicit API-key/environment connection must not be replaced.
|
|
73
|
+
Only managed models receive zero costs across every tier and configured limits.
|
|
74
|
+
|
|
75
|
+
Administrative commands use a typed RPC definition and a local TUI slash layer.
|
|
76
|
+
They return bounded, sanitized dialog content; no prompt endpoint carries the
|
|
77
|
+
arguments or output. The server command name is reserved and returns guidance
|
|
78
|
+
for clients that have no administrative result channel.
|
|
79
|
+
|
|
80
|
+
V2 message policies operate on a temporary view and replace only pruned results.
|
|
81
|
+
Tool aliases are request-local and reversible. Summary compaction is selected
|
|
82
|
+
explicitly. Normal host history loading skips incompatible native checkpoints
|
|
83
|
+
and re-expands the original transcript; a new session is not required, and no
|
|
84
|
+
history is corrupted, although context can grow substantially. The defensive
|
|
85
|
+
adapter guard rejects opaque checkpoint parts only if explicitly passed to it.
|
|
86
|
+
The optional Haiku rolling-summary hook remains a v1
|
|
87
|
+
fork capability and emits a diagnostic when configured on v2.
|
|
88
|
+
|
|
89
|
+
## Consequences and validation boundary
|
|
90
|
+
|
|
91
|
+
One extra pinned production dependency is required for a known parser/provider
|
|
92
|
+
contract. The standalone dual-package build bundles it; the host's dynamic
|
|
93
|
+
provider loader may also populate its own cache on first use. The loopback bridge adds
|
|
94
|
+
one local streaming hop; disposal, backpressure, request authentication,
|
|
95
|
+
interruption and non-replay after streamed bytes must be covered by tests.
|
|
96
|
+
|
|
97
|
+
Keep regression tests for the v1 exports and wire fixtures, deterministic v2
|
|
98
|
+
SDK/transport tests, isolated installer and bundle tests, and real host smoke
|
|
99
|
+
scripts. Smoke runs use temporary configuration roots and fake credentials.
|
|
100
|
+
They prove host integration; they do not prove an authenticated Anthropic session
|
|
101
|
+
or future host releases. See [support and usage](opencode-v2.md) for the recorded
|
|
102
|
+
validation matrix.
|
|
103
|
+
|
|
104
|
+
## Host-contract review sources
|
|
105
|
+
|
|
106
|
+
Inspected `v2.0.21`, `v2.0.22` and upstream `f74512b` without changing the host:
|
|
107
|
+
|
|
108
|
+
- **Options and SDK loading:** `packages/core/src/provider.ts:30-34` strips
|
|
109
|
+
only `aisdk:`. In `v2.0.21`, `aisdk.ts:429-442` derives the option key,
|
|
110
|
+
`:362-367` assigns the same metadata key and `:690-700` nests request options.
|
|
111
|
+
`session/runner/publish-llm-event.ts:120,421-436` persists reasoning state only
|
|
112
|
+
under that key. `aisdk-native.ts:57,135-143` maps the unversioned Anthropic SDK
|
|
113
|
+
to the native provider, including explicit `aisdk:` specs. This behavior is
|
|
114
|
+
unchanged in the inspected 2.0.22/HEAD sources. `aisdk.ts:280-288,324-347` runs
|
|
115
|
+
SDK hooks in registration order and caches their final SDK; the dynamic hook
|
|
116
|
+
(`plugin/provider/dynamic.ts:10-15`) installs only when `evt.sdk` is empty.
|
|
117
|
+
Installation is inside that hook, not an independent prerequisite:
|
|
118
|
+
`plugin/provider/sdk-factory.ts:5-16` calls `npm.add(packageName)` and resolves
|
|
119
|
+
`installed.name` in its installed directory. With the built-in hook first it
|
|
120
|
+
can install before our replacement, so retaining the resolvable pin matters.
|
|
121
|
+
`plugin/internal.ts:213-226` puts provider plugins in the pre-list;
|
|
122
|
+
`plugin/supervisor.ts:138-155` places that list before external plugins.
|
|
123
|
+
- **Cancellation:** `session.ts@v2.0.21:405-411` invokes auxiliary generation
|
|
124
|
+
directly, not a primary execution. `aisdk.ts@v2.0.21:715-718`,
|
|
125
|
+
`@v2.0.22:733-736` and `@f74512b:729-732` call `language.doStream(options)`
|
|
126
|
+
without the `Effect.tryPromise` signal. Auxiliary generation also uses this
|
|
127
|
+
streaming route, not a separate `doGenerate` cancellation path.
|
|
128
|
+
- **Timeouts:** `provider.ts@v2.0.22:149-164` (HEAD `:151-166`) supplies the
|
|
129
|
+
300,000 ms defaults; `aisdk.ts@v2.0.22:130-170` applies them around the
|
|
130
|
+
middleware fetch. `model-resolver.ts:373` merges provider settings after
|
|
131
|
+
removing model/variant transport settings. `packages/schema/src/provider.ts@v2.0.21:49-50`
|
|
132
|
+
permits `timeout: false` but only numeric `chunkTimeout`; `aisdk.ts:137-149`
|
|
133
|
+
treats zero as disabled. The executor's real budgets are in
|
|
134
|
+
`lib/host/runtime.mjs:2873,2912-2928,4278-4312,4393-4448`, not a total five-minute
|
|
135
|
+
deadline; `lib/backoff.mjs:11` caps account cooldowns, not whole requests.
|
|
136
|
+
- **History:** `session/history.ts@v2.0.21:23-26,99-107` filters incompatible
|
|
137
|
+
checkpoints and retains the original transcript for re-expansion.
|
|
138
|
+
|
|
139
|
+
The 2.0.21 → 2.0.22 diff contains no change to the consumed loader, OAuth
|
|
140
|
+
methods, SDK-hook, RPC or TUI-command interfaces. The plugin Promise/Effect
|
|
141
|
+
session APIs add `remove` and `compact`; the Core host adds parent-aware session
|
|
142
|
+
creation and metadata updates. Model capability overlays now merge instead of
|
|
143
|
+
replacing fields. None requires an adapter change here. The transport timeout
|
|
144
|
+
defaults and the expanded timeout schema are the relevant breaking behavior;
|
|
145
|
+
the new bounded host timeout retry policy does not supersede our managed retry
|
|
146
|
+
hook. Core also preserves HTTP options when rebuilding a session request and
|
|
147
|
+
adds content-filter explanations to published errors; neither changes our hook
|
|
148
|
+
shapes. Provider-specific Azure/DigitalOcean changes do not affect this route.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# OpenCode v2 adapter
|
|
2
|
+
|
|
3
|
+
## Validation recorded on 2026-09-30
|
|
4
|
+
|
|
5
|
+
Local validation used Windows and Node 24.21.0, plus each official OpenCode
|
|
6
|
+
release's own runtime. Host binaries were downloaded from npm with SHA512
|
|
7
|
+
integrity verification. All smoke configurations and credentials were temporary.
|
|
8
|
+
|
|
9
|
+
| Host | Result | Scope |
|
|
10
|
+
| ------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| 1.2.27 | Passed | Legacy loader, OAuth registration, command registration, 23 OAuth models with zero costs. |
|
|
12
|
+
| 1.18.29 | Passed | Dual entry, OAuth registration, one administrative command, 16 OAuth models with zero costs. |
|
|
13
|
+
| 1.18.34 | Passed | Dual entry and public provider model hook, OAuth/command registration, 19 OAuth models with zero costs. |
|
|
14
|
+
| 2.0.21 | Passed | Server load, OAuth registration, 19 models, RPC without model invocation, chat, auxiliary generation, title, summary compaction, cancellation before response headers and plugin reload. |
|
|
15
|
+
| 2.0.22 | Passed | Same local Windows smoke scope as 2.0.21, including managed timeout settings and plugin reload. |
|
|
16
|
+
|
|
17
|
+
The original complete automated suite passed **2,286 tests across 113 files**, with two
|
|
18
|
+
platform skips on Windows. Build, ESLint, formatting and invariant checks passed;
|
|
19
|
+
the invariant check retains the existing warning about differing reverse-engineering
|
|
20
|
+
documentation baselines. Installer and standalone package tests run in temporary
|
|
21
|
+
directories. The v2 host smoke uses a local Anthropic simulator and blocks direct
|
|
22
|
+
Anthropic API access. The v1 host smoke checks loading and registration, not inference.
|
|
23
|
+
|
|
24
|
+
The v2 host-contract review reran both 2.0.21 and 2.0.22 Windows smokes with the
|
|
25
|
+
option/metadata adapter and host timeout fixes. Auxiliary orphan bounds are
|
|
26
|
+
tested with interrupted Effect fibers and loopback HTTP, not claimed as immediate
|
|
27
|
+
auxiliary cancellation by those host binaries.
|
|
28
|
+
|
|
29
|
+
Review verification: `npm test` and `npm run coverage` passed **2,057 tests in
|
|
30
|
+
103 files**, with the same two platform skips. Coverage thresholds were unchanged;
|
|
31
|
+
the new language adapter had 100% statement/branch/function/line coverage.
|
|
32
|
+
ESLint, formatting, build and invariants passed (the existing baseline warning
|
|
33
|
+
remains). Negative controls temporarily removed the language wrapper, auxiliary
|
|
34
|
+
deadline and timeout settings and restored the inaccurate checkpoint wording:
|
|
35
|
+
six regression cases failed, then passed after restoring the fixes.
|
|
36
|
+
|
|
37
|
+
Run a pinned host again with `node scripts/run-host-smoke.mjs 2.0.21` or
|
|
38
|
+
`node scripts/run-host-smoke.mjs 2.0.22` (or one of the v1 versions above).
|
|
39
|
+
CI runs these five hosts on Linux and Windows; the
|
|
40
|
+
Linux jobs have not been run locally. Real OAuth login, real Anthropic inference
|
|
41
|
+
and interactive TUI rendering still require manual validation before release.
|
|
42
|
+
The TUI behavior is covered by deterministic tests, and its RPC runs in the real
|
|
43
|
+
v2 server smoke. See the [architecture decision](opencode-v2-architecture.md).
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
The npm package exposes separate `./server` and `./tui` entries for recent v1
|
|
48
|
+
and v2 hosts, and keeps the root `index.mjs` entry for legacy v1 loaders. Existing
|
|
49
|
+
`index.mjs`, `cli.mjs` and `lib/*` imports remain available. Use the v2 `plugins`
|
|
50
|
+
configuration key when loading the package in OpenCode 2.0.21.
|
|
51
|
+
|
|
52
|
+
For development or a standalone bundle, run:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
node scripts/install.mjs link --host=v2
|
|
56
|
+
# or
|
|
57
|
+
npm run build
|
|
58
|
+
node scripts/install.mjs copy --host=v2
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The explicit v2 mode installs the package in
|
|
62
|
+
`$XDG_CONFIG_HOME/opencode/node_modules/opencode-anthropic-fix`, falling back to
|
|
63
|
+
`~/.config/opencode/node_modules/opencode-anthropic-fix`. Development links use
|
|
64
|
+
a directory junction on Windows. The copy contains bundled server, TUI, RPC,
|
|
65
|
+
legacy plugin and CLI entries and requires no `npm install` at its destination.
|
|
66
|
+
The copy also contains a local SDK factory; initializing a managed model does
|
|
67
|
+
not resolve or download another AI SDK package into the host's cache.
|
|
68
|
+
The installer prints the exact configuration to add and leaves existing
|
|
69
|
+
configuration untouched. It reports old standalone plugin entries that could
|
|
70
|
+
cause duplicate loading; remove those entries before enabling the new package.
|
|
71
|
+
|
|
72
|
+
Use `node scripts/install.mjs uninstall --host=v2` to remove a managed package.
|
|
73
|
+
The installer refuses to replace or remove an unrecognized package directory or
|
|
74
|
+
one with additional files. Without `--host=v2`, installation keeps the legacy
|
|
75
|
+
standalone v1 behavior.
|
|
76
|
+
|
|
77
|
+
## Transport and version boundary
|
|
78
|
+
|
|
79
|
+
The adapter targets the public OpenCode **2.0.21 and 2.0.22** contracts. It routes managed
|
|
80
|
+
Anthropic models through `aisdk:file://…/v2-sdk.mjs`;
|
|
81
|
+
the shipped factory and public SDK hook use the pinned `@ai-sdk/anthropic`
|
|
82
|
+
**3.0.111** provider. The local specifier prevents the native-provider rewrite
|
|
83
|
+
and avoids a redundant registry install by the host's built-in dynamic hook,
|
|
84
|
+
which runs **before** external plugin hooks. This is also true in the standalone
|
|
85
|
+
bundle, where `v2-sdk.mjs` is self-contained. Core now uses `anthropic` for both
|
|
86
|
+
options and persisted metadata, matching the SDK. The language adapter still
|
|
87
|
+
accepts the former `anthropic@3.0.111` input key for signed reasoning replay.
|
|
88
|
+
See [the cold-initialization investigation](opencode-v2-sdk-initialization.md)
|
|
89
|
+
for the host npm failure, mitigation, negative control and repeated smoke results.
|
|
90
|
+
The shared plugin executor keeps account rotation,
|
|
91
|
+
wire construction, backoff and the response compatibility shim on the same
|
|
92
|
+
path as v1. Explicitly selected API-key or unrelated OAuth connections retain
|
|
93
|
+
their own provider settings. A connection owned by this plugin is disabled
|
|
94
|
+
when its account pool has no enabled account; it cannot fall back to a stale
|
|
95
|
+
credential cached by the host.
|
|
96
|
+
|
|
97
|
+
OpenCode 2.0.21 accepts only JSON data in provider/model settings, so an injected
|
|
98
|
+
`settings.fetch` function is unavailable. On first use, the adapter opens an
|
|
99
|
+
HTTP bridge bound to `127.0.0.1` on an ephemeral port. The public HTTP request
|
|
100
|
+
hook redirects the host request to that bridge with a single-use random ticket.
|
|
101
|
+
The bridge retrieves the original destination from its own ticket map, invokes
|
|
102
|
+
the shared executor and streams the response back. It does not accept an
|
|
103
|
+
arbitrary target URL from incoming HTTP requests. The host's normal HTTP
|
|
104
|
+
middleware remains in place; no functions are stored in model settings.
|
|
105
|
+
|
|
106
|
+
Neither host forwards its fiber cancellation signal to the AI SDK call (also
|
|
107
|
+
verified in upstream revision `f74512b`).
|
|
108
|
+
The adapter therefore associates tickets with session IDs and listens
|
|
109
|
+
for the public `session.execution.interrupted` event. Interrupting a session or
|
|
110
|
+
unloading the plugin aborts its requests; a lost event stream aborts outstanding
|
|
111
|
+
requests and prevents new ones. A disconnected loopback request also cancels
|
|
112
|
+
its upstream operation. An SDK `abortSignal`, when supplied by a caller, reaches
|
|
113
|
+
the upstream through that disconnect path. **Auxiliary `session.generate` fiber
|
|
114
|
+
interruption alone does not emit a primary-execution event or disconnect HTTP.**
|
|
115
|
+
The bridge bounds `generate` and `title` requests to **10 minutes total**, starting
|
|
116
|
+
at the HTTP hook, including retries and streaming. This is an orphan-work bound,
|
|
117
|
+
not immediate cancellation; a legitimately long auxiliary call can also hit it.
|
|
118
|
+
Primary and compaction streams do not have this auxiliary deadline. Immediate
|
|
119
|
+
auxiliary cancellation requires upstream to pass the Effect interruption signal
|
|
120
|
+
as `abortSignal` into `doStream` and preserve it through fetch/middleware.
|
|
121
|
+
The correlation header is removed before the shared
|
|
122
|
+
executor sends a request to Anthropic. Plugin cleanup closes the local listener.
|
|
123
|
+
|
|
124
|
+
OpenCode 2.0.22 (and inspected HEAD) defaults `headerTimeout` and `chunkTimeout`
|
|
125
|
+
to 300,000 ms. Managed provider settings set those and `timeout` to `false` so
|
|
126
|
+
host timers cannot cut off the shared executor's retry/rotation waits. The
|
|
127
|
+
executor has attempt budgets, not a finite total retry deadline: account slots
|
|
128
|
+
are bounded by the pool, service errors normally get two retries, forced
|
|
129
|
+
`x-should-retry` normally gets three, but Retry-After waits and repeated transient
|
|
130
|
+
429s can exceed five minutes. Primary cancellation/disconnect/disposal remain
|
|
131
|
+
available; auxiliary work has the separate bound above. 2.0.21 ignores the new
|
|
132
|
+
header setting; its number-only chunk schema requires `chunkTimeout: 0` to
|
|
133
|
+
disable that timer, while `timeout: false` is accepted on both hosts.
|
|
134
|
+
|
|
135
|
+
The compatibility route uses summary compaction. When switching from a native
|
|
136
|
+
provider, the host skips incompatible native checkpoints and re-expands the
|
|
137
|
+
original transcript (`session/history.ts:23-26,99-107` at `v2.0.21`). A new session
|
|
138
|
+
is not required and history is not corrupted, but context may grow substantially.
|
|
139
|
+
The adapter's rejection guard applies only if opaque checkpoint parts are
|
|
140
|
+
explicitly supplied to it; it does not describe normal host history loading.
|
|
141
|
+
Validation against other OpenCode versions is required before widening the
|
|
142
|
+
support claim. Host smoke tests exercise boot, RPC, streaming
|
|
143
|
+
generation and cancellation using a local mock upstream. They do not constitute
|
|
144
|
+
an authenticated end-to-end certification against Anthropic.
|
|
145
|
+
|
|
146
|
+
## Administrative commands and the TUI
|
|
147
|
+
|
|
148
|
+
On OpenCode v2, the `./tui` entry registers `/anthropic` as a local TUI command.
|
|
149
|
+
The TUI calls the server through the plugin's RPC method and shows the result in
|
|
150
|
+
a dialog. Failures appear as toast notifications. Output is not inserted into
|
|
151
|
+
the conversation and the command does not submit a model prompt. OpenCode v1
|
|
152
|
+
continues to use the existing server slash-command hook.
|
|
153
|
+
|
|
154
|
+
Run the usual commands, for example `/anthropic usage`, `/anthropic switch 2`,
|
|
155
|
+
or `/anthropic set debug true`. OAuth keeps the same two-step flow:
|
|
156
|
+
|
|
157
|
+
1. Run `/anthropic login` or `/anthropic reauth 1` and open the authorization URL.
|
|
158
|
+
2. Complete authorization in the browser.
|
|
159
|
+
3. Run `/anthropic login complete <code#state>` or
|
|
160
|
+
`/anthropic reauth complete <code#state>` in the same session.
|
|
161
|
+
|
|
162
|
+
When invoked from the home screen, the TUI creates an empty session before
|
|
163
|
+
running the command. That session holds the pending OAuth flow; it contains no
|
|
164
|
+
administrative output or authorization code. Commands entered through the
|
|
165
|
+
plugin's TUI layer are intercepted before the host's server command dispatch.
|
|
166
|
+
|
|
167
|
+
The RPC method is available to other OpenCode v2 clients. Use an existing
|
|
168
|
+
session and the location where the plugin is loaded:
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
import { anthropicCommandRpc } from "opencode-anthropic-fix/rpc";
|
|
172
|
+
|
|
173
|
+
const commands = client.rpc(anthropicCommandRpc);
|
|
174
|
+
const { output } = await commands.execute(
|
|
175
|
+
{ sessionID, arguments: "usage" },
|
|
176
|
+
{ location, signal: abortController.signal },
|
|
177
|
+
);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The server also reserves the `anthropic` slash-command name. Clients that call
|
|
181
|
+
the server command endpoint directly receive an error directing them to the TUI
|
|
182
|
+
or RPC, because that endpoint cannot return administrative output. This avoids
|
|
183
|
+
starting an OAuth flow whose URL could not be displayed and prevents command
|
|
184
|
+
arguments from falling through to a model prompt.
|
|
185
|
+
|
|
186
|
+
The RPC ID is `opencode-anthropic-fix`; its method is `execute`. Input is
|
|
187
|
+
`{ sessionID, arguments }` and successful output is `{ output }`. The server
|
|
188
|
+
verifies that the session exists before executing a command. Arguments must be
|
|
189
|
+
a single CLI-style line of at most 8,192 characters. Responses are bounded to
|
|
190
|
+
65,536 UTF-16 code units, with grapheme-safe truncation. The server removes
|
|
191
|
+
terminal escape sequences, token-shaped credentials and submitted OAuth
|
|
192
|
+
completion codes from responses and errors. Authorization URLs remain intact.
|
|
193
|
+
|
|
194
|
+
The plugin declares `command_failed` and `invalid_session` RPC errors. The host
|
|
195
|
+
can additionally return its standard `rpc.*` errors for unavailable services or
|
|
196
|
+
invalid requests. Unloading the TUI aborts outstanding RPC calls; disposing the
|
|
197
|
+
server registration propagates cancellation to its command executor and removes
|
|
198
|
+
the method. Neither RPC nor TUI requires an additional runtime SDK dependency.
|