@ngockhoale/ukit 3.1.2 → 3.1.4
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.
- package/CHANGELOG.md +8 -0
- package/manifests/platform.user.yaml +11 -0
- package/package.json +1 -1
- package/src/cli/commands/decision.js +150 -0
- package/src/cli/index.js +6 -1
- package/src/core/gatewayProbe.js +40 -24
- package/src/core/runtimeConfig.js +1 -1
- package/src/decision/client.js +5 -6
- package/template_project/.claude/ukit/index/unic-decision.mjs +36 -25
- package/template_project/ukit/storage/config.json +1 -1
- package/template_user/storage/config.json +6 -0
- package/template_user/storage/gatewayDecision.json +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 3.1.4 - 2026-09-26
|
|
6
|
+
|
|
7
|
+
- **`ukit decision` — user-facing decision-lane gateway setup.** Resolution order (per field): env `UKIT_DECISION_BASE_URL`/`UKIT_DECISION_API_KEY`/`UKIT_DECISION_SCHEME` → `~/.ukit/storage/gatewayDecision.json` (new per-lane file; `gateway<Lane>.json` pattern) → `~/.ukit/storage/config.json` `decisionGateway` → `openai.unicjsc.com` + shared UNIC token. File field names mirror the env vars; `scheme` selects `Authorization: Bearer` vs `x-api-key`. Both files are seeded by `ukit install` with the full key set (`mergeStrategy: skip` — `ukit update` never overwrites user values). Commands: `--status`, `--base-url/--key/--scheme`, `--set-unic`, `--clear`.
|
|
8
|
+
|
|
9
|
+
## 3.1.3 - 2026-09-26
|
|
10
|
+
|
|
11
|
+
- **`unic-decision-multilingual` becomes the shipped default checkpoint** — the variant is credential-bound on UNIC Provider (verified via live `health-probe` + `safe-vs-risky-lane` decision). `english`/`unknownLanguage` classes keep the bare `unic-decision` alias; `model_not_found` auto-fallback still covers unbound variants.
|
|
12
|
+
|
|
5
13
|
## 3.1.2 - 2026-09-26
|
|
6
14
|
|
|
7
15
|
- **Decision plane goes live on the UNIC provider** — the decision lane now resolves its own endpoint independent of the engine's LLM gateway, so UKit uses UNIC even when Claude Code/Codex/omp point elsewhere:
|
|
@@ -41,6 +41,17 @@ items:
|
|
|
41
41
|
packs:
|
|
42
42
|
- core
|
|
43
43
|
|
|
44
|
+
- id: user-gateway-decision
|
|
45
|
+
type: config
|
|
46
|
+
sourceTemplate: storage/gatewayDecision.json
|
|
47
|
+
targetPath: storage/gatewayDecision.json
|
|
48
|
+
requires: []
|
|
49
|
+
mergeStrategy: skip
|
|
50
|
+
variables: []
|
|
51
|
+
enabledByDefault: true
|
|
52
|
+
packs:
|
|
53
|
+
- core
|
|
54
|
+
|
|
44
55
|
- id: user-playbook-issue
|
|
45
56
|
type: config
|
|
46
57
|
sourceTemplate: playbooks/issue-implementation.md
|
package/package.json
CHANGED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// `ukit decision` — decision-lane gateway setup (S6 follow-up 2026-09-26).
|
|
2
|
+
//
|
|
3
|
+
// The decision lane resolves its own endpoint/key independently of the
|
|
4
|
+
// engine's LLM gateway: ~/.ukit/storage/gatewayDecision.json →
|
|
5
|
+
// ~/.ukit/storage/config.json "decisionGateway" → UKIT_DECISION_* env →
|
|
6
|
+
// default https://openai.unicjsc.com + shared UNIC token fallback. This
|
|
7
|
+
// command edits gatewayDecision.json (one file per lane: gateway<Lane>.json)
|
|
8
|
+
// so a user who runs the engine on a non-UNIC provider (e.g. OpenAI) can
|
|
9
|
+
// point the decision lane at UNIC — or anywhere — without hand-editing JSON.
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// ukit decision --status resolved baseUrl/key source + redacted key
|
|
13
|
+
// ukit decision --set-unic reset to UNIC defaults (empty overrides)
|
|
14
|
+
// ukit decision --base-url <url> --key <k> [--scheme auth-token|api-key]
|
|
15
|
+
// ukit decision --key <k> update key only (keeps stored baseUrl)
|
|
16
|
+
// ukit decision --clear reset fields to template defaults
|
|
17
|
+
//
|
|
18
|
+
// Writes are atomic-ish (tmp + rename); unknown keys are preserved verbatim.
|
|
19
|
+
|
|
20
|
+
import fs from 'node:fs/promises';
|
|
21
|
+
import os from 'node:os';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { resolveDecisionBaseUrl, resolveDecisionApiKey } from '../../core/gatewayProbe.js';
|
|
24
|
+
|
|
25
|
+
const GATEWAY_FILE = path.join('.ukit', 'storage', 'gatewayDecision.json');
|
|
26
|
+
const UNIC_DEFAULT = 'https://openai.unicjsc.com';
|
|
27
|
+
// Template defaults — written on --clear/--set-unic so the file always keeps
|
|
28
|
+
// its full key set (empty = default endpoint + shared UNIC token). Field
|
|
29
|
+
// names mirror the env vars exactly.
|
|
30
|
+
const TEMPLATE_BLOCK = {
|
|
31
|
+
_help: "unic-decision lane endpoint. Empty UKIT_DECISION_BASE_URL = default https://openai.unicjsc.com + shared UNIC token. UKIT_DECISION_SCHEME: 'auth-token' (Bearer) or 'api-key' (x-api-key).",
|
|
32
|
+
UKIT_DECISION_BASE_URL: '',
|
|
33
|
+
UKIT_DECISION_API_KEY: '',
|
|
34
|
+
UKIT_DECISION_SCHEME: 'auth-token',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const VALID_SCHEMES = new Set(['auth-token', 'api-key']);
|
|
38
|
+
const HELP_FLAGS = new Set(['--help', '-h', 'help']);
|
|
39
|
+
|
|
40
|
+
function printUsage(stream = console.log) {
|
|
41
|
+
stream('Usage: ukit decision --status');
|
|
42
|
+
stream(' ukit decision --base-url <url> --key <key> [--scheme auth-token|api-key]');
|
|
43
|
+
stream(' ukit decision --key <key>');
|
|
44
|
+
stream(' ukit decision --set-unic | --clear');
|
|
45
|
+
stream('');
|
|
46
|
+
stream('Configure the decision-lane gateway (unic-decision plane), stored in');
|
|
47
|
+
stream(`~/${GATEWAY_FILE.replaceAll(path.sep, '/')}. The decision`);
|
|
48
|
+
stream('lane is independent of the engine LLM endpoint (ANTHROPIC_BASE_URL etc.) —');
|
|
49
|
+
stream('point it at UNIC even when Claude/Codex/omp run on another provider.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseArgs(argv) {
|
|
53
|
+
const out = { setUnic: false, clear: false, status: false };
|
|
54
|
+
for (let i = 0; i < argv.length; i++) {
|
|
55
|
+
const a = argv[i];
|
|
56
|
+
if (a === '--base-url') out.baseUrl = argv[++i];
|
|
57
|
+
else if (a === '--key') out.key = argv[++i];
|
|
58
|
+
else if (a === '--scheme') out.scheme = argv[++i];
|
|
59
|
+
else if (a === '--set-unic') out.setUnic = true;
|
|
60
|
+
else if (a === '--clear') out.clear = true;
|
|
61
|
+
else if (a === '--status') out.status = true;
|
|
62
|
+
else return { error: `unknown flag: ${a}` };
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function readGatewayFile(filePath) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
70
|
+
} catch {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function writeGatewayFile(filePath, json) {
|
|
76
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
77
|
+
const tmp = `${filePath}.tmp-${process.pid}`;
|
|
78
|
+
await fs.writeFile(tmp, JSON.stringify(json, null, 2) + '\n', { mode: 0o600 });
|
|
79
|
+
await fs.rename(tmp, filePath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function redact(value) {
|
|
83
|
+
if (!value) return null;
|
|
84
|
+
return value.length <= 8 ? '***' : `${value.slice(0, 4)}…${value.slice(-4)}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function printStatus(projectRoot, homeDir = os.homedir()) {
|
|
88
|
+
const base = resolveDecisionBaseUrl({ homeDir });
|
|
89
|
+
const key = await resolveDecisionApiKey({ projectRoot, homeDir });
|
|
90
|
+
console.log(JSON.stringify({
|
|
91
|
+
baseUrl: base.baseUrl,
|
|
92
|
+
baseUrlSource: base.source,
|
|
93
|
+
apiKey: redact(key.value),
|
|
94
|
+
apiKeySource: key.source,
|
|
95
|
+
scheme: key.scheme,
|
|
96
|
+
overrideFile: path.join(homeDir, GATEWAY_FILE),
|
|
97
|
+
}, null, 2));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function runDecision({ projectRoot, argv, homeDir = os.homedir() }) {
|
|
101
|
+
if (argv.some((a) => HELP_FLAGS.has(a))) {
|
|
102
|
+
printUsage();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const args = parseArgs(argv);
|
|
106
|
+
if (args.error) {
|
|
107
|
+
console.error(args.error);
|
|
108
|
+
printUsage(console.error);
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (args.scheme && !VALID_SCHEMES.has(args.scheme)) {
|
|
113
|
+
console.error(`--scheme must be one of: ${[...VALID_SCHEMES].join(', ')}`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const filePath = path.join(homeDir, GATEWAY_FILE);
|
|
119
|
+
|
|
120
|
+
if (args.status || (!args.baseUrl && !args.key && !args.setUnic && !args.clear)) {
|
|
121
|
+
await printStatus(projectRoot, homeDir);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const json = await readGatewayFile(filePath);
|
|
126
|
+
|
|
127
|
+
if (args.clear || args.setUnic) {
|
|
128
|
+
// Reset to the full template block — the file always keeps its complete
|
|
129
|
+
// key set so the user only has to fill in values.
|
|
130
|
+
const out = { ...TEMPLATE_BLOCK };
|
|
131
|
+
if (args.setUnic) out.UKIT_DECISION_BASE_URL = UNIC_DEFAULT;
|
|
132
|
+
for (const key of Object.keys(json)) if (!(key in out)) out[key] = json[key];
|
|
133
|
+
await writeGatewayFile(filePath, out);
|
|
134
|
+
await printStatus(projectRoot, homeDir);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!args.baseUrl && !args.key && !args.scheme) {
|
|
139
|
+
console.error('nothing to set — pass --base-url, --key and/or --scheme');
|
|
140
|
+
process.exitCode = 1;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const out = { ...TEMPLATE_BLOCK, ...json };
|
|
144
|
+
if (args.baseUrl) out.UKIT_DECISION_BASE_URL = args.baseUrl;
|
|
145
|
+
if (args.key) out.UKIT_DECISION_API_KEY = args.key;
|
|
146
|
+
if (args.scheme) out.UKIT_DECISION_SCHEME = args.scheme;
|
|
147
|
+
|
|
148
|
+
await writeGatewayFile(filePath, out);
|
|
149
|
+
await printStatus(projectRoot, homeDir);
|
|
150
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { runMetrics } from './commands/metrics.js';
|
|
|
11
11
|
import { runTelemetry } from './commands/telemetry.js';
|
|
12
12
|
import { runFeedback } from './commands/feedback.js';
|
|
13
13
|
import { runPlaybook } from './commands/playbook.js';
|
|
14
|
-
|
|
14
|
+
import { runDecision } from './commands/decision.js';
|
|
15
15
|
const GLOBAL_FLAGS = new Set(['--help', '-h', '--version', '-v']);
|
|
16
16
|
|
|
17
17
|
export async function runCli({ argv, packageRoot, projectRoot, packageVersion }) {
|
|
@@ -94,6 +94,11 @@ export async function runCli({ argv, packageRoot, projectRoot, packageVersion })
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
if (command === 'decision') {
|
|
98
|
+
await runDecision({ projectRoot, argv: commandArgv });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
if (command === 'build' && (commandArgv[0] ?? '').toLowerCase() === 'index') {
|
|
98
103
|
await runIndexTools({ projectRoot, argv: ['build', ...commandArgv.slice(1)] });
|
|
99
104
|
return;
|
package/src/core/gatewayProbe.js
CHANGED
|
@@ -26,45 +26,61 @@ const SETTINGS_RELATIVE_PROBE_PATH = path.join('.claude', 'settings.json');
|
|
|
26
26
|
// --- Decision-plane gateway resolution (UNIC_DECISION_MIGRATION backlog) -----
|
|
27
27
|
// The decision plane is a SEPARATE lane from the host's LLM endpoint: it must
|
|
28
28
|
// reach the UNIC OpenAI-compatible provider no matter what Claude Code/omp is
|
|
29
|
-
// configured to.
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
29
|
+
// configured to. Override chain (first hit wins, per field):
|
|
30
|
+
// 1. env UKIT_DECISION_BASE_URL / UKIT_DECISION_API_KEY / UKIT_DECISION_SCHEME
|
|
31
|
+
// 2. ~/.ukit/storage/gatewayDecision.json — fields are named exactly like
|
|
32
|
+
// the env vars ({UKIT_DECISION_BASE_URL, UKIT_DECISION_API_KEY,
|
|
33
|
+
// UKIT_DECISION_SCHEME}); one file per lane: gateway<Lane>.json.
|
|
34
|
+
// 3. ~/.ukit/storage/config.json -> "decisionGateway" (same env-style names)
|
|
35
|
+
// 4. DEFAULT_DECISION_BASE_URL + the shared UNIC token (ANTHROPIC_*).
|
|
36
|
+
// Missing key → the caller surfaces 'unavailable' and deterministic policy
|
|
37
|
+
// stays authoritative.
|
|
34
38
|
export const DEFAULT_DECISION_BASE_URL = 'https://openai.unicjsc.com';
|
|
35
|
-
const USER_GATEWAY_REL = path.join('.ukit', 'storage', '
|
|
39
|
+
const USER_GATEWAY_REL = path.join('.ukit', 'storage', 'gatewayDecision.json');
|
|
40
|
+
const USER_CONFIG_REL = path.join('.ukit', 'storage', 'config.json');
|
|
36
41
|
|
|
37
|
-
function
|
|
38
|
-
if (!homeDir) return
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
+
function readUserDecisionBlock(homeDir) {
|
|
43
|
+
if (!homeDir) return {};
|
|
44
|
+
const g = safeReadJson(path.join(homeDir, USER_GATEWAY_REL));
|
|
45
|
+
const c = safeReadJson(path.join(homeDir, USER_CONFIG_REL))?.decisionGateway;
|
|
46
|
+
const gj = g && typeof g === 'object' ? g : null;
|
|
47
|
+
const cj = c && typeof c === 'object' ? c : null;
|
|
48
|
+
// Field-level merge — the lane file wins field-by-field over the config
|
|
49
|
+
// block (env still outranks both, applied by the resolvers below).
|
|
50
|
+
return {
|
|
51
|
+
baseUrl: pickString(gj?.UKIT_DECISION_BASE_URL, cj?.UKIT_DECISION_BASE_URL),
|
|
52
|
+
apiKey: pickString(gj?.UKIT_DECISION_API_KEY, cj?.UKIT_DECISION_API_KEY),
|
|
53
|
+
scheme: pickString(gj?.UKIT_DECISION_SCHEME, cj?.UKIT_DECISION_SCHEME),
|
|
54
|
+
source: gj ? 'gatewayDecision-json' : (cj ? 'ukit-config-json' : null),
|
|
55
|
+
};
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
// Endpoint for the decision lane. Never throws; file and env malformed values
|
|
45
59
|
// are ignored, the default is the final fallback (not ANTHROPIC_BASE_URL —
|
|
46
|
-
// that env may point at a non-decision gateway).
|
|
60
|
+
// that env may point at a non-decision gateway). Env wins over the user files.
|
|
47
61
|
export function resolveDecisionBaseUrl({ homeDir = os.homedir(), env = process.env } = {}) {
|
|
48
|
-
const file = readUserGatewayDecision(homeDir);
|
|
49
|
-
const fromFile = pickString(file?.baseUrl);
|
|
50
|
-
if (fromFile) return { baseUrl: fromFile, source: 'ukit-gateway-json' };
|
|
51
62
|
const fromEnv = pickString(env?.UKIT_DECISION_BASE_URL);
|
|
52
63
|
if (fromEnv) return { baseUrl: fromEnv, source: 'env' };
|
|
64
|
+
const block = readUserDecisionBlock(homeDir);
|
|
65
|
+
if (block.baseUrl) return { baseUrl: block.baseUrl, source: block.source };
|
|
53
66
|
return { baseUrl: DEFAULT_DECISION_BASE_URL, source: 'default' };
|
|
54
67
|
}
|
|
55
68
|
|
|
56
|
-
// API key for the decision lane. `scheme
|
|
57
|
-
//
|
|
69
|
+
// API key for the decision lane. `scheme` (env UKIT_DECISION_SCHEME or the
|
|
70
|
+
// file field of the same name): 'auth-token' → Authorization: Bearer (UNIC
|
|
71
|
+
// OpenAI-compatible default) | 'api-key' → x-api-key. Falls back to the
|
|
58
72
|
// host's UNIC token so existing installs need no duplicate credential.
|
|
59
73
|
export function resolveDecisionApiKey({ projectRoot, homeDir = os.homedir(), env = process.env } = {}) {
|
|
60
|
-
const file = readUserGatewayDecision(homeDir);
|
|
61
|
-
const fileKey = pickString(file?.apiKey);
|
|
62
|
-
if (fileKey) {
|
|
63
|
-
const scheme = file?.scheme === 'api-key' ? 'api-key' : 'auth-token';
|
|
64
|
-
return { value: fileKey, scheme, source: 'ukit-gateway-json' };
|
|
65
|
-
}
|
|
66
74
|
const envKey = pickString(env?.UKIT_DECISION_API_KEY);
|
|
67
|
-
if (envKey)
|
|
75
|
+
if (envKey) {
|
|
76
|
+
const envScheme = env?.UKIT_DECISION_SCHEME === 'api-key' ? 'api-key' : 'auth-token';
|
|
77
|
+
return { value: envKey, scheme: envScheme, source: 'env' };
|
|
78
|
+
}
|
|
79
|
+
const block = readUserDecisionBlock(homeDir);
|
|
80
|
+
if (block.apiKey) {
|
|
81
|
+
const scheme = block.scheme === 'api-key' ? 'api-key' : 'auth-token';
|
|
82
|
+
return { value: block.apiKey, scheme, source: block.source };
|
|
83
|
+
}
|
|
68
84
|
const shared = resolveGatewayApiKey({ projectRoot, homeDir, env });
|
|
69
85
|
if (shared?.value) return { ...shared, source: 'shared-unic-token' };
|
|
70
86
|
return { value: null, scheme: null, source: null };
|
|
@@ -304,7 +304,7 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
|
304
304
|
stage: 'off',
|
|
305
305
|
logicalModel: 'unic-decision',
|
|
306
306
|
checkpoints: {
|
|
307
|
-
default: 'unic-decision',
|
|
307
|
+
default: 'unic-decision-multilingual',
|
|
308
308
|
english: 'unic-decision',
|
|
309
309
|
unknownLanguage: 'unic-decision',
|
|
310
310
|
},
|
package/src/decision/client.js
CHANGED
|
@@ -42,11 +42,10 @@ import {
|
|
|
42
42
|
} from './statePacket.js';
|
|
43
43
|
|
|
44
44
|
const DEFAULT_CHECKPOINTS = {
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// unic-decision
|
|
48
|
-
|
|
49
|
-
default: 'unic-decision',
|
|
45
|
+
// 'unic-decision-multilingual' is credential-bound (verified live
|
|
46
|
+
// 2026-09-26) and is the shipped default; english/unknown classes keep the
|
|
47
|
+
// bare 'unic-decision' alias until laya/auto variants are bound.
|
|
48
|
+
default: 'unic-decision-multilingual',
|
|
50
49
|
english: 'unic-decision',
|
|
51
50
|
unknownLanguage: 'unic-decision',
|
|
52
51
|
};
|
|
@@ -211,7 +210,7 @@ export function createDecisionClient({
|
|
|
211
210
|
}
|
|
212
211
|
|
|
213
212
|
async function resolveEndpoint() {
|
|
214
|
-
// Decision lane resolves its own endpoint (
|
|
213
|
+
// Decision lane resolves its own endpoint (gatewayDecision.json →
|
|
215
214
|
// env → openai.unicjsc.com default), independent of the host's LLM
|
|
216
215
|
// endpoint — the UNIC provider is used even when the engine points
|
|
217
216
|
// elsewhere.
|
|
@@ -332,37 +332,49 @@ function pickString(...candidates) {
|
|
|
332
332
|
|
|
333
333
|
// --- Decision-plane gateway resolution -------------------------------------
|
|
334
334
|
// The decision lane is independent of the host's LLM endpoint: default is the
|
|
335
|
-
// UNIC OpenAI-compatible provider
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
//
|
|
335
|
+
// UNIC OpenAI-compatible provider. Override chain (first hit wins per field):
|
|
336
|
+
// 1. env UKIT_DECISION_BASE_URL / UKIT_DECISION_API_KEY / UKIT_DECISION_SCHEME
|
|
337
|
+
// 2. ~/.ukit/storage/gatewayDecision.json — fields named exactly like the
|
|
338
|
+
// env vars; one file per lane: gateway<Lane>.json.
|
|
339
|
+
// 3. ~/.ukit/storage/config.json -> "decisionGateway" (same env-style names)
|
|
340
|
+
// 4. shared UNIC token (ANTHROPIC_*) — existing installs need nothing extra.
|
|
339
341
|
const DEFAULT_DECISION_BASE_URL = 'https://openai.unicjsc.com';
|
|
340
|
-
const USER_GATEWAY_REL = () => path.join('.ukit', 'storage', '
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
342
|
+
const USER_GATEWAY_REL = () => path.join('.ukit', 'storage', 'gatewayDecision.json');
|
|
343
|
+
const USER_CONFIG_REL = () => path.join('.ukit', 'storage', 'config.json');
|
|
344
|
+
|
|
345
|
+
function readUserDecisionBlock(homeDir) {
|
|
346
|
+
if (!homeDir) return {};
|
|
347
|
+
const g = safeReadJson(path.join(homeDir, USER_GATEWAY_REL()));
|
|
348
|
+
const c = safeReadJson(path.join(homeDir, USER_CONFIG_REL()))?.decisionGateway;
|
|
349
|
+
const gj = g && typeof g === 'object' ? g : null;
|
|
350
|
+
const cj = c && typeof c === 'object' ? c : null;
|
|
351
|
+
return {
|
|
352
|
+
baseUrl: pickString(gj?.UKIT_DECISION_BASE_URL, cj?.UKIT_DECISION_BASE_URL),
|
|
353
|
+
apiKey: pickString(gj?.UKIT_DECISION_API_KEY, cj?.UKIT_DECISION_API_KEY),
|
|
354
|
+
scheme: pickString(gj?.UKIT_DECISION_SCHEME, cj?.UKIT_DECISION_SCHEME),
|
|
355
|
+
source: gj ? 'gatewayDecision-json' : (cj ? 'ukit-config-json' : null),
|
|
356
|
+
};
|
|
347
357
|
}
|
|
348
358
|
|
|
349
359
|
function resolveDecisionBaseUrl({ homeDir, env }) {
|
|
350
|
-
const fileUrl = pickString(readUserGatewayDecision(homeDir)?.baseUrl);
|
|
351
|
-
if (fileUrl) return { baseUrl: fileUrl, source: 'ukit-gateway-json' };
|
|
352
360
|
const envUrl = pickString(env?.UKIT_DECISION_BASE_URL);
|
|
353
361
|
if (envUrl) return { baseUrl: envUrl, source: 'env' };
|
|
362
|
+
const block = readUserDecisionBlock(homeDir);
|
|
363
|
+
if (block.baseUrl) return { baseUrl: block.baseUrl, source: block.source };
|
|
354
364
|
return { baseUrl: DEFAULT_DECISION_BASE_URL, source: 'default' };
|
|
355
365
|
}
|
|
356
366
|
|
|
357
367
|
function resolveDecisionApiKey({ projectRoot, homeDir, env }) {
|
|
358
|
-
const file = readUserGatewayDecision(homeDir);
|
|
359
|
-
const fileKey = pickString(file?.apiKey);
|
|
360
|
-
if (fileKey) {
|
|
361
|
-
const scheme = file?.scheme === 'api-key' ? 'api-key' : 'auth-token';
|
|
362
|
-
return { value: fileKey, scheme, source: 'ukit-gateway-json' };
|
|
363
|
-
}
|
|
364
368
|
const envKey = pickString(env?.UKIT_DECISION_API_KEY);
|
|
365
|
-
if (envKey)
|
|
369
|
+
if (envKey) {
|
|
370
|
+
const envScheme = env?.UKIT_DECISION_SCHEME === 'api-key' ? 'api-key' : 'auth-token';
|
|
371
|
+
return { value: envKey, scheme: envScheme, source: 'env' };
|
|
372
|
+
}
|
|
373
|
+
const block = readUserDecisionBlock(homeDir);
|
|
374
|
+
if (block.apiKey) {
|
|
375
|
+
const scheme = block.scheme === 'api-key' ? 'api-key' : 'auth-token';
|
|
376
|
+
return { value: block.apiKey, scheme, source: block.source };
|
|
377
|
+
}
|
|
366
378
|
const shared = resolveGatewayApiKey({ projectRoot, homeDir, env });
|
|
367
379
|
if (shared?.value) return { ...shared, source: 'shared-unic-token' };
|
|
368
380
|
return { value: null, scheme: null, source: null };
|
|
@@ -464,11 +476,10 @@ function readMergedConfig(rootDir, homeDir) {
|
|
|
464
476
|
// ---------------------------------------------------------------------------
|
|
465
477
|
// Client core — mirror of src/decision/client.js semantics minus the circuit
|
|
466
478
|
const DEFAULT_CHECKPOINTS = {
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
// unic-decision
|
|
470
|
-
|
|
471
|
-
default: 'unic-decision',
|
|
479
|
+
// 'unic-decision-multilingual' is credential-bound (verified live
|
|
480
|
+
// 2026-09-26) and is the shipped default; english/unknown classes keep the
|
|
481
|
+
// bare 'unic-decision' alias until laya/auto variants are bound.
|
|
482
|
+
default: 'unic-decision-multilingual',
|
|
472
483
|
english: 'unic-decision',
|
|
473
484
|
unknownLanguage: 'unic-decision',
|
|
474
485
|
};
|
|
@@ -12,5 +12,11 @@
|
|
|
12
12
|
"compact": {
|
|
13
13
|
"tokenThreshold": 150000,
|
|
14
14
|
"hardCapTokens": 500000
|
|
15
|
+
},
|
|
16
|
+
"decisionGateway": {
|
|
17
|
+
"_help": "unic-decision lane endpoint — field names mirror the env vars. Fill only to override the default UNIC provider (https://openai.unicjsc.com); the lane file ~/.ukit/storage/gatewayDecision.json takes precedence over this block. Empty = default + shared UNIC token. scheme: 'auth-token' (Bearer) or 'api-key' (x-api-key). Prefer `ukit decision` over manual edits; ukit update never overwrites your values.",
|
|
18
|
+
"UKIT_DECISION_BASE_URL": "",
|
|
19
|
+
"UKIT_DECISION_API_KEY": "",
|
|
20
|
+
"UKIT_DECISION_SCHEME": "auth-token"
|
|
15
21
|
}
|
|
16
22
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_help": "unic-decision lane endpoint — field names mirror the env vars. Fill only to override the default UNIC provider (https://openai.unicjsc.com). Empty values = default endpoint + shared UNIC token. UKIT_DECISION_SCHEME: 'auth-token' (Authorization: Bearer) or 'api-key' (x-api-key). Prefer `ukit decision --base-url <url> --key <key> [--scheme s]` over manual edits. UKit update never overwrites your values.",
|
|
3
|
+
"UKIT_DECISION_BASE_URL": "",
|
|
4
|
+
"UKIT_DECISION_API_KEY": "",
|
|
5
|
+
"UKIT_DECISION_SCHEME": "auth-token"
|
|
6
|
+
}
|