v2.2-paseo-providers: Paseo provider stack + v2.2.1 pane-scoped chat fixes
Ship Paseo-equivalent provider snapshot, AgentComposerBar, ACP dispatch rewrite with streaming/persist, permission prompts, and agent commands. Follow-up: pane-scoped chat resolution, CoderMessageList tool timeline, WS user-delta replace, and inference orphan tool_call stripping. Archive openspec v2-2; update CHANGELOG and CURRENT. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
154
apps/coder/src/services/__tests__/acp-derive.test.ts
Normal file
154
apps/coder/src/services/__tests__/acp-derive.test.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { SessionConfigOption } from '@agentclientprotocol/sdk';
|
||||
import {
|
||||
deriveModesFromACP,
|
||||
deriveModelDefinitionsFromACP,
|
||||
findThoughtLevelConfigId,
|
||||
} from '../acp-derive.js';
|
||||
|
||||
describe('deriveModesFromACP', () => {
|
||||
it('prefers modeState.availableModes when present', () => {
|
||||
const { modes, currentModeId } = deriveModesFromACP(
|
||||
[{ id: 'fallback', label: 'Fallback' }],
|
||||
{
|
||||
currentModeId: 'plan',
|
||||
availableModes: [
|
||||
{ id: 'plan', name: 'Plan', description: 'Read-only planning' },
|
||||
{ id: 'code', name: 'Code' },
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
expect(modes).toEqual([
|
||||
{ id: 'plan', label: 'Plan', description: 'Read-only planning' },
|
||||
{ id: 'code', label: 'Code', description: undefined },
|
||||
]);
|
||||
expect(currentModeId).toBe('plan');
|
||||
});
|
||||
|
||||
it('falls back to configOptions mode select', () => {
|
||||
const configOptions: SessionConfigOption[] = [
|
||||
{
|
||||
type: 'select',
|
||||
id: 'mode',
|
||||
category: 'mode',
|
||||
currentValue: 'auto',
|
||||
options: [
|
||||
{ value: 'auto', name: 'Auto' },
|
||||
{ value: 'manual', name: 'Manual', description: 'Ask first' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const { modes, currentModeId } = deriveModesFromACP([], null, configOptions);
|
||||
|
||||
expect(modes).toEqual([
|
||||
{ id: 'auto', label: 'Auto', description: undefined },
|
||||
{ id: 'manual', label: 'Manual', description: 'Ask first' },
|
||||
]);
|
||||
expect(currentModeId).toBe('auto');
|
||||
});
|
||||
|
||||
it('uses static fallback when no ACP mode data', () => {
|
||||
const fallback = [{ id: 'default', label: 'Default' }];
|
||||
const { modes, currentModeId } = deriveModesFromACP(fallback, null, null);
|
||||
|
||||
expect(modes).toEqual(fallback);
|
||||
expect(currentModeId).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('deriveModelDefinitionsFromACP', () => {
|
||||
it('maps availableModels with thought_level options', () => {
|
||||
const configOptions: SessionConfigOption[] = [
|
||||
{
|
||||
type: 'select',
|
||||
id: 'thought',
|
||||
category: 'thought_level',
|
||||
currentValue: 'medium',
|
||||
options: [
|
||||
{ value: 'low', name: 'Low' },
|
||||
{ value: 'medium', name: 'Medium' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const models = deriveModelDefinitionsFromACP(
|
||||
{
|
||||
currentModelId: 'gpt-4',
|
||||
availableModels: [
|
||||
{ modelId: 'gpt-4', name: 'GPT-4' },
|
||||
{ modelId: 'gpt-4-mini', name: 'Mini', description: 'Cheaper' },
|
||||
],
|
||||
},
|
||||
configOptions,
|
||||
);
|
||||
|
||||
expect(models).toEqual([
|
||||
{
|
||||
id: 'gpt-4',
|
||||
label: 'GPT-4',
|
||||
description: undefined,
|
||||
isDefault: true,
|
||||
thinkingOptions: [
|
||||
{ id: 'low', label: 'Low', isDefault: false },
|
||||
{ id: 'medium', label: 'Medium', isDefault: true },
|
||||
],
|
||||
defaultThinkingOptionId: 'medium',
|
||||
},
|
||||
{
|
||||
id: 'gpt-4-mini',
|
||||
label: 'Mini',
|
||||
description: 'Cheaper',
|
||||
isDefault: false,
|
||||
thinkingOptions: [
|
||||
{ id: 'low', label: 'Low', isDefault: false },
|
||||
{ id: 'medium', label: 'Medium', isDefault: true },
|
||||
],
|
||||
defaultThinkingOptionId: 'medium',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('falls back to model select config when no availableModels', () => {
|
||||
const configOptions: SessionConfigOption[] = [
|
||||
{
|
||||
type: 'select',
|
||||
id: 'model',
|
||||
category: 'model',
|
||||
currentValue: 'sonnet',
|
||||
options: [
|
||||
{ value: 'sonnet', name: 'Sonnet' },
|
||||
{ value: 'opus', name: 'Opus' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const models = deriveModelDefinitionsFromACP(null, configOptions);
|
||||
|
||||
expect(models).toEqual([
|
||||
{ id: 'sonnet', label: 'Sonnet', isDefault: true, defaultThinkingOptionId: undefined },
|
||||
{ id: 'opus', label: 'Opus', isDefault: false, defaultThinkingOptionId: undefined },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findThoughtLevelConfigId', () => {
|
||||
it('returns thought_level select id', () => {
|
||||
const configOptions: SessionConfigOption[] = [
|
||||
{
|
||||
type: 'select',
|
||||
id: 'effort',
|
||||
category: 'thought_level',
|
||||
currentValue: 'high',
|
||||
options: [{ value: 'high', name: 'High' }],
|
||||
},
|
||||
];
|
||||
|
||||
expect(findThoughtLevelConfigId(configOptions)).toBe('effort');
|
||||
});
|
||||
|
||||
it('returns null when missing', () => {
|
||||
expect(findThoughtLevelConfigId(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user