Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions ui/server/services/pilotdeckConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 })) {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down
45 changes: 45 additions & 0 deletions ui/server/services/pilotdeckConfig.router-model-ref.test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});