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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"./llm-connections": "./dist/llm-connections.js",
"./provider-registry": "./dist/provider-registry.js",
"./model-catalog": "./dist/model-catalog.js",
"./model-facts": "./dist/model-facts.js",
"./model-metadata": "./dist/model-metadata.js",
"./model-web-search": "./dist/model-web-search.js",
"./model-thinking": "./dist/model-thinking.js",
Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/__tests__/llm-connections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,33 @@ test('model reconciliation never invents a default the user cleared', () => {
);
});

test('model reconciliation preserves enabled model-fact overrides outside live inventory', () => {
assert.deepEqual(
reconcileConnectionAfterModelFetch(
{
defaultModel: 'custom-model',
enabledModelIds: ['custom-model'],
hasModelInventory: true,
},
[{ id: 'live-model' }],
{ factBackedModelIds: new Set(['custom-model']) },
),
{ defaultModel: 'custom-model', enabledModelIds: ['custom-model'] },
);
assert.deepEqual(
reconcileConnectionAfterModelFetch(
{
defaultModel: 'live-model',
enabledModelIds: ['live-model', 'custom-model'],
hasModelInventory: true,
},
[{ id: 'live-model' }, { id: 'other-live-model' }],
{ factBackedModelIds: new Set(['custom-model']) },
),
{ defaultModel: 'live-model', enabledModelIds: ['live-model', 'custom-model'] },
);
});

