Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# openclaw-muse-code

Muse Spark in OpenClaw, billed to the **Muse Code monthly subscription**
Muse Spark + Muse Wonder in OpenClaw, billed to the **Muse Code monthly subscription**
instead of API credits. Meta device-code login, no API key, no
third-party CLI.

Expand All @@ -23,6 +23,10 @@ plugins can be installed side by side.
| `muse-code/muse-spark-1.2` | Standard | 1,048,576 |
| `muse-code/muse-spark-1.2-contributor` | Contributor | 1,048,576 |
| `muse-code/muse-spark-1.1` | Standard | 1,048,576 |
| `muse-code/muse-wonder-1.0` | Standard | 1,048,576 |
| `muse-code/muse-wonder-1.0-contributor` | Contributor | 1,048,576 |
| `muse-code/muse-wonder-full-1.0` | Standard | 1,048,576 |
| `muse-code/muse-wonder-full-1.0-contributor` | Contributor | 1,048,576 |

> [!WARNING]
> Meta's [Terms of Service](https://dev.meta.ai/legal/terms-of-service)
Expand All @@ -34,7 +38,7 @@ plugins can be installed side by side.
> implicitly: the setup default is the standard `muse-spark-1.3`.

Model discovery is **live**: the catalog is fetched from
`GET {baseUrl}/models` (60s in-memory TTL, only `muse-spark-*` ids
`GET {baseUrl}/models` (60s in-memory TTL, only `muse-spark-*` and `muse-wonder-*` ids
admitted — other endpoint families such as `sam-*`, `muse-image-*` or
`muse-voice-*` are not chat models), so newly published models appear
automatically with no plugin update. The static table above is the
Expand Down
8 changes: 4 additions & 4 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Muse Spark model provider plugin for OpenClaw, billed to the Muse Code
* Muse Spark + Muse Wonder model provider plugin for OpenClaw, billed to the Muse Code
* monthly subscription instead of API credits.
*
* Authentication is a Meta device-code login performed once via
Expand Down Expand Up @@ -27,7 +27,7 @@ if (!process.env[ENV_VAR]?.trim()) {
/** Meta Model API base URL for Responses API traffic. */
const META_BASE_URL = "https://api.meta.ai/v1";
/**
* Known Muse Spark context window (official catalog: input + output share
* Known Muse family context window (official catalog: input + output share
* the budget). Used as the default when a row omits context_length.
*/
const MUSE_CONTEXT_WINDOW = 1_048_576;
Expand All @@ -39,7 +39,7 @@ const MAX_OUTPUT_TOKENS = 131_072;
/** Default model for setup flows: standard tier, never contributor. */
export const DEFAULT_MODEL_ID = "muse-spark-1.3";
/**
* Maps a Muse Spark baseline row to an OpenClaw model definition.
* Maps a Muse Spark / Muse Wonder baseline row to an OpenClaw model definition.
*
* Every model uses the Responses API transport. Contributor-tier variants
* keep working when selected explicitly (see README for the training-data
Expand Down Expand Up @@ -134,7 +134,7 @@ export function resolveMuseCodeDynamicModel(ctx) {
export default defineSingleProviderPluginEntry({
id: "muse-code",
name: "Muse Code",
description: "Muse Spark (api.meta.ai) model provider billed to the Muse Code monthly subscription.",
description: "Muse Spark + Muse Wonder (api.meta.ai) model provider billed to the Muse Code monthly subscription.",
provider: {
label: "Muse Code",
docsPath: "/providers/muse-code",
Expand Down
18 changes: 17 additions & 1 deletion dist/src/baseline.models.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Static Muse Spark model catalog.
* Static Muse Spark + Muse Wonder model catalog.
*
* Hand-maintained (there is no public unauthenticated catalog endpoint for
* the Meta Model API): model ids and context windows mirror the official
Expand All @@ -21,4 +21,20 @@ export const museCodeBaselineModels = [
context_length: 1048576,
},
{ id: "muse-spark-1.1", name: "Muse Spark 1.1", context_length: 1048576 },
{ id: "muse-wonder-1.0", name: "Muse Wonder 1.0", context_length: 1048576 },
{
id: "muse-wonder-1.0-contributor",
name: "Muse Wonder 1.0 Contributor",
context_length: 1048576,
},
{
id: "muse-wonder-full-1.0",
name: "Muse Wonder Full 1.0",
context_length: 1048576,
},
{
id: "muse-wonder-full-1.0-contributor",
name: "Muse Wonder Full 1.0 Contributor",
context_length: 1048576,
},
];
8 changes: 4 additions & 4 deletions dist/src/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { readCacheSync } from "./auth.js";
export const MODELS_PATH = "/models";
export const LIVE_CATALOG_TTL_MS = 60_000;
export const FETCH_TIMEOUT_MS = 8_000;
// Allowlist: this provider serves the Muse Spark chat family. The live
// Allowlist: this provider serves the Muse Spark + Muse Wonder chat families. The live
// endpoint also returns other families (sam-*, muse-image-*, muse-voice-*)
// that are not usable as chat/completions models — a denylist would leak
// the next such family, so only muse-spark-* is admitted. Anything else
// the next such family, so only muse-spark-* and muse-wonder-* are admitted. Anything else
// still resolves through the dynamic resolver if requested explicitly.
const CHAT_FAMILY_PREFIX = "muse-spark-";
const CHAT_FAMILY_PREFIXES = ["muse-spark-", "muse-wonder-"];
let cache = null;
/** Test-only cache reset. */
export function __clearLiveCatalogCache() {
Expand All @@ -39,7 +39,7 @@ function parseModelIds(body) {
(row.id.length > 0));
})
.map((row) => row.id)
.filter((id) => id.startsWith(CHAT_FAMILY_PREFIX));
.filter((id) => CHAT_FAMILY_PREFIXES.some((p) => id.startsWith(p)));
return ids.length > 0 ? ids : null;
}
/**
Expand Down
8 changes: 4 additions & 4 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Muse Spark model provider plugin for OpenClaw, billed to the Muse Code
* Muse Spark + Muse Wonder model provider plugin for OpenClaw, billed to the Muse Code
* monthly subscription instead of API credits.
*
* Authentication is a Meta device-code login performed once via
Expand Down Expand Up @@ -37,7 +37,7 @@ if (!process.env[ENV_VAR]?.trim()) {
const META_BASE_URL = "https://api.meta.ai/v1";

/**
* Known Muse Spark context window (official catalog: input + output share
* Known Muse family context window (official catalog: input + output share
* the budget). Used as the default when a row omits context_length.
*/
const MUSE_CONTEXT_WINDOW = 1_048_576;
Expand All @@ -59,7 +59,7 @@ export type MuseCodeModelRow = {
};

