Skip to content
Merged
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
26 changes: 20 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
{
"name": "@kenarihq/cli",
"version": "0.4.0",
"version": "0.4.1",
"description": "Official kenari CLI. Route Claude Code and Codex models per process.",
"license": "MIT",
"type": "module",
"bin": { "kenari": "bin/kenari.js" },
"files": ["bin", "src", "README.md", "LICENSE"],
"engines": { "node": ">=18" },
"scripts": { "test": "node --test" },
"repository": { "type": "git", "url": "git+https://github.com/kenarihq/cli.git" }
"bin": {
"kenari": "bin/kenari.js"
},
"files": [
"bin",
"src",
"README.md",
"LICENSE"
],
"engines": {
"node": ">=18"
},
"scripts": {
"test": "node --test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kenarihq/cli.git"
}
}
18 changes: 16 additions & 2 deletions src/runtime/codex.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,24 @@ export function loadCodexNativeModels(binary, env = process.env) {
throw new KenariError('cannot read the Codex native model catalog');
}

function normalizeModelId(id) {
return String(id || '').replace(/\./g, '-');
}

export function codexKenariModels(cache, nativeModels = []) {
const fallbackTemplate = nativeModels[0];
if ((cache?.models || []).length && !fallbackTemplate) {
throw new KenariError('a native Codex model is required to build the merged catalog');
}
const basePriority = Math.max(0, ...nativeModels.map((model) => model.priority || 0));
return (cache?.models || []).map((model, index) => {
const template = nativeModels.find((native) => native.slug === model.id) || fallbackTemplate;
const normalizedId = normalizeModelId(model.id);
const template = nativeModels.find((native) => normalizeModelId(native.slug) === normalizedId)
|| fallbackTemplate;
const efforts = model.reasoning_efforts?.length
? model.reasoning_efforts
: (template.supported_reasoning_levels || []).map((level) => level.effort);
return {
const entry = {
...template,
slug: `kenari/${model.id}`,
display_name: `Kenari ${model.id}`,
Expand All @@ -69,6 +75,14 @@ export function codexKenariModels(cache, nativeModels = []) {
max_context_window: model.context_limit || template.max_context_window,
upgrade: null,
};
// Kenari-routed models must use classic top-level function tools. The gpt-5.6
// templates carry OpenAI-private harness modes: in code_mode Codex sends an empty
// tools array and hides the real definitions in an "additional_tools" input item
// that only OpenAI's own backend expands, so the model ends up with no tools.
delete entry.tool_mode;
delete entry.multi_agent_version;
entry.use_responses_lite = false;
return entry;
});
}

Expand Down
44 changes: 44 additions & 0 deletions test/v2-runtime-supervisor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,50 @@ test('Codex launch injects temporary controls before original args', () => {
}
});

test('codexKenariModels removes OpenAI-private mode fields and matches dotted native slugs', () => {
const native = [{
slug: 'gpt-5.6-sol',
tool_mode: 'code_mode',
use_responses_lite: true,
multi_agent_version: 'v2',
supported_reasoning_levels: [{ effort: 'low', description: 'Low' }],
context_window: 100000,
max_context_window: 100000,
priority: 10,
}, {
slug: 'gpt-5.5',
supported_reasoning_levels: [{ effort: 'low', description: 'Low' }],
context_window: 100000,
max_context_window: 100000,
priority: 5,
}];
const kenari = codexKenariModels({
models: [
{ id: 'gpt-5-6-sol', context_limit: 200000, reasoning_efforts: ['medium', 'high'] },
{ id: 'glm-5-2', context_limit: 128000 },
],
}, native);
const sol = kenari.find((m) => m.slug === 'kenari/gpt-5-6-sol');
assert.ok(sol, 'gpt-5-6-sol entry exists');
assert.equal(sol.context_window, 200000);
assert.equal(sol.priority, 11);
assert.equal('tool_mode' in sol, false);
assert.equal('multi_agent_version' in sol, false);
assert.equal(sol.use_responses_lite, false);
assert.deepEqual(sol.supported_reasoning_levels, [
{ effort: 'medium', description: 'medium reasoning' },
{ effort: 'high', description: 'high reasoning' },
]);
assert.equal(sol.max_context_window, 200000);

const glm = kenari.find((m) => m.slug === 'kenari/glm-5-2');
assert.ok(glm, 'glm-5-2 entry exists');
assert.equal(glm.context_window, 128000);
assert.equal('tool_mode' in glm, false);
assert.equal('multi_agent_version' in glm, false);
assert.equal(glm.use_responses_lite, false);
});

test('Codex native upstream follows the active login method', () => {
const cases = [
{
Expand Down
Loading