@imricci/zaker 0.1.1 → 0.1.3
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/dist/commands/app-server.d.ts +67 -0
- package/dist/commands/app-server.js +601 -0
- package/dist/commands/app-server.js.map +1 -0
- package/dist/commands/audit.js +2 -1
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/build.d.ts +3 -0
- package/dist/commands/build.js +22 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/confirm.d.ts +0 -2
- package/dist/commands/confirm.js +2 -16
- package/dist/commands/confirm.js.map +1 -1
- package/dist/commands/dialog-handlers/auth.d.ts +3 -0
- package/dist/commands/dialog-handlers/auth.js +174 -0
- package/dist/commands/dialog-handlers/auth.js.map +1 -0
- package/dist/commands/dialog-handlers/basic.d.ts +7 -0
- package/dist/commands/dialog-handlers/basic.js +82 -0
- package/dist/commands/dialog-handlers/basic.js.map +1 -0
- package/dist/commands/dialog-handlers/bootstrap.d.ts +10 -0
- package/dist/commands/dialog-handlers/bootstrap.js +38 -0
- package/dist/commands/dialog-handlers/bootstrap.js.map +1 -0
- package/dist/commands/dialog-handlers/index.d.ts +2 -0
- package/dist/commands/dialog-handlers/index.js +6 -0
- package/dist/commands/dialog-handlers/index.js.map +1 -0
- package/dist/commands/dialog-handlers/message.d.ts +2 -0
- package/dist/commands/dialog-handlers/message.js +103 -0
- package/dist/commands/dialog-handlers/message.js.map +1 -0
- package/dist/commands/dialog-handlers/model.d.ts +2 -0
- package/dist/commands/dialog-handlers/model.js +168 -0
- package/dist/commands/dialog-handlers/model.js.map +1 -0
- package/dist/commands/dialog-handlers/new.d.ts +2 -0
- package/dist/commands/dialog-handlers/new.js +53 -0
- package/dist/commands/dialog-handlers/new.js.map +1 -0
- package/dist/commands/dialog-handlers/resume.d.ts +2 -0
- package/dist/commands/dialog-handlers/resume.js +25 -0
- package/dist/commands/dialog-handlers/resume.js.map +1 -0
- package/dist/commands/dialog-handlers/router.d.ts +11 -0
- package/dist/commands/dialog-handlers/router.js +112 -0
- package/dist/commands/dialog-handlers/router.js.map +1 -0
- package/dist/commands/dialog-handlers/run.d.ts +2 -0
- package/dist/commands/dialog-handlers/run.js +161 -0
- package/dist/commands/dialog-handlers/run.js.map +1 -0
- package/dist/commands/dialog-handlers/status.d.ts +2 -0
- package/dist/commands/dialog-handlers/status.js +13 -0
- package/dist/commands/dialog-handlers/status.js.map +1 -0
- package/dist/commands/dialog-handlers/types.d.ts +107 -0
- package/dist/commands/dialog-handlers/types.js +3 -0
- package/dist/commands/dialog-handlers/types.js.map +1 -0
- package/dist/commands/dialog.d.ts +92 -0
- package/dist/commands/dialog.js +1784 -236
- package/dist/commands/dialog.js.map +1 -1
- package/dist/commands/init.d.ts +3 -1
- package/dist/commands/init.js +6 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/plan.js +10 -6
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/run.js +8 -10
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/status.js +6 -12
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/tui-launcher.d.ts +1 -0
- package/dist/commands/tui-launcher.js +8 -0
- package/dist/commands/tui-launcher.js.map +1 -0
- package/dist/core/alignment-reply.d.ts +1 -0
- package/dist/core/alignment-reply.js +44 -0
- package/dist/core/alignment-reply.js.map +1 -0
- package/dist/core/checkpoint.js +3 -1
- package/dist/core/checkpoint.js.map +1 -1
- package/dist/core/planner.d.ts +16 -16
- package/dist/core/planner.js +3 -1
- package/dist/core/planner.js.map +1 -1
- package/dist/core/planning-prep.d.ts +12 -0
- package/dist/core/planning-prep.js +26 -0
- package/dist/core/planning-prep.js.map +1 -0
- package/dist/core/preflight.js +1 -2
- package/dist/core/preflight.js.map +1 -1
- package/dist/core/provider-onboarding.js +6 -11
- package/dist/core/provider-onboarding.js.map +1 -1
- package/dist/core/readonly-checkpoint.d.ts +2 -0
- package/dist/core/readonly-checkpoint.js +35 -0
- package/dist/core/readonly-checkpoint.js.map +1 -0
- package/dist/core/run-loop.js +3 -1
- package/dist/core/run-loop.js.map +1 -1
- package/dist/core/types.d.ts +20 -1
- package/dist/index.js +20 -6
- package/dist/index.js.map +1 -1
- package/dist/infra/artifact-schema.d.ts +25 -0
- package/dist/infra/artifact-schema.js +353 -0
- package/dist/infra/artifact-schema.js.map +1 -0
- package/dist/infra/config.d.ts +14 -1
- package/dist/infra/config.js +542 -22
- package/dist/infra/config.js.map +1 -1
- package/dist/infra/dependency-report.d.ts +5 -0
- package/dist/infra/dependency-report.js +22 -0
- package/dist/infra/dependency-report.js.map +1 -0
- package/dist/infra/dialog-session.d.ts +29 -0
- package/dist/infra/dialog-session.js +244 -0
- package/dist/infra/dialog-session.js.map +1 -0
- package/dist/infra/intent.js +63 -13
- package/dist/infra/intent.js.map +1 -1
- package/dist/infra/model-accounts.d.ts +22 -0
- package/dist/infra/model-accounts.js +172 -0
- package/dist/infra/model-accounts.js.map +1 -0
- package/dist/infra/model-catalog.d.ts +4 -1
- package/dist/infra/model-catalog.js +102 -27
- package/dist/infra/model-catalog.js.map +1 -1
- package/dist/infra/openai-codex-oauth.d.ts +18 -0
- package/dist/infra/openai-codex-oauth.js +267 -0
- package/dist/infra/openai-codex-oauth.js.map +1 -0
- package/dist/infra/provider-registry.d.ts +36 -0
- package/dist/infra/provider-registry.js +403 -0
- package/dist/infra/provider-registry.js.map +1 -0
- package/dist/infra/session-status.d.ts +6 -0
- package/dist/infra/session-status.js +34 -0
- package/dist/infra/session-status.js.map +1 -0
- package/dist/infra/tui-utils.d.ts +6 -0
- package/dist/infra/tui-utils.js +163 -0
- package/dist/infra/tui-utils.js.map +1 -0
- package/dist/infra/tui-view.d.ts +44 -0
- package/dist/infra/tui-view.js +314 -0
- package/dist/infra/tui-view.js.map +1 -0
- package/package.json +4 -1
package/dist/infra/config.js
CHANGED
|
@@ -13,11 +13,13 @@ const promises_1 = require("node:fs/promises");
|
|
|
13
13
|
const node_path_1 = require("node:path");
|
|
14
14
|
const openai_1 = __importDefault(require("openai"));
|
|
15
15
|
const zod_1 = require("zod");
|
|
16
|
+
const alignment_reply_1 = require("../core/alignment-reply");
|
|
16
17
|
const auditor_1 = require("../core/auditor");
|
|
17
18
|
const planner_1 = require("../core/planner");
|
|
18
19
|
const memory_1 = require("./memory");
|
|
19
20
|
const types_1 = require("../core/types");
|
|
20
21
|
const intent_1 = require("./intent");
|
|
22
|
+
const provider_registry_1 = require("./provider-registry");
|
|
21
23
|
exports.CONFIG_SCHEMA_VERSION = "zaker.config.v1";
|
|
22
24
|
const DEFAULT_RISK_PATH_FILE = ".zaker/risk_paths.yaml";
|
|
23
25
|
const DEFAULT_RISK_PATHS = "risk_paths: []\n";
|
|
@@ -29,13 +31,29 @@ const configSchema = zod_1.z.object({
|
|
|
29
31
|
forbidden_paths: zod_1.z.array(zod_1.z.string().min(1)).default([])
|
|
30
32
|
}),
|
|
31
33
|
model: zod_1.z.object({
|
|
32
|
-
provider: zod_1.z.
|
|
34
|
+
provider: zod_1.z.string().min(1).default("mock"),
|
|
35
|
+
auth_mode: zod_1.z.enum(["api_key", "oauth"]).default("api_key"),
|
|
33
36
|
endpoint: zod_1.z.string().optional(),
|
|
34
37
|
api_key_env: zod_1.z.string().optional(),
|
|
35
38
|
api_key: zod_1.z.string().optional(),
|
|
36
39
|
models_path: zod_1.z.string().optional(),
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
selection_policy: zod_1.z
|
|
41
|
+
.enum(["strict", "provider_default", "first_available"])
|
|
42
|
+
.default("provider_default"),
|
|
43
|
+
fallback_model: zod_1.z.string().default(""),
|
|
44
|
+
planner_model: zod_1.z.string().min(1).default("auto"),
|
|
45
|
+
auditor_model: zod_1.z.string().min(1).default("auto"),
|
|
46
|
+
accounts: zod_1.z
|
|
47
|
+
.record(zod_1.z.object({
|
|
48
|
+
auth_mode: zod_1.z.enum(["api_key", "oauth"]).optional(),
|
|
49
|
+
endpoint: zod_1.z.string().optional(),
|
|
50
|
+
api_key_env: zod_1.z.string().optional(),
|
|
51
|
+
api_key: zod_1.z.string().optional(),
|
|
52
|
+
default_model: zod_1.z.string().optional(),
|
|
53
|
+
models: zod_1.z.array(zod_1.z.string().min(1)).default([]),
|
|
54
|
+
updated_at: zod_1.z.string().optional()
|
|
55
|
+
}))
|
|
56
|
+
.default({}),
|
|
39
57
|
timeout_ms: zod_1.z.number().int().positive().optional()
|
|
40
58
|
}),
|
|
41
59
|
risk: zod_1.z.object({
|
|
@@ -51,13 +69,13 @@ const configSchema = zod_1.z.object({
|
|
|
51
69
|
execution: zod_1.z
|
|
52
70
|
.object({
|
|
53
71
|
provider: zod_1.z.enum(["local", "cheap_cloud", "ollama"]).default("local"),
|
|
54
|
-
model: zod_1.z.string().min(1).default("
|
|
72
|
+
model: zod_1.z.string().min(1).default("auto"),
|
|
55
73
|
ollama_host: zod_1.z.string().min(1).default("http://127.0.0.1:11434"),
|
|
56
74
|
max_attempts: zod_1.z.number().int().min(1).max(5).default(2)
|
|
57
75
|
})
|
|
58
76
|
.default({
|
|
59
77
|
provider: "local",
|
|
60
|
-
model: "
|
|
78
|
+
model: "auto",
|
|
61
79
|
ollama_host: "http://127.0.0.1:11434",
|
|
62
80
|
max_attempts: 2
|
|
63
81
|
})
|
|
@@ -70,12 +88,16 @@ const DEFAULT_CONFIG = {
|
|
|
70
88
|
},
|
|
71
89
|
model: {
|
|
72
90
|
provider: "mock",
|
|
91
|
+
auth_mode: "api_key",
|
|
73
92
|
endpoint: "",
|
|
74
93
|
api_key_env: "ZAKER_API_KEY",
|
|
75
94
|
api_key: "",
|
|
76
95
|
models_path: "/models",
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
selection_policy: "provider_default",
|
|
97
|
+
fallback_model: "",
|
|
98
|
+
planner_model: "auto",
|
|
99
|
+
auditor_model: "auto",
|
|
100
|
+
accounts: {},
|
|
79
101
|
timeout_ms: 30000
|
|
80
102
|
},
|
|
81
103
|
risk: {
|
|
@@ -87,11 +109,70 @@ const DEFAULT_CONFIG = {
|
|
|
87
109
|
},
|
|
88
110
|
execution: {
|
|
89
111
|
provider: "local",
|
|
90
|
-
model: "
|
|
112
|
+
model: "auto",
|
|
91
113
|
ollama_host: "http://127.0.0.1:11434",
|
|
92
114
|
max_attempts: 2
|
|
93
115
|
}
|
|
94
116
|
};
|
|
117
|
+
function normalizeProviderConfig(config) {
|
|
118
|
+
const provider = (0, provider_registry_1.toCanonicalModelProviderId)(config.model.provider);
|
|
119
|
+
const authMode = config.model.auth_mode || "api_key";
|
|
120
|
+
const apiKeyEnv = config.model.api_key_env?.trim() || (0, provider_registry_1.resolveProviderApiKeyEnv)(provider, authMode);
|
|
121
|
+
const selectionPolicy = config.model.selection_policy || "provider_default";
|
|
122
|
+
const fallbackModel = config.model.fallback_model?.trim() || "";
|
|
123
|
+
const plannerModel = config.model.planner_model?.trim() || "auto";
|
|
124
|
+
const auditorModel = config.model.auditor_model?.trim() || "auto";
|
|
125
|
+
const endpoint = config.model.endpoint?.trim() || "";
|
|
126
|
+
const accounts = {};
|
|
127
|
+
for (const [rawProviderId, rawAccount] of Object.entries(config.model.accounts || {})) {
|
|
128
|
+
let accountProvider;
|
|
129
|
+
try {
|
|
130
|
+
accountProvider = (0, provider_registry_1.toCanonicalModelProviderId)(rawProviderId);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const accountAuthMode = rawAccount.auth_mode || "api_key";
|
|
136
|
+
const accountApiKeyEnv = rawAccount.api_key_env?.trim() || (0, provider_registry_1.resolveProviderApiKeyEnv)(accountProvider, accountAuthMode);
|
|
137
|
+
accounts[accountProvider] = {
|
|
138
|
+
auth_mode: accountAuthMode,
|
|
139
|
+
endpoint: rawAccount.endpoint?.trim() || "",
|
|
140
|
+
api_key_env: accountApiKeyEnv,
|
|
141
|
+
api_key: rawAccount.api_key?.trim() || "",
|
|
142
|
+
default_model: rawAccount.default_model?.trim() || "",
|
|
143
|
+
models: [...new Set((rawAccount.models || []).map((item) => item.trim()).filter(Boolean))],
|
|
144
|
+
updated_at: rawAccount.updated_at
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (provider !== "mock") {
|
|
148
|
+
const existing = accounts[provider];
|
|
149
|
+
accounts[provider] = {
|
|
150
|
+
...existing,
|
|
151
|
+
auth_mode: authMode,
|
|
152
|
+
endpoint: endpoint || existing?.endpoint || "",
|
|
153
|
+
api_key_env: apiKeyEnv,
|
|
154
|
+
api_key: config.model.api_key?.trim() || existing?.api_key || "",
|
|
155
|
+
default_model: config.model.fallback_model?.trim() || existing?.default_model || "",
|
|
156
|
+
models: [...new Set((existing?.models || []).map((item) => item.trim()).filter(Boolean))],
|
|
157
|
+
updated_at: existing?.updated_at
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
...config,
|
|
162
|
+
model: {
|
|
163
|
+
...config.model,
|
|
164
|
+
provider,
|
|
165
|
+
auth_mode: authMode,
|
|
166
|
+
api_key_env: apiKeyEnv,
|
|
167
|
+
selection_policy: selectionPolicy,
|
|
168
|
+
fallback_model: fallbackModel,
|
|
169
|
+
planner_model: plannerModel,
|
|
170
|
+
auditor_model: auditorModel,
|
|
171
|
+
endpoint,
|
|
172
|
+
accounts
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
95
176
|
function cloneDefaultConfig() {
|
|
96
177
|
return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
97
178
|
}
|
|
@@ -118,7 +199,7 @@ async function writeConfig(configPath, config) {
|
|
|
118
199
|
await (0, promises_1.writeFile)(configPath, formatJson(config), "utf8");
|
|
119
200
|
}
|
|
120
201
|
async function writeZakerConfig(config, cwd = process.cwd()) {
|
|
121
|
-
const normalized = configSchema.parse(config);
|
|
202
|
+
const normalized = normalizeProviderConfig(configSchema.parse(config));
|
|
122
203
|
const zakerDir = (0, node_path_1.resolve)(cwd, ".zaker");
|
|
123
204
|
const configPath = (0, node_path_1.resolve)(zakerDir, "config.json");
|
|
124
205
|
await (0, promises_1.mkdir)(zakerDir, { recursive: true });
|
|
@@ -186,7 +267,7 @@ async function readConfig(cwd = process.cwd()) {
|
|
|
186
267
|
throw new Error("Missing .zaker/config.json. Run `zaker init` first.");
|
|
187
268
|
}
|
|
188
269
|
const raw = await (0, promises_1.readFile)(configPath, "utf8");
|
|
189
|
-
return configSchema.parse(JSON.parse(raw));
|
|
270
|
+
return normalizeProviderConfig(configSchema.parse(JSON.parse(raw)));
|
|
190
271
|
}
|
|
191
272
|
class MockLLMProvider {
|
|
192
273
|
async plan(prompt) {
|
|
@@ -209,6 +290,16 @@ class MockLLMProvider {
|
|
|
209
290
|
message: "Mock provider accepted checkpoint."
|
|
210
291
|
};
|
|
211
292
|
}
|
|
293
|
+
async align(message) {
|
|
294
|
+
return (0, alignment_reply_1.generateAlignmentReply)(message);
|
|
295
|
+
}
|
|
296
|
+
async detectBuildIntent() {
|
|
297
|
+
return {
|
|
298
|
+
ready: false,
|
|
299
|
+
confidence: 0,
|
|
300
|
+
signal: "mock_provider"
|
|
301
|
+
};
|
|
302
|
+
}
|
|
212
303
|
}
|
|
213
304
|
function resolveApiToken(apiKey, apiKeyEnv, fallbackEnv) {
|
|
214
305
|
const explicit = apiKey.trim();
|
|
@@ -230,7 +321,7 @@ function resolveApiToken(apiKey, apiKeyEnv, fallbackEnv) {
|
|
|
230
321
|
function parseJsonPayload(text, context) {
|
|
231
322
|
const trimmed = text.trim();
|
|
232
323
|
if (!trimmed) {
|
|
233
|
-
throw new Error(`
|
|
324
|
+
throw new Error(`Provider returned empty ${context} payload.`);
|
|
234
325
|
}
|
|
235
326
|
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
236
327
|
const candidate = fenced ? fenced[1].trim() : trimmed;
|
|
@@ -248,20 +339,106 @@ function parseJsonPayload(text, context) {
|
|
|
248
339
|
// try next candidate
|
|
249
340
|
}
|
|
250
341
|
}
|
|
251
|
-
throw new Error(`
|
|
342
|
+
throw new Error(`Provider returned non-JSON ${context} payload.`);
|
|
343
|
+
}
|
|
344
|
+
function normalizeBuildIntentSignal(raw) {
|
|
345
|
+
const clampConfidence = (value) => {
|
|
346
|
+
if (!Number.isFinite(value)) {
|
|
347
|
+
return 0;
|
|
348
|
+
}
|
|
349
|
+
return Math.max(0, Math.min(1, value));
|
|
350
|
+
};
|
|
351
|
+
const coerceBoolean = (value) => {
|
|
352
|
+
if (typeof value === "boolean") {
|
|
353
|
+
return value;
|
|
354
|
+
}
|
|
355
|
+
if (typeof value !== "string") {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
const normalized = value.trim().toLowerCase();
|
|
359
|
+
if (["true", "yes", "y", "ready", "build"].includes(normalized)) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
if (["false", "no", "n", "not_ready", "wait"].includes(normalized)) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
};
|
|
367
|
+
const extract = (input) => {
|
|
368
|
+
const readyValue = coerceBoolean(input.ready) ??
|
|
369
|
+
coerceBoolean(input.ready_to_build) ??
|
|
370
|
+
coerceBoolean(input.should_build) ??
|
|
371
|
+
coerceBoolean(input.execute_now) ??
|
|
372
|
+
false;
|
|
373
|
+
const confidenceSource = input.confidence;
|
|
374
|
+
const confidence = typeof confidenceSource === "number"
|
|
375
|
+
? clampConfidence(confidenceSource)
|
|
376
|
+
: readyValue
|
|
377
|
+
? 0.7
|
|
378
|
+
: 0;
|
|
379
|
+
const signalSource = input.signal ?? input.reason ?? input.label;
|
|
380
|
+
const signal = typeof signalSource === "string" && signalSource.trim()
|
|
381
|
+
? signalSource.trim().toLowerCase()
|
|
382
|
+
: readyValue
|
|
383
|
+
? "ready"
|
|
384
|
+
: "not_ready";
|
|
385
|
+
return { ready: readyValue, confidence, signal };
|
|
386
|
+
};
|
|
387
|
+
if (raw && typeof raw === "object") {
|
|
388
|
+
return extract(raw);
|
|
389
|
+
}
|
|
390
|
+
if (typeof raw === "string") {
|
|
391
|
+
const text = raw.trim();
|
|
392
|
+
if (!text) {
|
|
393
|
+
return { ready: false, confidence: 0, signal: "empty" };
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const parsed = parseJsonPayload(text, "build intent signal");
|
|
397
|
+
if (parsed && typeof parsed === "object") {
|
|
398
|
+
return extract(parsed);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
// fall through to text heuristic
|
|
403
|
+
}
|
|
404
|
+
const lowered = text.toLowerCase();
|
|
405
|
+
const ready = /(^|\b)(ready|build|start now|go ahead|execute now)(\b|$)/i.test(lowered);
|
|
406
|
+
return {
|
|
407
|
+
ready,
|
|
408
|
+
confidence: ready ? 0.65 : 0.2,
|
|
409
|
+
signal: ready ? "ready_text" : "not_ready_text"
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return { ready: false, confidence: 0, signal: "invalid" };
|
|
413
|
+
}
|
|
414
|
+
function createAbortError(message) {
|
|
415
|
+
const error = new Error(message);
|
|
416
|
+
error.name = "AbortError";
|
|
417
|
+
return error;
|
|
418
|
+
}
|
|
419
|
+
function isAbortLikeError(error) {
|
|
420
|
+
if (!(error instanceof Error)) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
if (error.name === "AbortError") {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
return /abort|cancel/i.test(error.message);
|
|
252
427
|
}
|
|
253
|
-
class
|
|
428
|
+
class OpenAICompatibleProvider {
|
|
429
|
+
provider;
|
|
254
430
|
plannerModel;
|
|
255
431
|
auditorModel;
|
|
256
432
|
timeoutMs;
|
|
257
433
|
client;
|
|
258
|
-
constructor(plannerModel, auditorModel, apiKeyEnv = "OPENAI_API_KEY", apiKey = "", timeoutMs = 30000, endpoint) {
|
|
434
|
+
constructor(provider, plannerModel, auditorModel, apiKeyEnv = "OPENAI_API_KEY", apiKey = "", timeoutMs = 30000, endpoint) {
|
|
435
|
+
this.provider = provider;
|
|
259
436
|
this.plannerModel = plannerModel;
|
|
260
437
|
this.auditorModel = auditorModel;
|
|
261
438
|
this.timeoutMs = timeoutMs;
|
|
262
439
|
const token = resolveApiToken(apiKey, apiKeyEnv, "OPENAI_API_KEY");
|
|
263
440
|
if (!token) {
|
|
264
|
-
throw new Error(`
|
|
441
|
+
throw new Error(`Provider "${this.provider}" selected but credential missing. Set model.api_key or env ${apiKeyEnv}.`);
|
|
265
442
|
}
|
|
266
443
|
this.client = new openai_1.default({
|
|
267
444
|
apiKey: token,
|
|
@@ -287,7 +464,7 @@ class OpenAICodexTeamProvider {
|
|
|
287
464
|
}
|
|
288
465
|
catch (error) {
|
|
289
466
|
const message = error instanceof Error ? error.message : String(error);
|
|
290
|
-
throw new Error(`
|
|
467
|
+
throw new Error(`Provider "${this.provider}" request failed: ${message}`);
|
|
291
468
|
}
|
|
292
469
|
}
|
|
293
470
|
async plan(prompt) {
|
|
@@ -314,6 +491,278 @@ class OpenAICodexTeamProvider {
|
|
|
314
491
|
}
|
|
315
492
|
return (0, auditor_1.normalizeAuditResult)(raw);
|
|
316
493
|
}
|
|
494
|
+
async align(message, context) {
|
|
495
|
+
if (context?.abort_signal?.aborted) {
|
|
496
|
+
throw createAbortError(`Provider "${this.provider}" align request cancelled.`);
|
|
497
|
+
}
|
|
498
|
+
const contextText = context?.intent_update?.trim();
|
|
499
|
+
const userPrompt = contextText
|
|
500
|
+
? `user_message: ${message}\nintent_update: ${contextText}`
|
|
501
|
+
: `user_message: ${message}`;
|
|
502
|
+
try {
|
|
503
|
+
const completion = await this.client.chat.completions.create({
|
|
504
|
+
model: this.plannerModel,
|
|
505
|
+
messages: [
|
|
506
|
+
{
|
|
507
|
+
role: "system",
|
|
508
|
+
content: [
|
|
509
|
+
"You are zaker alignment assistant.",
|
|
510
|
+
"Reply in concise Chinese.",
|
|
511
|
+
"Focus on requirement clarification and next actionable step.",
|
|
512
|
+
"Do not claim execution/test success."
|
|
513
|
+
].join(" ")
|
|
514
|
+
},
|
|
515
|
+
{ role: "user", content: userPrompt }
|
|
516
|
+
]
|
|
517
|
+
}, context?.abort_signal
|
|
518
|
+
? {
|
|
519
|
+
signal: context.abort_signal
|
|
520
|
+
}
|
|
521
|
+
: undefined);
|
|
522
|
+
const text = completion.choices[0]?.message?.content;
|
|
523
|
+
if (typeof text !== "string" || !text.trim()) {
|
|
524
|
+
throw new Error("missing alignment response content");
|
|
525
|
+
}
|
|
526
|
+
return text.trim();
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
if (isAbortLikeError(error)) {
|
|
530
|
+
throw createAbortError(`Provider "${this.provider}" align request cancelled.`);
|
|
531
|
+
}
|
|
532
|
+
const messageText = error instanceof Error ? error.message : String(error);
|
|
533
|
+
throw new Error(`Provider "${this.provider}" align request failed: ${messageText}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
async detectBuildIntent(message, context) {
|
|
537
|
+
if (context?.abort_signal?.aborted) {
|
|
538
|
+
throw createAbortError(`Provider "${this.provider}" build intent request cancelled.`);
|
|
539
|
+
}
|
|
540
|
+
const contextText = context?.intent_update?.trim();
|
|
541
|
+
const userPrompt = contextText
|
|
542
|
+
? `user_message: ${message}\nintent_update: ${contextText}`
|
|
543
|
+
: `user_message: ${message}`;
|
|
544
|
+
try {
|
|
545
|
+
const raw = await this.requestJson(this.plannerModel, [
|
|
546
|
+
"You are zaker build-intent classifier.",
|
|
547
|
+
"Return JSON only with keys: ready(boolean), confidence(number 0-1), signal(string).",
|
|
548
|
+
"Set ready=true only when user is explicitly asking to start implementation now.",
|
|
549
|
+
"If user is still exploring or unclear, set ready=false."
|
|
550
|
+
].join(" "), userPrompt);
|
|
551
|
+
return normalizeBuildIntentSignal(raw);
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
if (isAbortLikeError(error)) {
|
|
555
|
+
throw createAbortError(`Provider "${this.provider}" build intent request cancelled.`);
|
|
556
|
+
}
|
|
557
|
+
const messageText = error instanceof Error ? error.message : String(error);
|
|
558
|
+
throw new Error(`Provider "${this.provider}" build intent request failed: ${messageText}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function toPiAiProviderId(provider) {
|
|
563
|
+
if (provider === "mock" || provider === "http") {
|
|
564
|
+
throw new Error(`Provider "${provider}" cannot be mapped to pi-ai runtime.`);
|
|
565
|
+
}
|
|
566
|
+
return provider;
|
|
567
|
+
}
|
|
568
|
+
async function loadPiAiModule() {
|
|
569
|
+
const dynamicImport = new Function("specifier", "return import(specifier);");
|
|
570
|
+
const loaded = await dynamicImport("@mariozechner/pi-ai");
|
|
571
|
+
return loaded;
|
|
572
|
+
}
|
|
573
|
+
function applyModelEndpointOverride(model, endpoint) {
|
|
574
|
+
const normalizedEndpoint = endpoint?.trim();
|
|
575
|
+
if (!normalizedEndpoint || normalizedEndpoint === model.baseUrl) {
|
|
576
|
+
return model;
|
|
577
|
+
}
|
|
578
|
+
const normalizedApi = model.api === "openai-responses" || model.api === "openai-codex-responses"
|
|
579
|
+
? "openai-completions"
|
|
580
|
+
: model.api;
|
|
581
|
+
return {
|
|
582
|
+
...model,
|
|
583
|
+
api: normalizedApi,
|
|
584
|
+
baseUrl: normalizedEndpoint
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function extractPiAiText(message) {
|
|
588
|
+
return message.content
|
|
589
|
+
.filter((item) => item.type === "text" && typeof item.text === "string")
|
|
590
|
+
.map((item) => item.text?.trim() || "")
|
|
591
|
+
.filter(Boolean)
|
|
592
|
+
.join("\n")
|
|
593
|
+
.trim();
|
|
594
|
+
}
|
|
595
|
+
class PiAiProvider {
|
|
596
|
+
provider;
|
|
597
|
+
plannerModel;
|
|
598
|
+
auditorModel;
|
|
599
|
+
timeoutMs;
|
|
600
|
+
endpoint;
|
|
601
|
+
modulePromise;
|
|
602
|
+
providerId;
|
|
603
|
+
token;
|
|
604
|
+
constructor(provider, runtimeProviderId, plannerModel, auditorModel, apiKeyEnv = "OPENAI_API_KEY", apiKey = "", timeoutMs = 30000, endpoint) {
|
|
605
|
+
this.provider = provider;
|
|
606
|
+
this.plannerModel = plannerModel;
|
|
607
|
+
this.auditorModel = auditorModel;
|
|
608
|
+
this.timeoutMs = timeoutMs;
|
|
609
|
+
this.endpoint = endpoint;
|
|
610
|
+
this.providerId = runtimeProviderId?.trim() || toPiAiProviderId(provider);
|
|
611
|
+
const token = resolveApiToken(apiKey, apiKeyEnv, "OPENAI_API_KEY");
|
|
612
|
+
if (!token) {
|
|
613
|
+
throw new Error(`Provider "${this.provider}" selected but credential missing. Set model.api_key or env ${apiKeyEnv}.`);
|
|
614
|
+
}
|
|
615
|
+
this.token = token;
|
|
616
|
+
this.modulePromise = loadPiAiModule();
|
|
617
|
+
}
|
|
618
|
+
async resolveModel(requestedModel) {
|
|
619
|
+
const piAi = await this.modulePromise;
|
|
620
|
+
const models = piAi.getModels(this.providerId);
|
|
621
|
+
if (!Array.isArray(models) || models.length === 0) {
|
|
622
|
+
throw new Error(`Provider "${this.provider}" has no available models in pi-ai catalog.`);
|
|
623
|
+
}
|
|
624
|
+
const requested = requestedModel.trim();
|
|
625
|
+
const providerDefault = (0, provider_registry_1.resolveProviderDefaultModel)(this.provider).trim();
|
|
626
|
+
const targetModel = requested && requested !== "auto" ? requested : providerDefault;
|
|
627
|
+
let resolved = targetModel ? models.find((item) => item.id === targetModel) : undefined;
|
|
628
|
+
if (!resolved && requested && requested !== "auto") {
|
|
629
|
+
const endpointOverride = this.endpoint?.trim();
|
|
630
|
+
if (endpointOverride) {
|
|
631
|
+
const prototype = models[0];
|
|
632
|
+
if (!prototype) {
|
|
633
|
+
throw new Error(`Provider "${this.provider}" has no model prototype for endpoint override.`);
|
|
634
|
+
}
|
|
635
|
+
return applyModelEndpointOverride({
|
|
636
|
+
...prototype,
|
|
637
|
+
id: requested,
|
|
638
|
+
name: requested
|
|
639
|
+
}, endpointOverride);
|
|
640
|
+
}
|
|
641
|
+
const sample = models.slice(0, 8).map((item) => item.id).join(", ");
|
|
642
|
+
throw new Error(`Provider "${this.provider}" model "${requested}" not found in pi-ai catalog. Sample: ${sample}`);
|
|
643
|
+
}
|
|
644
|
+
if (!resolved) {
|
|
645
|
+
resolved = models[0];
|
|
646
|
+
}
|
|
647
|
+
if (!resolved) {
|
|
648
|
+
throw new Error(`Provider "${this.provider}" has no resolvable model in pi-ai catalog.`);
|
|
649
|
+
}
|
|
650
|
+
return applyModelEndpointOverride(resolved, this.endpoint);
|
|
651
|
+
}
|
|
652
|
+
async completeText(modelId, systemPrompt, userPrompt, abortSignal) {
|
|
653
|
+
if (abortSignal?.aborted) {
|
|
654
|
+
throw createAbortError(`Provider "${this.provider}" request cancelled.`);
|
|
655
|
+
}
|
|
656
|
+
const piAi = await this.modulePromise;
|
|
657
|
+
const model = await this.resolveModel(modelId);
|
|
658
|
+
const controller = new AbortController();
|
|
659
|
+
let abortedByExternal = false;
|
|
660
|
+
let timedOut = false;
|
|
661
|
+
const onExternalAbort = () => {
|
|
662
|
+
abortedByExternal = true;
|
|
663
|
+
controller.abort();
|
|
664
|
+
};
|
|
665
|
+
const timer = setTimeout(() => {
|
|
666
|
+
timedOut = true;
|
|
667
|
+
controller.abort();
|
|
668
|
+
}, this.timeoutMs);
|
|
669
|
+
if (abortSignal) {
|
|
670
|
+
abortSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
const message = await piAi.complete(model, {
|
|
674
|
+
systemPrompt,
|
|
675
|
+
messages: [
|
|
676
|
+
{
|
|
677
|
+
role: "user",
|
|
678
|
+
content: userPrompt,
|
|
679
|
+
timestamp: Date.now()
|
|
680
|
+
}
|
|
681
|
+
]
|
|
682
|
+
}, {
|
|
683
|
+
apiKey: this.token,
|
|
684
|
+
signal: controller.signal
|
|
685
|
+
});
|
|
686
|
+
if (message.stopReason === "error") {
|
|
687
|
+
throw new Error(message.errorMessage || "pi-ai returned stopReason=error");
|
|
688
|
+
}
|
|
689
|
+
const text = extractPiAiText(message);
|
|
690
|
+
if (!text) {
|
|
691
|
+
throw new Error(`missing response text: ${JSON.stringify(message)}`);
|
|
692
|
+
}
|
|
693
|
+
return text;
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
if (isAbortLikeError(error)) {
|
|
697
|
+
if (abortedByExternal || abortSignal?.aborted) {
|
|
698
|
+
throw createAbortError(`Provider "${this.provider}" request cancelled.`);
|
|
699
|
+
}
|
|
700
|
+
if (timedOut) {
|
|
701
|
+
throw new Error(`Provider "${this.provider}" request timeout after ${this.timeoutMs}ms.`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
705
|
+
throw new Error(`Provider "${this.provider}" request failed: ${message}`);
|
|
706
|
+
}
|
|
707
|
+
finally {
|
|
708
|
+
clearTimeout(timer);
|
|
709
|
+
if (abortSignal) {
|
|
710
|
+
abortSignal.removeEventListener("abort", onExternalAbort);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async plan(prompt) {
|
|
715
|
+
const text = await this.completeText(this.plannerModel, [
|
|
716
|
+
"You are ZAKER planner.",
|
|
717
|
+
"Return JSON only.",
|
|
718
|
+
"Output must follow zaker.sop.v1 with fields:",
|
|
719
|
+
"schema_version,sop_id,goal,scope,verification,limits,skills,tasks,patches."
|
|
720
|
+
].join(" "), prompt);
|
|
721
|
+
const raw = parseJsonPayload(text, "plan");
|
|
722
|
+
if (raw && typeof raw === "object" && "sop" in raw) {
|
|
723
|
+
return (0, planner_1.validateSOP)(raw.sop, prompt);
|
|
724
|
+
}
|
|
725
|
+
return (0, planner_1.validateSOP)(raw, prompt);
|
|
726
|
+
}
|
|
727
|
+
async audit(checkpoint) {
|
|
728
|
+
const text = await this.completeText(this.auditorModel, [
|
|
729
|
+
"You are ZAKER auditor.",
|
|
730
|
+
"Return JSON only.",
|
|
731
|
+
"Output must follow zaker.audit.v1 with fields:",
|
|
732
|
+
"schema_version,verdict,reason_code,message,challenge(optional)."
|
|
733
|
+
].join(" "), JSON.stringify({ checkpoint }, null, 2));
|
|
734
|
+
const raw = parseJsonPayload(text, "audit");
|
|
735
|
+
if (raw && typeof raw === "object" && "audit" in raw) {
|
|
736
|
+
return (0, auditor_1.normalizeAuditResult)(raw.audit);
|
|
737
|
+
}
|
|
738
|
+
return (0, auditor_1.normalizeAuditResult)(raw);
|
|
739
|
+
}
|
|
740
|
+
async align(message, context) {
|
|
741
|
+
const contextText = context?.intent_update?.trim();
|
|
742
|
+
const userPrompt = contextText
|
|
743
|
+
? `user_message: ${message}\nintent_update: ${contextText}`
|
|
744
|
+
: `user_message: ${message}`;
|
|
745
|
+
return await this.completeText(this.plannerModel, [
|
|
746
|
+
"You are zaker alignment assistant.",
|
|
747
|
+
"Reply in concise Chinese.",
|
|
748
|
+
"Focus on requirement clarification and next actionable step.",
|
|
749
|
+
"Do not claim execution/test success."
|
|
750
|
+
].join(" "), userPrompt, context?.abort_signal);
|
|
751
|
+
}
|
|
752
|
+
async detectBuildIntent(message, context) {
|
|
753
|
+
const contextText = context?.intent_update?.trim();
|
|
754
|
+
const userPrompt = contextText
|
|
755
|
+
? `user_message: ${message}\nintent_update: ${contextText}`
|
|
756
|
+
: `user_message: ${message}`;
|
|
757
|
+
const text = await this.completeText(this.plannerModel, [
|
|
758
|
+
"You are zaker build-intent classifier.",
|
|
759
|
+
"Return JSON only with keys: ready(boolean), confidence(number 0-1), signal(string).",
|
|
760
|
+
"Set ready=true only when user explicitly indicates starting implementation now.",
|
|
761
|
+
"If unsure, set ready=false."
|
|
762
|
+
].join(" "), userPrompt, context?.abort_signal);
|
|
763
|
+
const raw = parseJsonPayload(text, "build intent signal");
|
|
764
|
+
return normalizeBuildIntentSignal(raw);
|
|
765
|
+
}
|
|
317
766
|
}
|
|
318
767
|
class HttpLLMProvider {
|
|
319
768
|
endpoint;
|
|
@@ -330,8 +779,19 @@ class HttpLLMProvider {
|
|
|
330
779
|
this.apiKey = apiKey;
|
|
331
780
|
this.timeoutMs = timeoutMs;
|
|
332
781
|
}
|
|
333
|
-
async post(path, payload) {
|
|
782
|
+
async post(path, payload, abortSignal) {
|
|
783
|
+
if (abortSignal?.aborted) {
|
|
784
|
+
throw createAbortError(`LLM provider request cancelled: POST ${path}`);
|
|
785
|
+
}
|
|
334
786
|
const controller = new AbortController();
|
|
787
|
+
let abortedByExternal = false;
|
|
788
|
+
const onExternalAbort = () => {
|
|
789
|
+
abortedByExternal = true;
|
|
790
|
+
controller.abort();
|
|
791
|
+
};
|
|
792
|
+
if (abortSignal) {
|
|
793
|
+
abortSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
794
|
+
}
|
|
335
795
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
336
796
|
try {
|
|
337
797
|
const url = `${this.endpoint.replace(/\/+$/, "")}${path}`;
|
|
@@ -354,7 +814,10 @@ class HttpLLMProvider {
|
|
|
354
814
|
return await response.json();
|
|
355
815
|
}
|
|
356
816
|
catch (error) {
|
|
357
|
-
if (error
|
|
817
|
+
if (isAbortLikeError(error)) {
|
|
818
|
+
if (abortedByExternal || abortSignal?.aborted) {
|
|
819
|
+
throw createAbortError(`LLM provider request cancelled: POST ${path}`);
|
|
820
|
+
}
|
|
358
821
|
throw new Error(`LLM provider timeout after ${this.timeoutMs}ms: POST ${path}`);
|
|
359
822
|
}
|
|
360
823
|
if (error instanceof Error) {
|
|
@@ -364,6 +827,9 @@ class HttpLLMProvider {
|
|
|
364
827
|
}
|
|
365
828
|
finally {
|
|
366
829
|
clearTimeout(timer);
|
|
830
|
+
if (abortSignal) {
|
|
831
|
+
abortSignal.removeEventListener("abort", onExternalAbort);
|
|
832
|
+
}
|
|
367
833
|
}
|
|
368
834
|
}
|
|
369
835
|
async plan(prompt) {
|
|
@@ -380,18 +846,72 @@ class HttpLLMProvider {
|
|
|
380
846
|
}
|
|
381
847
|
return (0, auditor_1.normalizeAuditResult)(raw);
|
|
382
848
|
}
|
|
849
|
+
async align(message, context) {
|
|
850
|
+
const raw = await this.post("/align", {
|
|
851
|
+
message,
|
|
852
|
+
model: this.plannerModel,
|
|
853
|
+
intent_update: context?.intent_update || ""
|
|
854
|
+
}, context?.abort_signal);
|
|
855
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
856
|
+
return raw.trim();
|
|
857
|
+
}
|
|
858
|
+
if (raw && typeof raw === "object") {
|
|
859
|
+
if ("reply" in raw && typeof raw.reply === "string") {
|
|
860
|
+
return (raw.reply || "").trim();
|
|
861
|
+
}
|
|
862
|
+
if ("message" in raw && typeof raw.message === "string") {
|
|
863
|
+
return (raw.message || "").trim();
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
throw new Error("HTTP provider returned invalid /align payload.");
|
|
867
|
+
}
|
|
868
|
+
async detectBuildIntent(message, context) {
|
|
869
|
+
try {
|
|
870
|
+
const raw = await this.post("/intent-signal", {
|
|
871
|
+
message,
|
|
872
|
+
model: this.plannerModel,
|
|
873
|
+
intent_update: context?.intent_update || ""
|
|
874
|
+
}, context?.abort_signal);
|
|
875
|
+
return normalizeBuildIntentSignal(raw);
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
const raw = await this.post("/align", {
|
|
879
|
+
message: [
|
|
880
|
+
"Classify whether the user explicitly asks to start implementation now.",
|
|
881
|
+
"Return JSON only with keys ready/confidence/signal.",
|
|
882
|
+
`user_message: ${message}`,
|
|
883
|
+
`intent_update: ${context?.intent_update || ""}`
|
|
884
|
+
].join("\n"),
|
|
885
|
+
model: this.plannerModel,
|
|
886
|
+
intent_update: context?.intent_update || ""
|
|
887
|
+
}, context?.abort_signal);
|
|
888
|
+
return normalizeBuildIntentSignal(raw);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
383
891
|
}
|
|
384
892
|
function createLLMProvider(config) {
|
|
385
|
-
|
|
893
|
+
const provider = (0, provider_registry_1.toCanonicalModelProviderId)(config.model.provider);
|
|
894
|
+
if (provider === "http") {
|
|
386
895
|
const endpoint = config.model.endpoint?.trim();
|
|
387
896
|
if (!endpoint) {
|
|
388
897
|
throw new Error("HTTP provider selected but model.endpoint is empty.");
|
|
389
898
|
}
|
|
390
899
|
return new HttpLLMProvider(endpoint, config.model.planner_model, config.model.auditor_model, config.model.api_key_env || "ZAKER_API_KEY", config.model.api_key || "", config.model.timeout_ms || 30000);
|
|
391
900
|
}
|
|
392
|
-
if (
|
|
393
|
-
return new
|
|
901
|
+
if (provider === "mock") {
|
|
902
|
+
return new MockLLMProvider();
|
|
903
|
+
}
|
|
904
|
+
const authMode = config.model.auth_mode || "api_key";
|
|
905
|
+
const fallbackEnv = (0, provider_registry_1.resolveProviderApiKeyEnv)(provider, authMode);
|
|
906
|
+
const providerDefaultEndpoint = (0, provider_registry_1.resolveProviderEndpoint)(provider);
|
|
907
|
+
const explicitEndpoint = config.model.endpoint?.trim() || "";
|
|
908
|
+
const hasCustomGatewayEndpoint = explicitEndpoint.length > 0 &&
|
|
909
|
+
(!providerDefaultEndpoint || explicitEndpoint !== providerDefaultEndpoint);
|
|
910
|
+
if (hasCustomGatewayEndpoint) {
|
|
911
|
+
return new OpenAICompatibleProvider(provider, config.model.planner_model, config.model.auditor_model, config.model.api_key_env || fallbackEnv, config.model.api_key || "", config.model.timeout_ms || 30000, explicitEndpoint);
|
|
394
912
|
}
|
|
395
|
-
|
|
913
|
+
const runtimeProviderId = provider === "openai-codex" && authMode !== "oauth" ? "openai" : undefined;
|
|
914
|
+
const endpointOverride = hasCustomGatewayEndpoint ? explicitEndpoint : undefined;
|
|
915
|
+
return new PiAiProvider(provider, runtimeProviderId, config.model.planner_model, config.model.auditor_model, config.model.api_key_env || fallbackEnv, config.model.api_key || "", config.model.timeout_ms || 30000, endpointOverride);
|
|
396
916
|
}
|
|
397
917
|
//# sourceMappingURL=config.js.map
|