@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.
Files changed (121) hide show
  1. package/dist/commands/app-server.d.ts +67 -0
  2. package/dist/commands/app-server.js +601 -0
  3. package/dist/commands/app-server.js.map +1 -0
  4. package/dist/commands/audit.js +2 -1
  5. package/dist/commands/audit.js.map +1 -1
  6. package/dist/commands/build.d.ts +3 -0
  7. package/dist/commands/build.js +22 -0
  8. package/dist/commands/build.js.map +1 -0
  9. package/dist/commands/confirm.d.ts +0 -2
  10. package/dist/commands/confirm.js +2 -16
  11. package/dist/commands/confirm.js.map +1 -1
  12. package/dist/commands/dialog-handlers/auth.d.ts +3 -0
  13. package/dist/commands/dialog-handlers/auth.js +174 -0
  14. package/dist/commands/dialog-handlers/auth.js.map +1 -0
  15. package/dist/commands/dialog-handlers/basic.d.ts +7 -0
  16. package/dist/commands/dialog-handlers/basic.js +82 -0
  17. package/dist/commands/dialog-handlers/basic.js.map +1 -0
  18. package/dist/commands/dialog-handlers/bootstrap.d.ts +10 -0
  19. package/dist/commands/dialog-handlers/bootstrap.js +38 -0
  20. package/dist/commands/dialog-handlers/bootstrap.js.map +1 -0
  21. package/dist/commands/dialog-handlers/index.d.ts +2 -0
  22. package/dist/commands/dialog-handlers/index.js +6 -0
  23. package/dist/commands/dialog-handlers/index.js.map +1 -0
  24. package/dist/commands/dialog-handlers/message.d.ts +2 -0
  25. package/dist/commands/dialog-handlers/message.js +103 -0
  26. package/dist/commands/dialog-handlers/message.js.map +1 -0
  27. package/dist/commands/dialog-handlers/model.d.ts +2 -0
  28. package/dist/commands/dialog-handlers/model.js +168 -0
  29. package/dist/commands/dialog-handlers/model.js.map +1 -0
  30. package/dist/commands/dialog-handlers/new.d.ts +2 -0
  31. package/dist/commands/dialog-handlers/new.js +53 -0
  32. package/dist/commands/dialog-handlers/new.js.map +1 -0
  33. package/dist/commands/dialog-handlers/resume.d.ts +2 -0
  34. package/dist/commands/dialog-handlers/resume.js +25 -0
  35. package/dist/commands/dialog-handlers/resume.js.map +1 -0
  36. package/dist/commands/dialog-handlers/router.d.ts +11 -0
  37. package/dist/commands/dialog-handlers/router.js +112 -0
  38. package/dist/commands/dialog-handlers/router.js.map +1 -0
  39. package/dist/commands/dialog-handlers/run.d.ts +2 -0
  40. package/dist/commands/dialog-handlers/run.js +161 -0
  41. package/dist/commands/dialog-handlers/run.js.map +1 -0
  42. package/dist/commands/dialog-handlers/status.d.ts +2 -0
  43. package/dist/commands/dialog-handlers/status.js +13 -0
  44. package/dist/commands/dialog-handlers/status.js.map +1 -0
  45. package/dist/commands/dialog-handlers/types.d.ts +107 -0
  46. package/dist/commands/dialog-handlers/types.js +3 -0
  47. package/dist/commands/dialog-handlers/types.js.map +1 -0
  48. package/dist/commands/dialog.d.ts +92 -0
  49. package/dist/commands/dialog.js +1784 -236
  50. package/dist/commands/dialog.js.map +1 -1
  51. package/dist/commands/init.d.ts +3 -1
  52. package/dist/commands/init.js +6 -4
  53. package/dist/commands/init.js.map +1 -1
  54. package/dist/commands/plan.js +10 -6
  55. package/dist/commands/plan.js.map +1 -1
  56. package/dist/commands/run.js +8 -10
  57. package/dist/commands/run.js.map +1 -1
  58. package/dist/commands/status.js +6 -12
  59. package/dist/commands/status.js.map +1 -1
  60. package/dist/commands/tui-launcher.d.ts +1 -0
  61. package/dist/commands/tui-launcher.js +8 -0
  62. package/dist/commands/tui-launcher.js.map +1 -0
  63. package/dist/core/alignment-reply.d.ts +1 -0
  64. package/dist/core/alignment-reply.js +44 -0
  65. package/dist/core/alignment-reply.js.map +1 -0
  66. package/dist/core/checkpoint.js +3 -1
  67. package/dist/core/checkpoint.js.map +1 -1
  68. package/dist/core/planner.d.ts +16 -16
  69. package/dist/core/planner.js +3 -1
  70. package/dist/core/planner.js.map +1 -1
  71. package/dist/core/planning-prep.d.ts +12 -0
  72. package/dist/core/planning-prep.js +26 -0
  73. package/dist/core/planning-prep.js.map +1 -0
  74. package/dist/core/preflight.js +1 -2
  75. package/dist/core/preflight.js.map +1 -1
  76. package/dist/core/provider-onboarding.js +6 -11
  77. package/dist/core/provider-onboarding.js.map +1 -1
  78. package/dist/core/readonly-checkpoint.d.ts +2 -0
  79. package/dist/core/readonly-checkpoint.js +35 -0
  80. package/dist/core/readonly-checkpoint.js.map +1 -0
  81. package/dist/core/run-loop.js +3 -1
  82. package/dist/core/run-loop.js.map +1 -1
  83. package/dist/core/types.d.ts +20 -1
  84. package/dist/index.js +20 -6
  85. package/dist/index.js.map +1 -1
  86. package/dist/infra/artifact-schema.d.ts +25 -0
  87. package/dist/infra/artifact-schema.js +353 -0
  88. package/dist/infra/artifact-schema.js.map +1 -0
  89. package/dist/infra/config.d.ts +14 -1
  90. package/dist/infra/config.js +542 -22
  91. package/dist/infra/config.js.map +1 -1
  92. package/dist/infra/dependency-report.d.ts +5 -0
  93. package/dist/infra/dependency-report.js +22 -0
  94. package/dist/infra/dependency-report.js.map +1 -0
  95. package/dist/infra/dialog-session.d.ts +29 -0
  96. package/dist/infra/dialog-session.js +244 -0
  97. package/dist/infra/dialog-session.js.map +1 -0
  98. package/dist/infra/intent.js +63 -13
  99. package/dist/infra/intent.js.map +1 -1
  100. package/dist/infra/model-accounts.d.ts +22 -0
  101. package/dist/infra/model-accounts.js +172 -0
  102. package/dist/infra/model-accounts.js.map +1 -0
  103. package/dist/infra/model-catalog.d.ts +4 -1
  104. package/dist/infra/model-catalog.js +102 -27
  105. package/dist/infra/model-catalog.js.map +1 -1
  106. package/dist/infra/openai-codex-oauth.d.ts +18 -0
  107. package/dist/infra/openai-codex-oauth.js +267 -0
  108. package/dist/infra/openai-codex-oauth.js.map +1 -0
  109. package/dist/infra/provider-registry.d.ts +36 -0
  110. package/dist/infra/provider-registry.js +403 -0
  111. package/dist/infra/provider-registry.js.map +1 -0
  112. package/dist/infra/session-status.d.ts +6 -0
  113. package/dist/infra/session-status.js +34 -0
  114. package/dist/infra/session-status.js.map +1 -0
  115. package/dist/infra/tui-utils.d.ts +6 -0
  116. package/dist/infra/tui-utils.js +163 -0
  117. package/dist/infra/tui-utils.js.map +1 -0
  118. package/dist/infra/tui-view.d.ts +44 -0
  119. package/dist/infra/tui-view.js +314 -0
  120. package/dist/infra/tui-view.js.map +1 -0
  121. package/package.json +4 -1
@@ -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.enum(["mock", "http", "openai_codex_team"]).default("mock"),
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
- planner_model: zod_1.z.string().min(1).default("qwen3-coder-30b"),
38
- auditor_model: zod_1.z.string().min(1).default("kimi-k2.5"),
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("qwen3-coder-30b"),
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: "qwen3-coder-30b",
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
- planner_model: "qwen3-coder-30b",
78
- auditor_model: "kimi-k2.5",
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: "qwen3-coder-30b",
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(`OpenAI provider returned empty ${context} payload.`);
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(`OpenAI provider returned non-JSON ${context} payload.`);
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 OpenAICodexTeamProvider {
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(`OpenAI provider selected but API key missing. Set model.api_key or env ${apiKeyEnv}.`);
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(`OpenAI provider request failed: ${message}`);
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 instanceof Error && error.name === "AbortError") {
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
- if (config.model.provider === "http") {
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 (config.model.provider === "openai_codex_team") {
393
- return new OpenAICodexTeamProvider(config.model.planner_model, config.model.auditor_model, config.model.api_key_env || "OPENAI_API_KEY", config.model.api_key || "", config.model.timeout_ms || 30000, config.model.endpoint);
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
- return new MockLLMProvider();
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