diff --git a/open-sse/config/providerModels.js b/open-sse/config/providerModels.js
index 74b2576..64b4302 100644
--- a/open-sse/config/providerModels.js
+++ b/open-sse/config/providerModels.js
@@ -328,6 +328,13 @@ export const PROVIDER_MODELS = {
{ id: "deepseek/deepseek-chat", name: "DeepSeek Chat" },
{ id: "deepseek/deepseek-reasoner", name: "DeepSeek Reasoner" },
],
+ oc: [ // OpenCode
+ { id: "nemotron-3-super-free", name: "Nemotron 3 Super" },
+ { id: "qwen3.6-plus-free", name: "Qwen 3.6 Plus" },
+ // { id: "big-pickle", name: "Big Pickle", targetFormat: "claude" },
+ { id: "minimax-m2.5-free", name: "MiniMax M2.5", targetFormat: "claude" },
+ ],
+
cl: [ // Cline
{ id: "anthropic/claude-sonnet-4.6", name: "Claude Sonnet 4.6" },
{ id: "anthropic/claude-opus-4.6", name: "Claude Opus 4.6" },
@@ -589,6 +596,7 @@ const OAUTH_ALIASES = {
"kimi-coding": "kmc",
kilocode: "kc",
cline: "cl",
+ opencode: "oc",
vertex: "vertex",
"vertex-partner": "vertex-partner",
};
diff --git a/open-sse/config/providers.js b/open-sse/config/providers.js
index 4dd54ab..eb89fd2 100644
--- a/open-sse/config/providers.js
+++ b/open-sse/config/providers.js
@@ -332,4 +332,9 @@ export const PROVIDERS = {
baseUrl: "https://copilot.tencent.com/v1/chat/completions",
format: "openai",
},
+ opencode: {
+ baseUrl: "https://opencode.ai",
+ format: "openai",
+ headers: { "x-opencode-client": "desktop" }
+ },
};
diff --git a/open-sse/executors/index.js b/open-sse/executors/index.js
index 0c314fb..f77212c 100644
--- a/open-sse/executors/index.js
+++ b/open-sse/executors/index.js
@@ -8,6 +8,7 @@ import { CodexExecutor } from "./codex.js";
import { CursorExecutor } from "./cursor.js";
import { VertexExecutor } from "./vertex.js";
import { QwenExecutor } from "./qwen.js";
+import { OpenCodeExecutor } from "./opencode.js";
import { DefaultExecutor } from "./default.js";
const executors = {
@@ -23,6 +24,7 @@ const executors = {
vertex: new VertexExecutor("vertex"),
"vertex-partner": new VertexExecutor("vertex-partner"),
qwen: new QwenExecutor(),
+ opencode: new OpenCodeExecutor(),
};
const defaultCache = new Map();
@@ -49,3 +51,4 @@ export { CursorExecutor } from "./cursor.js";
export { VertexExecutor } from "./vertex.js";
export { DefaultExecutor } from "./default.js";
export { QwenExecutor } from "./qwen.js";
+export { OpenCodeExecutor } from "./opencode.js";
diff --git a/open-sse/executors/opencode.js b/open-sse/executors/opencode.js
new file mode 100644
index 0000000..5e77e12
--- /dev/null
+++ b/open-sse/executors/opencode.js
@@ -0,0 +1,27 @@
+import { BaseExecutor } from "./base.js";
+import { PROVIDERS } from "../config/providers.js";
+
+// Models that use /zen/v1/messages (claude format)
+const MESSAGES_MODELS = new Set(["big-pickle", "minimax-m2.5-free"]);
+
+export class OpenCodeExecutor extends BaseExecutor {
+ constructor() {
+ super("opencode", PROVIDERS.opencode);
+ }
+
+ buildUrl(model) {
+ const base = "https://opencode.ai";
+ return MESSAGES_MODELS.has(model)
+ ? `${base}/zen/v1/messages`
+ : `${base}/zen/v1/chat/completions`;
+ }
+
+ buildHeaders() {
+ return {
+ "Content-Type": "application/json",
+ "Authorization": "Bearer public",
+ "x-opencode-client": "desktop",
+ "Accept": "text/event-stream"
+ };
+ }
+}
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js
index ac08e74..4f3149a 100644
--- a/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js
+++ b/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js
@@ -22,6 +22,8 @@ export default function OpenClawToolCard({
const [message, setMessage] = useState(null);
const [selectedApiKey, setSelectedApiKey] = useState("");
const [selectedModel, setSelectedModel] = useState("");
+ const [agentModels, setAgentModels] = useState({}); // { [agentId]: modelId }
+ const [agentModalFor, setAgentModalFor] = useState(null); // agentId opening modal
const [modalOpen, setModalOpen] = useState(false);
const [modelAliases, setModelAliases] = useState({});
const [showManualConfigModal, setShowManualConfigModal] = useState(false);
@@ -74,14 +76,18 @@ export default function OpenClawToolCard({
const provider = openclawStatus.settings?.models?.providers?.["9router"];
if (provider) {
const primaryModel = openclawStatus.settings?.agents?.defaults?.model?.primary;
- if (primaryModel) {
- const modelId = primaryModel.replace("9router/", "");
- setSelectedModel(modelId);
- }
+ if (primaryModel) setSelectedModel(primaryModel.replace("9router/", ""));
if (provider.apiKey && apiKeys?.some(k => k.key === provider.apiKey)) {
setSelectedApiKey(provider.apiKey);
}
}
+ // Init per-agent models from enriched agents list
+ const agentList = openclawStatus.agents || [];
+ const initAgentModels = {};
+ agentList.forEach((agent) => {
+ if (agent.currentModel) initAgentModels[agent.id] = agent.currentModel;
+ });
+ setAgentModels(initAgentModels);
}
}, [openclawStatus, apiKeys]);
@@ -131,7 +137,8 @@ export default function OpenClawToolCard({
body: JSON.stringify({
baseUrl: getEffectiveBaseUrl(),
apiKey: keyToUse,
- model: selectedModel
+ model: selectedModel,
+ agentModels,
}),
});
const data = await res.json();
@@ -170,7 +177,12 @@ export default function OpenClawToolCard({
};
const handleModelSelect = (model) => {
- setSelectedModel(model.value);
+ if (agentModalFor) {
+ setAgentModels(prev => ({ ...prev, [agentModalFor]: model.value }));
+ setAgentModalFor(null);
+ } else {
+ setSelectedModel(model.value);
+ }
setModalOpen(false);
};
@@ -298,14 +310,31 @@ export default function OpenClawToolCard({
)}
- {/* Model */}
+ {/* Default Model */}
- Model
+ Default Model
arrow_forward
setSelectedModel(e.target.value)} placeholder="provider/model-id" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" />
-
+
{selectedModel && }
+
+ {/* Per-agent model overrides */}
+ {(openclawStatus.agents || []).filter(a => a.agentDir).map((agent) => (
+
+ Agent {agent.name || agent.id}
+ arrow_forward
+ setAgentModels(prev => ({ ...prev, [agent.id]: e.target.value }))}
+ placeholder={`default (${selectedModel || "provider/model-id"})`}
+ className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50"
+ />
+
+ {agentModels[agent.id] && }
+
+ ))}
{message && (
diff --git a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js
index 5b19256..543bb6f 100644
--- a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js
+++ b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js
@@ -472,7 +472,7 @@ export default function APIPageClient({ machineId }) {