opencode-muse 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/LICENSE +21 -0
- package/README.md +121 -2
- package/dist/catalog.d.ts +47 -0
- package/dist/catalog.js +159 -0
- package/dist/errors.d.ts +92 -0
- package/dist/errors.js +181 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +17 -0
- package/dist/model/agent-context.d.ts +10 -0
- package/dist/model/agent-context.js +35 -0
- package/dist/model/context.d.ts +26 -0
- package/dist/model/context.js +87 -0
- package/dist/model/language-model.d.ts +48 -0
- package/dist/model/language-model.js +540 -0
- package/dist/model/meta.d.ts +16 -0
- package/dist/model/meta.js +171 -0
- package/dist/model/prompt.d.ts +68 -0
- package/dist/model/prompt.js +175 -0
- package/dist/model/stream.d.ts +46 -0
- package/dist/model/stream.js +235 -0
- package/dist/msp/client.d.ts +49 -0
- package/dist/msp/client.js +229 -0
- package/dist/msp/host.d.ts +55 -0
- package/dist/msp/host.js +173 -0
- package/dist/provider.d.ts +18 -0
- package/dist/provider.js +112 -0
- package/dist/session/fingerprint.d.ts +9 -0
- package/dist/session/fingerprint.js +83 -0
- package/dist/session/manager.d.ts +40 -0
- package/dist/session/manager.js +353 -0
- package/dist/session/registry.d.ts +55 -0
- package/dist/session/registry.js +383 -0
- package/dist/session/workspace-lock.d.ts +26 -0
- package/dist/session/workspace-lock.js +288 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +1 -0
- package/package.json +58 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pau Garcia Quiles
|
|
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,122 @@
|
|
|
1
|
-
#
|
|
1
|
+
# opencode-muse
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An [OpenCode](https://opencode.ai) plugin that lets OpenCode (and therefore
|
|
4
|
+
[OpenChamber](https://openchamber.dev/)) use a Muse Code subscription with
|
|
5
|
+
auth and inference staying entirely inside the official Muse Code CLI
|
|
6
|
+
(`muse serve`, via `@muse-code/sdk`).
|
|
7
|
+
|
|
8
|
+
No subscription credential is ever extracted, cached, or injected into
|
|
9
|
+
OpenCode.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Add the published package to your project-level `opencode.jsonc` (or
|
|
14
|
+
`opencode.json`):
|
|
15
|
+
|
|
16
|
+
```jsonc
|
|
17
|
+
{
|
|
18
|
+
"$schema": "https://opencode.ai/config.json",
|
|
19
|
+
"plugins": ["opencode-muse@0.1.1"],
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
OpenCode downloads and manages the public package, so users do not need to run
|
|
24
|
+
`npm install` or create an npm account. Sign in with `muse login` (see below),
|
|
25
|
+
restart OpenCode or OpenChamber if it was already running, and choose a Muse
|
|
26
|
+
model.
|
|
27
|
+
|
|
28
|
+
**Compatibility fallback:** Current OpenCode V2 documentation supports package
|
|
29
|
+
entries in `plugins`, but our earlier probes did not observe config-array
|
|
30
|
+
loading on OpenCode 2.1.3; the embedded OpenCode 2.0.32 flow was verified with a
|
|
31
|
+
project-local plugin file. If your OpenCode version ignores the package entry,
|
|
32
|
+
install the package locally and add this file under `.opencode/plugins/`:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm install opencode-muse
|
|
36
|
+
mkdir -p .opencode/plugins
|
|
37
|
+
cat > .opencode/plugins/opencode-muse.js <<'EOF'
|
|
38
|
+
export { default } from "opencode-muse";
|
|
39
|
+
EOF
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The locally packed package passed provider discovery and a text-turn smoke
|
|
43
|
+
test. Muse models and a text turn were also confirmed in OpenChamber after
|
|
44
|
+
restarting it. The verified Muse CLI and SDK version is 1.4.12.
|
|
45
|
+
|
|
46
|
+
## Authentication
|
|
47
|
+
|
|
48
|
+
Sign in through the official Muse Code CLI before using the provider:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
muse login
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
OpenChamber's current provider UI does not expose OpenCode plugin `command`
|
|
55
|
+
authentication methods, so this plugin does not promise a sign-in button. It
|
|
56
|
+
uses the Muse CLI's existing account/configuration through `muse serve`; it
|
|
57
|
+
never reads Muse credential files, exports tokens, or changes subscription
|
|
58
|
+
mode. If the CLI has no usable model catalog, the provider remains a disabled
|
|
59
|
+
setup placeholder rather than advertising a model that will fail on use.
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- Use Muse Code models from OpenCode or OpenChamber after adding the package to
|
|
64
|
+
your OpenCode plugin configuration.
|
|
65
|
+
- Keep Muse sign-in and inference inside the official Muse CLI; the plugin
|
|
66
|
+
never copies or stores your subscription credentials.
|
|
67
|
+
- Continue text conversations in a mapped Muse session, including resuming
|
|
68
|
+
supported sessions and replaying available session updates after reconnects.
|
|
69
|
+
- Attach supported images (PNG, JPEG, GIF, WebP) and videos (MP4, MOV) to a
|
|
70
|
+
Muse turn.
|
|
71
|
+
- Let Muse run its own agent and tools, while the plugin prevents overlapping
|
|
72
|
+
writes to the same workspace.
|
|
73
|
+
|
|
74
|
+
## Current limitations
|
|
75
|
+
|
|
76
|
+
- **Approvals and questions:** OpenCode/OpenChamber cannot yet show Muse's exact
|
|
77
|
+
approval choices or all structured questions. If Muse asks for one, the
|
|
78
|
+
plugin stops safely rather than guessing or approving on your behalf. See
|
|
79
|
+
[#6](https://github.com/pgquiles/opencode-muse/issues/6) and
|
|
80
|
+
[#22](https://github.com/pgquiles/opencode-muse/issues/22).
|
|
81
|
+
- **Files and response formats:** The bridge accepts text, PNG/JPEG/GIF/WebP
|
|
82
|
+
images, and MP4/MOV video. Audio and PDF attachments are not supported.
|
|
83
|
+
Strict JSON output and some OpenCode generation settings (such as
|
|
84
|
+
temperature and token limits) are not supported; requests that require a
|
|
85
|
+
specific OpenCode tool are rejected. Muse's separate CLI transcription and
|
|
86
|
+
JSON-output commands are not exposed as OpenCode features.
|
|
87
|
+
- **Tools and agent instructions:** Muse runs its own agent and tools; OpenCode
|
|
88
|
+
tools are not executed for a Muse turn. Recognized custom-agent instructions
|
|
89
|
+
may be forwarded, but Muse otherwise follows its own workspace rules.
|
|
90
|
+
- **Rewind and session names:** Rewind/fork is unavailable on the currently
|
|
91
|
+
tested Linux Muse runtime, and custom session names may not work. Start a new
|
|
92
|
+
session after rewinding or editing the OpenCode conversation. Rewinding does
|
|
93
|
+
not undo files Muse changed. See
|
|
94
|
+
[#20](https://github.com/pgquiles/opencode-muse/issues/20).
|
|
95
|
+
- **Recovery after missed events:** Basic session resume and cursor replay have
|
|
96
|
+
been tested, but recovery from a real Muse event gap has only been tested
|
|
97
|
+
with a simulated gap. See
|
|
98
|
+
[#23](https://github.com/pgquiles/opencode-muse/issues/23).
|
|
99
|
+
- **Muse CLI compatibility:** The plugin is pinned and verified with Muse CLI
|
|
100
|
+
and SDK 1.4.12. A protocol/schema mismatch fails closed rather than risking
|
|
101
|
+
an incompatible session; broader version-compatibility policy is tracked in
|
|
102
|
+
[#21](https://github.com/pgquiles/opencode-muse/issues/21).
|
|
103
|
+
- **Billing and simultaneous work:** Pay-as-you-go behavior has not yet been
|
|
104
|
+
tested. The plugin also allows only one active writer per workspace, so a
|
|
105
|
+
new turn may be reported as busy while another is still finishing.
|
|
106
|
+
|
|
107
|
+
## Credits and references
|
|
108
|
+
|
|
109
|
+
- [opencode-claude](https://github.com/openchamber/opencode-claude) — a
|
|
110
|
+
reference for OpenCode v2 plugin setup, model/session hooks, bridge
|
|
111
|
+
lifecycle, and the local-proxy fallback. This plugin uses Muse-native tools;
|
|
112
|
+
auth and inference stay in the Muse CLI.
|
|
113
|
+
- [muse-code-sdk](https://github.com/meta-models/muse-code-sdk) — official
|
|
114
|
+
Muse Code SDK and the Muse Session Protocol used by this plugin.
|
|
115
|
+
- [Paseo's Muse Code integration](https://paseo.sh/docs/muse-code) — a
|
|
116
|
+
protocol-level reference implementation.
|
|
117
|
+
|
|
118
|
+
## Related projects
|
|
119
|
+
|
|
120
|
+
- [opencode-muse-auth](https://github.com/TheStreamCode/opencode-muse-auth) is another credential-extraction/direct-API approach; see its [issue #12](https://github.com/TheStreamCode/opencode-muse-auth/issues/12) for its discussion of subscription-term concerns.
|
|
121
|
+
- [opencode-muse-code](https://github.com/swalker326/opencode-muse-code) is an unofficial direct-API alternative. It reads the Muse CLI login, requests an API key from Meta, and sends model requests to Meta's API from OpenCode instead of routing inference through `muse serve` and MSP. Its subscription eligibility is separate from this plugin's CLI-mediated approach and has not been established by this project.
|
|
122
|
+
- [Official Muse Code documentation](https://dev.meta.ai/docs/quickstart#agent-setup) describes using Muse models in OpenCode through the Meta API.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Model } from "@opencode/plugin";
|
|
2
|
+
import type { MuseModelSelection } from "./types.js";
|
|
3
|
+
export declare const MUSE_PROVIDER_ID = "muse";
|
|
4
|
+
export interface MuseCatalogRow {
|
|
5
|
+
providerId: string;
|
|
6
|
+
profileId: string | null;
|
|
7
|
+
modelId: string;
|
|
8
|
+
displayLabel: string;
|
|
9
|
+
contextLimit: number | null;
|
|
10
|
+
outputLimit: number | null;
|
|
11
|
+
releaseDate: string | null;
|
|
12
|
+
isActive: boolean;
|
|
13
|
+
isDefault: boolean;
|
|
14
|
+
defaultReasoningEffort?: string;
|
|
15
|
+
variants?: string[] | "unknown";
|
|
16
|
+
reasoningEffortVariants?: Array<{
|
|
17
|
+
tier: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
}>;
|
|
20
|
+
cost: {
|
|
21
|
+
cached: string;
|
|
22
|
+
currency: string | null;
|
|
23
|
+
input: string;
|
|
24
|
+
output: string;
|
|
25
|
+
} | null;
|
|
26
|
+
}
|
|
27
|
+
export interface MuseCatalogSnapshot {
|
|
28
|
+
providerId: string;
|
|
29
|
+
profileId: string | null;
|
|
30
|
+
source: string;
|
|
31
|
+
models: MuseCatalogRow[];
|
|
32
|
+
}
|
|
33
|
+
export declare const fallbackCatalog: Model.Info[];
|
|
34
|
+
export declare function openCodeModelId(profileId: string | null, modelId: string): string;
|
|
35
|
+
export declare function buildOpenCodeCatalog(museCatalog: MuseCatalogSnapshot | undefined, fallback?: readonly Model.Info[]): Model.Info[];
|
|
36
|
+
export declare function buildMuseModelIndex(museCatalog: MuseCatalogSnapshot): Map<string, MuseModelSelection>;
|
|
37
|
+
export declare class CatalogCache {
|
|
38
|
+
private readonly fallback;
|
|
39
|
+
private currentModels;
|
|
40
|
+
private currentSelectionIndex;
|
|
41
|
+
private live;
|
|
42
|
+
constructor(fallback?: readonly Model.Info[]);
|
|
43
|
+
get models(): Model.Info[];
|
|
44
|
+
get selectionIndex(): ReadonlyMap<string, MuseModelSelection>;
|
|
45
|
+
get hasLiveCatalog(): boolean;
|
|
46
|
+
refresh(fetchCatalog: () => Promise<MuseCatalogSnapshot>, reload: () => Promise<void>): Promise<boolean>;
|
|
47
|
+
}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export const MUSE_PROVIDER_ID = "muse";
|
|
2
|
+
const FALLBACK_ID = "setup-required";
|
|
3
|
+
export const fallbackCatalog = [
|
|
4
|
+
{
|
|
5
|
+
id: FALLBACK_ID,
|
|
6
|
+
modelID: FALLBACK_ID,
|
|
7
|
+
providerID: MUSE_PROVIDER_ID,
|
|
8
|
+
name: "Muse setup required — run muse login, then refresh providers",
|
|
9
|
+
capabilities: { tools: false, input: ["text"], output: ["text"] },
|
|
10
|
+
variants: [],
|
|
11
|
+
time: { released: 0 },
|
|
12
|
+
cost: [],
|
|
13
|
+
status: "deprecated",
|
|
14
|
+
enabled: false,
|
|
15
|
+
limit: { context: 1, output: 1 },
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
export function openCodeModelId(profileId, modelId) {
|
|
19
|
+
const profileKey = profileId === null ? "no-profile" : `profile-${encodeURIComponent(profileId)}`;
|
|
20
|
+
return `muse-${profileKey}-model-${encodeURIComponent(modelId)}`;
|
|
21
|
+
}
|
|
22
|
+
function parseCost(value) {
|
|
23
|
+
if (!/^(?:\d+)(?:\.\d+)?$/.test(value))
|
|
24
|
+
return undefined;
|
|
25
|
+
const parsed = Number(value);
|
|
26
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
27
|
+
}
|
|
28
|
+
function releasedAt(releaseDate) {
|
|
29
|
+
if (releaseDate === null)
|
|
30
|
+
return 0;
|
|
31
|
+
const timestamp = Date.parse(releaseDate);
|
|
32
|
+
return Number.isFinite(timestamp) && timestamp >= 0 ? timestamp : 0;
|
|
33
|
+
}
|
|
34
|
+
function costInfo(row) {
|
|
35
|
+
if (!row.cost || row.cost.currency !== "USD")
|
|
36
|
+
return [];
|
|
37
|
+
const input = parseCost(row.cost.input);
|
|
38
|
+
const output = parseCost(row.cost.output);
|
|
39
|
+
const cached = parseCost(row.cost.cached);
|
|
40
|
+
if (input === undefined || output === undefined || cached === undefined)
|
|
41
|
+
return [];
|
|
42
|
+
return [{ input, output, cache: { read: cached, write: 0 } }];
|
|
43
|
+
}
|
|
44
|
+
function modelInfo(row) {
|
|
45
|
+
const limitsKnown = Number.isInteger(row.contextLimit)
|
|
46
|
+
&& row.contextLimit !== null
|
|
47
|
+
&& row.contextLimit > 0
|
|
48
|
+
&& Number.isInteger(row.outputLimit)
|
|
49
|
+
&& row.outputLimit !== null
|
|
50
|
+
&& row.outputLimit > 0;
|
|
51
|
+
const effortVariants = Array.isArray(row.variants) ? row.variants : [];
|
|
52
|
+
return {
|
|
53
|
+
id: openCodeModelId(row.profileId, row.modelId),
|
|
54
|
+
modelID: row.modelId,
|
|
55
|
+
providerID: MUSE_PROVIDER_ID,
|
|
56
|
+
name: limitsKnown ? row.displayLabel : `${row.displayLabel} (limits unavailable)`,
|
|
57
|
+
capabilities: {
|
|
58
|
+
tools: true,
|
|
59
|
+
input: ["text", "image", "video"],
|
|
60
|
+
output: ["text"],
|
|
61
|
+
},
|
|
62
|
+
variants: effortVariants.map((id) => ({ id, settings: {} })),
|
|
63
|
+
time: { released: releasedAt(row.releaseDate) },
|
|
64
|
+
cost: costInfo(row),
|
|
65
|
+
status: limitsKnown ? "active" : "deprecated",
|
|
66
|
+
enabled: limitsKnown,
|
|
67
|
+
limit: {
|
|
68
|
+
context: limitsKnown ? row.contextLimit : 1,
|
|
69
|
+
output: limitsKnown ? row.outputLimit : 1,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function buildOpenCodeCatalog(museCatalog, fallback = fallbackCatalog) {
|
|
74
|
+
if (museCatalog?.source !== "providerCatalog" || museCatalog.models.length === 0)
|
|
75
|
+
return [...fallback];
|
|
76
|
+
const rows = museCatalog.models.filter((row) => row.providerId.length > 0 && row.modelId.length > 0 && row.displayLabel.length > 0);
|
|
77
|
+
if (rows.length === 0)
|
|
78
|
+
return [...fallback];
|
|
79
|
+
return rows.map(modelInfo);
|
|
80
|
+
}
|
|
81
|
+
export function buildMuseModelIndex(museCatalog) {
|
|
82
|
+
const result = new Map();
|
|
83
|
+
if (museCatalog.source !== "providerCatalog")
|
|
84
|
+
return result;
|
|
85
|
+
for (const row of museCatalog.models) {
|
|
86
|
+
if (!row.providerId || !row.modelId || !row.displayLabel)
|
|
87
|
+
continue;
|
|
88
|
+
result.set(openCodeModelId(row.profileId, row.modelId), {
|
|
89
|
+
providerId: row.providerId,
|
|
90
|
+
profileId: row.profileId,
|
|
91
|
+
modelId: row.modelId,
|
|
92
|
+
isActive: row.isActive,
|
|
93
|
+
isDefault: row.isDefault,
|
|
94
|
+
...(row.defaultReasoningEffort !== undefined ? { defaultReasoningEffort: row.defaultReasoningEffort } : {}),
|
|
95
|
+
variantsKnown: Array.isArray(row.variants),
|
|
96
|
+
variants: Array.isArray(row.variants) ? [...row.variants] : [],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
export class CatalogCache {
|
|
102
|
+
fallback;
|
|
103
|
+
currentModels;
|
|
104
|
+
currentSelectionIndex = new Map();
|
|
105
|
+
live = false;
|
|
106
|
+
constructor(fallback = fallbackCatalog) {
|
|
107
|
+
this.fallback = fallback;
|
|
108
|
+
this.currentModels = [...fallback];
|
|
109
|
+
}
|
|
110
|
+
get models() {
|
|
111
|
+
return this.currentModels;
|
|
112
|
+
}
|
|
113
|
+
get selectionIndex() {
|
|
114
|
+
return this.currentSelectionIndex;
|
|
115
|
+
}
|
|
116
|
+
get hasLiveCatalog() {
|
|
117
|
+
return this.live;
|
|
118
|
+
}
|
|
119
|
+
async refresh(fetchCatalog, reload) {
|
|
120
|
+
let snapshot;
|
|
121
|
+
try {
|
|
122
|
+
snapshot = await fetchCatalog();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (snapshot.source !== "providerCatalog" || snapshot.models.length === 0)
|
|
128
|
+
return false;
|
|
129
|
+
let models;
|
|
130
|
+
let index;
|
|
131
|
+
try {
|
|
132
|
+
models = buildOpenCodeCatalog(snapshot, this.fallback);
|
|
133
|
+
index = buildMuseModelIndex(snapshot);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (index.size === 0 || models.length === this.fallback.length && models.every((model) => model.id === FALLBACK_ID)) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
const previousModels = this.currentModels;
|
|
142
|
+
const previousIndex = this.currentSelectionIndex;
|
|
143
|
+
const previousLive = this.live;
|
|
144
|
+
// Transform callbacks read this cache, so mutate it before provider.reload().
|
|
145
|
+
this.currentModels = models;
|
|
146
|
+
this.currentSelectionIndex = index;
|
|
147
|
+
this.live = true;
|
|
148
|
+
try {
|
|
149
|
+
await reload();
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
this.currentModels = previousModels;
|
|
154
|
+
this.currentSelectionIndex = previousIndex;
|
|
155
|
+
this.live = previousLive;
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export type MuseBridgeErrorCode = "MUSE_HOST_START_FAILED" | "MUSE_STARTUP_TIMEOUT" | "MUSE_COMMAND_TIMEOUT" | "MUSE_REQUEST_TIMEOUT" | "MUSE_COMMAND_REJECTED" | "MUSE_PROTOCOL_ERROR" | "MUSE_TRANSPORT_ERROR" | "MUSE_HOST_CLOSED" | "MUSE_REGISTRY_ERROR" | "MUSE_REGISTRY_STORAGE_ERROR" | "MUSE_WORKSPACE_BUSY" | "MUSE_WORKSPACE_LOCK_CORRUPT" | "MUSE_TURN_FAILED" | "MUSE_TURN_UNQUEUED" | "MUSE_UNSUPPORTED_REQUEST" | "MUSE_UNSUPPORTED_INPUT" | "MUSE_RECOVERY_CHOICE_REQUIRED" | "MUSE_TRANSCRIPT_DIVERGED";
|
|
2
|
+
/** Safe plugin error: never attach raw CLI stderr, MSP text, prompts, or payloads. */
|
|
3
|
+
export declare class MuseBridgeError extends Error {
|
|
4
|
+
readonly code: MuseBridgeErrorCode;
|
|
5
|
+
readonly retryable?: boolean | undefined;
|
|
6
|
+
constructor(code: MuseBridgeErrorCode, message: string, retryable?: boolean | undefined);
|
|
7
|
+
}
|
|
8
|
+
export declare class MspStartupTimeoutError extends MuseBridgeError {
|
|
9
|
+
readonly timeoutMs: number;
|
|
10
|
+
constructor(timeoutMs: number);
|
|
11
|
+
}
|
|
12
|
+
export declare class MspCommandTimeoutError extends MuseBridgeError {
|
|
13
|
+
readonly method: string;
|
|
14
|
+
readonly timeoutMs: number;
|
|
15
|
+
readonly outcomeMayBeUnknown = true;
|
|
16
|
+
constructor(method: string, timeoutMs: number);
|
|
17
|
+
}
|
|
18
|
+
export declare class MspRequestTimeoutError extends MuseBridgeError {
|
|
19
|
+
readonly method: string;
|
|
20
|
+
readonly timeoutMs: number;
|
|
21
|
+
constructor(method: string, timeoutMs: number);
|
|
22
|
+
}
|
|
23
|
+
export declare class MspCommandRejectedError extends MuseBridgeError {
|
|
24
|
+
readonly method: string;
|
|
25
|
+
readonly mspCode: number;
|
|
26
|
+
readonly mspKind: string;
|
|
27
|
+
constructor(method: string, mspCode: number, mspKind: string, retryable?: boolean);
|
|
28
|
+
}
|
|
29
|
+
export declare class MspProtocolError extends MuseBridgeError {
|
|
30
|
+
readonly method?: string | undefined;
|
|
31
|
+
constructor(method?: string | undefined);
|
|
32
|
+
}
|
|
33
|
+
export declare class MspTransportError extends MuseBridgeError {
|
|
34
|
+
readonly method?: string | undefined;
|
|
35
|
+
constructor(method?: string | undefined);
|
|
36
|
+
}
|
|
37
|
+
export declare class MspHostClosedError extends MuseBridgeError {
|
|
38
|
+
constructor();
|
|
39
|
+
}
|
|
40
|
+
export declare class MspHostStartError extends MuseBridgeError {
|
|
41
|
+
readonly reason: "spawn-failed" | "host-exited" | "initialize-rejected" | "unknown";
|
|
42
|
+
constructor(reason: "spawn-failed" | "host-exited" | "initialize-rejected" | "unknown");
|
|
43
|
+
}
|
|
44
|
+
export declare class MspInitializeRejectedError extends MuseBridgeError {
|
|
45
|
+
readonly mspCode: number;
|
|
46
|
+
readonly mspKind: string;
|
|
47
|
+
constructor(mspCode: number, mspKind: string);
|
|
48
|
+
}
|
|
49
|
+
export declare class MspSchemaMismatchError extends MuseBridgeError {
|
|
50
|
+
readonly pinned: string;
|
|
51
|
+
readonly served: string;
|
|
52
|
+
constructor(pinned: string, served: string);
|
|
53
|
+
}
|
|
54
|
+
export declare class RegistryValidationError extends MuseBridgeError {
|
|
55
|
+
constructor();
|
|
56
|
+
}
|
|
57
|
+
export declare class RegistryCorruptError extends MuseBridgeError {
|
|
58
|
+
constructor();
|
|
59
|
+
}
|
|
60
|
+
export declare class RegistryStorageError extends MuseBridgeError {
|
|
61
|
+
constructor();
|
|
62
|
+
}
|
|
63
|
+
export declare class RegistryWorkspaceMismatchError extends MuseBridgeError {
|
|
64
|
+
constructor();
|
|
65
|
+
}
|
|
66
|
+
export declare class WorkspaceBusyError extends MuseBridgeError {
|
|
67
|
+
constructor();
|
|
68
|
+
}
|
|
69
|
+
export declare class WorkspaceLockCorruptError extends MuseBridgeError {
|
|
70
|
+
constructor();
|
|
71
|
+
}
|
|
72
|
+
export declare class MuseTurnFailedError extends MuseBridgeError {
|
|
73
|
+
readonly turnErrorKind: string;
|
|
74
|
+
constructor(turnErrorKind: string, retryable: boolean | undefined);
|
|
75
|
+
}
|
|
76
|
+
export declare class MuseTurnUnqueuedError extends MuseBridgeError {
|
|
77
|
+
constructor();
|
|
78
|
+
}
|
|
79
|
+
export declare class MuseUnsupportedRequestError extends MuseBridgeError {
|
|
80
|
+
readonly requestKind: string;
|
|
81
|
+
constructor(requestKind: string);
|
|
82
|
+
}
|
|
83
|
+
export declare class MuseUnsupportedInputError extends MuseBridgeError {
|
|
84
|
+
readonly inputKind: string;
|
|
85
|
+
constructor(inputKind: string);
|
|
86
|
+
}
|
|
87
|
+
export declare class MuseRecoveryChoiceRequiredError extends MuseBridgeError {
|
|
88
|
+
constructor();
|
|
89
|
+
}
|
|
90
|
+
export declare class MuseTranscriptDivergedError extends MuseBridgeError {
|
|
91
|
+
constructor();
|
|
92
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** Safe plugin error: never attach raw CLI stderr, MSP text, prompts, or payloads. */
|
|
2
|
+
export class MuseBridgeError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
retryable;
|
|
5
|
+
constructor(code, message, retryable) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.retryable = retryable;
|
|
9
|
+
this.name = "MuseBridgeError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class MspStartupTimeoutError extends MuseBridgeError {
|
|
13
|
+
timeoutMs;
|
|
14
|
+
constructor(timeoutMs) {
|
|
15
|
+
super("MUSE_STARTUP_TIMEOUT", `Muse host did not initialize within ${timeoutMs}ms`, true);
|
|
16
|
+
this.timeoutMs = timeoutMs;
|
|
17
|
+
this.name = "MspStartupTimeoutError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class MspCommandTimeoutError extends MuseBridgeError {
|
|
21
|
+
method;
|
|
22
|
+
timeoutMs;
|
|
23
|
+
outcomeMayBeUnknown = true;
|
|
24
|
+
constructor(method, timeoutMs) {
|
|
25
|
+
super("MUSE_COMMAND_TIMEOUT", `Muse command ${method} timed out; its outcome may be unknown`, false);
|
|
26
|
+
this.method = method;
|
|
27
|
+
this.timeoutMs = timeoutMs;
|
|
28
|
+
this.name = "MspCommandTimeoutError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class MspRequestTimeoutError extends MuseBridgeError {
|
|
32
|
+
method;
|
|
33
|
+
timeoutMs;
|
|
34
|
+
constructor(method, timeoutMs) {
|
|
35
|
+
super("MUSE_REQUEST_TIMEOUT", `Muse request ${method} timed out`, true);
|
|
36
|
+
this.method = method;
|
|
37
|
+
this.timeoutMs = timeoutMs;
|
|
38
|
+
this.name = "MspRequestTimeoutError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class MspCommandRejectedError extends MuseBridgeError {
|
|
42
|
+
method;
|
|
43
|
+
mspCode;
|
|
44
|
+
mspKind;
|
|
45
|
+
constructor(method, mspCode, mspKind, retryable) {
|
|
46
|
+
super("MUSE_COMMAND_REJECTED", `Muse rejected command ${method} (${mspKind}, ${mspCode})`, retryable);
|
|
47
|
+
this.method = method;
|
|
48
|
+
this.mspCode = mspCode;
|
|
49
|
+
this.mspKind = mspKind;
|
|
50
|
+
this.name = "MspCommandRejectedError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class MspProtocolError extends MuseBridgeError {
|
|
54
|
+
method;
|
|
55
|
+
constructor(method) {
|
|
56
|
+
super("MUSE_PROTOCOL_ERROR", method ? `Muse protocol error during ${method}` : "Muse protocol error", false);
|
|
57
|
+
this.method = method;
|
|
58
|
+
this.name = "MspProtocolError";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class MspTransportError extends MuseBridgeError {
|
|
62
|
+
method;
|
|
63
|
+
constructor(method) {
|
|
64
|
+
super("MUSE_TRANSPORT_ERROR", method ? `Muse transport failed during ${method}` : "Muse transport failed", true);
|
|
65
|
+
this.method = method;
|
|
66
|
+
this.name = "MspTransportError";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export class MspHostClosedError extends MuseBridgeError {
|
|
70
|
+
constructor() {
|
|
71
|
+
super("MUSE_HOST_CLOSED", "Muse host is closed", false);
|
|
72
|
+
this.name = "MspHostClosedError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export class MspHostStartError extends MuseBridgeError {
|
|
76
|
+
reason;
|
|
77
|
+
constructor(reason) {
|
|
78
|
+
super("MUSE_HOST_START_FAILED", `Muse host failed to start (${reason})`, reason !== "initialize-rejected");
|
|
79
|
+
this.reason = reason;
|
|
80
|
+
this.name = "MspHostStartError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export class MspInitializeRejectedError extends MuseBridgeError {
|
|
84
|
+
mspCode;
|
|
85
|
+
mspKind;
|
|
86
|
+
constructor(mspCode, mspKind) {
|
|
87
|
+
super("MUSE_HOST_START_FAILED", `Muse rejected initialization (${mspKind}, ${mspCode})`, false);
|
|
88
|
+
this.mspCode = mspCode;
|
|
89
|
+
this.mspKind = mspKind;
|
|
90
|
+
this.name = "MspInitializeRejectedError";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export class MspSchemaMismatchError extends MuseBridgeError {
|
|
94
|
+
pinned;
|
|
95
|
+
served;
|
|
96
|
+
constructor(pinned, served) {
|
|
97
|
+
super("MUSE_HOST_START_FAILED", "Muse host schema fingerprint does not match the pinned MSP schema", false);
|
|
98
|
+
this.pinned = pinned;
|
|
99
|
+
this.served = served;
|
|
100
|
+
this.name = "MspSchemaMismatchError";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export class RegistryValidationError extends MuseBridgeError {
|
|
104
|
+
constructor() {
|
|
105
|
+
super("MUSE_REGISTRY_ERROR", "Muse session registry entry is invalid or contains unsupported fields", false);
|
|
106
|
+
this.name = "RegistryValidationError";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export class RegistryCorruptError extends MuseBridgeError {
|
|
110
|
+
constructor() {
|
|
111
|
+
super("MUSE_REGISTRY_ERROR", "Muse session registry entry is corrupt and could not be quarantined safely", false);
|
|
112
|
+
this.name = "RegistryCorruptError";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export class RegistryStorageError extends MuseBridgeError {
|
|
116
|
+
constructor() {
|
|
117
|
+
super("MUSE_REGISTRY_STORAGE_ERROR", "Muse session registry storage operation failed", true);
|
|
118
|
+
this.name = "RegistryStorageError";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export class RegistryWorkspaceMismatchError extends MuseBridgeError {
|
|
122
|
+
constructor() {
|
|
123
|
+
super("MUSE_REGISTRY_ERROR", "OpenCode session is already bound to a different workspace", false);
|
|
124
|
+
this.name = "RegistryWorkspaceMismatchError";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export class WorkspaceBusyError extends MuseBridgeError {
|
|
128
|
+
constructor() {
|
|
129
|
+
super("MUSE_WORKSPACE_BUSY", "Another OpenCode session currently owns this workspace", true);
|
|
130
|
+
this.name = "WorkspaceBusyError";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export class WorkspaceLockCorruptError extends MuseBridgeError {
|
|
134
|
+
constructor() {
|
|
135
|
+
super("MUSE_WORKSPACE_LOCK_CORRUPT", "Workspace lock is corrupt; refusing to assume it is stale", false);
|
|
136
|
+
this.name = "WorkspaceLockCorruptError";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export class MuseTurnFailedError extends MuseBridgeError {
|
|
140
|
+
turnErrorKind;
|
|
141
|
+
constructor(turnErrorKind, retryable) {
|
|
142
|
+
const safeKind = /^[A-Za-z][A-Za-z0-9]{0,63}$/.test(turnErrorKind) ? turnErrorKind : "unknown";
|
|
143
|
+
super("MUSE_TURN_FAILED", `Muse turn failed (${safeKind})`, retryable);
|
|
144
|
+
this.turnErrorKind = safeKind;
|
|
145
|
+
this.name = "MuseTurnFailedError";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export class MuseTurnUnqueuedError extends MuseBridgeError {
|
|
149
|
+
constructor() {
|
|
150
|
+
super("MUSE_TURN_UNQUEUED", "Muse queued turn was reclaimed before it started", false);
|
|
151
|
+
this.name = "MuseTurnUnqueuedError";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export class MuseUnsupportedRequestError extends MuseBridgeError {
|
|
155
|
+
requestKind;
|
|
156
|
+
constructor(requestKind) {
|
|
157
|
+
super("MUSE_UNSUPPORTED_REQUEST", `Muse bridge does not support this request kind (${requestKind})`, false);
|
|
158
|
+
this.requestKind = requestKind;
|
|
159
|
+
this.name = "MuseUnsupportedRequestError";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export class MuseUnsupportedInputError extends MuseBridgeError {
|
|
163
|
+
inputKind;
|
|
164
|
+
constructor(inputKind) {
|
|
165
|
+
super("MUSE_UNSUPPORTED_INPUT", `Muse bridge cannot represent this input kind (${inputKind})`, false);
|
|
166
|
+
this.inputKind = inputKind;
|
|
167
|
+
this.name = "MuseUnsupportedInputError";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export class MuseRecoveryChoiceRequiredError extends MuseBridgeError {
|
|
171
|
+
constructor() {
|
|
172
|
+
super("MUSE_RECOVERY_CHOICE_REQUIRED", "A prior Muse command has an unknown outcome; inspect or resume the session before continuing", false);
|
|
173
|
+
this.name = "MuseRecoveryChoiceRequiredError";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export class MuseTranscriptDivergedError extends MuseBridgeError {
|
|
177
|
+
constructor() {
|
|
178
|
+
super("MUSE_TRANSCRIPT_DIVERGED", "OpenCode history diverged from the mapped Muse session; start a fresh Muse session or recover it explicitly", false);
|
|
179
|
+
this.name = "MuseTranscriptDivergedError";
|
|
180
|
+
}
|
|
181
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode/plugin";
|
|
2
|
+
/** Factory hook required by OpenCode's bundled `aisdk:file://` dynamic provider. */
|
|
3
|
+
export declare function createMuse(): {
|
|
4
|
+
languageModel(_modelID: string): never;
|
|
5
|
+
};
|
|
6
|
+
export declare const MusePlugin: Plugin.Plugin;
|
|
7
|
+
export default MusePlugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createProviderPlugin } from "./provider.js";
|
|
2
|
+
/** Factory hook required by OpenCode's bundled `aisdk:file://` dynamic provider. */
|
|
3
|
+
export function createMuse() {
|
|
4
|
+
return {
|
|
5
|
+
languageModel(_modelID) {
|
|
6
|
+
throw new Error("opencode-muse: language hook did not engage (OpenCode seam drift?)");
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export const MusePlugin = {
|
|
11
|
+
id: "opencode-muse",
|
|
12
|
+
async setup(ctx) {
|
|
13
|
+
const runtime = await createProviderPlugin(ctx, import.meta.url);
|
|
14
|
+
return runtime.dispose;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
export default MusePlugin;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ContextFragment } from "./prompt.js";
|
|
2
|
+
export interface AgentContextResult {
|
|
3
|
+
supported: boolean;
|
|
4
|
+
fragments: ContextFragment[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Forward only a custom agent's bounded prompt prefix. Stock OpenCode prompts,
|
|
8
|
+
* tool catalogs, and unknown system layouts are never copied into Muse input.
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractAllowedAgentContext(agent: string, systemParts: readonly unknown[]): AgentContextResult;
|