@tekmidian/pai 0.66.0 → 0.66.2

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 (165) hide show
  1. package/dist/{aibroker-client-B8c42Lh8.mjs → aibroker-client-CHEEvJZW.mjs} +2 -2
  2. package/dist/{aibroker-client-C5Fw7DNz.mjs → aibroker-client-Dfv7j1Od.mjs} +3 -3
  3. package/dist/{aibroker-client-C5Fw7DNz.mjs.map → aibroker-client-Dfv7j1Od.mjs.map} +1 -1
  4. package/dist/{auto-route-o0BOsXn7.mjs → auto-route-BnizyALK.mjs} +65 -4
  5. package/dist/auto-route-BnizyALK.mjs.map +1 -0
  6. package/dist/auto-route-D6fW1Q8z.mjs +3 -0
  7. package/dist/{chain-D4PuQ2c-.mjs → chain-D5876UQX.mjs} +3 -4
  8. package/dist/{chain-D4PuQ2c-.mjs.map → chain-D5876UQX.mjs.map} +1 -1
  9. package/dist/cli/index.mjs +18 -23
  10. package/dist/cli/index.mjs.map +1 -1
  11. package/dist/cli/program.mjs +17 -22
  12. package/dist/{config-BbLFD7Uf.mjs → config-C-LiGlop.mjs} +2 -2
  13. package/dist/{config-BbLFD7Uf.mjs.map → config-C-LiGlop.mjs.map} +1 -1
  14. package/dist/{config-YinjgXEJ.mjs → config-DKIA7wgF.mjs} +1 -1
  15. package/dist/{context-handover-cache-pkHzmL3c.mjs → context-handover-cache-9PGvXRIw.mjs} +984 -10
  16. package/dist/context-handover-cache-9PGvXRIw.mjs.map +1 -0
  17. package/dist/daemon/index.mjs +17 -19
  18. package/dist/daemon/index.mjs.map +1 -1
  19. package/dist/daemon-BUgNvMUh.mjs +5029 -0
  20. package/dist/daemon-BUgNvMUh.mjs.map +1 -0
  21. package/dist/daemon-DL0eglMJ.mjs +18 -0
  22. package/dist/daemon-mcp/index.mjs +15 -10
  23. package/dist/daemon-mcp/index.mjs.map +1 -1
  24. package/dist/{embeddings-CcscYWwk.mjs → embeddings-CEBGrzwu.mjs} +1 -1
  25. package/dist/{embeddings-Bx3q0QOY.mjs → embeddings-DOLZnT1X.mjs} +1 -1
  26. package/dist/{embeddings-Bx3q0QOY.mjs.map → embeddings-DOLZnT1X.mjs.map} +1 -1
  27. package/dist/{env-JNEIrQWg.mjs → env-DiolswKQ.mjs} +1 -1
  28. package/dist/{env-JNEIrQWg.mjs.map → env-DiolswKQ.mjs.map} +1 -1
  29. package/dist/factory-DrBg24UT.mjs +8 -0
  30. package/dist/factory-Ypf8r0fK.mjs +2892 -0
  31. package/dist/factory-Ypf8r0fK.mjs.map +1 -0
  32. package/dist/{fallback-B5svk75f.mjs → fallback-D51R4shY.mjs} +27 -13
  33. package/dist/fallback-D51R4shY.mjs.map +1 -0
  34. package/dist/{chunker-BH4i-F2b.mjs → helpers-CZsi_49C.mjs} +221 -2
  35. package/dist/helpers-CZsi_49C.mjs.map +1 -0
  36. package/dist/hooks/block-sleep-poll.mjs.map +2 -2
  37. package/dist/hooks/load-project-context.mjs.map +2 -2
  38. package/dist/hooks/worker-guard.mjs +5 -0
  39. package/dist/hooks/worker-guard.mjs.map +2 -2
  40. package/dist/hooks/worker-status-line.mjs +1 -0
  41. package/dist/hooks/worker-status-line.mjs.map +2 -2
  42. package/dist/index.mjs +6 -7
  43. package/dist/{ipc-client-D16Xw6Uo.mjs → ipc-client-BLYX51nG.mjs} +2 -2
  44. package/dist/{ipc-client-D16Xw6Uo.mjs.map → ipc-client-BLYX51nG.mjs.map} +1 -1
  45. package/dist/main-resolver-BYgP86C1.mjs +7 -0
  46. package/dist/{main-resolver-DPPwHNtn.mjs → main-resolver-kVkMJZUD.mjs} +6 -6
  47. package/dist/{main-resolver-DPPwHNtn.mjs.map → main-resolver-kVkMJZUD.mjs.map} +1 -1
  48. package/dist/{migrate-BD7D8EEh.mjs → migrate-Bq0esMOa.mjs} +2 -2
  49. package/dist/{migrate-BD7D8EEh.mjs.map → migrate-Bq0esMOa.mjs.map} +1 -1
  50. package/dist/{pai-marker-D1MMswkz.mjs → pai-marker-DXVpFsYz.mjs} +1 -1
  51. package/dist/{pai-marker-D1MMswkz.mjs.map → pai-marker-DXVpFsYz.mjs.map} +1 -1
  52. package/dist/{planner-EJPZnnif.mjs → planner-D5yt0EfA.mjs} +10 -7
  53. package/dist/{planner-EJPZnnif.mjs.map → planner-D5yt0EfA.mjs.map} +1 -1
  54. package/dist/postgres-BmLr0MUm.mjs +5 -0
  55. package/dist/{postgres-Ceqsa64C.mjs → postgres-D3xc2RB4.mjs} +364 -9
  56. package/dist/postgres-D3xc2RB4.mjs.map +1 -0
  57. package/dist/{program-DSETzrWg.mjs → program-DIXRtAFy.mjs} +80 -70
  58. package/dist/program-DIXRtAFy.mjs.map +1 -0
  59. package/dist/{query-feedback-BIaZTTFO.mjs → query-feedback-B7FYE4JR.mjs} +1 -1
  60. package/dist/{query-feedback-BIaZTTFO.mjs.map → query-feedback-B7FYE4JR.mjs.map} +1 -1
  61. package/dist/{reranker-DKv80KO5.mjs → reranker-3lnggwgq.mjs} +1 -1
  62. package/dist/{reranker-DKv80KO5.mjs.map → reranker-3lnggwgq.mjs.map} +1 -1
  63. package/dist/{reranker-CFiEzHQu.mjs → reranker-CZ2mP4cf.mjs} +1 -1
  64. package/dist/router-BK-hFeQ6.mjs +3 -0
  65. package/dist/{router-a5G7q8_3.mjs → router-BaTbc9VX.mjs} +2 -2
  66. package/dist/{router-a5G7q8_3.mjs.map → router-BaTbc9VX.mjs.map} +1 -1
  67. package/dist/{run-Z4akoa8G.mjs → run-CLYTOm0x.mjs} +764 -25
  68. package/dist/run-CLYTOm0x.mjs.map +1 -0
  69. package/dist/{run-env-DRcp7A8K.mjs → run-env-Bnmf6bRI.mjs} +2 -2
  70. package/dist/{run-env-DRcp7A8K.mjs.map → run-env-Bnmf6bRI.mjs.map} +1 -1
  71. package/dist/{runtime-paths-CHTg3ywb.mjs → runtime-paths-D3FKAm69.mjs} +1 -1
  72. package/dist/{runtime-paths-CHTg3ywb.mjs.map → runtime-paths-D3FKAm69.mjs.map} +1 -1
  73. package/dist/{search-BOSphCJ1.mjs → search-CNAGTiJP.mjs} +2 -2
  74. package/dist/{search-BOSphCJ1.mjs.map → search-CNAGTiJP.mjs.map} +1 -1
  75. package/dist/{sources-CijVso3n.mjs → sources-CM2g-CLT.mjs} +2 -2
  76. package/dist/{sources-CijVso3n.mjs.map → sources-CM2g-CLT.mjs.map} +1 -1
  77. package/dist/{stop-words-BdQuaE9K.mjs → stop-words-DtxaTWU_.mjs} +1 -1
  78. package/dist/{stop-words-BdQuaE9K.mjs.map → stop-words-DtxaTWU_.mjs.map} +1 -1
  79. package/dist/{utils-C9HsYDpO.mjs → utils-DddRwMsG.mjs} +2 -2
  80. package/dist/{utils-C9HsYDpO.mjs.map → utils-DddRwMsG.mjs.map} +1 -1
  81. package/dist/{zettelkasten-CtGHQWPU.mjs → zettelkasten-BhZMvmoK.mjs} +149 -7
  82. package/dist/zettelkasten-BhZMvmoK.mjs.map +1 -0
  83. package/dist/zettelkasten-BwV1pluL.mjs +5 -0
  84. package/docs/commands/worker.md +2 -0
  85. package/package.json +1 -1
  86. package/src/hooks/ts/lib/worker-guard.test.ts +12 -0
  87. package/src/hooks/ts/lib/worker-guard.ts +9 -0
  88. package/dist/async-C2Bm_Lal.mjs +0 -300
  89. package/dist/async-C2Bm_Lal.mjs.map +0 -1
  90. package/dist/auto-route-o0BOsXn7.mjs.map +0 -1
  91. package/dist/chunker-BH4i-F2b.mjs.map +0 -1
  92. package/dist/clusters-BCtD3fbe.mjs +0 -169
  93. package/dist/clusters-BCtD3fbe.mjs.map +0 -1
  94. package/dist/context-handover-cache-pkHzmL3c.mjs.map +0 -1
  95. package/dist/daemon-BEoVgVfJ.mjs +0 -1741
  96. package/dist/daemon-BEoVgVfJ.mjs.map +0 -1
  97. package/dist/daemon-DPAYU9hp.mjs +0 -19
  98. package/dist/detector-BEPFsINR.mjs +0 -3
  99. package/dist/detector-BRTtAWrM.mjs +0 -65
  100. package/dist/detector-BRTtAWrM.mjs.map +0 -1
  101. package/dist/factory-CNpPQQ-5.mjs +0 -249
  102. package/dist/factory-CNpPQQ-5.mjs.map +0 -1
  103. package/dist/factory-DY7x73mE.mjs +0 -3
  104. package/dist/fallback-B5svk75f.mjs.map +0 -1
  105. package/dist/federation-db-BTyoufBh.mjs +0 -139
  106. package/dist/federation-db-BTyoufBh.mjs.map +0 -1
  107. package/dist/federation-db-HfIFc7FG.mjs +0 -3
  108. package/dist/helpers-BRCJg0G3.mjs +0 -223
  109. package/dist/helpers-BRCJg0G3.mjs.map +0 -1
  110. package/dist/indexer-backend-DFF2FrYx.mjs +0 -5
  111. package/dist/indexer-backend-isSLg6yE.mjs +0 -1
  112. package/dist/kg-entity-DCOcsVFD.mjs +0 -29
  113. package/dist/kg-entity-DCOcsVFD.mjs.map +0 -1
  114. package/dist/latent-ideas-wwAXquGe.mjs +0 -191
  115. package/dist/latent-ideas-wwAXquGe.mjs.map +0 -1
  116. package/dist/link-boost-CnI7UVMJ.mjs +0 -34
  117. package/dist/link-boost-CnI7UVMJ.mjs.map +0 -1
  118. package/dist/main-resolver-BKz_OuEh.mjs +0 -7
  119. package/dist/merge-2gqRPFu2.mjs +0 -3
  120. package/dist/merge-DgU9OgZy.mjs +0 -6
  121. package/dist/merge-DgU9OgZy.mjs.map +0 -1
  122. package/dist/module-paths-DdRzbkUI.mjs +0 -44
  123. package/dist/module-paths-DdRzbkUI.mjs.map +0 -1
  124. package/dist/neighborhood-2FsmzoxG.mjs +0 -114
  125. package/dist/neighborhood-2FsmzoxG.mjs.map +0 -1
  126. package/dist/note-context-De63k8na.mjs +0 -106
  127. package/dist/note-context-De63k8na.mjs.map +0 -1
  128. package/dist/postgres-Ceqsa64C.mjs.map +0 -1
  129. package/dist/program-DSETzrWg.mjs.map +0 -1
  130. package/dist/query-feedback-B_iigYj-.mjs +0 -3
  131. package/dist/registry-db-C7voqML9.mjs +0 -213
  132. package/dist/registry-db-C7voqML9.mjs.map +0 -1
  133. package/dist/registry-db-JHPhA8vF.mjs +0 -3
  134. package/dist/registry-postgres-nOfyBSIW.mjs +0 -795
  135. package/dist/registry-postgres-nOfyBSIW.mjs.map +0 -1
  136. package/dist/registry-sqlite-DrCW1aRK.mjs +0 -593
  137. package/dist/registry-sqlite-DrCW1aRK.mjs.map +0 -1
  138. package/dist/router-CXUGsv85.mjs +0 -3
  139. package/dist/run-Z4akoa8G.mjs.map +0 -1
  140. package/dist/search-Bf3Kub3F.mjs +0 -3
  141. package/dist/server-DgmAHyFK.mjs +0 -403
  142. package/dist/server-DgmAHyFK.mjs.map +0 -1
  143. package/dist/session-keepalive-BDhytXEo.mjs +0 -582
  144. package/dist/session-keepalive-BDhytXEo.mjs.map +0 -1
  145. package/dist/sqlite-CqsTy6xo.mjs +0 -936
  146. package/dist/sqlite-CqsTy6xo.mjs.map +0 -1
  147. package/dist/state-DW8zdweW.mjs +0 -3
  148. package/dist/state-HyjqTihC.mjs +0 -76
  149. package/dist/state-HyjqTihC.mjs.map +0 -1
  150. package/dist/themes-CTaOj3e1.mjs +0 -148
  151. package/dist/themes-CTaOj3e1.mjs.map +0 -1
  152. package/dist/tools-BcOKPBtg.mjs +0 -1489
  153. package/dist/tools-BcOKPBtg.mjs.map +0 -1
  154. package/dist/tools-C-_l0o8Z.mjs +0 -6
  155. package/dist/trace-CtV0RO6n.mjs +0 -137
  156. package/dist/trace-CtV0RO6n.mjs.map +0 -1
  157. package/dist/utils-BrX9FLeK.mjs +0 -3
  158. package/dist/vault-indexer-BG1sEVyM.mjs +0 -537
  159. package/dist/vault-indexer-BG1sEVyM.mjs.map +0 -1
  160. package/dist/wakeup-D8n33hYV.mjs +0 -335
  161. package/dist/wakeup-D8n33hYV.mjs.map +0 -1
  162. package/dist/work-queue-worker-4tLa5gpQ.mjs +0 -567
  163. package/dist/work-queue-worker-4tLa5gpQ.mjs.map +0 -1
  164. package/dist/work-queue-worker-rFZvTO3p.mjs +0 -11
  165. package/dist/zettelkasten-CtGHQWPU.mjs.map +0 -1
