blob: 26f32a5770da35afb3b8c7051624bbf30edcc404 [file]
import assert from 'node:assert/strict';
import test from 'node:test';
import {
PRICING_EXCLUDED_PROVIDER_TYPES,
PROVIDERS,
toMetadata,
toPricing,
} from './sync-model-metadata.mjs';
const PROVIDER = { doc: 'https://example.com/docs' };
const BASE_MODEL = {
name: 'Test Model',
limit: { context: 128_000, output: 8_192 },
reasoning: true,
tool_call: true,
};
test('models.dev reasoning_options effort values pass through to thinkingOptions', () => {
const metadata = toMetadata('test', 'm', PROVIDER, {
...BASE_MODEL,
reasoning_options: [{ type: 'effort', values: ['high', 'max'] }],
});
assert.deepEqual(metadata.thinkingOptions, { efforts: ['high', 'max'] });
});
test('models.dev facts preserve the extended metadata contract', () => {
const metadata = toMetadata('test', 'm', PROVIDER, {
...BASE_MODEL,
description: 'A useful model',
knowledge: '2025-12-01',
last_updated: '2026-01-02',
structured_output: false,
status: 'beta',
limit: { context: 128_000, input: 96_000, output: 8_192 },
modalities: { input: ['text', 'image', 'pdf', 'video'], output: ['text'] },
});
assert.equal(metadata.description, 'A useful model');
assert.equal(metadata.knowledgeCutoff, '2025-12-01');
assert.equal(metadata.inputLimit, 96_000);
assert.equal(metadata.structuredOutput, false);
assert.equal(metadata.lastUpdated, '2026-01-02');
assert.equal(metadata.lifecycle, 'beta');
assert.deepEqual(metadata.modalities, { input: ['text', 'image', 'pdf'], output: ['text'] });
assert.equal(
toMetadata('test', 'alpha-model', PROVIDER, {
...BASE_MODEL,
status: 'alpha',
}).lifecycle,
'alpha',
);
});
test('models.dev pricing skips models with tiered rates until runtime supports tiers', () => {
for (const cost of [
{ input: 3, output: 15, context_over_200k: { input: 6, output: 30 } },
{ input: 3, output: 15, tiers: [{ input: 6, output: 30 }] },
]) {
assert.equal(toPricing('openai', 'gpt-test', { cost }), undefined);
}
});
test('subscription and plan access paths do not inherit public API pricing', () => {
assert.equal(PRICING_EXCLUDED_PROVIDER_TYPES.has('github-copilot'), true);
assert.equal(PRICING_EXCLUDED_PROVIDER_TYPES.has('gemini-cli'), true);
assert.equal(PRICING_EXCLUDED_PROVIDER_TYPES.has('zai-coding-plan'), true);
});
test('models.dev reasoning_options toggle passes through to thinkingOptions', () => {
const metadata = toMetadata('test', 'm', PROVIDER, {
...BASE_MODEL,
reasoning_options: [{ type: 'toggle' }, { type: 'effort', values: ['low', 'high'] }],
});
assert.deepEqual(metadata.thinkingOptions, { efforts: ['low', 'high'], toggle: true });
});
test('models without reasoning_options declare no thinkingOptions', () => {
const metadata = toMetadata('test', 'm', PROVIDER, BASE_MODEL);
assert.equal('thinkingOptions' in metadata, false);
});
test('a toggle-only model declares thinkingOptions without efforts', () => {
const metadata = toMetadata('test', 'm', PROVIDER, {
...BASE_MODEL,
reasoning_options: [{ type: 'toggle' }],
});
assert.deepEqual(metadata.thinkingOptions, { toggle: true });
});
test('budget_tokens is recognized and skipped until a wire consumes it', () => {
const metadata = toMetadata('test', 'm', PROVIDER, {
...BASE_MODEL,
reasoning_options: [{ type: 'budget_tokens' }, { type: 'effort', values: ['high'] }],
});
assert.deepEqual(metadata.thinkingOptions, { efforts: ['high'] });
});
test('an unknown reasoning_options type fails loudly instead of drifting', () => {
assert.throws(
() =>
toMetadata('test', 'm', PROVIDER, {
...BASE_MODEL,
reasoning_options: [{ type: 'mystery' }],
}),
/unsupported shape/,
);
});
test('an empty reasoning_options list declares no thinkingOptions', () => {
const metadata = toMetadata('test', 'm', PROVIDER, { ...BASE_MODEL, reasoning_options: [] });
assert.equal('thinkingOptions' in metadata, false);
});
test('malformed reasoning_options are rejected as an unsupported shape', () => {
assert.throws(
() => toMetadata('test', 'm', PROVIDER, { ...BASE_MODEL, reasoning_options: 'effort' }),
/unsupported shape/,
);
assert.throws(
() =>
toMetadata('test', 'm', PROVIDER, {
...BASE_MODEL,
reasoning_options: [{ type: 'effort', values: 'high' }],
}),
/unsupported shape/,
);
});
test('main() syncs only the mapped providers into the generated snapshot', async () => {
// End-to-end over the real main() path (the place the kimi/stepfun orphan
// bug lived): a fixture catalog covering every mapped source id plus one
// unmapped neighbour; only the mapped ones may produce segments.
const catalog = {};
for (const sourceId of Object.values(PROVIDERS)) {
catalog[sourceId] = {
id: sourceId,
name: sourceId,
doc: `https://${sourceId}.example/docs`,
api: `https://${sourceId}.example/v1`,
models: {},
};
}
catalog['kimi-for-coding'].api = 'https://api.kimi.com/coding/v1';
catalog['kimi-for-coding'].models.k3 = {
name: 'Kimi K3',
limit: { context: 1_048_576, output: 131_072 },
reasoning: true,
tool_call: true,
cost: { input: 1, output: 2 },
reasoning_options: [{ type: 'toggle' }, { type: 'effort', values: ['low', 'high', 'max'] }],
};
catalog['github-copilot'].models['gpt-5.5'] = {
name: 'GPT-5.5',
limit: { context: 400_000, input: 272_000, output: 128_000 },
reasoning: true,
tool_call: true,
cost: { input: 5, output: 30 },
};
catalog.google.models['gemini-2.5-pro'] = {
name: 'Gemini 2.5 Pro',
limit: { context: 1_000_000, input: 900_000, output: 64_000 },
reasoning: true,
tool_call: true,
cost: {
input: 1.25,
output: 10,
context_over_200k: { input: 2.5, output: 15 },
},
};
catalog.google.models['gemini-2.5-flash'] = {
name: 'Gemini 2.5 Flash',
limit: { context: 1_000_000, input: 900_000, output: 64_000 },
reasoning: true,
tool_call: true,
cost: { input: 0.3, output: 2.5 },
};
catalog['unmapped-provider'] = {
id: 'unmapped-provider',
name: 'Unmapped',
doc: 'https://example.com/docs',
api: 'https://unmapped.example/v1',
models: {
'm-1': {
name: 'M1',
limit: { context: 8_192, output: 4_096 },
reasoning: false,
tool_call: true,
},
},
};
const { mkdtemp, writeFile, readFile, rm } = await import('node:fs/promises');
const { tmpdir } = await import('node:os');
const { join } = await import('node:path');
const dir = await mkdtemp(join(tmpdir(), 'maka-sync-'));
const input = join(dir, 'catalog.json');
const output = join(dir, 'out.ts');
const pricingOutput = join(dir, 'pricing.ts');
await writeFile(input, JSON.stringify(catalog));
const { main } = await import('./sync-model-metadata.mjs');
await main(['--input', input, '--output', output, '--pricing-output', pricingOutput]);
const out = await readFile(output, 'utf8');
const pricing = await readFile(pricingOutput, 'utf8');
await rm(dir, { recursive: true, force: true });
assert.match(out, /"kimi-coding-plan": \{/);
assert.match(out, /"k3": \{/);
assert.match(out, /"thinkingOptions":\{"efforts":\["low","high","max"\],"toggle":true\}/);
assert.match(out, /"kimi-for-coding": \{/);
assert.match(out, /"kimi-for-coding": \{"api":"https:\/\/api\.kimi\.com\/coding\/v1"\}/);
assert.doesNotMatch(pricing, /"modelKey":"kimi-coding-plan:k3"/);
assert.doesNotMatch(pricing, /"modelKey":"github-copilot:gpt-5\.5"/);
assert.doesNotMatch(pricing, /"modelKey":"google:gemini-2\.5-pro"/);
assert.match(pricing, /"modelKey":"google:gemini-2\.5-flash","inputUsdPer1M":0\.3/);
assert.doesNotMatch(pricing, /"modelKey":"gemini-cli:gemini-2\.5-pro"/);
// The unmapped provider must appear only in the directory (the complete
// upstream catalog), never as a snapshot segment or provider fact.
const directoryStart = out.indexOf('GENERATED_MODELS_DEV_DIRECTORY');
assert.ok(directoryStart > 0);
assert.doesNotMatch(out.slice(0, directoryStart), /unmapped-provider/);
assert.match(out.slice(directoryStart), /"unmapped-provider": \{/);
});