test('a renamed id follows its model, and only for a caller that supplies the table', () => {
const curated = [{ id: 'claude-opus-5' }, { id: 'claude-haiku-4-5' }];
const stored = {
Expand Down
40 changes: 40 additions & 0 deletions packages/core/src/__tests__/model-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,27 @@ test('connection catalogs preserve user-choice provenance without inventing avai
assert.deepEqual(entries[2]?.provenance.sources?.userChoice, ['session_model']);
});

test('catalog provenance follows the projected model facts marker used in production', () => {
const [entry] = buildConnectionModelCatalogEntries({
connection: {
slug: 'facts',
providerType: 'openai',
defaultModel: 'custom-model',
models: [
{
id: 'custom-model',
contextWindow: 200_000,
capabilities: { chat: true },
factOverriddenFields: ['contextWindow', 'capabilities'],
},
],
modelSource: 'fetched',
},
});
assert.equal(entry?.capabilitySource, 'user_override');
assert.equal(entry?.contextWindow, 200_000);
});

test('unknown persisted provider ids return an empty catalog', () => {
assert.deepEqual(
buildConnectionModelCatalogEntries({
Expand Down Expand Up @@ -174,3 +195,22 @@ test('Alibaba Token Plan catalogs the formal Qwen3.8 model instead of its retire
assert.equal(model?.canUseAsChatDefault, true, providerType);
}
});

test('saved model choices do not expose unrelated entries', () => {
const entries = buildConnectionModelCatalogEntries({
connection: {
slug: 'zai-live',
providerType: 'zai-coding-plan',
defaultModel: 'glm-4.7',
models: [{ id: 'glm-4.7' }],
modelSource: 'fetched',
},
savedModelIds: [{ id: 'saved-custom', source: 'session_model' }],
});
const saved = entries.find((entry) => entry.id === 'saved-custom');
assert.equal(saved?.displayName, undefined);
assert.equal(
entries.some((entry) => entry.id === 'hidden'),
false,
);
});
121 changes: 121 additions & 0 deletions packages/core/src/__tests__/model-facts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
applyModelFactOverride,
applyModelFactOverridesToConnection,
decodeModelFactsDocument,
modelFactKey,
} from '../model-facts.js';

test('model facts use provider:model keys and merge fields without replacing provider facts', () => {
const key = modelFactKey('openai', 'o4-mini');
const document = decodeModelFactsDocument({
schemaVersion: 1,
overrides: { [key]: { contextWindow: 200_000, capabilities: { vision: false } } },
});
const model = applyModelFactOverride(
{
id: 'o4-mini',
displayName: 'Provider name',
maxOutputTokens: 4_000,
capabilities: { chat: true, vision: true },
},
document.overrides[key],
);
assert.equal(model.displayName, 'Provider name');
assert.equal(model.contextWindow, 200_000);
assert.deepEqual(model.capabilities, { chat: true, vision: false });
});

test('malformed and unknown model fact fields are rejected', () => {
assert.throws(() =>
decodeModelFactsDocument({ schemaVersion: 1, overrides: { 'openai:o4-mini': { nope: true } } }),
);
assert.throws(() =>
decodeModelFactsDocument({ schemaVersion: 1, overrides: { 'o4-mini': { contextWindow: 1 } } }),
);
assert.throws(() =>
decodeModelFactsDocument({
schemaVersion: 1,
overrides: { 'openai:o4-mini': { contextWindow: 0 } },
}),
);
assert.throws(() =>
decodeModelFactsDocument({
schemaVersion: 1,
overrides: { 'openai:o4-mini': { capabilities: { toString: true } } },
}),
);
assert.throws(() =>
decodeModelFactsDocument({
schemaVersion: 1,
overrides: { 'toString:model': { contextWindow: 1 } },
}),
);
});

test('model fact keys preserve colons in provider model ids', () => {
const key = modelFactKey('ollama-cloud', 'gpt-oss:120b');
assert.equal(key, 'ollama-cloud:gpt-oss:120b');
const document = decodeModelFactsDocument({
schemaVersion: 1,
overrides: { [key]: { contextWindow: 131_072 } },
});
assert.equal(document.overrides[key]?.contextWindow, 131_072);
});

test('override-only models are projected only when enabled', () => {
const connection = {
slug: 'openai',
providerType: 'openai' as const,
defaultModel: 'custom',
enabledModelIds: ['custom'],
models: [{ id: 'provider-model' }],
};
const result = applyModelFactOverridesToConnection(connection, {
'openai:custom': { contextWindow: 64_000 },
'openai:hidden': { contextWindow: 1_000 },
});
assert.deepEqual(result.models, [
{ id: 'provider-model' },
{
id: 'custom',
contextWindow: 64_000,
inputLimit: 64_000,
factOverriddenFields: ['contextWindow', 'inputLimit'],
},
]);
});

test('context window facts cannot be truncated by an older input limit', () => {
const result = applyModelFactOverride(
{ id: 'model', contextWindow: 8_192, inputLimit: 8_192 },
{ contextWindow: 200_000 },
);
assert.equal(result.contextWindow, 200_000);
assert.equal(result.inputLimit, 200_000);
});

test('overrides replace fields on discovered models while preserving untouched provider facts', () => {
const result = applyModelFactOverridesToConnection(
{
providerType: 'openai',
defaultModel: 'provider-model',
enabledModelIds: ['provider-model'],
models: [
{ id: 'provider-model', contextWindow: 8_000, capabilities: { chat: true, vision: true } },
],
},
{ 'openai:provider-model': { contextWindow: 64_000, capabilities: { vision: false } } },
);
assert.equal(result.models?.[0]?.contextWindow, 64_000);
assert.deepEqual(result.models?.[0]?.capabilities, { chat: true, vision: false });
});

test('catalog capabilities preserve web search facts from metadata and overrides', () => {
const result = applyModelFactOverride(
{ id: 'web-model', capabilities: { webSearch: true } },
{ capabilities: { chat: true } },
);
assert.deepEqual(result.capabilities, { webSearch: true, chat: true });
});
44 changes: 44 additions & 0 deletions packages/core/src/__tests__/runtime-policy-codec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,50 @@ test('normalizes exact bounded model discovery results', () => {
}
});

test('normalizes extended model facts used by the runtime host catalog', () => {
const result = normalizeConnectionModelDiscoveryResult({
models: [
{
id: 'custom-model',
description: 'A custom model',
inputLimit: 120_000,
knowledgeCutoff: '2025-01',
structuredOutput: true,
lastUpdated: '2026-01-01',
modalities: { input: ['text', 'image'], output: ['text'] },
},
],
source: 'fetched',
fetchedAt: 42,
});
assert.deepEqual(result.models[0], {
id: 'custom-model',
description: 'A custom model',
inputLimit: 120_000,
knowledgeCutoff: '2025-01',
structuredOutput: true,
lastUpdated: '2026-01-01',
modalities: { input: ['text', 'image'], output: ['text'] },
});
});

test('rejects sparse model modality arrays', () => {
assert.throws(
() =>
normalizeConnectionModelDiscoveryResult({
models: [
{
id: 'custom-model',
modalities: { input: Array(1), output: ['text'] },
},
],
source: 'fetched',
fetchedAt: 42,
}),
RuntimePolicyDomainDecodeError,
);
});

test('credential domain validation requires material but leaves capacity to callers', () => {
const input = normalizeSetCredentialInput({
locator: {
Expand Down
36 changes: 33 additions & 3 deletions packages/core/src/llm-connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,27 @@ export interface ModelInfo {
input: Array<'text' | 'image' | 'audio' | 'pdf'>;
output: Array<'text' | 'image' | 'audio'>;
};
/**
* Read-time provenance for values overlaid from model-facts.json. This is
* never persisted in a provider inventory; it lets catalog consumers show
* where a projected value came from.
*/
factOverriddenFields?: readonly ModelFactField[];
}

export type ModelFactField =
| 'displayName'
| 'description'
| 'apiProtocol'
| 'contextWindow'
| 'inputLimit'
| 'maxOutputTokens'
| 'knowledgeCutoff'
| 'structuredOutput'
| 'lastUpdated'
| 'capabilities'
| 'modalities';

export type ModelDiscoverySource = 'fetched' | 'fallback';

export interface ModelDiscoveryResult {
Expand Down Expand Up @@ -258,6 +277,12 @@ export function reconcileConnectionAfterModelFetch(
* caller that knows the provider's naming supplies the table.
*/
readonly aliases?: Readonly<Record<string, string>>;
/**
* Model-fact-backed ids that were already enabled on this connection.
* These remain selectable when a provider omits them from live discovery;
* the ids are never added to the provider inventory itself.
*/
readonly factBackedModelIds?: ReadonlySet<string>;
},
): {
defaultModel: string;
Expand Down Expand Up @@ -290,6 +315,7 @@ export function reconcileConnectionAfterModelFetch(
),
),
];
const factBackedModelIds = options?.factBackedModelIds ?? new Set<string>();

if (liveIds.length === 0) {
const defaultModel = previousDefault;
Expand All @@ -308,20 +334,24 @@ export function reconcileConnectionAfterModelFetch(
if (connection.hasModelInventory || previousEnabled.length > 0) {
return {
defaultModel: '',
enabledModelIds: previousEnabled.filter((id) => live.has(id)),
enabledModelIds: previousEnabled.filter((id) => live.has(id) || factBackedModelIds.has(id)),
};
}
return { defaultModel: liveIds[0]!, enabledModelIds: [liveIds[0]!] };
}

const defaultModel =
(live.has(previousDefault) ? previousDefault : undefined) ??
(live.has(previousDefault) || factBackedModelIds.has(previousDefault)
? previousDefault
: undefined) ??
previousEnabled.find((id) => live.has(id)) ??
liveIds[0]!;

// Keep previously enabled ids that still exist live, plus the (possibly
// repaired) default. Do not auto-enable the entire discovered catalog.
const keptEnabled = previousEnabled.filter((id) => live.has(id) || id === defaultModel);
const keptEnabled = previousEnabled.filter(
(id) => live.has(id) || factBackedModelIds.has(id) || id === defaultModel,
);
return {
defaultModel,
enabledModelIds: connectionEnabledModelIds({
Expand Down
Loading