mirror of
https://github.com/expo/code-review-cli.git
synced 2026-11-15 03:38:09 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65ffc81014 |
No files matched your search
@@ -763,20 +763,25 @@ fallback key; `ecr doctor` diagnoses setup.
|
||||
} }
|
||||
```
|
||||
|
||||
- **Meta / Muse Spark 1.2 (public Model API).** Set the default model and any
|
||||
model-pinned agent frontmatter to `meta/muse-spark-1.2`. ECR supplies the fixed
|
||||
- **Meta / Muse Spark 1.3 (public Model API).** Set the default model and any
|
||||
model-pinned agent frontmatter to `meta/muse-spark-1.3`. ECR supplies the fixed
|
||||
public Responses endpoint and the `@ai-sdk/openai` adapter; the generic
|
||||
Chat-Completions compatibility adapter is intentionally not used because it
|
||||
loses Muse's reasoning continuity across tool turns. Standard and Contributor
|
||||
model ids are supported.
|
||||
|
||||
```jsonc
|
||||
"model": "meta/muse-spark-1.2",
|
||||
"model": "meta/muse-spark-1.3",
|
||||
"auth": { "providers": {
|
||||
"meta": { "mode": "api-key", "tokenEnv": "META_API_KEY" }
|
||||
} }
|
||||
```
|
||||
|
||||
Standard and Contributor 1.2/1.3 model ids have known capability metadata. Any
|
||||
future model explicitly named as `meta/<model>` is still routed to the fixed Meta
|
||||
endpoint with neutral metadata, so a renamed family such as `meta/muse-foo` does
|
||||
not require an ECR release; Meta validates an unknown id on its first request.
|
||||
|
||||
Create the key in the [Meta AI developer portal](https://developer.meta.com/ai/).
|
||||
Locally, export it as `META_API_KEY`. In each review-running workflow, replace
|
||||
the Anthropic credential line with
|
||||
|
||||
@@ -109,12 +109,15 @@ the Responses API and preserve encrypted reasoning across tool turns. The generi
|
||||
Completions path loses that continuity [observed:
|
||||
`opencode.ts` `META_MODEL_API_BASE_URL` / `META_MUSE_MODELS` / `buildOpencodeConfig`].
|
||||
|
||||
The declared model catalog is an allowlist (`muse-spark-1.2` and
|
||||
`muse-spark-1.2-contributor`), not a reflection of arbitrary config strings. This keeps
|
||||
the existing preflight honest: a misspelled or not-yet-supported Meta model remains
|
||||
absent from the server's provider model list and fails once before any pass. Both models
|
||||
declare their public context/output limits, multimodal inputs, high reasoning, and
|
||||
encrypted reasoning inclusion [observed: `opencode.ts` `META_MUSE_MODELS`].
|
||||
Provider selection is forward-compatible at the provider boundary, not coupled to the
|
||||
current Muse family name: every referenced non-empty `meta/<model>` id is declared on
|
||||
the synthesized Meta provider. Known Muse Spark 1.2 and 1.3 standard/Contributor ids
|
||||
receive their published limits, multimodal inputs, high reasoning, and encrypted
|
||||
reasoning inclusion from `META_MUSE_MODELS`; a future id such as `meta/muse-foo` is
|
||||
declared with neutral metadata rather than guessed capabilities. This deliberately
|
||||
moves validation of an unknown-but-well-formed Meta id from OpenCode's local preflight
|
||||
to its first Meta request, in exchange for new Meta names working without an ECR
|
||||
release [observed: `opencode.ts` `META_MUSE_MODELS` / `buildOpencodeConfig`].
|
||||
|
||||
## Claude Code CLI Containment
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ The three workflows share a posture built entirely around one fact: a reviewer j
|
||||
**Concurrency defaults by auth mode.** The commented `chunk.concurrency` default is 6 for an API key and 3 for a subscription (OAuth) credential, "one account handles many parallel streams poorly, and several PRs may review on the same credential at once" [observed: `templates/config.jsonc:28-30`].
|
||||
|
||||
**Muse Spark is opt-in without weakening the default secret surface.** The root config
|
||||
documents `meta/muse-spark-1.2` plus a `META_API_KEY` auth entry, and each
|
||||
documents `meta/muse-spark-1.3` plus a `META_API_KEY` auth entry, and each
|
||||
review-running workflow shows the corresponding secret line commented out. It is not
|
||||
forwarded by the default scaffold: adopters replace the Anthropic line and set
|
||||
`ECR_EXPECTED_TOKEN_ENV=META_API_KEY`, so the model process receives only the
|
||||
|
||||
@@ -342,10 +342,10 @@ test("an upstream-alias auth entry synthesizes a provider block with exactly the
|
||||
expect(opencode.provider?.openai).toBeUndefined();
|
||||
});
|
||||
|
||||
test("Meta auth synthesizes the public Muse Responses provider without embedding its key", () => {
|
||||
test("Meta auth synthesizes the public Muse 1.3 Responses provider without embedding its key", () => {
|
||||
const config = configWith({
|
||||
agents: [{ id: "correctness", model: "meta/muse-spark-1.2" }],
|
||||
coordinatorModel: "meta/muse-spark-1.2",
|
||||
agents: [{ id: "correctness", model: "meta/muse-spark-1.3" }],
|
||||
coordinatorModel: "meta/muse-spark-1.3-contributor",
|
||||
auth: [{ mode: "api-key", provider: "meta", tokenEnv: "META_API_KEY" }],
|
||||
});
|
||||
const opencode = buildOpencodeConfig(config) as {
|
||||
@@ -354,7 +354,14 @@ test("Meta auth synthesizes the public Muse Responses provider without embedding
|
||||
{
|
||||
npm: string;
|
||||
options: { baseURL: string; apiKey: string };
|
||||
models: Record<string, { reasoning?: boolean; options?: Record<string, unknown> }>;
|
||||
models: Record<
|
||||
string,
|
||||
{
|
||||
reasoning?: boolean;
|
||||
limit?: { context: number; output: number };
|
||||
options?: Record<string, unknown>;
|
||||
}
|
||||
>;
|
||||
}
|
||||
>;
|
||||
};
|
||||
@@ -365,12 +372,17 @@ test("Meta auth synthesizes the public Muse Responses provider without embedding
|
||||
baseURL: META_MODEL_API_BASE_URL,
|
||||
apiKey: "{env:META_API_KEY}",
|
||||
});
|
||||
expect(Object.keys(meta!.models)).toEqual(["muse-spark-1.2"]);
|
||||
expect(meta!.models["muse-spark-1.2"]?.reasoning).toBe(true);
|
||||
expect(meta!.models["muse-spark-1.2"]?.options).toMatchObject({
|
||||
expect(Object.keys(meta!.models)).toEqual(["muse-spark-1.3", "muse-spark-1.3-contributor"]);
|
||||
expect(meta!.models["muse-spark-1.3"]?.reasoning).toBe(true);
|
||||
expect(meta!.models["muse-spark-1.3"]?.limit).toEqual({
|
||||
context: 1_048_576,
|
||||
output: 1_048_576,
|
||||
});
|
||||
expect(meta!.models["muse-spark-1.3"]?.options).toMatchObject({
|
||||
reasoningEffort: "high",
|
||||
include: ["reasoning.encrypted_content"],
|
||||
});
|
||||
expect(meta!.models["muse-spark-1.3-contributor"]?.reasoning).toBe(true);
|
||||
});
|
||||
|
||||
test("a Meta-named upstream alias does not redirect the upstream credential to Meta", () => {
|
||||
@@ -400,25 +412,22 @@ test("a Meta-named upstream alias does not redirect the upstream credential to M
|
||||
expect(Object.keys(alias!.models)).toEqual(["muse-spark-1.2"]);
|
||||
});
|
||||
|
||||
test("Meta provider does not register an unknown Muse id, so preflight can reject it", () => {
|
||||
test("Meta provider registers future model families neutrally and only for the meta prefix", () => {
|
||||
const config = configWith({
|
||||
agents: [{ id: "correctness", model: "meta/muse-spark-typo" }],
|
||||
coordinatorModel: "meta/muse-spark-typo",
|
||||
agents: [
|
||||
{ id: "correctness", model: "meta/muse-foo" },
|
||||
{ id: "security", model: "openai/muse-foo" },
|
||||
],
|
||||
coordinatorModel: "meta/a-future-family-2",
|
||||
auth: [{ mode: "api-key", provider: "meta", tokenEnv: "META_API_KEY" }],
|
||||
});
|
||||
const opencode = buildOpencodeConfig(config) as {
|
||||
provider?: Record<string, { models: Record<string, unknown> }>;
|
||||
};
|
||||
expect(opencode.provider?.meta?.models).toEqual({});
|
||||
expect(
|
||||
findUnknownModels(["meta/muse-spark-typo"], { meta: ["muse-spark-1.2"] }, ["meta"]),
|
||||
).toEqual([
|
||||
{
|
||||
model: "meta/muse-spark-typo",
|
||||
reason: "model",
|
||||
suggestions: ["muse-spark-1.2"],
|
||||
},
|
||||
]);
|
||||
expect(opencode.provider?.meta?.models).toEqual({
|
||||
"muse-foo": {},
|
||||
"a-future-family-2": {},
|
||||
});
|
||||
});
|
||||
|
||||
test("buildOpencodeConfig registers the no-tools stack verifier (empty tool list)", () => {
|
||||
|
||||
+46
-8
@@ -20,10 +20,10 @@ import type { ResearchMcpRuntime } from "./research.js";
|
||||
export const CLAUDE_CODE_ENGINE = "claude-code" as const;
|
||||
|
||||
/** Fixed so repo config cannot redirect the Meta credential. */
|
||||
// @ref LLP 0003#muse-spark-via-meta-model-api [implements] — fixed endpoint and model allowlist
|
||||
// @ref LLP 0003#muse-spark-via-meta-model-api [implements] — fixed endpoint, provider routing, and known-model metadata
|
||||
export const META_MODEL_API_BASE_URL = "https://api.meta.ai/v1";
|
||||
|
||||
/** Supported public Muse models. Unknown ids remain visible to preflight. */
|
||||
/** Known public Muse models. Other explicit `meta/...` ids use neutral metadata. */
|
||||
export const META_MUSE_MODELS: Record<string, Record<string, unknown>> = {
|
||||
"muse-spark-1.2": {
|
||||
name: "Muse Spark 1.2",
|
||||
@@ -55,6 +55,36 @@ export const META_MUSE_MODELS: Record<string, Record<string, unknown>> = {
|
||||
include: ["reasoning.encrypted_content"],
|
||||
},
|
||||
},
|
||||
"muse-spark-1.3": {
|
||||
name: "Muse Spark 1.3",
|
||||
reasoning: true,
|
||||
limit: { context: 1_048_576, output: 1_048_576 },
|
||||
modalities: {
|
||||
input: ["text", "image", "pdf", "video"],
|
||||
output: ["text"],
|
||||
},
|
||||
options: {
|
||||
store: false,
|
||||
reasoningEffort: "high",
|
||||
reasoningSummary: "auto",
|
||||
include: ["reasoning.encrypted_content"],
|
||||
},
|
||||
},
|
||||
"muse-spark-1.3-contributor": {
|
||||
name: "Muse Spark 1.3 Contributor",
|
||||
reasoning: true,
|
||||
limit: { context: 1_048_576, output: 1_048_576 },
|
||||
modalities: {
|
||||
input: ["text", "image", "pdf", "video"],
|
||||
output: ["text"],
|
||||
},
|
||||
options: {
|
||||
store: false,
|
||||
reasoningEffort: "high",
|
||||
reasoningSummary: "auto",
|
||||
include: ["reasoning.encrypted_content"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -265,7 +295,19 @@ export function buildOpencodeConfig(
|
||||
entry.tokenEnv &&
|
||||
!entry.upstream
|
||||
) {
|
||||
// Muse needs the Responses adapter; openai-compatible uses Chat Completions.
|
||||
// Meta's Model API uses the Responses adapter; openai-compatible uses Chat
|
||||
// Completions. Register every explicitly referenced `meta/…` model so a new
|
||||
// Meta family/name does not require an ECR release merely to select the right
|
||||
// provider. Known Muse ids get their published capabilities; unknown ids stay
|
||||
// neutral and are validated by Meta on their first request.
|
||||
const models: Record<string, unknown> = {};
|
||||
for (const id of referencedModels) {
|
||||
const slash = id.indexOf("/");
|
||||
if (slash > 0 && id.slice(0, slash) === "meta" && id.length > slash + 1) {
|
||||
const model = id.slice(slash + 1);
|
||||
models[model] = META_MUSE_MODELS[model] ?? {};
|
||||
}
|
||||
}
|
||||
provider.meta = {
|
||||
npm: "@ai-sdk/openai",
|
||||
name: "Meta Model API",
|
||||
@@ -273,11 +315,7 @@ export function buildOpencodeConfig(
|
||||
baseURL: META_MODEL_API_BASE_URL,
|
||||
apiKey: `{env:${entry.tokenEnv}}`,
|
||||
},
|
||||
models: Object.fromEntries(
|
||||
Object.entries(META_MUSE_MODELS).filter(([model]) =>
|
||||
referencedModels.includes(`meta/${model}`),
|
||||
),
|
||||
),
|
||||
models,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
// "auth": { "mode": "api-key", "provider": "openai", "tokenEnv": "OPENAI_API_KEY" }
|
||||
//
|
||||
// Muse Spark via the public Meta Model API Responses endpoint:
|
||||
// set models to `meta/muse-spark-1.2` and configure:
|
||||
// set models to `meta/muse-spark-1.3` and configure:
|
||||
// "auth": { "providers": {
|
||||
// "meta": { "mode": "api-key", "tokenEnv": "META_API_KEY" }
|
||||
// } }
|
||||
|
||||
Reference in new issue
Block a user