feat(opencode): add plan mode, reasoning effort, and status telemetry (#688)

* feat(opencode): support plan mode

* feat(opencode): support reasoning effort

* feat(opencode): surface context usage in web

Bridge OpenCode ACP usage updates into the existing token-count pipeline so the web status bar can show live context and cache information without a separate UI path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(opencode): restrict plan mode to remote, rollback reasoning effort on failure

- Block local OpenCode plan startup (tools not enforced in local path)
- Allow remote OpenCode plan only (ACP permission handler denies tools)
- Guard web /permission-mode endpoint for local OpenCode plan sessions
- Rollback session reasoning effort when OpenCode rejects set_config_option
- Wire rollback callback through opencodeLoop to runOpencode closure
- Add tests: local plan rejected, remote plan allowed, web guard, effort rollback

* fix(web): auto-retry OpenCode models query to populate model selector without refresh

- Retry early failures (RPC may still be registering on new sessions)
- Poll briefly until availableModels is non-empty
- Stop polling once model options are discovered
- Add tests for retry/poll/stop policy

* fix(opencode): cap model discovery polling

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
SSU-WEI HUANG
2026-05-27 11:17:24 +08:00
committed by GitHub
co-authored by Cursor
parent d5a67b717c
commit 6f2bb7d32b
43 changed files with 1263 additions and 68 deletions
+11
View File
@@ -60,6 +60,17 @@ describe('buildCliArgs', () => {
expect(args).toContain('ollama/exaone:4.5-33b-q8')
})
it('passes --model-reasoning-effort through for opencode', () => {
const args = buildCliArgs('opencode', {
directory: '/tmp',
modelReasoningEffort: 'high',
})
expect(args).toContain('--model-reasoning-effort')
expect(args).toContain('high')
})
it('validates all known permission modes', () => {
for (const mode of ['default', 'acceptEdits', 'bypassPermissions', 'plan', 'ask', 'read-only', 'safe-yolo', 'yolo']) {
const args = buildCliArgs('claude', {
+1 -1
View File
@@ -933,7 +933,7 @@ export function buildCliArgs(
if (options.effort && agent === 'claude') {
args.push('--effort', options.effort);
}
if (options.modelReasoningEffort && agent === 'codex') {
if (options.modelReasoningEffort && (agent === 'codex' || agent === 'opencode')) {
args.push('--model-reasoning-effort', options.modelReasoningEffort);
}
if (options.permissionMode && (PERMISSION_MODES as readonly string[]).includes(options.permissionMode)) {