Files
goodbuddy/src/main/agent/runtime-selection.test.ts
T
mesalogo 56ebd40bd8 feat: unify execution and add workspace file imports
Remove Ask/Execute modes and general product tool approvals so requests use the selected runtime, enabled capabilities, and execution account permissions. Migrate retired application metadata while preserving user content.

Retire the Results page while retaining conversation images and stored artifacts. Add local and remote workspace file imports, timestamps and sorting, improve image discovery and saving, and refine workspace navigation and supervision responsiveness. Include the accompanying web-search changes, tests, probes, and product documentation.

Remote imports require an updated Agent. Existing artifacts are not automatically copied into project directories. Validation evidence and outstanding failures remain documented in feature progress files; no full test suite or real-model calls were rerun for this commit.

Release note: Run requests without switching work modes, import files into a selected local or remote workspace folder, sort workspace files by date, and keep using conversation images after removal of the Results page.
2026-10-04 20:02:59 +08:00

311 lines
10 KiB
TypeScript

import type { ResolvedRuntimeSettings } from '../runtime-settings-store'
import { defaultRuntimeCustomizationSettings } from '../../shared/contracts'
import { describe, expect, it } from 'vitest'
import {
applyRuntimeSelection,
getConfiguredRuntimeTarget,
resolveConfiguredAgentRuntimeSelection
} from './runtime-selection'
const defaultProfileId = '00000000-0000-4000-8000-000000000001'
const secondProfileId = '00000000-0000-4000-8000-000000000002'
const responsesProfileId = '00000000-0000-4000-8000-000000000003'
const imageProfileId = '00000000-0000-4000-8000-000000000004'
const harnessProfileId = '00000000-0000-4000-8000-000000000005'
function settings(
overrides: Partial<ResolvedRuntimeSettings> = {}
): ResolvedRuntimeSettings {
return {
provider: 'auto',
modelBaseUrl: 'https://default.example/v1',
modelName: 'default-model',
modelProtocol: 'anthropic-messages',
modelAuthentication: 'api-key',
imageGenerationQuality: 'auto',
apiKey: 'default-key',
modelProfiles: [
{
id: defaultProfileId,
name: '默认模型',
baseUrl: 'https://default.example/v1',
modelName: 'default-model',
protocol: 'anthropic-messages',
authentication: 'api-key',
imageGenerationQuality: 'auto',
apiKey: 'default-key'
},
{
id: secondProfileId,
name: '第二模型',
baseUrl: 'https://second.example/v1',
modelName: 'second-model',
protocol: 'openai-chat-completions',
authentication: 'none',
supportsImageInput: true,
imageGenerationQuality: 'auto'
},
{
id: responsesProfileId,
name: 'Responses 模型',
baseUrl: 'https://responses.example/v1',
modelName: 'responses-model',
protocol: 'openai-responses',
authentication: 'api-key',
imageGenerationQuality: 'auto',
apiKey: 'responses-key'
},
{
id: imageProfileId,
name: '图像模型',
baseUrl: 'https://images.example/v1',
modelName: 'image-model',
protocol: 'openai-images-generations',
authentication: 'api-key',
imageGenerationQuality: 'auto',
apiKey: 'image-key'
},
{
id: harnessProfileId,
name: 'OpenAI-compatible gateway',
baseUrl: 'https://gateway.example/openai/v1',
modelName: 'qwen-plus',
protocol: 'openai-chat-completions',
authentication: 'api-key',
imageGenerationQuality: 'auto',
apiKey: 'deepseek-key'
}
],
defaultModelProfileId: defaultProfileId,
opencodeBaseUrl: '',
opencodeEmbedded: true,
opencodeBinaryPath: '',
opencodeConfigPath: '',
continueBinaryPath: '',
continueConfigPath: '',
continueMode: 'chat',
subagentSmartRoutingEnabled: false,
knowledgeEmbeddingEnabled: false,
knowledgeEmbeddingBaseUrl:
'http://127.0.0.1:11434/v1/embeddings',
knowledgeEmbeddingModel: 'embedding',
knowledgeRerankEnabled: false,
knowledgeRerankEndpoint: 'https://api.cohere.com/v1/rerank',
knowledgeRerankModel: 'rerank-v3.5',
runtimeCustomization: defaultRuntimeCustomizationSettings,
workspacePath: process.cwd(),
...overrides
}
}
describe('runtime selection', () => {
it('resolves direct defaults per request while fixed selections and started requests stay unchanged', () => {
const before = settings()
const started = applyRuntimeSelection(before, { provider: 'model' })
const after = settings({ defaultModelProfileId: secondProfileId })
expect(applyRuntimeSelection(after, { provider: 'model' }).settings.defaultModelProfileId).toBe(secondProfileId)
expect(applyRuntimeSelection(after, { provider: 'model', profileId: defaultProfileId }).settings.defaultModelProfileId).toBe(defaultProfileId)
expect(started.settings.defaultModelProfileId).toBe(defaultProfileId)
})
it.each(['opencode', 'continue', 'deepseek-harness'] as const)(
'resolves %s references from its current configuration and preserves explicit overrides', (provider) => {
const base = settings()
const first = base.modelProfiles[4]!
const second = { ...first, id: responsesProfileId }
const key = provider === 'opencode' ? 'opencodeModelProfile' : provider === 'continue' ? 'continueModelProfile' : 'deepseekHarnessModelProfile'
const configured = settings({ [key]: first })
const started = applyRuntimeSelection(configured, { provider })
const changed = settings({ [key]: second })
expect(applyRuntimeSelection(changed, { provider }).settings[key]?.id).toBe(second.id)
expect(applyRuntimeSelection(changed, { provider, profileId: first.id }).settings[key]?.id).toBe(first.id)
expect(started.settings[key]?.id).toBe(first.id)
}
)
it('selects an independent direct model profile without changing defaults', () => {
const original = settings()
const selected = applyRuntimeSelection(original, {
provider: 'model',
profileId: secondProfileId
})
expect(selected.target).toBe('model')
expect(selected.settings).toMatchObject({
provider: 'model',
modelBaseUrl: 'https://second.example/v1',
modelName: 'second-model',
modelProtocol: 'openai-chat-completions',
modelAuthentication: 'none',
supportsImageInput: true,
defaultModelProfileId: secondProfileId
})
expect(original.defaultModelProfileId).toBe(defaultProfileId)
})
it.each([
['opencode', defaultProfileId],
['opencode', secondProfileId],
['opencode', responsesProfileId],
['continue', defaultProfileId],
['continue', secondProfileId],
['continue', responsesProfileId]
] as const)(
'selects %s with text profile %s',
(provider, profileId) => {
const selected = applyRuntimeSelection(settings(), {
provider,
profileId
})
expect(
provider === 'opencode'
? selected.settings.opencodeModelProfile?.id
: selected.settings.continueModelProfile?.id
).toBe(profileId)
}
)
it('rejects deleted or incompatible profile selections', () => {
expect(() =>
applyRuntimeSelection(settings(), {
provider: 'model',
profileId: '00000000-0000-4000-8000-000000000099'
})
).toThrow('不存在')
expect(() =>
applyRuntimeSelection(settings(), {
provider: 'opencode',
profileId: imageProfileId
})
).toThrow('不支持图像生成协议')
expect(() =>
applyRuntimeSelection(
settings({ opencodeBaseUrl: 'http://127.0.0.1:4096' }),
{
provider: 'opencode',
profileId: defaultProfileId
}
)
).toThrow('自动启动')
})
it('selects DeepSeek Harness with a compatible gateway profile', () => {
const selected = applyRuntimeSelection(settings(), {
provider: 'deepseek-harness',
profileId: harnessProfileId
})
expect(selected.target).toBe('deepseek-harness')
expect(selected.settings).toMatchObject({
provider: 'deepseek-harness',
deepseekHarnessModelProfile: { id: harnessProfileId }
})
expect(() =>
applyRuntimeSelection(settings(), {
provider: 'deepseek-harness',
profileId: secondProfileId
})
).toThrow('API Key')
})
it('keeps the controlled platform Harness profile when selected without a profile ID', () => {
const base = settings()
const platformProfile = {
...base.modelProfiles[4]!,
id: 'goodbuddy-platform-harness',
name: '管理员预置模型',
modelName: 'qwen-plus'
}
const selected = applyRuntimeSelection(
settings({ deepseekHarnessModelProfile: platformProfile }),
{ provider: 'deepseek-harness' }
)
expect(selected.settings).toMatchObject({
provider: 'deepseek-harness',
deepseekHarnessModelProfile: {
id: 'goodbuddy-platform-harness',
modelName: 'qwen-plus'
}
})
})
it('resolves Agent Runtime backends from the global Runtime configuration', () => {
const base = settings()
const configured = settings({
opencodeModelProfile: base.modelProfiles[1],
continueModelProfile: base.modelProfiles[2],
deepseekHarnessModelProfile: base.modelProfiles[4]
})
expect(
resolveConfiguredAgentRuntimeSelection(configured, {
provider: 'deepseek-harness'
})
).toEqual({
provider: 'deepseek-harness',
profileId: harnessProfileId
})
expect(
resolveConfiguredAgentRuntimeSelection(configured, {
provider: 'opencode',
profileId: defaultProfileId
})
).toEqual({
provider: 'opencode',
profileId: defaultProfileId
})
expect(
resolveConfiguredAgentRuntimeSelection(configured, {
provider: 'continue'
})
).toEqual({
provider: 'continue',
profileId: responsesProfileId
})
expect(
resolveConfiguredAgentRuntimeSelection(configured, {
provider: 'model',
profileId: defaultProfileId
})
).toEqual({
provider: 'model',
profileId: defaultProfileId
})
})
it('keeps the controlled platform Harness source profile-free across configured selection repair', () => {
const base = settings()
const configured = settings({
deepseekHarnessModelProfile: {
...base.modelProfiles[4]!,
id: 'goodbuddy-platform-harness',
name: '管理员预置模型'
}
})
expect(
resolveConfiguredAgentRuntimeSelection(configured, {
provider: 'deepseek-harness'
})
).toEqual({ provider: 'deepseek-harness' })
})
it('treats legacy automatic settings as the direct model, matching settings normalization', () => {
expect(getConfiguredRuntimeTarget(settings())).toBe('model')
expect(
getConfiguredRuntimeTarget(settings({ provider: 'opencode' }))
).toBe('opencode')
})
it('resolves own configuration explicitly instead of the configured profile', () => {
const configured = settings({
provider: 'opencode',
opencodeModelProfile: settings().modelProfiles[0]
})
expect(
applyRuntimeSelection(configured, { provider: 'opencode', runtimeConfig: true })
.settings.opencodeModelProfile
).toBeUndefined()
})
})