kenaz 0.3.13 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -15,9 +15,12 @@ const os = require('os');
|
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const { spawn, spawnSync } = require('child_process');
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// First = default. From GET https://api.deepseek.com/models on 2026-10-11 (V4 ids, 1M context); the V3 ids still
|
|
19
|
+
// answer but are no longer listed. `kenaz launch` re-reads the context window from that endpoint at start.
|
|
20
|
+
const DEEPSEEK_MODELS = ['deepseek-v4-pro', 'deepseek-flash', 'deepseek-chat', 'deepseek-reasoner'];
|
|
19
21
|
const DEEPSEEK_BASE_URL = 'https://api.deepseek.com/anthropic';
|
|
20
|
-
const DEEPSEEK_CONTEXT_TOKENS = 128000;
|
|
22
|
+
const DEEPSEEK_CONTEXT_TOKENS = { 'deepseek-v4-pro': 1048576, 'deepseek-flash': 1048576, 'deepseek-chat': 128000, 'deepseek-reasoner': 128000 };
|
|
23
|
+
const DEEPSEEK_FAST = 'deepseek-flash'; // Claude Code's small/fast slot (titles, summaries): the cheap model
|
|
21
24
|
const CLAUDE_TOOLS = ['Bash', 'Edit', 'Write', 'Read', 'Glob', 'Grep'];
|
|
22
25
|
|
|
23
26
|
const codexSpec = (id) => require('./openai/relay.js').resolveCodexModelSpec(id);
|
|
@@ -66,14 +69,26 @@ function buildChild({ adapter, id, worktree, lastFile, key, gitDir }) {
|
|
|
66
69
|
/** Claude Code env for DeepSeek's Anthropic-compatible endpoint (shared by exec.js and `kenaz launch --provider deepseek`).
|
|
67
70
|
* Every model slot points at DeepSeek: background requests (small/fast = haiku) and Agent-tool subagents
|
|
68
71
|
* (sonnet/opus aliases) would otherwise ask DeepSeek for Claude model names it does not serve. */
|
|
69
|
-
function deepseekEnv(id, key) {
|
|
72
|
+
function deepseekEnv(id, key, contextTokens) {
|
|
70
73
|
return {
|
|
71
|
-
ANTHROPIC_BASE_URL: DEEPSEEK_BASE_URL, ANTHROPIC_AUTH_TOKEN: key, ANTHROPIC_MODEL: id, ANTHROPIC_SMALL_FAST_MODEL:
|
|
72
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL:
|
|
73
|
-
// Claude Code does not know DeepSeek ids and assumes a 200k window
|
|
74
|
-
|
|
74
|
+
ANTHROPIC_BASE_URL: DEEPSEEK_BASE_URL, ANTHROPIC_AUTH_TOKEN: key, ANTHROPIC_MODEL: id, ANTHROPIC_SMALL_FAST_MODEL: DEEPSEEK_FAST,
|
|
75
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: DEEPSEEK_FAST, ANTHROPIC_DEFAULT_SONNET_MODEL: id, ANTHROPIC_DEFAULT_OPUS_MODEL: id, CLAUDE_CODE_SUBAGENT_MODEL: id,
|
|
76
|
+
// Claude Code does not know DeepSeek ids and assumes a 200k window: tell it the real one (V4 = 1M; a 128k
|
|
77
|
+
// value here made a V4 session compact right after the Ansuz boot).
|
|
78
|
+
CLAUDE_CODE_MAX_CONTEXT_TOKENS: String(contextTokens || DEEPSEEK_CONTEXT_TOKENS[id] || 128000),
|
|
75
79
|
};
|
|
76
80
|
}
|
|
81
|
+
|
|
82
|
+
/** {id: context_window} from GET /models (the live list); null on any failure (callers fall back to the table). */
|
|
83
|
+
async function deepseekModels(key, fetchFn = globalThis.fetch, timeoutMs = 3000) {
|
|
84
|
+
try {
|
|
85
|
+
const r = await fetchFn('https://api.deepseek.com/models', { headers: { Authorization: 'Bearer ' + key }, signal: AbortSignal.timeout(timeoutMs) });
|
|
86
|
+
const j = await r.json();
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const m of (j && j.data) || []) if (m && m.id) out[m.id] = Number(m.context_window) || null;
|
|
89
|
+
return Object.keys(out).length ? out : null;
|
|
90
|
+
} catch { return null; }
|
|
91
|
+
}
|
|
77
92
|
const DEEPSEEK_UNSET = ['ANTHROPIC_API_KEY']; // never send a real Anthropic key to another vendor
|
|
78
93
|
|
|
79
94
|
const git = (cwd, args) => spawnSync('git', args, { cwd, encoding: 'utf8', windowsHide: true });
|
|
@@ -144,4 +159,4 @@ async function main(argv) {
|
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
if (require.main === module) main(process.argv.slice(2)).then((c) => { process.exitCode = c; });
|
|
147
|
-
module.exports = { parseExecutorModel, deepseekKey, deepseekEnv, DEEPSEEK_UNSET, winQuote, buildChild, run, main, DEEPSEEK_MODELS };
|
|
162
|
+
module.exports = { parseExecutorModel, deepseekKey, deepseekEnv, deepseekModels, DEEPSEEK_UNSET, winQuote, buildChild, run, main, DEEPSEEK_MODELS };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
/**
|
|
4
|
-
* `kenaz launch --provider deepseek [--model deepseek-
|
|
4
|
+
* `kenaz launch --provider deepseek [--model deepseek-v4-pro|deepseek-flash|...] [claude args...]`
|
|
5
5
|
* Starts Claude Code with every model request sent to another vendor's Anthropic-compatible endpoint, so
|
|
6
6
|
* Ansuz (/kenaz:Ansuz), its subagents and background requests run on that vendor and use no Claude quota.
|
|
7
7
|
* The key is read from the env or ~/.kenaz/credentials.yaml and set only in the claude child's env: never in
|
|
@@ -9,18 +9,17 @@
|
|
|
9
9
|
*/
|
|
10
10
|
const os = require('os');
|
|
11
11
|
const { spawn } = require('child_process');
|
|
12
|
-
const { deepseekKey, deepseekEnv, DEEPSEEK_UNSET, DEEPSEEK_MODELS, winQuote } = require('./exec');
|
|
12
|
+
const { deepseekKey, deepseekEnv, deepseekModels, DEEPSEEK_UNSET, DEEPSEEK_MODELS, winQuote } = require('./exec');
|
|
13
13
|
|
|
14
14
|
const PROVIDERS = {
|
|
15
15
|
deepseek: {
|
|
16
|
-
models: DEEPSEEK_MODELS, key: deepseekKey, env: deepseekEnv, unset: DEEPSEEK_UNSET,
|
|
16
|
+
models: DEEPSEEK_MODELS, key: deepseekKey, env: deepseekEnv, live: deepseekModels, unset: DEEPSEEK_UNSET,
|
|
17
17
|
keyHelp: 'set DEEPSEEK_API_KEY, or add `deepseek_api_key: <key>` to ~/.kenaz/credentials.yaml',
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
20
|
const USAGE = 'usage: kenaz launch --provider <' + Object.keys(PROVIDERS).join('|') + '> [--model <id>] [claude args...]';
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
function plan(argv, env = process.env, home = os.homedir()) {
|
|
22
|
+
function parse(argv) {
|
|
24
23
|
const rest = []; let provider = null; let model = null;
|
|
25
24
|
for (let i = 0; i < argv.length; i++) {
|
|
26
25
|
const m = /^--(provider|model)(?:=(.*))?$/.exec(argv[i]);
|
|
@@ -28,22 +27,35 @@ function plan(argv, env = process.env, home = os.homedir()) {
|
|
|
28
27
|
const v = m[2] !== undefined ? m[2] : argv[++i];
|
|
29
28
|
if (m[1] === 'provider') provider = v; else model = v;
|
|
30
29
|
}
|
|
30
|
+
return { rest, provider, model };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Child process plan: { bin, args, env }. live = {id: context_window} from the vendor's model list (or null):
|
|
34
|
+
* a listed id is valid even if it is newer than the built-in list, and its window drives auto-compact.
|
|
35
|
+
* Throws with a user-facing message. */
|
|
36
|
+
function plan(argv, env = process.env, home = os.homedir(), live = null) {
|
|
37
|
+
const { rest, provider, model } = parse(argv);
|
|
31
38
|
const p = PROVIDERS[provider];
|
|
32
39
|
if (!p) throw new Error((provider ? `unknown provider '${provider}'. ` : '') + USAGE);
|
|
33
40
|
const id = model || p.models[0];
|
|
34
|
-
|
|
41
|
+
const valid = [...new Set([...p.models, ...Object.keys(live || {})])];
|
|
42
|
+
if (!valid.includes(id)) throw new Error(`unknown ${provider} model '${id}'; valid: ${valid.join(', ')}`);
|
|
35
43
|
const key = p.key(env, home);
|
|
36
44
|
if (!key) throw new Error(`${provider}: no API key (${p.keyHelp})`);
|
|
37
|
-
const childEnv = { ...env, ...p.env(id, key), CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1' };
|
|
45
|
+
const childEnv = { ...env, ...p.env(id, key, live && live[id]), CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1' };
|
|
38
46
|
for (const k of p.unset) delete childEnv[k];
|
|
39
|
-
return { bin: 'claude', args: rest, env: childEnv, provider, model: id };
|
|
47
|
+
return { bin: 'claude', args: rest, env: childEnv, provider, model: id, context: childEnv.CLAUDE_CODE_MAX_CONTEXT_TOKENS };
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
/** Resolves to the child's exit code. deps: { spawn, env, home, platform } for tests. */
|
|
43
|
-
function main(argv, out = console.log, deps = {}) {
|
|
51
|
+
async function main(argv, out = console.log, deps = {}) {
|
|
52
|
+
const env = deps.env || process.env, home = deps.home || os.homedir();
|
|
53
|
+
const p = PROVIDERS[parse(argv).provider];
|
|
54
|
+
const key = p && p.key(env, home);
|
|
55
|
+
const live = key ? await (deps.live || p.live)(key) : null;
|
|
44
56
|
let pl;
|
|
45
|
-
try { pl = plan(argv,
|
|
46
|
-
out(`kenaz launch: Claude Code on ${pl.provider} (${pl.model}); no Claude quota is used in this session`);
|
|
57
|
+
try { pl = plan(argv, env, home, live); } catch (e) { out(e.message); return 2; }
|
|
58
|
+
out(`kenaz launch: Claude Code on ${pl.provider} (${pl.model}, context ${pl.context} tokens); no Claude quota is used in this session`);
|
|
47
59
|
const win = (deps.platform || process.platform) === 'win32'; // claude is a .cmd shim on npm installs
|
|
48
60
|
const child = (deps.spawn || spawn)(win ? [pl.bin, ...pl.args].map(winQuote).join(' ') : pl.bin, win ? [] : pl.args,
|
|
49
61
|
{ stdio: 'inherit', env: pl.env, shell: win });
|
|
@@ -16,7 +16,7 @@ function fakeSpawn(calls, code = 0) {
|
|
|
16
16
|
test('deepseek: every model slot points at DeepSeek, key only in the child env, Anthropic key removed', async () => {
|
|
17
17
|
const calls = [], lines = [];
|
|
18
18
|
const rc = await main(['--provider', 'deepseek', '--resume', 'abc'], (l) => lines.push(l),
|
|
19
|
-
{ spawn: fakeSpawn(calls), env: { DEEPSEEK_API_KEY: KEY, ANTHROPIC_API_KEY: 'real-anthropic', PATH: 'p' }, home: home(), platform: 'linux' });
|
|
19
|
+
{ spawn: fakeSpawn(calls), live: async () => null, env: { DEEPSEEK_API_KEY: KEY, ANTHROPIC_API_KEY: 'real-anthropic', PATH: 'p' }, home: home(), platform: 'linux' });
|
|
20
20
|
assert.strictEqual(rc, 0);
|
|
21
21
|
const c = calls[0];
|
|
22
22
|
assert.strictEqual(c.cmd, 'claude');
|
|
@@ -24,9 +24,11 @@ test('deepseek: every model slot points at DeepSeek, key only in the child env,
|
|
|
24
24
|
const e = c.opts.env;
|
|
25
25
|
assert.strictEqual(e.ANTHROPIC_BASE_URL, 'https://api.deepseek.com/anthropic');
|
|
26
26
|
assert.strictEqual(e.ANTHROPIC_AUTH_TOKEN, KEY);
|
|
27
|
-
for (const k of ['ANTHROPIC_MODEL', '
|
|
28
|
-
assert.strictEqual(e[k], 'deepseek-
|
|
27
|
+
for (const k of ['ANTHROPIC_MODEL', 'ANTHROPIC_DEFAULT_SONNET_MODEL', 'ANTHROPIC_DEFAULT_OPUS_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL']) {
|
|
28
|
+
assert.strictEqual(e[k], 'deepseek-v4-pro', k);
|
|
29
29
|
}
|
|
30
|
+
assert.strictEqual(e.ANTHROPIC_SMALL_FAST_MODEL, 'deepseek-flash'); assert.strictEqual(e.ANTHROPIC_DEFAULT_HAIKU_MODEL, 'deepseek-flash');
|
|
31
|
+
assert.strictEqual(e.CLAUDE_CODE_MAX_CONTEXT_TOKENS, '1048576', 'V4 window from the built-in table when the live list is unavailable');
|
|
30
32
|
assert.strictEqual(e.ANTHROPIC_API_KEY, undefined);
|
|
31
33
|
assert.strictEqual(e.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, '1');
|
|
32
34
|
assert.strictEqual(e.PATH, 'p', 'the rest of the env is kept');
|
|
@@ -37,11 +39,12 @@ test('--model, key from ~/.kenaz/credentials.yaml, Windows goes through the shel
|
|
|
37
39
|
const h = home(); fs.mkdirSync(path.join(h, '.kenaz'));
|
|
38
40
|
fs.writeFileSync(path.join(h, '.kenaz', 'credentials.yaml'), `relay: x\ndeepseek_api_key: "${KEY}"\n`);
|
|
39
41
|
const calls = [];
|
|
40
|
-
const rc = await main(['--provider=deepseek', '--model', 'deepseek-reasoner'], () => {}, { spawn: fakeSpawn(calls), env: {}, home: h, platform: 'win32' });
|
|
42
|
+
const rc = await main(['--provider=deepseek', '--model', 'deepseek-reasoner'], () => {}, { spawn: fakeSpawn(calls), live: async () => null, env: {}, home: h, platform: 'win32' });
|
|
41
43
|
assert.strictEqual(rc, 0);
|
|
42
44
|
assert.strictEqual(calls[0].cmd, 'claude');
|
|
43
45
|
assert.strictEqual(calls[0].opts.shell, true);
|
|
44
46
|
assert.strictEqual(calls[0].opts.env.ANTHROPIC_MODEL, 'deepseek-reasoner');
|
|
47
|
+
assert.strictEqual(calls[0].opts.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS, '128000', 'legacy V3 id keeps its 128k window');
|
|
45
48
|
assert.ok(!calls[0].cmd.includes(KEY));
|
|
46
49
|
});
|
|
47
50
|
|
|
@@ -50,13 +53,23 @@ test('control: no key, unknown provider or model -> exit 2 and claude is never s
|
|
|
50
53
|
[['--provider', 'deepseek'], {}, /no API key/],
|
|
51
54
|
[['--provider', 'nope'], { DEEPSEEK_API_KEY: KEY }, /unknown provider 'nope'/],
|
|
52
55
|
[[], { DEEPSEEK_API_KEY: KEY }, /usage: kenaz launch --provider <deepseek>/],
|
|
53
|
-
[['--provider', 'deepseek', '--model', 'gpt-x'], { DEEPSEEK_API_KEY: KEY }, /valid: deepseek-chat, deepseek-reasoner/],
|
|
56
|
+
[['--provider', 'deepseek', '--model', 'gpt-x'], { DEEPSEEK_API_KEY: KEY }, /valid: deepseek-v4-pro, deepseek-flash, deepseek-chat, deepseek-reasoner/],
|
|
54
57
|
]) {
|
|
55
58
|
const calls = [], lines = [];
|
|
56
|
-
const rc = await main(argv, (l) => lines.push(l), { spawn: fakeSpawn(calls), env, home: home(), platform: 'linux' });
|
|
59
|
+
const rc = await main(argv, (l) => lines.push(l), { spawn: fakeSpawn(calls), live: async () => null, env, home: home(), platform: 'linux' });
|
|
57
60
|
assert.strictEqual(rc, 2, argv.join(' '));
|
|
58
61
|
assert.strictEqual(calls.length, 0, 'claude not started: ' + argv.join(' '));
|
|
59
62
|
assert.match(lines.join('\n'), re);
|
|
60
63
|
}
|
|
61
64
|
assert.throws(() => plan(['--provider', 'deepseek'], {}, home()), /no API key/);
|
|
62
65
|
});
|
|
66
|
+
|
|
67
|
+
test('live model list: a newer listed id is accepted and its context window drives auto-compact', async () => {
|
|
68
|
+
const calls = [], lines = [];
|
|
69
|
+
const live = async (k) => { assert.strictEqual(k, KEY); return { 'deepseek-v5-preview': 2097152, 'deepseek-v4-pro': 1048576 }; };
|
|
70
|
+
const rc = await main(['--provider', 'deepseek', '--model', 'deepseek-v5-preview'], (l) => lines.push(l), { spawn: fakeSpawn(calls), live, env: { DEEPSEEK_API_KEY: KEY }, home: home(), platform: 'linux' });
|
|
71
|
+
assert.strictEqual(rc, 0);
|
|
72
|
+
assert.strictEqual(calls[0].opts.env.ANTHROPIC_MODEL, 'deepseek-v5-preview');
|
|
73
|
+
assert.strictEqual(calls[0].opts.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS, '2097152');
|
|
74
|
+
assert.match(lines[0], /context 2097152 tokens/);
|
|
75
|
+
});
|
package/core/KENAZ_CORE_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.14
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kenaz",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Install Kenaz (plugin, hooks, kenaz-server) for Claude Code and Codex: npx kenaz install",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kenaz": "bin/kenaz.js"
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"test": "node --test lib/cli.test.js"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@kenazai/server-darwin-arm64": "0.3.
|
|
24
|
-
"@kenazai/server-darwin-x64": "0.3.
|
|
25
|
-
"@kenazai/server-linux-arm64": "0.3.
|
|
26
|
-
"@kenazai/server-linux-x64": "0.3.
|
|
27
|
-
"@kenazai/server-win32-x64": "0.3.
|
|
23
|
+
"@kenazai/server-darwin-arm64": "0.3.14",
|
|
24
|
+
"@kenazai/server-darwin-x64": "0.3.14",
|
|
25
|
+
"@kenazai/server-linux-arm64": "0.3.14",
|
|
26
|
+
"@kenazai/server-linux-x64": "0.3.14",
|
|
27
|
+
"@kenazai/server-win32-x64": "0.3.14"
|
|
28
28
|
},
|
|
29
29
|
"license": "UNLICENSED",
|
|
30
30
|
"repository": {
|