opencode-muse-auth 0.4.0 → 0.5.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/ADAPTER_CONTRACT.md +132 -0
- package/AUTH_CONTRACT.md +11 -3
- package/DISCLAIMER.md +13 -0
- package/LICENSE +1 -1
- package/README.md +150 -52
- package/dist/adapter.d.ts +97 -0
- package/dist/adapter.js +554 -0
- package/dist/auth.js +20 -9
- package/dist/index.d.ts +15 -0
- package/dist/index.js +133 -57
- package/package.json +12 -11
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Muse wire adapter contract (v3)
|
|
2
|
+
|
|
3
|
+
Every Muse Code plugin in this family sends subscription inference through the same local
|
|
4
|
+
adapter. This file pins the wire rules; `ADAPTER_VERSION` in the code is the matching constant.
|
|
5
|
+
Change both together in all four repositories:
|
|
6
|
+
|
|
7
|
+
- `opencode-muse-auth`
|
|
8
|
+
- `hermes-muse-code`
|
|
9
|
+
- `openclaw-muse-code`
|
|
10
|
+
- `dsh-muse-code`
|
|
11
|
+
|
|
12
|
+
## Why the adapter exists
|
|
13
|
+
|
|
14
|
+
Since 2026-10-29/30, `https://api.meta.ai/v1` refuses a Muse Code subscription credential when
|
|
15
|
+
the request declares a flat function tool whose name is exactly one of Muse Code's builtin tool
|
|
16
|
+
names. The refusal is HTTP 403 with `code: client_attestation_denied`:
|
|
17
|
+
|
|
18
|
+
> This API key is billed to a Muse Code subscription. Muse Code subscriptions cover requests from
|
|
19
|
+
> the Muse Code client only. Use it in Muse Code, or use a pay-as-you-go API key for other clients.
|
|
20
|
+
|
|
21
|
+
The gate inspects the declared tool names, not the client identity: headers, user agent, system
|
|
22
|
+
prompt and the rest of the payload do not change the outcome. The adapter aliases only the
|
|
23
|
+
colliding names on the way out and restores them on the way back, so the host keeps its own tool
|
|
24
|
+
names and no credential or signature is forged.
|
|
25
|
+
|
|
26
|
+
## Measured trigger (live subscription key, 2026-10-30)
|
|
27
|
+
|
|
28
|
+
| Request tool declarations | Result |
|
|
29
|
+
|---|---|
|
|
30
|
+
| no `tools` | 200 |
|
|
31
|
+
| flat `get_weather` | 200 |
|
|
32
|
+
| flat `client_read_file` | 200 |
|
|
33
|
+
| flat `read_file` | 403 |
|
|
34
|
+
| flat `bash` | 403 |
|
|
35
|
+
| flat `web_search` | 403 |
|
|
36
|
+
| flat names outside the first six, e.g. `web_fetch`, `workflow`, `powershell`, `add_memory` | 403 (each, separate probe) |
|
|
37
|
+
| flat `read_file` + `get_weather` | 403 |
|
|
38
|
+
| namespace `client` containing `read_file` | 200 |
|
|
39
|
+
| namespace `muse` with the genuine builtin declaration | 200 |
|
|
40
|
+
| namespace `muse` with a re-schematized `read_file` | 403 |
|
|
41
|
+
|
|
42
|
+
Two consequences drive the rules below:
|
|
43
|
+
|
|
44
|
+
1. A single colliding flat name is enough to fail the whole request.
|
|
45
|
+
2. Wrapping client tools in a namespace also passes today, but Meta validates the contents of a
|
|
46
|
+
namespace named `muse`, and a wrapper changes the tool surface the model sees. The adapter
|
|
47
|
+
therefore keeps the flat shape and only renames the colliding declarations.
|
|
48
|
+
|
|
49
|
+
## Builtin collision set (v3)
|
|
50
|
+
|
|
51
|
+
`workflow`, `read_file`, `write_file`, `edit_file`, `bash`, `bash_input`, `search`,
|
|
52
|
+
`web_search`, `web_fetch`, `powershell`, `powershell_input`, `read_memory`,
|
|
53
|
+
`add_memory`, `edit_memory`, `read_skill`, `write_todos`, `monitor`, `work_status`,
|
|
54
|
+
`work_list`, `work_stop`, `snooze_reminder`, `apply_patch`, `shell`, `create_goal`,
|
|
55
|
+
`get_goal`, `update_goal`, `report_progress`, `request_user_input`, `cron_create`,
|
|
56
|
+
`cron_delete`, `cron_list`, `send_session_message`, `list_peer_sessions`,
|
|
57
|
+
`subagent_send_message`, `subagent_cancel`, `subagent_spawn`, `subagent_status`,
|
|
58
|
+
`subagent_wait`, `subagent_read_result`, `tool_search`.
|
|
59
|
+
|
|
60
|
+
The set comes from the 1.4.14 binary's tool catalog plus `muse exec` captures;
|
|
61
|
+
every name was probed individually against api.meta.ai on 2026-10-30: all but
|
|
62
|
+
`tool_search` returned 403, and `tool_search` stays in the set defensively. The
|
|
63
|
+
catalog includes the goal, cron, session and patch tools as well as the file,
|
|
64
|
+
shell and web families.
|
|
65
|
+
|
|
66
|
+
The set is pinned in code (`COLLIDING_TOOL_NAMES`). Add a name only with evidence that Meta
|
|
67
|
+
rejects it, and bump `ADAPTER_VERSION`.
|
|
68
|
+
|
|
69
|
+
## Request rules
|
|
70
|
+
|
|
71
|
+
1. **Aliasing.** Every flat tool of `type: "function"` whose `name` is in the collision set is
|
|
72
|
+
renamed to `client_<name>`. If that alias is already taken by another declared tool, append
|
|
73
|
+
`_2`, `_3`, and so on. The same mapping is applied to `input[].function_call.name` for
|
|
74
|
+
`type: "function_call"` history items. Non-function tools, namespace containers and
|
|
75
|
+
non-colliding names are left untouched.
|
|
76
|
+
2. **`tool_choice`.** Meta accepts only `"auto"`. Any other value (named function, `"none"`,
|
|
77
|
+
`"required"`) is replaced with `"auto"`.
|
|
78
|
+
3. **Reasoning effort.** A top-level `reasoning_effort` string moves to `reasoning.effort`; an
|
|
79
|
+
explicit `reasoning.effort` already present wins.
|
|
80
|
+
4. **Output floor.** `max_output_tokens` below 16 is raised to 16.
|
|
81
|
+
5. **Reasoning replay.** `reasoning.encrypted_content` is appended to `include` when missing, so a
|
|
82
|
+
host that replays provider items can keep the encrypted reasoning item.
|
|
83
|
+
6. **Version header.** `x-api-version: 1.0.0` is added when the host did not set it.
|
|
84
|
+
7. Unknown fields are preserved untouched, and the caller's body object is never mutated.
|
|
85
|
+
|
|
86
|
+
## Automatic fallback
|
|
87
|
+
|
|
88
|
+
If Meta refuses a request with `client_attestation_denied` even after these
|
|
89
|
+
rules, the adapter retries the same request once with every flat function tool
|
|
90
|
+
aliased (`client_<name>`), and restores the names in the answer. That keeps a
|
|
91
|
+
Muse builtin name this release does not know from taking the lane down; the
|
|
92
|
+
retry is logged as `gate refused; retried with all N tools aliased`.
|
|
93
|
+
|
|
94
|
+
## Response rules
|
|
95
|
+
|
|
96
|
+
1. Aliased `function_call` names are restored to the original name in:
|
|
97
|
+
`response.output_item.added` / `.done` items, the `output` array of every `response.*` event,
|
|
98
|
+
the `tools` echo of the response object, the top-level `name` of
|
|
99
|
+
`response.function_call_arguments.done`, and the `output` array of a non-streaming JSON body.
|
|
100
|
+
2. If an authoritative `output_item.done` or completed item carries an empty `arguments`, the
|
|
101
|
+
value accumulated from `response.function_call_arguments.delta` events is written back.
|
|
102
|
+
3. SSE is relayed line by line and flushed per line; responses are never buffered.
|
|
103
|
+
|
|
104
|
+
## Transport rules
|
|
105
|
+
|
|
106
|
+
- The adapter listens on `127.0.0.1` only, on the first free port of `43219`, `43220`, `43221`,
|
|
107
|
+
`43222`, then an ephemeral port.
|
|
108
|
+
- The provider base URL becomes `http://127.0.0.1:<port>/v1`.
|
|
109
|
+
- `Authorization` is forwarded unchanged; the adapter never reads, stores or logs credentials,
|
|
110
|
+
request bodies or response bodies.
|
|
111
|
+
- Every path other than the Responses and Chat completion paths is relayed untouched, so
|
|
112
|
+
`GET /v1/models` discovery keeps working through the same base URL.
|
|
113
|
+
- `MUSE_CODE_ADAPTER=off` turns the wire rules off while keeping the proxy in place, so a
|
|
114
|
+
persisted base URL stays valid. Diagnostic logging is one line per adapted request, through the
|
|
115
|
+
host's debug logger.
|
|
116
|
+
- If the adapter cannot bind, the plugin logs the failure and leaves the provider on the direct
|
|
117
|
+
endpoint, which is the pre-0.3 behavior.
|
|
118
|
+
|
|
119
|
+
## Failure modes
|
|
120
|
+
|
|
121
|
+
- Meta changes the gate to a real per-client signature: aliasing stops helping and requests fail
|
|
122
|
+
again with `client_attestation_denied`. Nothing in this design can fake a signature.
|
|
123
|
+
- Meta renames or removes a builtin: the collision set needs an update.
|
|
124
|
+
- A host does not preserve provider reasoning items across turns: tool arguments may degrade to
|
|
125
|
+
`{}`. The adapter repairs the empty-arguments field, but not a host that never replays the
|
|
126
|
+
reasoning item.
|
|
127
|
+
|
|
128
|
+
## Verification
|
|
129
|
+
|
|
130
|
+
- Offline: the adapter unit and proxy tests in each repository, against a fake upstream.
|
|
131
|
+
- Live: start the adapter against `https://api.meta.ai`, send a request with a colliding tool
|
|
132
|
+
name, and require HTTP 200 with the original name restored in the response.
|
package/AUTH_CONTRACT.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# Muse auth contract (v1)
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
this file and the pinned constants/tests together in all
|
|
3
|
+
The four Muse Code plugins implement the same Meta device-code flow. Change
|
|
4
|
+
this file and the pinned constants/tests together in all four repositories:
|
|
5
5
|
|
|
6
6
|
- `opencode-muse-auth`
|
|
7
7
|
- `hermes-muse-code`
|
|
8
8
|
- `openclaw-muse-code`
|
|
9
|
+
- `dsh-muse-code`
|
|
9
10
|
|
|
10
11
|
## Wire contract
|
|
11
12
|
|
|
@@ -18,6 +19,7 @@ this file and the pinned constants/tests together in all three repositories:
|
|
|
18
19
|
| API version header | `x-api-version: 1.0.0` |
|
|
19
20
|
| Cache fields | `apiKey`, `accountId`, `email` (never the OAuth access token) |
|
|
20
21
|
| Inference base URL | `https://api.meta.ai/v1` |
|
|
22
|
+
| Inference wire adapter | see `ADAPTER_CONTRACT.md` (v3) |
|
|
21
23
|
|
|
22
24
|
## Behaviour
|
|
23
25
|
|
|
@@ -28,5 +30,11 @@ this file and the pinned constants/tests together in all three repositories:
|
|
|
28
30
|
key; reject an inactive subscription or a pending payment.
|
|
29
31
|
- Persistence: only the inference key and account identity are stored; the
|
|
30
32
|
OAuth access token stays in memory.
|
|
33
|
+
- Inference traffic on the V2 plugin API goes through the loopback adapter
|
|
34
|
+
described in `ADAPTER_CONTRACT.md` (tool-name aliasing, parameter
|
|
35
|
+
normalization, response name restoration). The adapter is part of the wire
|
|
36
|
+
contract: a change to those rules bumps `ADAPTER_VERSION` and the pinned
|
|
37
|
+
tests in every repository that vendors the adapter. The V1 code path keeps
|
|
38
|
+
sending directly to `https://api.meta.ai/v1`.
|
|
31
39
|
- Any change to this contract bumps `AUTH_CONTRACT_VERSION` in the code and
|
|
32
|
-
requires updating the pinned tests in all
|
|
40
|
+
requires updating the pinned tests in all four repositories.
|
package/DISCLAIMER.md
CHANGED
|
@@ -25,6 +25,19 @@ Meta may change, restrict or enforce its terms at any time, and may suspend,
|
|
|
25
25
|
restrict or terminate accounts, subscriptions or credentials involved. The
|
|
26
26
|
authors cannot prevent, predict or reverse that outcome.
|
|
27
27
|
|
|
28
|
+
## Request shaping
|
|
29
|
+
|
|
30
|
+
Since 2026-10-29 Meta refuses subscription requests from other clients when the
|
|
31
|
+
declared client tool names collide with Muse Code's builtin tool names. On the
|
|
32
|
+
V2 plugin API this plugin therefore runs a local adapter that renames exactly
|
|
33
|
+
those declarations on the way to `api.meta.ai` and restores them in the
|
|
34
|
+
response, so the request matches Muse Code's wire format. This is a deliberate
|
|
35
|
+
workaround of Meta's client restriction: it does not forge a credential, token
|
|
36
|
+
or signature, and the subscription key remains your own, but Meta may treat the
|
|
37
|
+
reshaping itself as a violation of its terms. The suspension, revocation and
|
|
38
|
+
no-refund risks described above apply with full force, and revocation can
|
|
39
|
+
happen immediately.
|
|
40
|
+
|
|
28
41
|
## No warranty, no guarantee
|
|
29
42
|
|
|
30
43
|
- The integration is provided "as is"; there is no guarantee that it is
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
# opencode-muse-auth
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
**Muse Code monthly subscription** — no pay-as-you-go API key, no third-party CLI.
|
|
3
|
+
Muse Code subscription sign-in for opencode
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
[](https://github.com/TheStreamCode/opencode-muse-auth/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://www.npmjs.com/package/opencode-muse-auth)
|
|
8
|
+
|
|
9
|
+
opencode plugin that registers the **Muse Code subscription** login method for the built-in `meta`
|
|
10
|
+
provider. No API key to copy and no third-party CLI to install. On the V2 plugin API the plugin also
|
|
11
|
+
routes subscription inference through a local wire adapter that keeps requests compatible with Muse
|
|
12
|
+
Code's tool declarations; see [ADAPTER_CONTRACT.md](ADAPTER_CONTRACT.md). A successful login still
|
|
13
|
+
does not guarantee model access: Meta may reject or restrict this use.
|
|
14
|
+
|
|
15
|
+
> **Unofficial and experimental.** This plugin uses a Muse Code subscription credential from
|
|
16
|
+
> opencode. Meta's terms limit that credential to Muse Code, so this use may not be supported and
|
|
17
|
+
> Meta may suspend or revoke accounts or credentials. Use it at your own risk; the authors cannot
|
|
18
|
+
> recover an account. Read [DISCLAIMER.md](DISCLAIMER.md) before installing.
|
|
11
19
|
|
|
12
20
|
## Install
|
|
13
21
|
|
|
22
|
+
Add the plugin to `opencode.json`; opencode installs it from npm:
|
|
23
|
+
|
|
14
24
|
```jsonc
|
|
15
25
|
// opencode.json
|
|
16
26
|
{
|
|
@@ -19,64 +29,152 @@ Use Meta **Muse Spark** in [opencode](https://opencode.ai) billed to your
|
|
|
19
29
|
}
|
|
20
30
|
```
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
## Sign in
|
|
33
|
+
|
|
34
|
+
1. Run `/connect` in opencode.
|
|
35
|
+
2. Pick the `meta` provider and the **Muse Code subscription** method.
|
|
36
|
+
3. Open the printed Meta device page, enter the printed code, and approve the login in your own
|
|
37
|
+
browser. The login completes by itself.
|
|
38
|
+
4. Select a `muse-*` model exposed by the `meta` provider, for example `meta/muse-spark-1.3`.
|
|
39
|
+
On opencode 2.x the plugin routes inference through the local adapter, so client tool names that
|
|
40
|
+
collide with Muse Code's builtins no longer trip Meta's client gate. The V1 plugin API still
|
|
41
|
+
sends requests straight to `api.meta.ai` and keeps receiving the 403.
|
|
42
|
+
|
|
43
|
+
The plugin mints a stable, account-bound inference key and hands it to opencode. The OAuth access
|
|
44
|
+
token from the login flow is kept in memory only and is never written to disk.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- Registers an `auth` hook for the built-in `meta` provider; there is no separate provider id to
|
|
49
|
+
configure.
|
|
50
|
+
- Runs the Meta device-code exchange (RFC 8628) inside `/connect`, then mints the subscription
|
|
51
|
+
inference key through the Model API.
|
|
52
|
+
- Routes through the Responses API; the `meta.txt` system prompt applies automatically to `muse-*`
|
|
53
|
+
model ids.
|
|
54
|
+
- Runs a loopback wire adapter for the V2 plugin API: it aliases the client tool names that collide
|
|
55
|
+
with Muse Code's builtins on the way to `api.meta.ai`, restores them in the response, and
|
|
56
|
+
normalizes parameters Meta rejects (`tool_choice`, reasoning effort, output-token floor,
|
|
57
|
+
encrypted-reasoning include). The adapter's `/v1` origin becomes the per-request base URL; on
|
|
58
|
+
routes that ignore that override the plugin rewrites the assembled request onto the same origin. If Meta still refuses the request, the
|
|
59
|
+
adapter retries once with every tool aliased. See [ADAPTER_CONTRACT.md](ADAPTER_CONTRACT.md).
|
|
60
|
+
- V1 storage: the OpenCode auth store is the primary source, and the loader falls back to the
|
|
61
|
+
legacy cache only when the store has no key.
|
|
62
|
+
- V2 storage: the credential lives in the OpenCode integration store, and no local cache is
|
|
63
|
+
written on this path.
|
|
64
|
+
- Never re-mints on its own; re-run `/connect` only after a 401, because the mint endpoint is
|
|
65
|
+
aggressively rate-limited.
|
|
66
|
+
- Flow parameters are compatible with the published behavior of oh-my-pi (MIT); see [NOTICE](NOTICE)
|
|
67
|
+
and [AUTH_CONTRACT.md](AUTH_CONTRACT.md).
|
|
30
68
|
|
|
31
69
|
## Requirements
|
|
32
70
|
|
|
33
|
-
- opencode with plugin support
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
uses; the store is `~/.local/share/opencode/auth.json`);
|
|
66
|
-
- delete `~/.config/opencode/muse-code-sub.json` if it exists from an older
|
|
67
|
-
version;
|
|
71
|
+
- opencode with plugin support, including the v2 plugin module shape accepted by opencode 1.18.29+
|
|
72
|
+
and 2.x.
|
|
73
|
+
- The loopback adapter requires the V2 plugin API (opencode 2.x). V1 installs keep authenticating
|
|
74
|
+
but still receive Meta's `client_attestation_denied` 403.
|
|
75
|
+
- Node.js 22.6 or newer for development; CI runs Node.js 22 and 24.
|
|
76
|
+
- An active Muse Code subscription on the Meta account used at login.
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
| Setting | Purpose | Default |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `plugins` entry in `opencode.json` | Loads the plugin. | not set |
|
|
83
|
+
| Auth store | Primary credential storage used by the V1 loader. | `~/.local/share/opencode/auth.json` |
|
|
84
|
+
| Legacy cache | Read-only fallback for credentials written by earlier versions. | `~/.config/opencode/muse-code-sub.json` |
|
|
85
|
+
| `/connect` method | Selects Muse Code subscription login for the `meta` provider. | `meta` provider |
|
|
86
|
+
| `MUSE_CODE_ADAPTER` | `off` keeps the local proxy but disables the tool-name reshaping and parameter normalization. | unset |
|
|
87
|
+
|
|
88
|
+
## Privacy and security
|
|
89
|
+
|
|
90
|
+
The inference key is held by opencode: in the V1 auth store or the V2 integration store. The
|
|
91
|
+
legacy cache, when present, stores only `apiKey`, `accountId`, and `email`; the OAuth access token
|
|
92
|
+
is never written to disk. The plugin talks only to `auth.meta.com` for the device login and
|
|
93
|
+
`api.meta.ai` for key minting and inference. The V2 wire adapter listens on `127.0.0.1` only,
|
|
94
|
+
forwards the `Authorization` header unchanged, and never reads, stores or logs credentials,
|
|
95
|
+
request bodies or response bodies. See [SECURITY.md](SECURITY.md) for vulnerability reporting and
|
|
96
|
+
[ADAPTER_CONTRACT.md](ADAPTER_CONTRACT.md) for the inference wire rules.
|
|
97
|
+
|
|
98
|
+
To remove access:
|
|
99
|
+
|
|
100
|
+
- disconnect the provider or log out in opencode (the store is
|
|
101
|
+
`~/.local/share/opencode/auth.json`);
|
|
102
|
+
- delete `~/.config/opencode/muse-code-sub.json` if an older version created it;
|
|
68
103
|
- run `muse logout` if you also want to clear the Muse Code CLI session;
|
|
69
104
|
- remove `opencode-muse-auth` from `opencode.json`.
|
|
70
105
|
|
|
106
|
+
## Troubleshooting
|
|
107
|
+
|
|
108
|
+
### `/connect` shows no Muse Code subscription method
|
|
109
|
+
|
|
110
|
+
The plugin is not loaded. Check the JSON syntax of `opencode.json`, the plugin name spelling, and
|
|
111
|
+
the package installation, then restart opencode.
|
|
112
|
+
|
|
113
|
+
### Requests fail with 401 or access was revoked
|
|
114
|
+
|
|
115
|
+
A 401 may indicate a rotated or revoked key. Re-run `/connect` once; the plugin never re-mints on
|
|
116
|
+
its own.
|
|
117
|
+
|
|
118
|
+
### Meta rejects subscription requests from this client
|
|
119
|
+
|
|
120
|
+
Meta may return a 403 mentioning `client_attestation_denied` or that Muse Code subscriptions cover
|
|
121
|
+
the Muse Code client only. With the V2 adapter running this should not happen for tool names that
|
|
122
|
+
collide with Muse Code's builtins. If it appears anyway, one of these is true:
|
|
123
|
+
|
|
124
|
+
- the adapter could not bind its loopback port; opencode's log records
|
|
125
|
+
`the local request adapter could not start`, and the provider stays on the direct endpoint;
|
|
126
|
+
- `MUSE_CODE_ADAPTER=off` is set, which keeps the proxy but disables the reshaping;
|
|
127
|
+
- you are on the V1 plugin API, which has no adapter;
|
|
128
|
+
- Meta changed the gate, for example to a real per-client signature. Nothing in this plugin can
|
|
129
|
+
forge one.
|
|
130
|
+
|
|
131
|
+
This is a client restriction, not evidence of an expired key. Signing in again does not remove it.
|
|
132
|
+
Use Muse Code for the subscription, or a separate pay-as-you-go Meta Model API key for other
|
|
133
|
+
clients, subject to Meta's terms.
|
|
134
|
+
|
|
135
|
+
### The login times out or is denied
|
|
136
|
+
|
|
137
|
+
Approve the printed code in the browser before the flow expires, then retry. A login rejected by
|
|
138
|
+
Meta usually means the account has no active Muse Code subscription.
|
|
139
|
+
|
|
140
|
+
### A legacy cache file is still present
|
|
141
|
+
|
|
142
|
+
It is harmless: the V1 loader prefers the OpenCode auth store and reads the cache only when the
|
|
143
|
+
store has no key. Delete the file if you are migrating to V2 storage.
|
|
144
|
+
|
|
71
145
|
## Development
|
|
72
146
|
|
|
73
|
-
|
|
147
|
+
Requirements: Node.js 22.6 or newer and npm.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
git clone https://github.com/TheStreamCode/opencode-muse-auth.git
|
|
151
|
+
cd opencode-muse-auth
|
|
74
152
|
npm install
|
|
75
153
|
npm run build
|
|
76
154
|
npm test
|
|
77
155
|
```
|
|
78
156
|
|
|
157
|
+
`npm test` builds with `tsc` and runs the TypeScript test suite with the Node test runner. CI runs
|
|
158
|
+
the same gate on Node.js 22 and 24; Node 22 needs 22.6 or newer for test-runner type stripping.
|
|
159
|
+
No Meta account is needed for the test suite.
|
|
160
|
+
|
|
161
|
+
## Support
|
|
162
|
+
|
|
163
|
+
Usage questions and workflows belong in GitHub Discussions; reproducible defects belong in GitHub
|
|
164
|
+
Issues using the bug report form. See [SUPPORT.md](SUPPORT.md). Problems in opencode or Meta
|
|
165
|
+
themselves belong to their respective projects.
|
|
166
|
+
|
|
167
|
+
## Contributing
|
|
168
|
+
|
|
169
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Pull requests must pass the required CI checks.
|
|
170
|
+
|
|
79
171
|
## License
|
|
80
172
|
|
|
81
|
-
MIT — see [LICENSE](LICENSE). The software is provided "as is"; see
|
|
173
|
+
MIT — see [`LICENSE`](LICENSE). The software is provided "as is"; see
|
|
82
174
|
[DISCLAIMER.md](DISCLAIMER.md) for the risk notice and liability terms.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
Maintained by [Michael Gasperini (Mikesoft)](https://mikesoft.it).
|
|
179
|
+
If this project is useful to you, consider
|
|
180
|
+
[sponsoring its maintenance](https://github.com/sponsors/TheStreamCode).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Version of the wire rules below; bump with ADAPTER_CONTRACT.md. */
|
|
2
|
+
export declare const ADAPTER_VERSION = 3;
|
|
3
|
+
/** Default upstream; tests override it with a local fake. */
|
|
4
|
+
export declare const DEFAULT_UPSTREAM = "https://api.meta.ai";
|
|
5
|
+
/**
|
|
6
|
+
* Muse Code builtin tool names.
|
|
7
|
+
*
|
|
8
|
+
* Reconstructed from the 1.4.14 binary's tool catalog and from `muse exec`
|
|
9
|
+
* captures, then probed one by one against api.meta.ai on 2026-10-30: a flat
|
|
10
|
+
* client tool with one of these exact names is refused with 403
|
|
11
|
+
* `client_attestation_denied`. The catalog includes goal, cron, session and
|
|
12
|
+
* patch tools as well as the file, shell and web families. `tool_search` is
|
|
13
|
+
* included defensively: one probe returned 200, but field reports see a 403.
|
|
14
|
+
*/
|
|
15
|
+
export declare const COLLIDING_TOOL_NAMES: ReadonlySet<string>;
|
|
16
|
+
type JsonRecord = Record<string, unknown>;
|
|
17
|
+
/** One alias plan: original name to wire name, plus the reverse map. */
|
|
18
|
+
export interface AliasPlan {
|
|
19
|
+
readonly aliases: ReadonlyMap<string, string>;
|
|
20
|
+
readonly reverse: ReadonlyMap<string, string>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Decide which declared tool names must be aliased.
|
|
24
|
+
*
|
|
25
|
+
* Only flat `function` tools are considered: those are the declarations Meta
|
|
26
|
+
* compares against its builtin set. A namespace container is left untouched.
|
|
27
|
+
* With `all`, every flat function tool is aliased; the proxy uses that as a
|
|
28
|
+
* one-shot retry when the gate refuses names this release does not know.
|
|
29
|
+
*/
|
|
30
|
+
export declare function planToolAliases(tools: unknown, options?: {
|
|
31
|
+
all?: boolean;
|
|
32
|
+
}): AliasPlan;
|
|
33
|
+
/** Result of adapting a request body: the body to send, its plan and a log line. */
|
|
34
|
+
export interface AdaptedRequest {
|
|
35
|
+
readonly body: JsonRecord;
|
|
36
|
+
readonly plan: AliasPlan;
|
|
37
|
+
readonly notes: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Apply the wire rules to one Responses/Chat request body.
|
|
41
|
+
*
|
|
42
|
+
* The input object is not mutated: a shallow copy is returned. Unknown fields
|
|
43
|
+
* are preserved untouched. Pass a plan from {@link planToolAliases} to reuse a
|
|
44
|
+
* mapping, for example the full-aliasing retry.
|
|
45
|
+
*/
|
|
46
|
+
export declare function transformRequestBody(body: JsonRecord, plan?: AliasPlan): AdaptedRequest;
|
|
47
|
+
/**
|
|
48
|
+
* Streaming name restorer.
|
|
49
|
+
*
|
|
50
|
+
* Feeds complete SSE lines and returns the line to relay. It restores aliased
|
|
51
|
+
* function-call names and repairs empty `arguments` from the streamed deltas:
|
|
52
|
+
* Meta's authoritative `response.output_item.done` sometimes ships "" even
|
|
53
|
+
* though the deltas carried the JSON.
|
|
54
|
+
*/
|
|
55
|
+
export declare class SseRestorer {
|
|
56
|
+
private readonly reverse;
|
|
57
|
+
private readonly arguments;
|
|
58
|
+
constructor(reverse: ReadonlyMap<string, string>);
|
|
59
|
+
/** Adapt one complete SSE line (without the trailing newline). */
|
|
60
|
+
feed(line: string): string;
|
|
61
|
+
private adapt;
|
|
62
|
+
/** Restore names in an emitted response object (tools echo and output items). */
|
|
63
|
+
private adaptResponse;
|
|
64
|
+
private collectArguments;
|
|
65
|
+
private repairArguments;
|
|
66
|
+
}
|
|
67
|
+
/** Restore aliased names and empty arguments in a complete JSON response. */
|
|
68
|
+
export declare function transformResponseBody(body: JsonRecord, reverse: ReadonlyMap<string, string>): JsonRecord;
|
|
69
|
+
/** Options for {@link startMuseCodeAdapter}. */
|
|
70
|
+
export interface AdapterOptions {
|
|
71
|
+
/** Upstream origin; override for tests only. */
|
|
72
|
+
readonly upstream?: string;
|
|
73
|
+
/** Candidate loopback ports; the first free one wins, then an ephemeral port. */
|
|
74
|
+
readonly ports?: readonly number[];
|
|
75
|
+
/** Diagnostic sink; one short line per adapted request. */
|
|
76
|
+
readonly log?: (message: string) => void;
|
|
77
|
+
/** Disable the wire rules but keep proxying (MUSE_CODE_ADAPTER=off). */
|
|
78
|
+
readonly passthrough?: boolean;
|
|
79
|
+
}
|
|
80
|
+
/** A running adapter. */
|
|
81
|
+
export interface RunningAdapter {
|
|
82
|
+
/** Loopback origin to use as the provider baseURL, e.g. http://127.0.0.1:43219/v1. */
|
|
83
|
+
readonly baseUrl: string;
|
|
84
|
+
/** Actual loopback port. */
|
|
85
|
+
readonly port: number;
|
|
86
|
+
/** Stop accepting and close the server. */
|
|
87
|
+
close(): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Start the loopback adapter.
|
|
91
|
+
*
|
|
92
|
+
* The server binds 127.0.0.1 only. Requests to the Responses and Chat
|
|
93
|
+
* completion paths are adapted; every other path is relayed untouched, so
|
|
94
|
+
* the provider's model discovery keeps working through the same baseURL.
|
|
95
|
+
*/
|
|
96
|
+
export declare function startMuseCodeAdapter(options?: AdapterOptions): Promise<RunningAdapter>;
|
|
97
|
+
export {};
|