mirror of
https://github.com/Heretek-AI/opencode-4-openclaude.git
synced 2026-11-07 02:45:58 -10:00
feat: unlock 1M context window and configurable reasoning effort for Muse Spark 1.3
This commit is contained in:
1 parent
11cf9c0066
commit
c138a9c8f3
9 files changed
+171
-13
No files matched your search
@@ -5,6 +5,8 @@ A standalone plugin and native provider bridge for [OpenClaude](https://github.c
|
||||
Features:
|
||||
- **Authentic OpenCode v2 Harness Reverse Engineering**: Generates authentic 30-character descending session identifiers (`ses_<timestamp_base62>`), client affinity tracking, and full harness request headers (`User-Agent`, `x-opencode-client`, `x-opencode-session`, `x-opencode-project`, `x-session-affinity`).
|
||||
- **Seamless Protocol Translation**: OpenClaude talks standard OpenAI `/v1/chat/completions`. Upstream models like **`muse-spark-1.3-contributor`** require OpenAI `/responses` API. The bridge transparently translates requests and bi-directionally streams Server-Sent Events (SSE).
|
||||
- **1M Native Context Window**: Automatically configures OpenClaude for the full 1,048,576 token context window on `muse-spark-1.3-contributor` instead of falling back to 128K.
|
||||
- **Configurable Reasoning Effort**: Unlocks OpenClaude's native `/effort` selector (`low`, `medium`, `high`, `xhigh`) and translates effort parameters into upstream OpenCode Go reasoning structures.
|
||||
- **Target Default Model**: Defaults to **`muse-spark-1.3-contributor`** with verified support for **`space-bunny`** and all 36+ OpenCode Go models.
|
||||
- **Model Name Normalizer**: Strips any client prefixes (`opencode-unlocked-*`, `opencode-go-*`) to prevent upstream rejection.
|
||||
- **Zero-Secret Distribution**: The repository contains no hardcoded keys or `.env` files. Users provide their key via environment variable, interactive command, or local config.
|
||||
@@ -88,12 +90,38 @@ Manage the background bridge daemon using `./bin/opencode-unlocked`:
|
||||
# Switch active default model
|
||||
./bin/opencode-unlocked switch space-bunny
|
||||
|
||||
# View or set reasoning effort (low, medium, high, xhigh)
|
||||
./bin/opencode-unlocked effort high
|
||||
|
||||
# Run live connectivity verification
|
||||
./bin/opencode-unlocked test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1M Context Window & Reasoning Effort
|
||||
|
||||
### 1,048,576 Token Context Window
|
||||
OpenCode Go's `muse-spark-1.3-contributor` natively supports a **1M (1,048,576 tokens)** context window. Normally, OpenClaude falls back to a 128,000 token limit on unknown OpenAI-compatible endpoints.
|
||||
|
||||
Opencode Unlocked automatically configures:
|
||||
- OpenClaude provider limits (`maxContextLength: 1048576`)
|
||||
- `CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS` environment mapping
|
||||
- OpenClaude `modelLimits` in `settings.json`
|
||||
|
||||
### Multi-Level Reasoning Effort
|
||||
OpenCode Go `/responses` models support four levels of reasoning: `low`, `medium`, `high`, and `xhigh`.
|
||||
|
||||
You can adjust reasoning effort in two ways:
|
||||
1. **In OpenClaude Interactively**:
|
||||
Use the native `/effort` slash command or the interactive picker to select `low`, `medium`, `high`, or `xhigh`.
|
||||
2. **Via Plugin CLI**:
|
||||
```bash
|
||||
./bin/opencode-unlocked effort medium
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Supported Models
|
||||
|
||||
All 36+ OpenCode Go models are available, including:
|
||||
|
||||
+26
-2
@@ -109,6 +109,8 @@ async function printStatus() {
|
||||
console.log(`Gateway Status : ${running ? "🟢 ONLINE" : "🔴 OFFLINE"}`);
|
||||
console.log(`Gateway URL : http://${CONFIG.HOST}:${CONFIG.PORT}/v1`);
|
||||
console.log(`Default Model : ${config.defaultModel || CONFIG.DEFAULT_MODEL}`);
|
||||
console.log(`Context Window : 1,048,576 tokens (1M native)`);
|
||||
console.log(`Reasoning Effort: ${config.reasoningEffort || "auto (low, medium, high, xhigh supported)"}`);
|
||||
console.log(`API Key Loaded : ${apiKey ? `✔ Configured (${apiKey.slice(0, 8)}...)` : "❌ Missing"}`);
|
||||
console.log(`Config Path : ${getUserConfigPath()}`);
|
||||
|
||||
@@ -156,6 +158,23 @@ async function setModel(modelName) {
|
||||
console.log(`✔ Default model updated to: ${modelName}`);
|
||||
}
|
||||
|
||||
async function setEffort(effortLevel) {
|
||||
if (!effortLevel) {
|
||||
const config = loadUserConfig();
|
||||
console.log(`Current reasoning effort: ${config.reasoningEffort || "auto (model default)"}`);
|
||||
console.log("Supported levels: low, medium, high, xhigh, auto");
|
||||
return;
|
||||
}
|
||||
const valid = ["low", "medium", "high", "xhigh", "auto"];
|
||||
const normalized = effortLevel.toLowerCase();
|
||||
if (!valid.includes(normalized)) {
|
||||
console.error(`Invalid effort level "${effortLevel}". Valid options: ${valid.join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
saveUserConfig({ reasoningEffort: normalized });
|
||||
console.log(`✔ Default reasoning effort updated to: ${normalized}`);
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
console.log("Running live verification suite...");
|
||||
const child = spawn(process.execPath, [path.join(ROOT_DIR, "test", "live-models.test.mjs")], {
|
||||
@@ -192,6 +211,10 @@ switch (command) {
|
||||
case "set-model":
|
||||
await setModel(args[1]);
|
||||
break;
|
||||
case "effort":
|
||||
case "reasoning":
|
||||
await setEffort(args[1]);
|
||||
break;
|
||||
case "test":
|
||||
await runTests();
|
||||
break;
|
||||
@@ -210,12 +233,13 @@ Commands:
|
||||
start Start the local bridge daemon
|
||||
stop Stop the running daemon
|
||||
restart Restart the daemon
|
||||
status Display daemon health, active model, and token usage
|
||||
status Display daemon health, active model, context window, and token usage
|
||||
models List all 36+ available OpenCode Go models
|
||||
switch <model> Change default model (e.g. space-bunny, muse-spark-1.3-contributor)
|
||||
effort [level] View or set reasoning effort (low, medium, high, xhigh, auto)
|
||||
set-key <key> Save OpenCode API key securely to user configuration
|
||||
test Run live verification on muse-spark and space-bunny
|
||||
setup Configure OpenClaude provider profile to use this gateway
|
||||
setup Configure OpenClaude provider profile to use this gateway (1M context & effort enabled)
|
||||
help Show this help dialog
|
||||
`);
|
||||
break;
|
||||
|
||||
+43
-3
@@ -41,6 +41,7 @@ export function configureOpenClaudeProfile(options = {}) {
|
||||
provider: "openai",
|
||||
baseUrl,
|
||||
model: targetModel,
|
||||
maxContextLength: 1048576,
|
||||
apiKey: "opencode-unlocked"
|
||||
};
|
||||
|
||||
@@ -55,19 +56,51 @@ export function configureOpenClaudeProfile(options = {}) {
|
||||
// Register model options cache for picker UI
|
||||
globalConfig.openaiAdditionalModelOptionsCacheByProfile = globalConfig.openaiAdditionalModelOptionsCacheByProfile || {};
|
||||
globalConfig.openaiAdditionalModelOptionsCacheByProfile[PROVIDER_ID] = [
|
||||
{ value: "muse-spark-1.3-contributor", label: "Muse Spark 1.3 Contributor", description: "Default Reasoning Model" },
|
||||
{ value: "muse-spark-1.3-contributor", label: "Muse Spark 1.3 Contributor", description: "Default Reasoning Model (1M Context, Multi-Level Effort)" },
|
||||
{ value: "space-bunny", label: "Space Bunny", description: "Lightweight Chat & Code Model" },
|
||||
{ value: "deepseek-v4-flash", label: "DeepSeek V4 Flash", description: "High-Throughput Model" },
|
||||
{ value: "glm-5.3", label: "GLM 5.3", description: "General Language Model" }
|
||||
];
|
||||
|
||||
fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfig, null, 2), "utf-8");
|
||||
console.log(`✔ Updated ~/.openclaude.json (active provider set to: ${PROVIDER_NAME})`);
|
||||
console.log(`✔ Updated ~/.openclaude.json (active provider set to: ${PROVIDER_NAME}, maxContextLength: 1M)`);
|
||||
} catch (err) {
|
||||
console.warn(`⚠ Could not update ~/.openclaude.json: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Configure modelLimits and reasoning effort in ~/.openclaude/settings.json
|
||||
const settingsPath = path.join(openclaudeDir, "settings.json");
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
try {
|
||||
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
||||
settings.env = settings.env || {};
|
||||
settings.env.CLAUDE_CODE_ALWAYS_ENABLE_EFFORT = "1";
|
||||
settings.env.CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS = JSON.stringify({
|
||||
"muse-spark-1.3-contributor": 1048576,
|
||||
"muse-spark-1.2-contributor": 1048576,
|
||||
"muse-spark": 1048576
|
||||
});
|
||||
settings.modelLimits = settings.modelLimits || {};
|
||||
settings.modelLimits["muse-spark-1.3-contributor"] = {
|
||||
contextWindow: 1048576,
|
||||
maxOutputTokens: 65536
|
||||
};
|
||||
settings.modelLimits["muse-spark-1.2-contributor"] = {
|
||||
contextWindow: 1048576,
|
||||
maxOutputTokens: 65536
|
||||
};
|
||||
settings.modelLimits["muse-spark"] = {
|
||||
contextWindow: 1048576,
|
||||
maxOutputTokens: 65536
|
||||
};
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
||||
console.log(`✔ Configured 1M context limits & reasoning effort in ~/.openclaude/settings.json`);
|
||||
} catch (err) {
|
||||
console.warn(`⚠ Could not update ~/.openclaude/settings.json: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Configure legacy mirror ~/.openclaude/.openclaude-profile.json
|
||||
if (!fs.existsSync(openclaudeDir)) {
|
||||
fs.mkdirSync(openclaudeDir, { recursive: true });
|
||||
@@ -80,10 +113,17 @@ export function configureOpenClaudeProfile(options = {}) {
|
||||
|
||||
const legacyProfile = {
|
||||
profile: "openai",
|
||||
maxContextLength: 1048576,
|
||||
env: {
|
||||
OPENAI_BASE_URL: baseUrl,
|
||||
OPENAI_MODEL: targetModel,
|
||||
OPENAI_API_KEY: "opencode-unlocked"
|
||||
OPENAI_API_KEY: "opencode-unlocked",
|
||||
CLAUDE_CODE_ALWAYS_ENABLE_EFFORT: "1",
|
||||
CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS: JSON.stringify({
|
||||
"muse-spark-1.3-contributor": 1048576,
|
||||
"muse-spark-1.2-contributor": 1048576,
|
||||
"muse-spark": 1048576
|
||||
})
|
||||
},
|
||||
displayName: PROVIDER_NAME,
|
||||
createdAt: new Date().toISOString()
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
description: Manage Opencode Unlocked bridge, switch models, or configure keys
|
||||
argument-hint: "[models|switch <model>|key <key>|test]"
|
||||
description: Manage Opencode Unlocked bridge, switch models, configure keys, or set effort
|
||||
argument-hint: "[models|switch <model>|effort <level>|key <key>|test]"
|
||||
---
|
||||
|
||||
Manage Opencode Unlocked:
|
||||
- If argument is `models`: Run `${CLAUDE_PLUGIN_ROOT}/bin/opencode-unlocked models` to list all 36+ available models.
|
||||
- If argument is `switch <model>`: Run `${CLAUDE_PLUGIN_ROOT}/bin/opencode-unlocked switch $1` to change the default model.
|
||||
- If argument is `effort <level>`: Run `${CLAUDE_PLUGIN_ROOT}/bin/opencode-unlocked effort $1` to set reasoning effort (low, medium, high, xhigh, auto).
|
||||
- If argument is `key <key>`: Run `${CLAUDE_PLUGIN_ROOT}/bin/opencode-unlocked set-key $1` to securely store your OpenCode API key.
|
||||
- If argument is `test`: Run `${CLAUDE_PLUGIN_ROOT}/bin/opencode-unlocked test` to test live connectivity to Muse Spark 1.3 Contributor and Space Bunny.
|
||||
- Otherwise, run `${CLAUDE_PLUGIN_ROOT}/bin/opencode-unlocked status`.
|
||||
@@ -42,6 +42,8 @@ export OPENAI_BASE_URL="${GATEWAY_URL}/v1"
|
||||
if [ -z "\$OPENAI_MODEL" ]; then
|
||||
export OPENAI_MODEL="muse-spark-1.3-contributor"
|
||||
fi
|
||||
export CLAUDE_CODE_ALWAYS_ENABLE_EFFORT="1"
|
||||
export CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS='{"muse-spark-1.3-contributor":1048576,"muse-spark-1.2-contributor":1048576,"muse-spark":1048576}'
|
||||
EOF
|
||||
fi
|
||||
|
||||
@@ -50,7 +52,7 @@ cat << EOF
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": "Opencode Unlocked active at ${GATEWAY_URL}/v1. Default model: muse-spark-1.3-contributor (Space Bunny and 36+ models available)."
|
||||
"additionalContext": "Opencode Unlocked active at ${GATEWAY_URL}/v1. Default model: muse-spark-1.3-contributor (1M context window, multi-level reasoning effort)."
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -123,6 +123,14 @@ export function chatCompletionsToResponsesBody(body) {
|
||||
if (typeof max_output_tokens === "number") responsesPayload.max_output_tokens = max_output_tokens;
|
||||
else if (typeof max_tokens === "number") responsesPayload.max_output_tokens = max_tokens;
|
||||
|
||||
// Translate reasoning_effort (OpenAI Chat Completions) to reasoning: { effort } (Responses API)
|
||||
const incomingEffort = body.reasoning_effort || (typeof body.reasoning === "object" ? body.reasoning?.effort : undefined);
|
||||
if (incomingEffort && incomingEffort !== "auto" && incomingEffort !== "none") {
|
||||
responsesPayload.reasoning = {
|
||||
effort: incomingEffort
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(tools)) {
|
||||
responsesPayload.tools = tools.map((t) => {
|
||||
if (t.type === "function" && t.function) {
|
||||
|
||||
@@ -14,7 +14,7 @@ OpenCode Go hosts 36+ cutting-edge models behind strict harness fingerprinting.
|
||||
3. **Model Prefix Stripping**: Normalizes prefix variations (`opencode-unlocked-*` -> `*`).
|
||||
|
||||
## Primary Models
|
||||
- **`muse-spark-1.3-contributor`** (Default): High-capability reasoning and coding model. Requires `/responses` protocol; translated automatically by the bridge.
|
||||
- **`muse-spark-1.3-contributor`** (Default): High-capability reasoning and coding model with a full **1M (1,048,576 token)** context window. Supports configurable reasoning effort (`low`, `medium`, `high`, `xhigh`). Requires `/responses` protocol; translated automatically by the bridge.
|
||||
- **`space-bunny`**: Fast, lightweight general and coding model. Native `/chat/completions`.
|
||||
- **`deepseek-v4-flash`**: High-throughput flash model.
|
||||
- **`glm-5.3`**: General language model.
|
||||
@@ -25,5 +25,6 @@ OpenCode Go hosts 36+ cutting-edge models behind strict harness fingerprinting.
|
||||
|
||||
## Operational Commands
|
||||
- Check daemon health and usage: `/opencode-unlocked:status`
|
||||
- Manage models and keys: `/opencode-unlocked:unlocked`
|
||||
- CLI Management: `opencode-unlocked [start|stop|restart|status|models|test|set-key]`
|
||||
- Manage models, keys, and effort: `/opencode-unlocked:unlocked [models|switch <model>|effort <level>|key <key>|test]`
|
||||
- Adjust reasoning effort directly: `/effort [low|medium|high|xhigh|auto]`
|
||||
- CLI Management: `opencode-unlocked [start|stop|restart|status|models|switch|effort|test|set-key|setup]`
|
||||
@@ -64,11 +64,32 @@ server.listen(TEST_PORT, "127.0.0.1", async () => {
|
||||
const spaceDuration = ((Date.now() - spaceStart) / 1000).toFixed(2);
|
||||
console.log(`✔ Space Bunny responded in ${spaceDuration}s:`);
|
||||
console.log(` Output: "${spaceReply}"`);
|
||||
console.log(` Usage: ${JSON.stringify(spaceJson.usage)}`);
|
||||
assert.ok(spaceReply && /yellow/i.test(spaceReply), "Space Bunny should correctly reply with yellow");
|
||||
|
||||
// 3. Verify Reasoning Effort with Muse Spark 1.3 Contributor
|
||||
console.log("\n[3/3] Verifying Configurable Reasoning Effort: Muse Spark 1.3 with reasoning_effort='high'...");
|
||||
const effortStart = Date.now();
|
||||
const effortRes = await fetch(`${baseUrl}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
model: "muse-spark-1.3-contributor",
|
||||
messages: [{ role: "user", content: "Is 97 prime? Reply yes or no only." }],
|
||||
reasoning_effort: "high",
|
||||
stream: false
|
||||
})
|
||||
});
|
||||
|
||||
assert.equal(effortRes.status, 200, `Muse Spark with reasoning_effort should be 200 (was ${effortRes.status})`);
|
||||
const effortJson = await effortRes.json();
|
||||
const effortReply = effortJson.choices?.[0]?.message?.content?.trim();
|
||||
const effortDuration = ((Date.now() - effortStart) / 1000).toFixed(2);
|
||||
console.log(`✔ Muse Spark with reasoning_effort='high' responded in ${effortDuration}s:`);
|
||||
console.log(` Output: "${effortReply}"`);
|
||||
assert.ok(effortReply && /yes/i.test(effortReply), "Muse Spark should answer yes for prime 97");
|
||||
|
||||
console.log("\n==================================================");
|
||||
console.log("✔ LIVE VERIFICATION PASSED FOR BOTH MODELS!");
|
||||
console.log("✔ LIVE VERIFICATION PASSED FOR ALL MODELS & REASONING!");
|
||||
console.log("==================================================");
|
||||
} catch (err) {
|
||||
console.error("\n❌ Live model verification failed:", err);
|
||||
|
||||
@@ -36,6 +36,39 @@ assert.equal(responsesPayload.input.length, 2);
|
||||
assert.equal(responsesPayload.input[0].role, "system");
|
||||
assert.equal(responsesPayload.input[1].content, "Hello world");
|
||||
|
||||
// 2b. Verify reasoning effort translation
|
||||
const effortHighReq = {
|
||||
model: "muse-spark-1.3-contributor",
|
||||
messages: [{ role: "user", content: "Hi" }],
|
||||
reasoning_effort: "high"
|
||||
};
|
||||
const effortHighPayload = chatCompletionsToResponsesBody(effortHighReq);
|
||||
assert.deepEqual(effortHighPayload.reasoning, { effort: "high" });
|
||||
|
||||
const effortXHighReq = {
|
||||
model: "muse-spark-1.3-contributor",
|
||||
messages: [{ role: "user", content: "Hi" }],
|
||||
reasoning_effort: "xhigh"
|
||||
};
|
||||
const effortXHighPayload = chatCompletionsToResponsesBody(effortXHighReq);
|
||||
assert.deepEqual(effortXHighPayload.reasoning, { effort: "xhigh" });
|
||||
|
||||
const effortNoneReq = {
|
||||
model: "muse-spark-1.3-contributor",
|
||||
messages: [{ role: "user", content: "Hi" }],
|
||||
reasoning_effort: "none"
|
||||
};
|
||||
const effortNonePayload = chatCompletionsToResponsesBody(effortNoneReq);
|
||||
assert.equal(effortNonePayload.reasoning, undefined);
|
||||
|
||||
const effortNestedReq = {
|
||||
model: "muse-spark-1.3-contributor",
|
||||
messages: [{ role: "user", content: "Hi" }],
|
||||
reasoning: { effort: "low" }
|
||||
};
|
||||
const effortNestedPayload = chatCompletionsToResponsesBody(effortNestedReq);
|
||||
assert.deepEqual(effortNestedPayload.reasoning, { effort: "low" });
|
||||
|
||||
// 3. Responses API non-streaming JSON -> Chat Completions JSON
|
||||
const rawResponsesResp = {
|
||||
id: "resp_1234567890",
|
||||
|
||||
Reference in new issue
Block a user