/**
* Maps a Muse Spark baseline row to an OpenClaw model definition.
* Maps a Muse Spark / Muse Wonder baseline row to an OpenClaw model definition.
*
* Every model uses the Responses API transport. Contributor-tier variants
* keep working when selected explicitly (see README for the training-data
Expand Down Expand Up @@ -170,7 +170,7 @@ export default defineSingleProviderPluginEntry({
id: "muse-code",
name: "Muse Code",
description:
"Muse Spark (api.meta.ai) model provider billed to the Muse Code monthly subscription.",
"Muse Spark + Muse Wonder (api.meta.ai) model provider billed to the Muse Code monthly subscription.",
provider: {
label: "Muse Code",
docsPath: "/providers/muse-code",
Expand Down
46 changes: 45 additions & 1 deletion openclaw.plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "muse-code",
"name": "Muse Code",
"description": "Muse Spark (api.meta.ai) model provider plugin billed to the Muse Code monthly subscription.",
"description": "Muse Spark + Muse Wonder (api.meta.ai) model provider plugin billed to the Muse Code monthly subscription.",
"providers": ["muse-code"],
"modelSupport": {
"modelPrefixes": ["muse-code/"]
Expand Down Expand Up @@ -95,6 +95,50 @@
"contextWindow": 1048576,
"maxTokens": 131072,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "muse-wonder-1.0",
"name": "Muse Wonder 1.0",
"api": "openai-responses",
"baseUrl": "https://api.meta.ai/v1",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 1048576,
"maxTokens": 131072,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "muse-wonder-1.0-contributor",
"name": "Muse Wonder 1.0 Contributor",
"api": "openai-responses",
"baseUrl": "https://api.meta.ai/v1",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 1048576,
"maxTokens": 131072,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "muse-wonder-full-1.0",
"name": "Muse Wonder Full 1.0",
"api": "openai-responses",
"baseUrl": "https://api.meta.ai/v1",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 1048576,
"maxTokens": 131072,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "muse-wonder-full-1.0-contributor",
"name": "Muse Wonder Full 1.0 Contributor",
"api": "openai-responses",
"baseUrl": "https://api.meta.ai/v1",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 1048576,
"maxTokens": 131072,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@thestreamcode/openclaw-muse-code",
"version": "0.2.0",
"description": "Muse Spark in OpenClaw billed to the Muse Code monthly subscription (Meta device login, no API key)",
"version": "0.3.0",
"description": "Muse Spark + Muse Wonder in OpenClaw billed to the Muse Code monthly subscription (Meta device login, no API key)",
"type": "module",
"license": "MIT",
"private": false,
Expand Down Expand Up @@ -37,6 +37,7 @@
"openclaw-plugin",
"muse-code",
"muse-spark",
"muse-wonder",
"meta-ai",
"llm",
"provider"
Expand Down
18 changes: 17 additions & 1 deletion src/baseline.models.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Static Muse Spark model catalog.
* Static Muse Spark + Muse Wonder model catalog.
*
* Hand-maintained (there is no public unauthenticated catalog endpoint for
* the Meta Model API): model ids and context windows mirror the official
Expand Down Expand Up @@ -28,4 +28,20 @@ export const museCodeBaselineModels: MuseCodeBaselineRow[] = [
context_length: 1048576,
},
{ id: "muse-spark-1.1", name: "Muse Spark 1.1", context_length: 1048576 },
{ id: "muse-wonder-1.0", name: "Muse Wonder 1.0", context_length: 1048576 },
{
id: "muse-wonder-1.0-contributor",
name: "Muse Wonder 1.0 Contributor",
context_length: 1048576,
},
{
id: "muse-wonder-full-1.0",
name: "Muse Wonder Full 1.0",
context_length: 1048576,
},
{
id: "muse-wonder-full-1.0-contributor",
name: "Muse Wonder Full 1.0 Contributor",
context_length: 1048576,
},
];
8 changes: 4 additions & 4 deletions src/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export const MODELS_PATH = "/models";
export const LIVE_CATALOG_TTL_MS = 60_000;
export const FETCH_TIMEOUT_MS = 8_000;

// Allowlist: this provider serves the Muse Spark chat family. The live
// Allowlist: this provider serves the Muse Spark + Muse Wonder chat families. The live
// endpoint also returns other families (sam-*, muse-image-*, muse-voice-*)
// that are not usable as chat/completions models — a denylist would leak
// the next such family, so only muse-spark-* is admitted. Anything else
// the next such family, so only muse-spark-* and muse-wonder-* are admitted. Anything else
// still resolves through the dynamic resolver if requested explicitly.
const CHAT_FAMILY_PREFIX = "muse-spark-";
const CHAT_FAMILY_PREFIXES = ["muse-spark-", "muse-wonder-"];

type ModelsResponse = {
data?: unknown;
Expand Down Expand Up @@ -55,7 +55,7 @@ function parseModelIds(body: unknown): string[] | null {
);
})
.map((row) => row.id)
.filter((id) => id.startsWith(CHAT_FAMILY_PREFIX));
.filter((id) => CHAT_FAMILY_PREFIXES.some((p) => id.startsWith(p)));
return ids.length > 0 ? ids : null;
}

Expand Down
16 changes: 10 additions & 6 deletions test/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,22 @@ const LIVE_OK = {
data: [
{ id: "muse-spark-1.3" },
{ id: "muse-spark-9.9" },
{ id: "muse-wonder-1.0" },
{ id: "muse-wonder-full-1.0" },
{ id: "sam-3.1" },
{ id: "muse-image-1.0" },
{ id: "muse-voice-2.0" },
],
};

describe("fetchLiveModelIds", () => {
it("admits only the muse-spark chat family", async () => {
it("admits only the muse-spark + muse-wonder chat families", async () => {
stubFetch(() => ({ status: 200, body: LIVE_OK }));
expect(await fetchLiveModelIds("https://x.test/v1", "k")).toEqual([
"muse-spark-1.3",
"muse-spark-9.9",
"muse-wonder-1.0",
"muse-wonder-full-1.0",
]);
});

Expand Down Expand Up @@ -93,21 +97,21 @@ describe("fetchLiveModelIds", () => {
describe("getLiveModelIdsCached", () => {
it("serves warm entries without refetching", async () => {
const counter = stubFetch(() => ({ status: 200, body: LIVE_OK }));
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 60_000)).toHaveLength(2);
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 60_000)).toHaveLength(2);
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 60_000)).toHaveLength(4);
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 60_000)).toHaveLength(4);
expect(counter.calls()).toBe(1);
});

it("refetches after expiry", async () => {
const counter = stubFetch(() => ({ status: 200, body: LIVE_OK }));
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 0)).toHaveLength(2);
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 60_000)).toHaveLength(2);
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 0)).toHaveLength(4);
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 60_000)).toHaveLength(4);
expect(counter.calls()).toBe(2);
});

it("clears stale entries on failure", async () => {
stubFetch(() => ({ status: 200, body: LIVE_OK }));
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 60_000)).toHaveLength(2);
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 60_000)).toHaveLength(4);
stubFetch(() => ({ status: 500, body: {} }));
__clearLiveCatalogCache();
expect(await getLiveModelIdsCached("https://x.test/v1", "k", 60_000)).toBeNull();
Expand Down
6 changes: 5 additions & 1 deletion test/modelMapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,18 @@ describe("projectModel", () => {
});

describe("static baseline (pre-credential discovery)", () => {
it("ships the five-model Muse Spark family", () => {
it("ships the nine-model Muse Spark + Wonder family", () => {
const ids = museCodeBaselineModels.map((r) => r.id);
expect(ids).toEqual([
"muse-spark-1.3",
"muse-spark-1.3-contributor",
"muse-spark-1.2",
"muse-spark-1.2-contributor",
"muse-spark-1.1",
"muse-wonder-1.0",
"muse-wonder-1.0-contributor",
"muse-wonder-full-1.0",
"muse-wonder-full-1.0-contributor",
]);
});

Expand Down