@thestreamcode/dsh-muse-code 0.0.0-stage → 0.1.1
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/AUTH_CONTRACT.md +33 -0
- package/DISCLAIMER.md +62 -0
- package/LICENSE +21 -0
- package/README.md +159 -2
- package/cordis.patch.yml +68 -0
- package/lib/auth.d.ts +22 -0
- package/lib/auth.js +157 -0
- package/lib/index.d.ts +31 -0
- package/lib/index.js +40 -0
- package/package.json +45 -4
package/AUTH_CONTRACT.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Muse auth contract (v1)
|
|
2
|
+
|
|
3
|
+
This bundle implements the same Meta device-code flow as the other Muse Code
|
|
4
|
+
plugins. Change this file and the pinned constants/tests together:
|
|
5
|
+
|
|
6
|
+
- `opencode-muse-auth`
|
|
7
|
+
- `hermes-muse-code`
|
|
8
|
+
- `openclaw-muse-code`
|
|
9
|
+
- `dsh-muse-code`
|
|
10
|
+
|
|
11
|
+
## Wire contract
|
|
12
|
+
|
|
13
|
+
| Item | Value |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `CLIENT_ID` | `1031625952748946` |
|
|
16
|
+
| Device authorization | `https://auth.meta.com/oidc/device/authorization/` |
|
|
17
|
+
| Device token | `https://auth.meta.com/oidc/device/token/` |
|
|
18
|
+
| Key mint | `https://api.meta.ai/muse-code/key` |
|
|
19
|
+
| API version header | `x-api-version: 1.0.0` |
|
|
20
|
+
| Credential reference | `MUSE_CODE_SUB_TOKEN` |
|
|
21
|
+
| Inference base URL | `https://api.meta.ai/v1` |
|
|
22
|
+
|
|
23
|
+
## Behaviour
|
|
24
|
+
|
|
25
|
+
- Device flow: request a device code, poll the token endpoint, accept
|
|
26
|
+
`authorization_pending`, increase the interval on `slow_down`, stop on a
|
|
27
|
+
terminal error or at `expires_in`.
|
|
28
|
+
- Key mint: exchange the OAuth access token for the account-bound inference
|
|
29
|
+
key; reject an inactive subscription or a pending payment.
|
|
30
|
+
- Persistence: the key is stored through the DSH credential seam under
|
|
31
|
+
`MUSE_CODE_SUB_TOKEN`; the OAuth access token stays in memory.
|
|
32
|
+
- Any change to this contract bumps `AUTH_CONTRACT_VERSION` in the code and
|
|
33
|
+
requires updating the pinned tests in all four repositories.
|
package/DISCLAIMER.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Disclaimer and risk notice
|
|
2
|
+
|
|
3
|
+
This project is an unofficial, community-built integration. It is not affiliated
|
|
4
|
+
with, endorsed by, or supported by Meta Platforms, Inc. or its affiliates
|
|
5
|
+
("Meta"), or by DeepSeek. "Meta", "Muse", "Muse Code", "Muse Spark" and related
|
|
6
|
+
names are trademarks of Meta.
|
|
7
|
+
|
|
8
|
+
## Why this notice exists
|
|
9
|
+
|
|
10
|
+
This plugin signs in with a Meta account that has an active Muse Code
|
|
11
|
+
subscription and uses the subscription-linked credential to send inference
|
|
12
|
+
requests from DeepSeek Harness, a third-party coding client.
|
|
13
|
+
|
|
14
|
+
Meta's [Meta Model API Terms of Service](https://dev.meta.ai/legal/terms-of-service),
|
|
15
|
+
Section 3.2, states that a credential connected for a Coding Harness
|
|
16
|
+
Subscription "is for use only with the coding harness under your Coding Harness
|
|
17
|
+
Subscription" and "may not be used to access the Services outside the coding
|
|
18
|
+
harness or to circumvent your subscription's usage limits or billing". Meta's
|
|
19
|
+
[Muse Code subscription documentation](https://dev.meta.ai/docs/muse-code/subscriptions)
|
|
20
|
+
likewise states that the subscription credential "is for use with Muse Code
|
|
21
|
+
only".
|
|
22
|
+
|
|
23
|
+
This project therefore relies on a use that Meta does not document as supported.
|
|
24
|
+
Meta may change, restrict or enforce its terms at any time, and may suspend,
|
|
25
|
+
restrict or terminate accounts, subscriptions or credentials involved. The
|
|
26
|
+
authors cannot prevent, predict or reverse that outcome.
|
|
27
|
+
|
|
28
|
+
## No warranty, no guarantee
|
|
29
|
+
|
|
30
|
+
- The integration is provided "as is"; there is no guarantee that it is
|
|
31
|
+
permitted by Meta's terms, that it will keep working, or that the subscription
|
|
32
|
+
will remain available.
|
|
33
|
+
- The authors cannot restore an account, subscription, credential or data after
|
|
34
|
+
Meta takes action, and cannot intervene with Meta on your behalf.
|
|
35
|
+
- This notice is informational and is not legal advice.
|
|
36
|
+
|
|
37
|
+
## Your responsibility
|
|
38
|
+
|
|
39
|
+
By installing or using this software you acknowledge that:
|
|
40
|
+
|
|
41
|
+
1. you are responsible for complying with Meta's terms, including Section 3.2;
|
|
42
|
+
2. you use your own account and credentials and accept the risk of account or
|
|
43
|
+
subscription action by Meta;
|
|
44
|
+
3. you will not use this software where losing access would be unacceptable, and
|
|
45
|
+
you will not share credential caches or exported keys;
|
|
46
|
+
4. you will delete stored credentials when you stop using the software.
|
|
47
|
+
|
|
48
|
+
## Limitation of liability
|
|
49
|
+
|
|
50
|
+
To the maximum extent permitted by applicable law, the authors and contributors
|
|
51
|
+
are not liable for any loss or damage arising out of or related to this software
|
|
52
|
+
or its use, including account suspension, subscription termination, credential
|
|
53
|
+
revocation, loss of data, or service interruption. Nothing in this notice
|
|
54
|
+
excludes or limits liability that cannot be excluded or limited by applicable
|
|
55
|
+
law. The [MIT License](LICENSE) contains the warranty disclaimer and liability
|
|
56
|
+
limitation that govern the software; this notice does not modify it.
|
|
57
|
+
|
|
58
|
+
## Clearly supported alternatives
|
|
59
|
+
|
|
60
|
+
If you need an integration that is unambiguously within Meta's terms, use a Meta
|
|
61
|
+
Model API key billed pay-as-you-go, or drive Muse Code through its official
|
|
62
|
+
`muse serve` session interface.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Gasperini (Mikesoft)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,160 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-muse-code
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Muse Code subscription models and device-code sign-in for DeepSeek Harness
|
|
4
|
+
|
|
5
|
+
[](https://github.com/TheStreamCode/dsh-muse-code/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/TheStreamCode/dsh-muse-code/releases/latest)
|
|
8
|
+
|
|
9
|
+
DeepSeek Harness (DSH) bundle that serves Meta Muse Code models through a Muse Code
|
|
10
|
+
subscription and adds a native device-code login flow. No API key is copied by hand: the
|
|
11
|
+
plugin runs Meta's device login, mints the account-bound inference key, and stores it in the
|
|
12
|
+
DSH credential store.
|
|
13
|
+
|
|
14
|
+
> **Unofficial and experimental.** This plugin uses a Muse Code subscription credential from
|
|
15
|
+
> DeepSeek Harness. Meta's terms limit that credential to Muse Code, so this use may not be
|
|
16
|
+
> supported and Meta may suspend or revoke accounts or credentials. Use it at your own risk;
|
|
17
|
+
> the authors cannot recover an account. Read [DISCLAIMER.md](DISCLAIMER.md) before installing.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
From the npm registry:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
dsh plugin --profile desktop add @thestreamcode/dsh-muse-code
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
From the public GitHub repository:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
dsh plugin --profile desktop add github:TheStreamCode/dsh-muse-code
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
From a local checkout:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
dsh plugin --profile desktop add link:C:\path\to\dsh-muse-code
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The bundle inserts its route and auth plugin through `cordis.patch.yml`; no manual profile
|
|
40
|
+
edit is required. Restart DSH if the profile does not pick up the bundle immediately.
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
1. Install the bundle with the command above.
|
|
45
|
+
2. Open **Settings**, select **Authorization** (the credential and sign-in surface), then
|
|
46
|
+
**Muse Code (subscription)**.
|
|
47
|
+
3. Open the printed Meta URL, enter the printed code, and approve the login.
|
|
48
|
+
4. Select the `muse-code` provider and a Muse model in the DSH model picker, for example
|
|
49
|
+
`muse-code/muse-spark-1.3`.
|
|
50
|
+
|
|
51
|
+
The plugin commits the minted key as the `MUSE_CODE_SUB_TOKEN` credential, and the route
|
|
52
|
+
reads it per request, so the key never appears in a configuration file.
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- Adds a `muse-code` model route through a second `@deepseek-ai/dsh-llm-pi-ai` instance
|
|
57
|
+
(`api: openai-responses`, `baseURL: https://api.meta.ai/v1`), so the existing `llm-pi-ai`
|
|
58
|
+
configuration is not replaced.
|
|
59
|
+
- Registers the **Muse Code (subscription)** authorization flow: Meta device login, key
|
|
60
|
+
mint, and credential commit.
|
|
61
|
+
- Stores the key in the DSH credential store under `MUSE_CODE_SUB_TOKEN` and reads it through
|
|
62
|
+
`apiKeyEnv`.
|
|
63
|
+
- Serves `muse-spark-1.3`, `muse-spark-1.2`, `muse-wonder-1.0`, and `muse-wonder-full-1.0`
|
|
64
|
+
with text and image input, a 1,048,576-token context window, and reasoning-effort mapping
|
|
65
|
+
from `minimal` to `max`.
|
|
66
|
+
- Accepts an explicit `MUSE_CODE_SUB_TOKEN` environment variable instead of the stored
|
|
67
|
+
credential.
|
|
68
|
+
|
|
69
|
+
## Requirements
|
|
70
|
+
|
|
71
|
+
- DeepSeek Harness `0.2.1-rc.2` or newer.
|
|
72
|
+
- Node.js 20 or newer on the DSH host; CI runs Node.js 20, 22, and 24.
|
|
73
|
+
- A Meta account with an active Muse Code subscription.
|
|
74
|
+
- The plugin needs no separate service and no third-party CLI.
|
|
75
|
+
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
| Setting | Purpose | Default |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `MUSE_CODE_SUB_TOKEN` | Inference key read by the `muse-code` route through `apiKeyEnv`. An explicit value wins over the stored credential. | from the authorization flow |
|
|
81
|
+
| `muse-code` provider | Provider entry added by `cordis.patch.yml`; select it in the DSH model picker. | enabled after install |
|
|
82
|
+
| Model id | Model served by the route; availability depends on the subscription plan. | `muse-spark-1.3` |
|
|
83
|
+
| Reasoning effort | Maps DSH effort levels to Meta's wire vocabulary. | provider default |
|
|
84
|
+
|
|
85
|
+
## Privacy and security
|
|
86
|
+
|
|
87
|
+
The subscription key is stored only in the DSH credential store under `MUSE_CODE_SUB_TOKEN`;
|
|
88
|
+
it is never written to a configuration file, and the OAuth access token stays in memory. The
|
|
89
|
+
plugin talks only to `auth.meta.com` for the device login and `api.meta.ai` for key minting and
|
|
90
|
+
inference. Do not share credential caches or exported keys.
|
|
91
|
+
|
|
92
|
+
To remove access, delete the `MUSE_CODE_SUB_TOKEN` credential from the DSH credential store or
|
|
93
|
+
sign out of the authorization record, then remove the bundle:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
dsh plugin --profile desktop remove @thestreamcode/dsh-muse-code
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
See [SECURITY.md](SECURITY.md) for vulnerability reporting and
|
|
100
|
+
[AUTH_CONTRACT.md](AUTH_CONTRACT.md) for the pinned login contract.
|
|
101
|
+
|
|
102
|
+
## Troubleshooting
|
|
103
|
+
|
|
104
|
+
### The provider does not appear in the model picker
|
|
105
|
+
|
|
106
|
+
The bundle was not loaded by the active profile. Re-run the install command with the same
|
|
107
|
+
profile, restart DSH, and check that `cordis.patch.yml` is part of the installed bundle.
|
|
108
|
+
|
|
109
|
+
### Muse Code subscription is inactive for this account
|
|
110
|
+
|
|
111
|
+
The signed-in account has no active Muse Code subscription, or the subscription lapsed.
|
|
112
|
+
Renew it, or sign in with the account that holds the subscription.
|
|
113
|
+
|
|
114
|
+
### The device login expires or is denied
|
|
115
|
+
|
|
116
|
+
Start the authorization flow again and approve the printed code before the expiry printed by
|
|
117
|
+
the flow. The login completes in your own browser on Meta's authorization page.
|
|
118
|
+
|
|
119
|
+
### Requests fail with 401 after a subscription change
|
|
120
|
+
|
|
121
|
+
The stored key was revoked or rotated. Remove the `MUSE_CODE_SUB_TOKEN` credential, run the
|
|
122
|
+
authorization flow again, and restart DSH if a running process cached the old value.
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
Requirements: Node.js 20 or newer and npm.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git clone https://github.com/TheStreamCode/dsh-muse-code.git
|
|
130
|
+
cd dsh-muse-code
|
|
131
|
+
npm install
|
|
132
|
+
npm run typecheck
|
|
133
|
+
npm test
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`npm test` builds `lib/` and runs the `node:test` suite. `lib/` is committed because DSH
|
|
137
|
+
bundle installs from Git may not run a build; regenerate it with `npm run build` after any
|
|
138
|
+
source change. The offline tests cover the device flow, the credential wiring, and the plugin
|
|
139
|
+
manifest; no Meta account is needed.
|
|
140
|
+
|
|
141
|
+
## Support
|
|
142
|
+
|
|
143
|
+
Usage questions and workflows belong in GitHub Discussions; reproducible defects belong in
|
|
144
|
+
GitHub Issues using the bug report form. See [SUPPORT.md](SUPPORT.md). The plugin cannot
|
|
145
|
+
obtain, restore, or protect a Meta account, subscription, or credential.
|
|
146
|
+
|
|
147
|
+
## Contributing
|
|
148
|
+
|
|
149
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Pull requests must pass the required CI checks.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT — see [`LICENSE`](LICENSE). The software is provided "as is"; see
|
|
154
|
+
[DISCLAIMER.md](DISCLAIMER.md) for the risk notice and liability terms.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
Maintained by [Michael Gasperini (Mikesoft)](https://mikesoft.it).
|
|
159
|
+
If this project is useful to you, consider
|
|
160
|
+
[sponsoring its maintenance](https://github.com/sponsors/TheStreamCode).
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Muse Code subscription provider for DeepSeek Harness.
|
|
2
|
+
#
|
|
3
|
+
# A second pi-ai adapter instance serves the `muse-code` route, so the
|
|
4
|
+
# existing `llm-pi-ai` provider configuration is not replaced. The auth
|
|
5
|
+
# plugin registers the device-code flow and stores the minted key under the
|
|
6
|
+
# MUSE_CODE_SUB_TOKEN credential reference used by apiKeyEnv.
|
|
7
|
+
- insert:
|
|
8
|
+
- id: muse-code-llm
|
|
9
|
+
name: '@deepseek-ai/dsh-llm-pi-ai'
|
|
10
|
+
config:
|
|
11
|
+
providers:
|
|
12
|
+
muse-code:
|
|
13
|
+
displayName: Muse Code (subscription)
|
|
14
|
+
apiKeyEnv: MUSE_CODE_SUB_TOKEN
|
|
15
|
+
api: openai-responses
|
|
16
|
+
baseURL: https://api.meta.ai/v1
|
|
17
|
+
models:
|
|
18
|
+
- id: muse-spark-1.3
|
|
19
|
+
name: Muse Spark 1.3
|
|
20
|
+
contextWindow: 1048576
|
|
21
|
+
maxTokens: 131072
|
|
22
|
+
input: [text, image]
|
|
23
|
+
reasoningEfforts:
|
|
24
|
+
minimal: minimal
|
|
25
|
+
low: low
|
|
26
|
+
medium: medium
|
|
27
|
+
high: high
|
|
28
|
+
xhigh: xhigh
|
|
29
|
+
max: max
|
|
30
|
+
- id: muse-spark-1.2
|
|
31
|
+
name: Muse Spark 1.2
|
|
32
|
+
contextWindow: 1048576
|
|
33
|
+
maxTokens: 131072
|
|
34
|
+
input: [text, image]
|
|
35
|
+
reasoningEfforts:
|
|
36
|
+
minimal: minimal
|
|
37
|
+
low: low
|
|
38
|
+
medium: medium
|
|
39
|
+
high: high
|
|
40
|
+
xhigh: xhigh
|
|
41
|
+
max: max
|
|
42
|
+
- id: muse-wonder-1.0
|
|
43
|
+
name: Muse Wonder 1.0
|
|
44
|
+
contextWindow: 1048576
|
|
45
|
+
maxTokens: 131072
|
|
46
|
+
input: [text, image]
|
|
47
|
+
reasoningEfforts:
|
|
48
|
+
minimal: minimal
|
|
49
|
+
low: low
|
|
50
|
+
medium: medium
|
|
51
|
+
high: high
|
|
52
|
+
xhigh: xhigh
|
|
53
|
+
max: max
|
|
54
|
+
- id: muse-wonder-full-1.0
|
|
55
|
+
name: Muse Wonder Full 1.0
|
|
56
|
+
contextWindow: 1048576
|
|
57
|
+
maxTokens: 131072
|
|
58
|
+
input: [text, image]
|
|
59
|
+
reasoningEfforts:
|
|
60
|
+
minimal: minimal
|
|
61
|
+
low: low
|
|
62
|
+
medium: medium
|
|
63
|
+
high: high
|
|
64
|
+
xhigh: xhigh
|
|
65
|
+
max: max
|
|
66
|
+
- id: muse-code-auth
|
|
67
|
+
name: '@thestreamcode/dsh-muse-code'
|
|
68
|
+
config: {}
|
package/lib/auth.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const CLIENT_ID = "1031625952748946";
|
|
2
|
+
export declare const DEVICE_URL = "https://auth.meta.com/oidc/device/authorization/";
|
|
3
|
+
export declare const TOKEN_URL = "https://auth.meta.com/oidc/device/token/";
|
|
4
|
+
export declare const KEY_URL = "https://api.meta.ai/muse-code/key";
|
|
5
|
+
export declare const API_VERSION = "1.0.0";
|
|
6
|
+
export declare const AUTH_CONTRACT_VERSION = 1;
|
|
7
|
+
export interface DeviceAuthorization {
|
|
8
|
+
device_code: string;
|
|
9
|
+
user_code: string;
|
|
10
|
+
verification_uri: string;
|
|
11
|
+
verification_uri_complete?: string;
|
|
12
|
+
interval: number;
|
|
13
|
+
expires_in: number;
|
|
14
|
+
}
|
|
15
|
+
export interface MintedCredential {
|
|
16
|
+
apiKey: string;
|
|
17
|
+
accountId: string;
|
|
18
|
+
email?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function deviceAuthorize(signal?: AbortSignal): Promise<DeviceAuthorization>;
|
|
21
|
+
export declare function pollToken(deviceCode: string, intervalS: number, expiresInS: number, signal?: AbortSignal): Promise<string>;
|
|
22
|
+
export declare function mintKey(accessToken: string, signal?: AbortSignal): Promise<MintedCredential>;
|
package/lib/auth.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Meta device-code login and subscription-key minting for Muse Code.
|
|
2
|
+
// The wire contract is shared with the other Muse Code plugins; see
|
|
3
|
+
// AUTH_CONTRACT.md before changing any constant here.
|
|
4
|
+
export const CLIENT_ID = "1031625952748946";
|
|
5
|
+
export const DEVICE_URL = "https://auth.meta.com/oidc/device/authorization/";
|
|
6
|
+
export const TOKEN_URL = "https://auth.meta.com/oidc/device/token/";
|
|
7
|
+
export const KEY_URL = "https://api.meta.ai/muse-code/key";
|
|
8
|
+
export const API_VERSION = "1.0.0";
|
|
9
|
+
export const AUTH_CONTRACT_VERSION = 1;
|
|
10
|
+
const HEADERS = {
|
|
11
|
+
Accept: "application/json",
|
|
12
|
+
"x-api-version": API_VERSION,
|
|
13
|
+
};
|
|
14
|
+
const REQUEST_TIMEOUT_MS = 25_000;
|
|
15
|
+
const MIN_INTERVAL_S = 1;
|
|
16
|
+
const SLOW_DOWN_STEP_S = 5;
|
|
17
|
+
function combinedSignal(signal) {
|
|
18
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
19
|
+
return signal ? AbortSignal.any([timeout, signal]) : timeout;
|
|
20
|
+
}
|
|
21
|
+
async function postForm(url, params, signal) {
|
|
22
|
+
const res = await fetch(url, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: { ...HEADERS, "Content-Type": "application/x-www-form-urlencoded" },
|
|
25
|
+
body: new URLSearchParams(params).toString(),
|
|
26
|
+
signal: combinedSignal(signal),
|
|
27
|
+
});
|
|
28
|
+
const text = await res.text();
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
throw new Error(`HTTP ${res.status} ${text.slice(0, 200)}`.trim());
|
|
31
|
+
return JSON.parse(text);
|
|
32
|
+
}
|
|
33
|
+
function delay(ms, signal) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const timer = setTimeout(resolve, ms);
|
|
36
|
+
if (!signal)
|
|
37
|
+
return;
|
|
38
|
+
if (signal.aborted) {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
reject(new Error("authorization cancelled"));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
signal.addEventListener("abort", () => {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
reject(new Error("authorization cancelled"));
|
|
46
|
+
}, { once: true });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export async function deviceAuthorize(signal) {
|
|
50
|
+
const body = (await postForm(DEVICE_URL, { client_id: CLIENT_ID }, signal));
|
|
51
|
+
const { device_code, user_code, verification_uri, interval, expires_in } = body;
|
|
52
|
+
if (typeof device_code !== "string" ||
|
|
53
|
+
!device_code ||
|
|
54
|
+
typeof user_code !== "string" ||
|
|
55
|
+
!user_code ||
|
|
56
|
+
typeof verification_uri !== "string" ||
|
|
57
|
+
!verification_uri ||
|
|
58
|
+
typeof interval !== "number" ||
|
|
59
|
+
typeof expires_in !== "number") {
|
|
60
|
+
throw new Error("device authorization response is missing fields");
|
|
61
|
+
}
|
|
62
|
+
const complete = body.verification_uri_complete;
|
|
63
|
+
return {
|
|
64
|
+
device_code,
|
|
65
|
+
user_code,
|
|
66
|
+
verification_uri,
|
|
67
|
+
...(typeof complete === "string" && complete ? { verification_uri_complete: complete } : {}),
|
|
68
|
+
interval,
|
|
69
|
+
expires_in,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export async function pollToken(deviceCode, intervalS, expiresInS, signal) {
|
|
73
|
+
let interval = Math.max(MIN_INTERVAL_S, Number(intervalS) || 5);
|
|
74
|
+
const deadline = Date.now() + Number(expiresInS || 600) * 1000;
|
|
75
|
+
let slowDowns = 0;
|
|
76
|
+
while (Date.now() < deadline) {
|
|
77
|
+
if (signal?.aborted)
|
|
78
|
+
throw new Error("authorization cancelled");
|
|
79
|
+
// The token endpoint answers pending/slow_down polls with HTTP errors as
|
|
80
|
+
// part of the normal flow: classify the body instead of throwing.
|
|
81
|
+
let body = null;
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(TOKEN_URL, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: { ...HEADERS, "Content-Type": "application/x-www-form-urlencoded" },
|
|
86
|
+
body: new URLSearchParams({
|
|
87
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
88
|
+
client_id: CLIENT_ID,
|
|
89
|
+
device_code: deviceCode,
|
|
90
|
+
}).toString(),
|
|
91
|
+
signal: combinedSignal(signal),
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
body = (await res.json());
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
body = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (signal?.aborted)
|
|
102
|
+
throw new Error("authorization cancelled");
|
|
103
|
+
if (error instanceof Error && error.name === "AbortError")
|
|
104
|
+
throw error;
|
|
105
|
+
body = null;
|
|
106
|
+
}
|
|
107
|
+
if (body && !body.error) {
|
|
108
|
+
if (typeof body.access_token !== "string" || !body.access_token) {
|
|
109
|
+
throw new Error("token response has no access_token");
|
|
110
|
+
}
|
|
111
|
+
return body.access_token;
|
|
112
|
+
}
|
|
113
|
+
if (body?.error === "slow_down") {
|
|
114
|
+
slowDowns += 1;
|
|
115
|
+
interval = Math.max(MIN_INTERVAL_S, interval + SLOW_DOWN_STEP_S);
|
|
116
|
+
}
|
|
117
|
+
else if (body?.error !== "authorization_pending") {
|
|
118
|
+
throw new Error(`login failed: ${String(body?.error || "unreadable token response")}`);
|
|
119
|
+
}
|
|
120
|
+
const remaining = deadline - Date.now();
|
|
121
|
+
if (remaining <= 0)
|
|
122
|
+
break;
|
|
123
|
+
await delay(Math.min(interval, remaining) * 1000, signal);
|
|
124
|
+
}
|
|
125
|
+
throw new Error(slowDowns ? "login timed out after slow_down responses" : "login timed out waiting for approval");
|
|
126
|
+
}
|
|
127
|
+
export async function mintKey(accessToken, signal) {
|
|
128
|
+
const res = await fetch(KEY_URL, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: {
|
|
131
|
+
...HEADERS,
|
|
132
|
+
"Content-Type": "application/json",
|
|
133
|
+
Authorization: `Bearer ${accessToken}`,
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({ onboard: true }),
|
|
136
|
+
signal: combinedSignal(signal),
|
|
137
|
+
});
|
|
138
|
+
const text = await res.text();
|
|
139
|
+
if (!res.ok)
|
|
140
|
+
throw new Error(`key exchange failed: HTTP ${res.status} ${text.slice(0, 200)}`.trim());
|
|
141
|
+
const body = JSON.parse(text);
|
|
142
|
+
if (body.is_subs_active === false) {
|
|
143
|
+
throw new Error("Muse Code subscription is inactive for this account");
|
|
144
|
+
}
|
|
145
|
+
if (typeof body.api_key !== "string" || !body.api_key.trim()) {
|
|
146
|
+
const action = String(body.action_url || body.require_payment_action_url || "").trim();
|
|
147
|
+
if (body.require_payment === true || action) {
|
|
148
|
+
throw new Error(`Muse Code subscription is required${action ? ": " + action : ""}`);
|
|
149
|
+
}
|
|
150
|
+
throw new Error("key response is missing api_key");
|
|
151
|
+
}
|
|
152
|
+
const email = String(body.user_email || "").trim().toLowerCase() || undefined;
|
|
153
|
+
const accountId = String(body.user_id || "").trim() || email;
|
|
154
|
+
if (!accountId)
|
|
155
|
+
throw new Error("key response is missing account identity");
|
|
156
|
+
return { apiKey: body.api_key.trim(), accountId, ...(email ? { email } : {}) };
|
|
157
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type CredentialStore = {
|
|
2
|
+
set(reference: string, value: string): Promise<void>;
|
|
3
|
+
modifyRecord(key: string, update: () => Promise<unknown>): Promise<unknown>;
|
|
4
|
+
};
|
|
5
|
+
type AuthorizationSession = {
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
notify(input: {
|
|
8
|
+
message: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
}): void;
|
|
11
|
+
};
|
|
12
|
+
type AuthorizationFlow = {
|
|
13
|
+
key: string;
|
|
14
|
+
label: string;
|
|
15
|
+
methods: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
}>;
|
|
19
|
+
run(session: AuthorizationSession): Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
type HostContext = {
|
|
22
|
+
credentials: CredentialStore;
|
|
23
|
+
authorization: {
|
|
24
|
+
registerFlow(flow: AuthorizationFlow): () => void;
|
|
25
|
+
};
|
|
26
|
+
effect?(callback: () => (() => void) | void): () => void;
|
|
27
|
+
};
|
|
28
|
+
export declare const name = "dsh-muse-code";
|
|
29
|
+
export declare const inject: string[];
|
|
30
|
+
export declare function apply(ctx: HostContext): void;
|
|
31
|
+
export {};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// DeepSeek Harness bundle: Muse Code subscription route + login flow.
|
|
2
|
+
//
|
|
3
|
+
// The route is declared by cordis.patch.yml through a second pi-ai adapter
|
|
4
|
+
// instance. This plugin owns the human sign-in: it runs Meta's device flow,
|
|
5
|
+
// mints the account-bound inference key, stores it under the
|
|
6
|
+
// MUSE_CODE_SUB_TOKEN credential reference, and commits the authorization
|
|
7
|
+
// record the seam requires.
|
|
8
|
+
import { deviceAuthorize, mintKey, pollToken } from "./auth.js";
|
|
9
|
+
export const name = "dsh-muse-code";
|
|
10
|
+
export const inject = ["credentials", "authorization"];
|
|
11
|
+
const FLOW_KEY = "dsh-muse-code/subscription";
|
|
12
|
+
const CREDENTIAL_REF = "MUSE_CODE_SUB_TOKEN";
|
|
13
|
+
export function apply(ctx) {
|
|
14
|
+
const flow = {
|
|
15
|
+
key: FLOW_KEY,
|
|
16
|
+
label: "Muse Code (subscription)",
|
|
17
|
+
methods: [{ id: "device", label: "Sign in with Meta device code" }],
|
|
18
|
+
async run(session) {
|
|
19
|
+
const device = await deviceAuthorize(session.signal);
|
|
20
|
+
const url = device.verification_uri_complete || device.verification_uri;
|
|
21
|
+
session.notify({ message: `Open ${url} and enter code ${device.user_code}`, url });
|
|
22
|
+
const accessToken = await pollToken(device.device_code, device.interval, device.expires_in, session.signal);
|
|
23
|
+
const minted = await mintKey(accessToken, session.signal);
|
|
24
|
+
await ctx.credentials.set(CREDENTIAL_REF, minted.apiKey);
|
|
25
|
+
await ctx.credentials.modifyRecord(FLOW_KEY, async () => ({
|
|
26
|
+
kind: "grant",
|
|
27
|
+
payload: {
|
|
28
|
+
accountId: minted.accountId,
|
|
29
|
+
...(minted.email ? { email: minted.email } : {}),
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
session.notify({ message: `Signed in as ${minted.email || minted.accountId}` });
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const register = () => ctx.authorization.registerFlow(flow);
|
|
36
|
+
if (ctx.effect)
|
|
37
|
+
ctx.effect(register);
|
|
38
|
+
else
|
|
39
|
+
register();
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thestreamcode/dsh-muse-code",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Muse Code subscription model provider and credential login for DeepSeek Harness.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Michael Gasperini",
|
|
8
|
+
"url": "https://mikesoft.it"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/TheStreamCode/dsh-muse-code",
|
|
11
|
+
"repository": "github:TheStreamCode/dsh-muse-code",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/TheStreamCode/dsh-muse-code/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./lib/index.js",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./lib/index.js",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"lib",
|
|
23
|
+
"cordis.patch.yml",
|
|
24
|
+
"README.md",
|
|
25
|
+
"DISCLAIMER.md",
|
|
26
|
+
"AUTH_CONTRACT.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"dsh": {
|
|
30
|
+
"bundle": {
|
|
31
|
+
"patch": "./cordis.patch.yml"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.json",
|
|
39
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
40
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
41
|
+
"prepack": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^26.6.5",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
}
|
|
47
|
+
}
|