mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-08 19:19:42 +00:00
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:
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
const mockOpencodeSession = vi.hoisted(() => ({
|
||||
setModel: vi.fn(),
|
||||
setPermissionMode: vi.fn(),
|
||||
setModelReasoningEffort: vi.fn(),
|
||||
pushKeepAlive: vi.fn(),
|
||||
thinking: false,
|
||||
stopKeepAlive: vi.fn()
|
||||
@@ -89,6 +90,7 @@ describe('runOpencode set-session-config handler', () => {
|
||||
harness.opencodeLoopError = null;
|
||||
mockOpencodeSession.setModel.mockReset();
|
||||
mockOpencodeSession.setPermissionMode.mockReset();
|
||||
mockOpencodeSession.setModelReasoningEffort.mockReset();
|
||||
mockOpencodeSession.pushKeepAlive.mockReset();
|
||||
harness.session.onUserMessage.mockReset();
|
||||
harness.session.rpcHandlerManager.registerHandler.mockReset();
|
||||
@@ -109,6 +111,20 @@ describe('runOpencode set-session-config handler', () => {
|
||||
return configHandler![1] as (payload: unknown) => Promise<unknown>;
|
||||
}
|
||||
|
||||
it('rejects plan mode for local OpenCode startup', async () => {
|
||||
await expect(runOpencode({ permissionMode: 'plan' })).rejects.toThrow(
|
||||
'OpenCode plan mode is only supported in remote mode'
|
||||
);
|
||||
expect(harness.opencodeLoopArgs).toEqual([]);
|
||||
});
|
||||
|
||||
it('allows plan mode for remote OpenCode startup', async () => {
|
||||
await runOpencode({ permissionMode: 'plan', startingMode: 'remote' });
|
||||
|
||||
expect(harness.opencodeLoopArgs[0]?.permissionMode).toBe('plan');
|
||||
expect(harness.opencodeLoopArgs[0]?.startingMode).toBe('remote');
|
||||
});
|
||||
|
||||
it('applies model change via set-session-config RPC', async () => {
|
||||
await runOpencode({});
|
||||
|
||||
@@ -178,6 +194,30 @@ describe('runOpencode set-session-config handler', () => {
|
||||
expect(applied.permissionMode).toBe('yolo');
|
||||
});
|
||||
|
||||
it('accepts plan mode via set-session-config RPC', async () => {
|
||||
await runOpencode({});
|
||||
|
||||
const handler = getConfigHandler();
|
||||
const result = await handler({ permissionMode: 'plan' }) as Record<string, unknown>;
|
||||
const applied = result.applied as Record<string, unknown>;
|
||||
|
||||
expect(applied.permissionMode).toBe('plan');
|
||||
expect(mockOpencodeSession.setPermissionMode).toHaveBeenLastCalledWith('plan');
|
||||
});
|
||||
|
||||
|
||||
|
||||
it('accepts model reasoning effort via set-session-config RPC', async () => {
|
||||
await runOpencode({});
|
||||
|
||||
const handler = getConfigHandler();
|
||||
const result = await handler({ modelReasoningEffort: 'high' }) as Record<string, unknown>;
|
||||
const applied = result.applied as Record<string, unknown>;
|
||||
|
||||
expect(applied.modelReasoningEffort).toBe('high');
|
||||
expect(mockOpencodeSession.setModelReasoningEffort).toHaveBeenLastCalledWith('high');
|
||||
});
|
||||
|
||||
it('passes initial model from opts through to the loop', async () => {
|
||||
await runOpencode({ model: 'ollama/exaone:4.5-33b-q8' });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user