feat(opencode): slash command support (#671) (#753)

This commit is contained in:
SSU-WEI HUANG
2026-05-31 19:35:31 +08:00
committed by GitHub
parent 31dd4353d4
commit 5b797bb95d
17 changed files with 775 additions and 28 deletions
+19 -7
View File
@@ -25,6 +25,7 @@ class OpencodeRemoteLauncher extends RemoteLauncherBase {
private displayPermissionMode: PermissionMode | null = null;
private instructionsSent = false;
private currentBackendModel: string | null = null;
private defaultBackendModel: string | null = null;
private currentBackendEffort: string | null = null;
private defaultBackendEffort: string | null = null;
private setModelSupported: boolean | undefined = undefined;
@@ -103,6 +104,7 @@ class OpencodeRemoteLauncher extends RemoteLauncherBase {
// does not trigger a redundant setModel on the very first turn.
const initialMetadata = backend.getSessionModelsMetadata?.(acpSessionId);
this.currentBackendModel = initialMetadata?.currentModelId ?? null;
this.defaultBackendModel = this.currentBackendModel;
const thoughtLevelOption = backend.getThoughtLevelConfigOption?.(acpSessionId);
this.currentBackendEffort = thoughtLevelOption?.currentValue ?? null;
this.defaultBackendEffort = this.currentBackendEffort;
@@ -153,19 +155,29 @@ class OpencodeRemoteLauncher extends RemoteLauncherBase {
// RPC, we learn that from the first method-not-found response and stop
// attempting it for the rest of this session.
//
// `batch.mode.model` semantics: a string is a specific model id;
// `null` means "reset to whatever model the backend launched with"
// (emitted by `/model default`); `undefined` means "no change".
const requestedModel = batch.mode.model === null
? this.defaultBackendModel
: batch.mode.model;
// The very first batch seeds currentBackendModel — the OpenCode CLI was
// launched with that model via --model and there is nothing to switch yet.
if (batch.mode.model && this.currentBackendModel === null) {
this.currentBackendModel = batch.mode.model;
} else if (batch.mode.model && batch.mode.model !== this.currentBackendModel) {
if (requestedModel && this.currentBackendModel === null) {
this.currentBackendModel = requestedModel;
} else if (requestedModel && requestedModel !== this.currentBackendModel) {
if (!backend.setModel || this.setModelSupported === false) {
batch.mode.model = this.currentBackendModel ?? undefined;
} else {
logger.debug(`[opencode-remote] Switching model inline: ${this.currentBackendModel} -> ${batch.mode.model}`);
logger.debug(`[opencode-remote] Switching model inline: ${this.currentBackendModel} -> ${requestedModel}`);
try {
await backend.setModel(acpSessionId, batch.mode.model, { flavor: 'opencode' });
this.currentBackendModel = batch.mode.model;
await backend.setModel(acpSessionId, requestedModel, { flavor: 'opencode' });
this.currentBackendModel = requestedModel;
this.setModelSupported = true;
// Reflect the resolved model back into the batch so
// downstream display logic sees the concrete id rather
// than a `null` placeholder.
batch.mode.model = requestedModel;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const methodNotFound = /method not found/i.test(message);
@@ -180,7 +192,7 @@ class OpencodeRemoteLauncher extends RemoteLauncherBase {
logger.warn('[opencode-remote] Inline model switch failed', error);
session.sendSessionEvent({
type: 'message',
message: `Failed to switch model to ${batch.mode.model}. Continuing with ${this.currentBackendModel ?? '(default)'}.`
message: `Failed to switch model to ${requestedModel}. Continuing with ${this.currentBackendModel ?? '(default)'}.`
});
}
batch.mode.model = this.currentBackendModel ?? undefined;