@@ -1,3 +0,0 @@
1
- import { i as populateSlugs, n as buildFtsQuery, r as isQuerySyntaxError, t as applyRecencyBoost } from "./search-BOSphCJ1.mjs";
2
-
3
- export { applyRecencyBoost };
@@ -1,403 +0,0 @@
1
- import { a as paiHomePath, o as resolvePaiFile } from "./pai-home-Cm9rcJgX.mjs";
2
- import { O as resolveCapability, T as providerCostTier, _ as classModelCapability, b as getProviderOrNative, h as WorkersConfigError, m as WORKER_CLASSES, o as DEFAULT_LOG_DIR, r as ANTHROPIC_NATIVE, y as expandHome } from "./run-env-DRcp7A8K.mjs";
3
- import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
4
- import { dirname, join } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { execFileSync, spawn } from "node:child_process";
7
- import { connect } from "node:net";
8
-
9
- //#region src/workers/paths.ts
10
- /**
11
- * paths.ts — where worker run artefacts live.
12
- *
13
- * Everything a run writes (event mirror, status file, pane registry, routing
14
- * state, the empty MCP config) sits under one logDir so the whole tree is
15
- * disposable and configurable: `workers.logDir`, default ~/.claude/logs/workers.
16
- */
17
- /**
18
- * Absolute logDir for the given config. An explicit `logDir` in workers.yaml
19
- * is honored verbatim (the user chose it). Otherwise this resolves like every
20
- * other PAI_HOME file: PAI_HOME/logs/workers if it exists, else the old
21
- * ~/.claude/logs/workers (with a one-time notice), else the new path.
22
- */
23
- function workersLogDir(config) {
24
- const expanded = expandHome(config.logDir);
25
- if (config.logDir !== DEFAULT_LOG_DIR) return expanded;
26
- return resolvePaiFile(paiHomePath("logs", "workers"), [expanded], "pai config migrate --logs");
27
- }
28
- /** The strict empty MCP config for headless runs, written on demand. */
29
- function ensureNoMcpConfig(logDir) {
30
- const path = noMcpConfigPath(logDir);
31
- if (!existsSync(path)) {
32
- mkdirSync(logDir, { recursive: true });
33
- writeFileSync(path, "{ \"mcpServers\": {} }\n", "utf8");
34
- }
35
- return path;
36
- }
37
- function statusPath(logDir, id) {
38
- return join(logDir, `${id}.status`);
39
- }
40
- function eventsPath(logDir, id) {
41
- return join(logDir, `${id}.jsonl`);
42
- }
43
- function ledgerPath(logDir) {
44
- return join(logDir, "ledger.log");
45
- }
46
- function routingStatePath(logDir) {
47
- return join(logDir, "routing-state.json");
48
- }
49
- function panesDir(logDir) {
50
- return join(logDir, "panes");
51
- }
52
- /**
53
- * The strict empty MCP config handed to headless workers. Written into the
54
- * logDir on demand (never into the user's vendor config directory — this is
55
- * PAI state, not vendor state).
56
- */
57
- function noMcpConfigPath(logDir) {
58
- return join(logDir, "no-mcp.json");
59
- }
60
-
61
- //#endregion
62
- //#region src/workers/routing.ts
63
- /**
64
- * routing.ts — provider selection: flag > class > active (possibly "auto").
65
- *
66
- * Auto-routing walks `workers.routing.order` — or the class's own `order` —
67
- * and takes the first provider that is enabled, out of cooldown, (when it
68
- * defines a quotaProbe) under its quotaSkipAt threshold, and — when the class
69
- * constrains it — within `maxCostTier` and carrying all `requireTags`. A run
70
- * that dies of a quota/rate error puts its provider in cooldown for
71
- * cooldownMinutes; when that happens before the first tool call and
72
- * retryOnQuota is set, the runner restarts the same task on the next provider
73
- * (ledger: WORKER-REROUTE).
74
- *
75
- * An explicit --provider or a class mapping that pins a provider always
76
- * bypasses all of this.
77
- */
78
- const QUOTA_SKIP_DEFAULT = 95;
79
- const PROBE_TIMEOUT_MS = 1e4;
80
- function readRoutingState(logDir) {
81
- const path = routingStatePath(logDir);
82
- if (!existsSync(path)) return { cooldowns: {} };
83
- try {
84
- return { cooldowns: JSON.parse(readFileSync(path, "utf8")).cooldowns ?? {} };
85
- } catch {
86
- return { cooldowns: {} };
87
- }
88
- }
89
- function writeRoutingState(logDir, state) {
90
- const path = routingStatePath(logDir);
91
- const dir = dirname(path);
92
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
93
- const tmp = `${path}.tmp`;
94
- writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n", "utf8");
95
- renameSync(tmp, path);
96
- }
97
- function cooldownRemaining(state, provider, now = /* @__PURE__ */ new Date()) {
98
- const end = state.cooldowns[provider];
99
- if (!end) return 0;
100
- const ms = Date.parse(end) - now.getTime();
101
- return ms > 0 ? ms : 0;
102
- }
103
- function setCooldown(logDir, provider, minutes, now = /* @__PURE__ */ new Date()) {
104
- const state = readRoutingState(logDir);
105
- state.cooldowns[provider] = new Date(now.getTime() + minutes * 6e4).toISOString();
106
- writeRoutingState(logDir, state);
107
- }
108
- function clearCooldown(logDir, provider) {
109
- const state = readRoutingState(logDir);
110
- if (!(provider in state.cooldowns)) return;
111
- delete state.cooldowns[provider];
112
- writeRoutingState(logDir, state);
113
- }
114
- /**
115
- * Run a provider's quotaProbe and return its percentage (0–100), or null when
116
- * there is no probe or it printed nothing usable. A probe must never break
117
- * routing: failures read as "unknown", not as "full".
118
- */
119
- function probeQuota(provider) {
120
- if (!provider.quotaProbe) return null;
121
- try {
122
- const m = execFileSync("/bin/sh", ["-c", provider.quotaProbe], {
123
- timeout: PROBE_TIMEOUT_MS,
124
- encoding: "utf8",
125
- stdio: [
126
- "ignore",
127
- "pipe",
128
- "ignore"
129
- ]
130
- }).match(/(\d+(?:\.\d+)?)/);
131
- if (!m) return null;
132
- return Math.min(100, Math.max(0, Math.round(parseFloat(m[1]))));
133
- } catch {
134
- return null;
135
- }
136
- }
137
- function quotaSkipThreshold(provider) {
138
- return provider.quotaSkipAt ?? QUOTA_SKIP_DEFAULT;
139
- }
140
- /** Does this provider exceed its quota threshold right now? */
141
- function quotaExceeded(provider) {
142
- const used = probeQuota(provider);
143
- return used !== null && used >= quotaSkipThreshold(provider);
144
- }
145
- var NoProviderError = class extends WorkersConfigError {};
146
- function mustExist(config, name) {
147
- const p = getProviderOrNative(config, name);
148
- if (!p) throw new NoProviderError(`no worker provider named "${name}". Configured: ${Object.keys(config.providers).join(", ") || "(none)"}.\nAdd one with: pai worker providers add <name> --base-url <url> --key-file <path> --model <model>`);
149
- return p;
150
- }
151
- function mustBeRunnable(name, p) {
152
- if (!p.enabled) throw new NoProviderError(`provider "${name}" is disabled. Enable it with: pai worker providers enable ${name}`);
153
- return p;
154
- }
155
- /** Why one provider of a routing order did not qualify (message fragment). */
156
- function exclusionReason(config, state, name, cls) {
157
- const p = getProviderOrNative(config, name);
158
- if (!p) return "not configured";
159
- if (!p.enabled) return "disabled";
160
- if (cooldownRemaining(state, name) > 0) return "cooldown";
161
- if (quotaExceeded(p)) return "quota";
162
- const tier = providerCostTier(p);
163
- if (cls?.maxCostTier !== void 0 && tier > cls.maxCostTier) return `cost tier ${tier} > max ${cls.maxCostTier}`;
164
- if (cls?.requireTags?.length) {
165
- const have = p.tags ?? [];
166
- const missing = cls.requireTags.filter((t) => !have.includes(t));
167
- if (missing.length) return `missing tags: ${missing.join(", ")}`;
168
- }
169
- return null;
170
- }
171
- /**
172
- * Resolve which provider (and model alias) a run uses.
173
- *
174
- * @param flagProvider --provider value, highest precedence
175
- * @param className --class value (the old --role), looked up in workers.classes
176
- */
177
- function resolveTarget(config, logDir, opts = {}) {
178
- if (opts.flagProvider) return {
179
- providerName: opts.flagProvider,
180
- provider: mustBeRunnable(opts.flagProvider, mustExist(config, opts.flagProvider)),
181
- modelAlias: null,
182
- classMcp: null,
183
- via: "flag"
184
- };
185
- const clsTarget = opts.className ? config.classes[opts.className] : void 0;
186
- const cls = typeof clsTarget === "object" && clsTarget !== null ? clsTarget : void 0;
187
- if (opts.className && clsTarget !== void 0 && typeof clsTarget !== "object") {
188
- const [name, alias] = clsTarget.split("/");
189
- return {
190
- providerName: name,
191
- provider: mustBeRunnable(name, mustExist(config, name)),
192
- modelAlias: alias ?? null,
193
- classMcp: null,
194
- via: "class"
195
- };
196
- }
197
- if (opts.className && cls?.provider) {
198
- const provider = mustBeRunnable(cls.provider, mustExist(config, cls.provider));
199
- return {
200
- providerName: cls.provider,
201
- provider,
202
- modelAlias: null,
203
- classMcp: cls.mcp ?? null,
204
- via: "class"
205
- };
206
- }
207
- if (opts.className && clsTarget === void 0) {
208
- if (!WORKER_CLASSES.includes(opts.className)) throw new NoProviderError(`no class named "${opts.className}". Standard classes: ${WORKER_CLASSES.join(", ")}.Defined: ${Object.keys(config.classes).join(", ") || "(none)"}.\nSet one with: pai worker classes set ${opts.className}=<provider[/alias]>`);
209
- }
210
- if (config.active !== "auto") {
211
- const name = config.active ?? ANTHROPIC_NATIVE;
212
- return {
213
- providerName: name,
214
- provider: mustBeRunnable(name, mustExist(config, name)),
215
- modelAlias: null,
216
- classMcp: null,
217
- via: "active"
218
- };
219
- }
220
- const state = readRoutingState(logDir);
221
- const order = cls?.order ?? config.routing.order;
222
- const excluded = [];
223
- for (const name of order) {
224
- const p = getProviderOrNative(config, name);
225
- const why = exclusionReason(config, state, name, cls);
226
- if (why) {
227
- excluded.push(`${name}: ${why}`);
228
- continue;
229
- }
230
- return {
231
- providerName: name,
232
- provider: p,
233
- modelAlias: null,
234
- classMcp: cls?.mcp ?? null,
235
- via: "auto"
236
- };
237
- }
238
- throw new NoProviderError(`auto-routing found no usable provider${opts.className ? ` for class "${opts.className}"` : ""} (order: [${order.join(", ")}]).${excluded.length ? `\nExcluded: ${excluded.join("; ")}.` : ""}\nClear a cooldown with: pai worker providers enable <name>; widen the class with: pai worker classes set ${opts.className ?? "<class>"}=<target>`);
239
- }
240
- /**
241
- * Next provider after `from` in auto order, applying the same filters.
242
- * Used by rerouting; null when the order is exhausted.
243
- */
244
- function nextAutoProvider(config, logDir, from, now = /* @__PURE__ */ new Date()) {
245
- const state = readRoutingState(logDir);
246
- const order = config.routing.order;
247
- const start = order.indexOf(from);
248
- for (let i = start + 1; i < order.length; i++) {
249
- const name = order[i];
250
- const p = getProviderOrNative(config, name);
251
- if (!p || !p.enabled) continue;
252
- const end = state.cooldowns[name];
253
- if (end && Date.parse(end) > now.getTime()) continue;
254
- if (quotaExceeded(p)) continue;
255
- return name;
256
- }
257
- return null;
258
- }
259
- /**
260
- * Was this failure a quota/rate failure? Detected from the result text the
261
- * endpoint produced (HTTP status is not visible in the stream events).
262
- */
263
- function isQuotaFailure(resultText) {
264
- return /usage limit reached|rate limit|quota/i.test(resultText);
265
- }
266
- /**
267
- * Whether a run should resolve through `resolveCapability` instead of
268
- * `resolveTarget`: an explicit `--capability` always does. A `--class`
269
- * whose implied capability is not "default" (e.g. `image`) does too, but
270
- * only when the class itself names no provider — a class already pinned to
271
- * one (`image: glm/image`) always wins unchanged, same as any other class.
272
- */
273
- function capabilityForRun(config, opts) {
274
- if (opts.capabilityFlag) return opts.capabilityFlag;
275
- if (!opts.className) return null;
276
- const capability = classModelCapability(opts.className);
277
- if (capability === "default") return null;
278
- const clsTarget = config.classes[opts.className];
279
- return (typeof clsTarget === "string" ? true : Boolean(clsTarget?.provider)) ? null : capability;
280
- }
281
- /**
282
- * resolveCapability, turned into a runnable provider (NoProviderError-shaped
283
- * on failure). Unlike resolveCapability itself — which falls back to the
284
- * active provider's default model so `describeCapabilities` has something to
285
- * report — an actual run refuses the fallback: silently running a normal
286
- * model on a prompt written for a capability it does not have (an image
287
- * prompt fed to a text model, say) produces a confusing non-answer instead
288
- * of a clear error naming the config key to set.
289
- */
290
- function resolveCapabilityRun(config, capability) {
291
- const r = resolveCapability(config, capability);
292
- if (r.fellBack) throw new NoProviderError(`no provider declares the "${capability}" capability — configure one with: pai worker capability ${capability} <provider>[,<provider>…], or pai worker model ${capability} <model-id> --provider <name>`);
293
- if (capability === "image" && r.engine !== "image") throw new NoProviderError(`provider "${r.provider}" has no image engine (it would run "${r.model}" on its normal ${r.engine} engine, not generate an image) — configure an engine=image provider with: pai worker providers add <name> --engine image --url <url> --key <key> --model <id>, then pai worker capability image <name>`);
294
- const provider = getProviderOrNative(config, r.provider);
295
- if (!provider) throw new NoProviderError(`capability "${capability}" resolved to unknown provider "${r.provider}"`);
296
- return {
297
- providerName: r.provider,
298
- provider,
299
- model: r.model,
300
- engine: r.engine,
301
- fellBack: r.fellBack,
302
- capability
303
- };
304
- }
305
-
306
- //#endregion
307
- //#region src/workers/proxy/server.ts
308
- /**
309
- * server.ts — the PAI worker proxy: Anthropic Messages API on the front,
310
- * OpenAI Chat Completions on the back, loopback only.
311
- *
312
- * One proxy serves every OpenAI-protocol provider: Claude Code points
313
- * ANTHROPIC_BASE_URL at `http://127.0.0.1:8797/<provider>` and the provider
314
- * name in the path selects the upstream (`upstreamUrl` + `keyFile` from the
315
- * workers config, re-read per request so config edits apply without a
316
- * restart). `run` starts the proxy on demand (detached, pid file under the
317
- * logDir) and `pai worker proxy stop` stops it again.
318
- *
319
- * All translation lives in translate.ts; this file is only HTTP plumbing.
320
- */
321
- const DEFAULT_PROXY_PORT = 8797;
322
- const HOST = "127.0.0.1";
323
- function proxyPidPath(logDir) {
324
- return join(logDir, "proxy.pid");
325
- }
326
- /** True when something already accepts connections on the port. */
327
- function proxyListening(port = DEFAULT_PROXY_PORT, timeoutMs = 400) {
328
- return new Promise((resolve) => {
329
- const sock = connect({
330
- port,
331
- host: HOST
332
- });
333
- const done = (ok) => {
334
- sock.removeAllListeners();
335
- sock.destroy();
336
- resolve(ok);
337
- };
338
- sock.setTimeout(timeoutMs, () => done(false));
339
- sock.once("connect", () => done(true));
340
- sock.once("error", () => done(false));
341
- });
342
- }
343
- /**
344
- * Where the standalone proxy artefact lives, found from this module's own
345
- * location (dist/cli bundle → ../hooks/, dev checkout → <repo>/dist/hooks).
346
- */
347
- function standaloneProxyPath() {
348
- let dir = dirname(fileURLToPath(import.meta.url));
349
- for (let i = 0; i < 6; i++) {
350
- for (const cand of [
351
- join(dir, "worker-proxy.mjs"),
352
- join(dir, "hooks", "worker-proxy.mjs"),
353
- join(dir, "dist", "hooks", "worker-proxy.mjs")
354
- ]) if (existsSync(cand)) return cand;
355
- const parent = dirname(dir);
356
- if (parent === dir) break;
357
- dir = parent;
358
- }
359
- return null;
360
- }
361
- /**
362
- * Ensure a proxy is listening on the port; start the detached standalone when
363
- * nothing answers. Returns the base URL (`http://127.0.0.1:<port>`).
364
- */
365
- async function ensureProxyRunning(port, logDir) {
366
- if (await proxyListening(port)) return `http://${HOST}:${port}`;
367
- const script = standaloneProxyPath();
368
- if (!script) throw new Error(`the PAI worker proxy is not running and its build was not found — run \`bun run build\` (or start one with: pai worker proxy --port ${port})`);
369
- if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
370
- const child = spawn(process.execPath, [
371
- script,
372
- "--port",
373
- String(port)
374
- ], {
375
- detached: true,
376
- stdio: "ignore"
377
- });
378
- child.unref();
379
- writeFileSync(proxyPidPath(logDir), `${child.pid}\n`, "utf8");
380
- for (let i = 0; i < 40; i++) {
381
- if (await proxyListening(port)) return `http://${HOST}:${port}`;
382
- await new Promise((r) => setTimeout(r, 250));
383
- }
384
- throw new Error(`the PAI worker proxy did not come up on port ${port} (see ${proxyPidPath(logDir)})`);
385
- }
386
- /** `pai worker proxy stop`: SIGTERM the pid from the pid file. */
387
- function stopProxy(logDir) {
388
- const path = proxyPidPath(logDir);
389
- if (!existsSync(path)) return "no proxy pid file — nothing to stop";
390
- const pid = parseInt(readFileSync(path, "utf8").trim(), 10);
391
- unlinkSync(path);
392
- if (!Number.isFinite(pid)) return "stale proxy pid file removed";
393
- try {
394
- process.kill(pid, "SIGTERM");
395
- } catch {
396
- return `proxy pid ${pid} was not running (pid file removed)`;
397
- }
398
- return `proxy pid ${pid} stopped`;
399
- }
400
-
401
- //#endregion
402
- export { statusPath as _, clearCooldown as a, probeQuota as c, resolveTarget as d, setCooldown as f, panesDir as g, ledgerPath as h, capabilityForRun as i, quotaSkipThreshold as l, eventsPath as m, ensureProxyRunning as n, isQuotaFailure as o, ensureNoMcpConfig as p, stopProxy as r, nextAutoProvider as s, DEFAULT_PROXY_PORT as t, resolveCapabilityRun as u, workersLogDir as v };
403
- //# sourceMappingURL=server-DgmAHyFK.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-DgmAHyFK.mjs","names":[],"sources":["../src/workers/paths.ts","../src/workers/routing.ts","../src/workers/proxy/server.ts"],"sourcesContent":["/**\n * paths.ts — where worker run artefacts live.\n *\n * Everything a run writes (event mirror, status file, pane registry, routing\n * state, the empty MCP config) sits under one logDir so the whole tree is\n * disposable and configurable: `workers.logDir`, default ~/.claude/logs/workers.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { expandHome, DEFAULT_LOG_DIR, type WorkersConfig } from \"./config.js\";\nimport { paiHomePath, resolvePaiFile } from \"../config/pai-home.js\";\n\n/**\n * Absolute logDir for the given config. An explicit `logDir` in workers.yaml\n * is honored verbatim (the user chose it). Otherwise this resolves like every\n * other PAI_HOME file: PAI_HOME/logs/workers if it exists, else the old\n * ~/.claude/logs/workers (with a one-time notice), else the new path.\n */\nexport function workersLogDir(config: WorkersConfig): string {\n const expanded = expandHome(config.logDir);\n if (config.logDir !== DEFAULT_LOG_DIR) return expanded;\n return resolvePaiFile(paiHomePath(\"logs\", \"workers\"), [expanded], \"pai config migrate --logs\");\n}\n\n/** The strict empty MCP config for headless runs, written on demand. */\nexport function ensureNoMcpConfig(logDir: string): string {\n const path = noMcpConfigPath(logDir);\n if (!existsSync(path)) {\n mkdirSync(logDir, { recursive: true });\n writeFileSync(path, '{ \"mcpServers\": {} }\\n', \"utf8\");\n }\n return path;\n}\n\nexport function statusPath(logDir: string, id: string): string {\n return join(logDir, `${id}.status`);\n}\n\nexport function eventsPath(logDir: string, id: string): string {\n return join(logDir, `${id}.jsonl`);\n}\n\nexport function ledgerPath(logDir: string): string {\n return join(logDir, \"ledger.log\");\n}\n\nexport function routingStatePath(logDir: string): string {\n return join(logDir, \"routing-state.json\");\n}\n\nexport function panesDir(logDir: string): string {\n return join(logDir, \"panes\");\n}\n\n/**\n * The strict empty MCP config handed to headless workers. Written into the\n * logDir on demand (never into the user's vendor config directory — this is\n * PAI state, not vendor state).\n */\nexport function noMcpConfigPath(logDir: string): string {\n return join(logDir, \"no-mcp.json\");\n}\n","/**\n * routing.ts — provider selection: flag > class > active (possibly \"auto\").\n *\n * Auto-routing walks `workers.routing.order` — or the class's own `order` —\n * and takes the first provider that is enabled, out of cooldown, (when it\n * defines a quotaProbe) under its quotaSkipAt threshold, and — when the class\n * constrains it — within `maxCostTier` and carrying all `requireTags`. A run\n * that dies of a quota/rate error puts its provider in cooldown for\n * cooldownMinutes; when that happens before the first tool call and\n * retryOnQuota is set, the runner restarts the same task on the next provider\n * (ledger: WORKER-REROUTE).\n *\n * An explicit --provider or a class mapping that pins a provider always\n * bypasses all of this.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport {\n type WorkerEngine,\n type WorkerProvider,\n type WorkersConfig,\n type ProviderTag,\n WORKER_CLASSES,\n ANTHROPIC_NATIVE,\n WorkersConfigError,\n classModelCapability,\n providerCostTier,\n getProviderOrNative,\n resolveCapability,\n} from \"./config.js\";\nimport { routingStatePath } from \"./paths.js\";\n\nconst QUOTA_SKIP_DEFAULT = 95;\nconst PROBE_TIMEOUT_MS = 10_000;\n\nexport interface RoutingState {\n /** provider name → ISO timestamp when its cooldown ends */\n cooldowns: Record<string, string>;\n}\n\nexport function readRoutingState(logDir: string): RoutingState {\n const path = routingStatePath(logDir);\n if (!existsSync(path)) return { cooldowns: {} };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as RoutingState;\n return { cooldowns: parsed.cooldowns ?? {} };\n } catch {\n return { cooldowns: {} };\n }\n}\n\nexport function writeRoutingState(logDir: string, state: RoutingState): void {\n const path = routingStatePath(logDir);\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(state, null, 2) + \"\\n\", \"utf8\");\n renameSync(tmp, path);\n}\n\nexport function cooldownRemaining(\n state: RoutingState,\n provider: string,\n now: Date = new Date()\n): number {\n const end = state.cooldowns[provider];\n if (!end) return 0;\n const ms = Date.parse(end) - now.getTime();\n return ms > 0 ? ms : 0;\n}\n\nexport function setCooldown(\n logDir: string,\n provider: string,\n minutes: number,\n now: Date = new Date()\n): void {\n const state = readRoutingState(logDir);\n state.cooldowns[provider] = new Date(now.getTime() + minutes * 60_000).toISOString();\n writeRoutingState(logDir, state);\n}\n\nexport function clearCooldown(logDir: string, provider: string): void {\n const state = readRoutingState(logDir);\n if (!(provider in state.cooldowns)) return;\n delete state.cooldowns[provider];\n writeRoutingState(logDir, state);\n}\n\n/**\n * Run a provider's quotaProbe and return its percentage (0–100), or null when\n * there is no probe or it printed nothing usable. A probe must never break\n * routing: failures read as \"unknown\", not as \"full\".\n */\nexport function probeQuota(provider: WorkerProvider): number | null {\n if (!provider.quotaProbe) return null;\n try {\n const out = execFileSync(\"/bin/sh\", [\"-c\", provider.quotaProbe], {\n timeout: PROBE_TIMEOUT_MS,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n const m = out.match(/(\\d+(?:\\.\\d+)?)/);\n if (!m) return null;\n return Math.min(100, Math.max(0, Math.round(parseFloat(m[1]))));\n } catch {\n return null;\n }\n}\n\nexport function quotaSkipThreshold(provider: WorkerProvider): number {\n return provider.quotaSkipAt ?? QUOTA_SKIP_DEFAULT;\n}\n\n/** Does this provider exceed its quota threshold right now? */\nexport function quotaExceeded(provider: WorkerProvider): boolean {\n const used = probeQuota(provider);\n return used !== null && used >= quotaSkipThreshold(provider);\n}\n\nexport interface ResolvedTarget {\n providerName: string;\n provider: WorkerProvider;\n /** Model alias from the class (\"glm/fast\" → \"fast\"), null = provider default. */\n modelAlias: string | null;\n /** MCP servers (or set names) from the class target, null when it sets none. */\n classMcp: string[] | null;\n /** How the provider was chosen — names the bypass rule for rerouting. */\n via: \"flag\" | \"class\" | \"active\" | \"auto\";\n}\n\nexport class NoProviderError extends WorkersConfigError {}\n\nfunction mustExist(config: WorkersConfig, name: string): WorkerProvider {\n const p = getProviderOrNative(config, name);\n if (!p) {\n throw new NoProviderError(\n `no worker provider named \"${name}\". Configured: ` +\n `${Object.keys(config.providers).join(\", \") || \"(none)\"}.` +\n `\\nAdd one with: pai worker providers add <name> --base-url <url> --key-file <path> --model <model>`\n );\n }\n return p;\n}\n\nfunction mustBeRunnable(name: string, p: WorkerProvider): WorkerProvider {\n if (!p.enabled) {\n throw new NoProviderError(\n `provider \"${name}\" is disabled. Enable it with: pai worker providers enable ${name}`\n );\n }\n return p;\n}\n\n/** Why one provider of a routing order did not qualify (message fragment). */\nfunction exclusionReason(\n config: WorkersConfig,\n state: RoutingState,\n name: string,\n cls?: { maxCostTier?: number; requireTags?: string[] }\n): string | null {\n const p = getProviderOrNative(config, name);\n if (!p) return \"not configured\";\n if (!p.enabled) return \"disabled\";\n if (cooldownRemaining(state, name) > 0) return \"cooldown\";\n if (quotaExceeded(p)) return \"quota\";\n const tier = providerCostTier(p);\n if (cls?.maxCostTier !== undefined && tier > cls.maxCostTier) {\n return `cost tier ${tier} > max ${cls.maxCostTier}`;\n }\n if (cls?.requireTags?.length) {\n const have = p.tags ?? [];\n const missing = cls.requireTags.filter((t) => !have.includes(t as ProviderTag));\n if (missing.length) return `missing tags: ${missing.join(\", \")}`;\n }\n return null;\n}\n\n/**\n * Resolve which provider (and model alias) a run uses.\n *\n * @param flagProvider --provider value, highest precedence\n * @param className --class value (the old --role), looked up in workers.classes\n */\nexport function resolveTarget(\n config: WorkersConfig,\n logDir: string,\n opts: { flagProvider?: string; className?: string } = {}\n): ResolvedTarget {\n if (opts.flagProvider) {\n return {\n providerName: opts.flagProvider,\n provider: mustBeRunnable(opts.flagProvider, mustExist(config, opts.flagProvider)),\n modelAlias: null,\n classMcp: null,\n via: \"flag\",\n };\n }\n\n // class constraints (present whether or not the target pins a provider)\n const clsTarget = opts.className ? config.classes[opts.className] : undefined;\n const cls =\n typeof clsTarget === \"object\" && clsTarget !== null ? clsTarget : undefined;\n\n if (opts.className && clsTarget !== undefined && typeof clsTarget !== \"object\") {\n const [name, alias] = clsTarget.split(\"/\");\n return {\n providerName: name,\n provider: mustBeRunnable(name, mustExist(config, name)),\n modelAlias: alias ?? null,\n classMcp: null,\n via: \"class\",\n };\n }\n if (opts.className && cls?.provider) {\n const provider = mustBeRunnable(cls.provider, mustExist(config, cls.provider));\n return {\n providerName: cls.provider,\n provider,\n modelAlias: null,\n classMcp: cls.mcp ?? null,\n via: \"class\",\n };\n }\n if (opts.className && clsTarget === undefined) {\n // a standard class may simply be unconfigured (it then routes like a run\n // without a class); anything else is a typo and must not pass silently\n if (!(WORKER_CLASSES as readonly string[]).includes(opts.className)) {\n throw new NoProviderError(\n `no class named \"${opts.className}\". Standard classes: ${WORKER_CLASSES.join(\", \")}.` +\n `Defined: ${Object.keys(config.classes).join(\", \") || \"(none)\"}.` +\n `\\nSet one with: pai worker classes set ${opts.className}=<provider[/alias]>`\n );\n }\n }\n\n if (config.active !== \"auto\") {\n // nothing active and no class entry: the built-in provider, no config written\n const name = config.active ?? ANTHROPIC_NATIVE;\n return {\n providerName: name,\n provider: mustBeRunnable(name, mustExist(config, name)),\n modelAlias: null,\n classMcp: null,\n via: \"active\",\n };\n }\n\n // auto: first provider in (class or global) order that is enabled,\n // cooled-down-free, under quota and within the class constraints\n const state = readRoutingState(logDir);\n const order = cls?.order ?? config.routing.order;\n const excluded: string[] = [];\n for (const name of order) {\n const p = getProviderOrNative(config, name);\n const why = exclusionReason(config, state, name, cls);\n if (why) {\n excluded.push(`${name}: ${why}`);\n continue;\n }\n // exclusionReason already returned null (no exclusion) above, which is\n // only possible when getProviderOrNative found a real provider\n return { providerName: name, provider: p!, modelAlias: null, classMcp: cls?.mcp ?? null, via: \"auto\" };\n }\n throw new NoProviderError(\n `auto-routing found no usable provider${opts.className ? ` for class \"${opts.className}\"` : \"\"} ` +\n `(order: [${order.join(\", \")}]).` +\n `${excluded.length ? `\\nExcluded: ${excluded.join(\"; \")}.` : \"\"}` +\n `\\nClear a cooldown with: pai worker providers enable <name>; widen the class with: pai worker classes set ${opts.className ?? \"<class>\"}=<target>`\n );\n}\n\n/**\n * Next provider after `from` in auto order, applying the same filters.\n * Used by rerouting; null when the order is exhausted.\n */\nexport function nextAutoProvider(\n config: WorkersConfig,\n logDir: string,\n from: string,\n now: Date = new Date()\n): string | null {\n const state = readRoutingState(logDir);\n const order = config.routing.order;\n const start = order.indexOf(from);\n for (let i = start + 1; i < order.length; i++) {\n const name = order[i];\n const p = getProviderOrNative(config, name);\n if (!p || !p.enabled) continue;\n const end = state.cooldowns[name];\n if (end && Date.parse(end) > now.getTime()) continue;\n if (quotaExceeded(p)) continue;\n return name;\n }\n return null;\n}\n\n/**\n * Was this failure a quota/rate failure? Detected from the result text the\n * endpoint produced (HTTP status is not visible in the stream events).\n */\nexport function isQuotaFailure(resultText: string): boolean {\n return /usage limit reached|rate limit|quota/i.test(resultText);\n}\n\nexport interface CapabilityRunResolution {\n providerName: string;\n provider: WorkerProvider;\n model: string;\n engine: WorkerEngine;\n fellBack: boolean;\n capability: string;\n}\n\n/**\n * Whether a run should resolve through `resolveCapability` instead of\n * `resolveTarget`: an explicit `--capability` always does. A `--class`\n * whose implied capability is not \"default\" (e.g. `image`) does too, but\n * only when the class itself names no provider — a class already pinned to\n * one (`image: glm/image`) always wins unchanged, same as any other class.\n */\nexport function capabilityForRun(\n config: WorkersConfig,\n opts: { capabilityFlag?: string; className?: string }\n): string | null {\n if (opts.capabilityFlag) return opts.capabilityFlag;\n if (!opts.className) return null;\n const capability = classModelCapability(opts.className);\n if (capability === \"default\") return null;\n const clsTarget = config.classes[opts.className];\n const namesProvider = typeof clsTarget === \"string\" ? true : Boolean(clsTarget?.provider);\n return namesProvider ? null : capability;\n}\n\n/**\n * resolveCapability, turned into a runnable provider (NoProviderError-shaped\n * on failure). Unlike resolveCapability itself — which falls back to the\n * active provider's default model so `describeCapabilities` has something to\n * report — an actual run refuses the fallback: silently running a normal\n * model on a prompt written for a capability it does not have (an image\n * prompt fed to a text model, say) produces a confusing non-answer instead\n * of a clear error naming the config key to set.\n */\nexport function resolveCapabilityRun(config: WorkersConfig, capability: string): CapabilityRunResolution {\n const r = resolveCapability(config, capability);\n if (r.fellBack) {\n throw new NoProviderError(\n `no provider declares the \"${capability}\" capability — configure one with: ` +\n `pai worker capability ${capability} <provider>[,<provider>…], or ` +\n `pai worker model ${capability} <model-id> --provider <name>`\n );\n }\n // \"image\" specifically promises real pixel output — a provider whose only\n // claim to it is a `models.image` entry from the classes-based idiom\n // (`image: glm/image`, a plain model choice on an unchanged claude/codex\n // engine) cannot deliver that through this cross-provider run path, so a\n // run refuses it rather than spawning claude on a prompt it cannot answer.\n if (capability === \"image\" && r.engine !== \"image\") {\n throw new NoProviderError(\n `provider \"${r.provider}\" has no image engine (it would run \"${r.model}\" on its normal ` +\n `${r.engine} engine, not generate an image) — configure an engine=image provider with: ` +\n `pai worker providers add <name> --engine image --url <url> --key <key> --model <id>, then ` +\n `pai worker capability image <name>`\n );\n }\n const provider = getProviderOrNative(config, r.provider);\n if (!provider) {\n throw new NoProviderError(`capability \"${capability}\" resolved to unknown provider \"${r.provider}\"`);\n }\n return {\n providerName: r.provider,\n provider,\n model: r.model,\n engine: r.engine,\n fellBack: r.fellBack,\n capability,\n };\n}\n","/**\n * server.ts — the PAI worker proxy: Anthropic Messages API on the front,\n * OpenAI Chat Completions on the back, loopback only.\n *\n * One proxy serves every OpenAI-protocol provider: Claude Code points\n * ANTHROPIC_BASE_URL at `http://127.0.0.1:8797/<provider>` and the provider\n * name in the path selects the upstream (`upstreamUrl` + `keyFile` from the\n * workers config, re-read per request so config edits apply without a\n * restart). `run` starts the proxy on demand (detached, pid file under the\n * logDir) and `pai worker proxy stop` stops it again.\n *\n * All translation lives in translate.ts; this file is only HTTP plumbing.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createServer, type Server } from \"node:http\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { connect } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { readWorkersSection, resolveProviderKey, type WorkerProvider } from \"../config.js\";\nimport { workersLogDir } from \"../paths.js\";\nimport {\n anthropicError,\n anthropicToOpenAi,\n openAiToAnthropic,\n OpenAiStreamTranslator,\n type AnthropicRequest,\n} from \"./translate.js\";\n\nexport const DEFAULT_PROXY_PORT = 8797;\nconst HOST = \"127.0.0.1\";\n\n/** How providers are looked up — swapped out by the tests. */\nexport type ProviderResolver = () => Record<string, WorkerProvider>;\n\nconst configProviders: ProviderResolver = () => readWorkersSection().workers.providers;\n\nexport function proxyPidPath(logDir: string): string {\n return join(logDir, \"proxy.pid\");\n}\n\n// ---------------------------------------------------------------------------\n// HTTP server\n// ---------------------------------------------------------------------------\n\ninterface ProxyServerOptions {\n port?: number;\n resolveProvider?: ProviderResolver;\n /** Test hook: called with every (translated) upstream request body. */\n onUpstreamRequest?: (url: string, body: unknown) => void;\n}\n\n/**\n * Create (but not start) the proxy server. Routes:\n * GET /healthz → 200 ok\n * POST /<provider>/v1/messages (and /<provider>/messages) → translated call\n */\nexport function createProxyServer(opts: ProxyServerOptions = {}): Server {\n const resolveProvider = opts.resolveProvider ?? configProviders;\n return createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", `http://${HOST}`);\n if (req.method === \"GET\" && url.pathname === \"/healthz\") {\n res.writeHead(200, { \"content-type\": \"text/plain\" });\n res.end(\"ok\");\n return;\n }\n const m = url.pathname.match(/^\\/([a-zA-Z0-9_-]+)\\/(v1\\/)?messages$/);\n if (req.method !== \"POST\" || !m) {\n res.writeHead(404, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(404, `no such route: ${req.method} ${url.pathname}`)));\n return;\n }\n const providerName = m[1];\n const chunks: Buffer[] = [];\n req.on(\"data\", (c: Buffer) => chunks.push(c));\n req.on(\"end\", () => {\n void handleMessages(providerName, Buffer.concat(chunks).toString(\"utf8\"), resolveProvider, res, opts);\n });\n req.on(\"error\", () => {\n res.writeHead(400, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(400, \"request body read failed\")));\n });\n });\n}\n\nasync function handleMessages(\n providerName: string,\n bodyText: string,\n resolveProvider: ProviderResolver,\n res: import(\"node:http\").ServerResponse,\n opts: ProxyServerOptions\n): Promise<void> {\n const fail = (status: number, message: string) => {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(status, JSON.stringify({ message }))));\n };\n let reqBody: AnthropicRequest;\n try {\n reqBody = JSON.parse(bodyText) as AnthropicRequest;\n } catch {\n fail(400, \"request body is not valid JSON\");\n return;\n }\n const provider = resolveProvider()[providerName];\n if (!provider) {\n fail(404, `no worker provider named \"${providerName}\"`);\n return;\n }\n if (provider.protocol !== \"openai\" || !provider.upstreamUrl) {\n fail(400, `provider \"${providerName}\" is not an openai-protocol provider`);\n return;\n }\n\n const model = provider.models.default;\n const openaiBody = anthropicToOpenAi(reqBody, model);\n opts.onUpstreamRequest?.(`${provider.upstreamUrl}/chat/completions`, openaiBody);\n\n let upstream: Response;\n try {\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n let token: string | null;\n try {\n token = resolveProviderKey(provider);\n } catch (e) {\n fail(500, e instanceof Error ? e.message : String(e));\n return;\n }\n if (token) headers.authorization = `Bearer ${token}`;\n upstream = await fetch(`${provider.upstreamUrl}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(openaiBody),\n });\n } catch (e) {\n fail(502, `upstream unreachable: ${e instanceof Error ? e.message : String(e)}`);\n return;\n }\n\n if (!upstream.ok) {\n const text = await upstream.text().catch(() => \"\");\n res.writeHead(upstream.status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(upstream.status, text)));\n return;\n }\n\n if (reqBody.stream) {\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n });\n const translator = new OpenAiStreamTranslator(model);\n let buf = \"\";\n if (!upstream.body) {\n res.end(translator.finish());\n return;\n }\n const reader = upstream.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buf += new TextDecoder().decode(value, { stream: true });\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line.startsWith(\"data:\")) continue;\n const payload = line.slice(5).trim();\n if (payload === \"[DONE]\") {\n res.write(translator.finish());\n } else {\n try {\n res.write(translator.feed(JSON.parse(payload) as Parameters<typeof translator.feed>[0]));\n } catch {\n // skip a malformed chunk rather than kill the stream\n }\n }\n }\n }\n } finally {\n res.write(translator.finish()); // no-op when [DONE] already finished it\n res.end();\n }\n return;\n }\n\n const json = (await upstream.json().catch(() => null)) as unknown;\n if (!json) {\n fail(502, \"upstream returned a non-JSON body\");\n return;\n }\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(openAiToAnthropic(json as Parameters<typeof openAiToAnthropic>[0], model)));\n}\n\n// ---------------------------------------------------------------------------\n// Start / stop\n// ---------------------------------------------------------------------------\n\n/** Listen on host:port (loopback). Resolves with the bound port. */\nexport function listenProxy(server: Server, port = DEFAULT_PROXY_PORT): Promise<number> {\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, HOST, () => {\n const addr = server.address();\n resolve(typeof addr === \"object\" && addr ? addr.port : port);\n });\n });\n}\n\n/** True when something already accepts connections on the port. */\nexport function proxyListening(port = DEFAULT_PROXY_PORT, timeoutMs = 400): Promise<boolean> {\n return new Promise((resolve) => {\n const sock = connect({ port, host: HOST });\n const done = (ok: boolean) => {\n sock.removeAllListeners();\n sock.destroy();\n resolve(ok);\n };\n sock.setTimeout(timeoutMs, () => done(false));\n sock.once(\"connect\", () => done(true));\n sock.once(\"error\", () => done(false));\n });\n}\n\n/**\n * Where the standalone proxy artefact lives, found from this module's own\n * location (dist/cli bundle → ../hooks/, dev checkout → <repo>/dist/hooks).\n */\nexport function standaloneProxyPath(): string | null {\n let dir = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 6; i++) {\n for (const cand of [\n join(dir, \"worker-proxy.mjs\"),\n join(dir, \"hooks\", \"worker-proxy.mjs\"),\n join(dir, \"dist\", \"hooks\", \"worker-proxy.mjs\"),\n ]) {\n if (existsSync(cand)) return cand;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\n/**\n * Ensure a proxy is listening on the port; start the detached standalone when\n * nothing answers. Returns the base URL (`http://127.0.0.1:<port>`).\n */\nexport async function ensureProxyRunning(port: number, logDir: string): Promise<string> {\n if (await proxyListening(port)) return `http://${HOST}:${port}`;\n const script = standaloneProxyPath();\n if (!script) {\n throw new Error(\n `the PAI worker proxy is not running and its build was not found — ` +\n `run \\`bun run build\\` (or start one with: pai worker proxy --port ${port})`\n );\n }\n if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });\n const child = spawn(process.execPath, [script, \"--port\", String(port)], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n writeFileSync(proxyPidPath(logDir), `${child.pid}\\n`, \"utf8\");\n for (let i = 0; i < 40; i++) {\n if (await proxyListening(port)) return `http://${HOST}:${port}`;\n await new Promise((r) => setTimeout(r, 250));\n }\n throw new Error(`the PAI worker proxy did not come up on port ${port} (see ${proxyPidPath(logDir)})`);\n}\n\n/** `pai worker proxy stop`: SIGTERM the pid from the pid file. */\nexport function stopProxy(logDir: string): string {\n const path = proxyPidPath(logDir);\n if (!existsSync(path)) return \"no proxy pid file — nothing to stop\";\n const pid = parseInt(readFileSync(path, \"utf8\").trim(), 10);\n unlinkSync(path);\n if (!Number.isFinite(pid)) return \"stale proxy pid file removed\";\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n return `proxy pid ${pid} was not running (pid file removed)`;\n }\n return `proxy pid ${pid} stopped`;\n}\n"],"mappings":"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"}