diff --git a/ui/server/services/pilotdeckConfig.js b/ui/server/services/pilotdeckConfig.js index 430dcbbd..77152d54 100644 --- a/ui/server/services/pilotdeckConfig.js +++ b/ui/server/services/pilotdeckConfig.js @@ -42,6 +42,13 @@ function normalizeString(value) { return typeof value === 'string' ? value.trim() : ''; } +function describeModelRef(value) { + if (value === undefined) return 'undefined'; + if (value === null) return 'null'; + if (typeof value === 'object') return JSON.stringify(value); + return String(value); +} + function deepMerge(base, override) { if (!isRecord(base)) return clone(override); const output = clone(base); @@ -181,6 +188,10 @@ function validateProvider(id, provider, errors) { } function validateModelRef(config, ref, label, errors) { + if (ref !== undefined && ref !== null && typeof ref !== 'string') { + errors.push(`${label} must be a provider/model string; got ${describeModelRef(ref)}`); + return; + } const modelRef = normalizeString(ref); if (!modelRef) return; if (!resolveModel(config, modelRef, { allowMissing: true })) { @@ -462,8 +473,6 @@ export function syncAgentModelWithRouter(config) { if (!agentRef) return config; const slash = agentRef.indexOf('/'); if (slash <= 0 || slash >= agentRef.length - 1) return config; - const providerId = agentRef.slice(0, slash); - const modelId = agentRef.slice(slash + 1); if (!isRecord(config.router)) return config; if (!isRecord(config.router.scenarios)) return config; @@ -473,9 +482,7 @@ export function syncAgentModelWithRouter(config) { ? currentDefault.trim() : (isRecord(currentDefault) ? normalizeString(currentDefault.id) : ''); if (currentId === agentRef) return config; - config.router.scenarios.default = typeof currentDefault === 'string' - ? agentRef - : { id: agentRef, provider: providerId, model: modelId }; + config.router.scenarios.default = agentRef; return config; } diff --git a/ui/server/services/pilotdeckConfig.router-model-ref.test.js b/ui/server/services/pilotdeckConfig.router-model-ref.test.js new file mode 100644 index 00000000..11607f30 --- /dev/null +++ b/ui/server/services/pilotdeckConfig.router-model-ref.test.js @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest'; +import { syncAgentModelWithRouter, validatePilotDeckConfig } from './pilotdeckConfig.js'; + +function configWithRouter(defaultRef) { + return { + schemaVersion: 1, + agent: { model: 'main/gpt-test' }, + model: { + providers: { + main: { + protocol: 'openai', + url: 'https://example.com/v1', + apiKey: 'sk-test', + models: { + 'gpt-test': {}, + }, + }, + }, + }, + router: { + scenarios: { + default: defaultRef, + }, + }, + }; +} + +describe('router model references', () => { + it('rejects object-shaped router model refs', () => { + const validation = validatePilotDeckConfig( + configWithRouter({ id: 'main/gpt-test', provider: 'main', model: 'gpt-test' }), + ); + + expect(validation.valid).toBe(false); + expect(validation.errors[0]).toContain('router.scenarios.default must be a provider/model string'); + }); + + it('syncs router.scenarios.default as a provider/model string', () => { + const config = configWithRouter({ id: 'old/model', provider: 'old', model: 'model' }); + + syncAgentModelWithRouter(config); + + expect(config.router.scenarios.default).toBe('main/gpt-test'); + }); +});