@poolzin/pool-bot 2026.3.22 → 2026.3.24

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 (159) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/.buildstamp +1 -1
  3. package/dist/acp/bindings-store.js +209 -0
  4. package/dist/acp/control-plane/runtime-cache.js +54 -0
  5. package/dist/acp/control-plane/runtime-options.js +215 -0
  6. package/dist/acp/control-plane/session-actor-queue.js +36 -0
  7. package/dist/acp/policy.js +52 -0
  8. package/dist/acp/runtime/errors.js +47 -0
  9. package/dist/acp/runtime/registry.js +86 -0
  10. package/dist/acp/runtime/types.js +1 -0
  11. package/dist/acp/translator.js +97 -0
  12. package/dist/agents/btw.js +280 -0
  13. package/dist/agents/failover-error.js +145 -47
  14. package/dist/agents/fast-mode.js +24 -0
  15. package/dist/agents/live-model-errors.js +23 -0
  16. package/dist/agents/model-auth-env-vars.js +44 -0
  17. package/dist/agents/model-auth-markers.js +69 -0
  18. package/dist/agents/models-config.providers.discovery.js +180 -0
  19. package/dist/agents/models-config.providers.static.js +480 -0
  20. package/dist/auto-reply/reply/typing-policy.js +15 -0
  21. package/dist/browser/browser-profile-manager.js +319 -0
  22. package/dist/browser/cdp-proxy-bypass.js +129 -0
  23. package/dist/browser/cdp-timeouts.js +41 -0
  24. package/dist/browser/chrome-extension-validator.js +406 -0
  25. package/dist/browser/chrome-mcp-snapshot.js +222 -0
  26. package/dist/browser/chrome-mcp.js +421 -0
  27. package/dist/browser/chrome-mcp.snapshot.js +133 -0
  28. package/dist/browser/errors.js +67 -0
  29. package/dist/browser/form-fields.js +22 -0
  30. package/dist/browser/output-atomic.js +44 -0
  31. package/dist/browser/profile-capabilities.js +47 -0
  32. package/dist/browser/safe-filename.js +25 -0
  33. package/dist/browser/snapshot-roles.js +60 -0
  34. package/dist/build-info.json +3 -3
  35. package/dist/channels/account-snapshot-fields.js +176 -0
  36. package/dist/channels/draft-stream-controls.js +89 -0
  37. package/dist/channels/inbound-debounce-policy.js +28 -0
  38. package/dist/channels/typing-lifecycle.js +39 -0
  39. package/dist/cli/program/command-registry.js +52 -0
  40. package/dist/commands/agent-binding.js +123 -0
  41. package/dist/commands/agents.commands.bind.js +280 -0
  42. package/dist/commands/backup-shared.js +186 -0
  43. package/dist/commands/backup-verify.js +236 -0
  44. package/dist/commands/backup.js +166 -0
  45. package/dist/commands/channel-account-context.js +15 -0
  46. package/dist/commands/channel-account.js +190 -0
  47. package/dist/commands/gateway-install-token.js +117 -0
  48. package/dist/commands/oauth-tls-preflight.js +121 -0
  49. package/dist/commands/ollama-setup.js +402 -0
  50. package/dist/commands/security-owner-only.js +86 -0
  51. package/dist/commands/self-hosted-provider-setup.js +207 -0
  52. package/dist/commands/session-store-targets.js +12 -0
  53. package/dist/commands/sessions-cleanup.js +97 -0
  54. package/dist/control-ui/assets/{index-Dvkl4Xlx.js → index-D7shnQwQ.js} +404 -388
  55. package/dist/control-ui/assets/index-D7shnQwQ.js.map +1 -0
  56. package/dist/control-ui/index.html +1 -1
  57. package/dist/cron/cron-filters.js +150 -0
  58. package/dist/cron/heartbeat-policy.js +26 -0
  59. package/dist/gateway/device-pairing-security.js +197 -0
  60. package/dist/gateway/event-deduplication.js +167 -0
  61. package/dist/gateway/hooks-mapping.js +46 -7
  62. package/dist/gateway/run-tracker.js +253 -0
  63. package/dist/gateway/server-methods/nodes.js +14 -0
  64. package/dist/gateway/websocket-preauth-security.js +188 -0
  65. package/dist/hooks/module-loader.js +28 -0
  66. package/dist/infra/agent-command-binding.js +144 -0
  67. package/dist/infra/backup.js +328 -0
  68. package/dist/infra/channel-account-context.js +173 -0
  69. package/dist/infra/errors.js +53 -13
  70. package/dist/infra/exec-approvals-security.js +217 -0
  71. package/dist/infra/security/command-analyzer.js +257 -0
  72. package/dist/infra/session-cleanup.js +143 -0
  73. package/dist/plugins/loader.js +16 -8
  74. package/dist/security/external-content.js +51 -1
  75. package/dist/sessions/session-costs.js +228 -0
  76. package/dist/shared/param-key.js +16 -0
  77. package/dist/shared/poll-params.js +58 -0
  78. package/dist/shared/polls.js +55 -0
  79. package/docs/DASHBOARD-GAP-ANALYSIS-AND-PLAN.md +430 -0
  80. package/docs/FEATURES.md +523 -0
  81. package/docs/FINAL-IMPLEMENTATION-REVIEW.md +274 -0
  82. package/docs/FINAL-IMPLEMENTATION-SUMMARY.md +356 -0
  83. package/docs/FINAL-PROFESSIONAL-EVALUATION.md +312 -0
  84. package/docs/IMPLEMENTATION-PRIORITY-EVALUATION.md +298 -0
  85. package/docs/IMPLEMENTATION-PROGRESS.md +237 -0
  86. package/docs/IMPLEMENTATION-REVIEW-PHASE1-2.md +381 -0
  87. package/docs/IMPLEMENTATION-REVIEW-PHASE4.md +389 -0
  88. package/docs/IMPLEMENTATION-REVIEW-PHASE5.md +420 -0
  89. package/docs/IMPLEMENTATION-REVIEW-PHASE6.md +422 -0
  90. package/docs/IMPLEMENTATION-REVIEW-PHASE7-FINAL.md +184 -0
  91. package/docs/MIKRODASH-ANALYSIS.md +412 -0
  92. package/docs/OPENCLAW-GAP-ANALYSIS-FINAL.md +431 -0
  93. package/docs/OPENCLAW-VS-POOLBOT-ANALYSIS.md +351 -0
  94. package/docs/PHASE-7-SUMMARY.md +144 -0
  95. package/docs/POOLBOT-OFFICE-PLAN.md +697 -0
  96. package/docs/PROJECT-FINAL-STATUS.md +237 -0
  97. package/docs/README.md +116 -0
  98. package/docs/REAL-IMPROVEMENTS-EVALUATION.md +477 -0
  99. package/docs/SECURITY-HARDENING-IMPLEMENTATION.md +161 -0
  100. package/docs/channels/googlechat.md +235 -206
  101. package/docs/channels/irc.md +332 -0
  102. package/docs/channels/nostr.md +255 -168
  103. package/docs/components/command-palette.md +166 -0
  104. package/docs/components/login-gate.md +219 -0
  105. package/docs/getting-started/installation.md +191 -0
  106. package/docs/getting-started/introduction.md +120 -0
  107. package/docs/improvements/USAGE-GUIDE.md +359 -0
  108. package/docs/plans/2026-03-15-openclaw-features-implementation.md +1632 -0
  109. package/docs/reference/deadcode-detection.md +72 -0
  110. package/extensions/acpx/node_modules/.bin/acpx +21 -0
  111. package/extensions/agency-agents/node_modules/.bin/vite +4 -4
  112. package/extensions/agency-agents/node_modules/.bin/vitest +2 -2
  113. package/extensions/googlechat/node_modules/.bin/tsc +21 -0
  114. package/extensions/googlechat/node_modules/.bin/tsserver +21 -0
  115. package/extensions/googlechat/node_modules/.bin/vitest +21 -0
  116. package/extensions/googlechat/package.json +11 -28
  117. package/extensions/googlechat/src/googlechat-channel.test.ts +60 -0
  118. package/extensions/googlechat/src/googlechat-channel.ts +120 -0
  119. package/extensions/googlechat/src/index.ts +14 -0
  120. package/extensions/irc/node_modules/.bin/tsc +21 -0
  121. package/extensions/irc/node_modules/.bin/tsserver +21 -0
  122. package/extensions/irc/node_modules/.bin/vitest +21 -0
  123. package/extensions/irc/package.json +16 -8
  124. package/extensions/irc/src/index.ts +14 -0
  125. package/extensions/irc/src/irc-channel.test.ts +43 -0
  126. package/extensions/irc/src/irc-channel.ts +191 -0
  127. package/extensions/keyed-async-queue/node_modules/.bin/tsc +21 -0
  128. package/extensions/keyed-async-queue/node_modules/.bin/tsserver +21 -0
  129. package/extensions/keyed-async-queue/node_modules/.bin/vitest +21 -0
  130. package/extensions/keyed-async-queue/package.json +20 -0
  131. package/extensions/keyed-async-queue/src/index.ts +14 -0
  132. package/extensions/keyed-async-queue/src/queue.test.ts +135 -0
  133. package/extensions/keyed-async-queue/src/queue.ts +200 -0
  134. package/extensions/memory-core/node_modules/.bin/tsc +21 -0
  135. package/extensions/memory-core/node_modules/.bin/tsserver +21 -0
  136. package/extensions/memory-core/node_modules/.bin/vitest +21 -0
  137. package/extensions/memory-core/package.json +11 -8
  138. package/extensions/memory-core/src/index.ts +14 -0
  139. package/extensions/memory-core/src/memory-manager.test.ts +124 -0
  140. package/extensions/memory-core/src/memory-manager.ts +186 -0
  141. package/extensions/nostr/node_modules/.bin/tsc +2 -2
  142. package/extensions/nostr/node_modules/.bin/tsserver +2 -2
  143. package/extensions/nostr/node_modules/.bin/vitest +21 -0
  144. package/extensions/nostr/package.json +15 -24
  145. package/extensions/nostr/src/index.ts +14 -0
  146. package/extensions/nostr/src/nostr-channel.test.ts +55 -0
  147. package/extensions/nostr/src/nostr-channel.ts +228 -0
  148. package/extensions/page-agent/node_modules/.bin/vitest +2 -2
  149. package/extensions/test-utils/node_modules/.bin/jiti +21 -0
  150. package/extensions/test-utils/node_modules/.bin/playwright +21 -0
  151. package/extensions/test-utils/node_modules/.bin/tsx +21 -0
  152. package/extensions/test-utils/node_modules/.bin/vite +21 -0
  153. package/extensions/test-utils/node_modules/.bin/vitest +21 -0
  154. package/extensions/test-utils/node_modules/.bin/yaml +21 -0
  155. package/extensions/xyops/node_modules/.bin/vitest +2 -2
  156. package/package.json +2 -1
  157. package/dist/control-ui/assets/index-Dvkl4Xlx.js.map +0 -1
  158. package/extensions/googlechat/node_modules/.bin/poolbot +0 -21
  159. package/extensions/memory-core/node_modules/.bin/poolbot +0 -21
@@ -1,5 +1,5 @@
1
- import { classifyFailoverReason } from "./pi-embedded-helpers.js";
2
- const TIMEOUT_HINT_RE = /timeout|timed out|deadline exceeded|context deadline exceeded/i;
1
+ import { readErrorName } from "../infra/errors.js";
2
+ import { classifyFailoverReason, isTimeoutErrorMessage, } from "./pi-embedded-helpers.js";
3
3
  const ABORT_TIMEOUT_RE = /request was aborted|request aborted/i;
4
4
  export class FailoverError extends Error {
5
5
  reason;
@@ -28,104 +28,200 @@ export function resolveFailoverStatus(reason) {
28
28
  return 402;
29
29
  case "rate_limit":
30
30
  return 429;
31
+ case "overloaded":
32
+ return 503;
31
33
  case "auth":
32
34
  return 401;
35
+ case "auth_permanent":
36
+ return 403;
33
37
  case "timeout":
34
38
  return 408;
35
39
  case "format":
36
40
  return 400;
37
41
  case "model_not_found":
38
42
  return 404;
43
+ case "session_expired":
44
+ return 410; // Gone - session no longer exists
39
45
  default:
40
46
  return undefined;
41
47
  }
42
48
  }
43
- function getStatusCode(err) {
44
- if (!err || typeof err !== "object")
49
+ function findErrorProperty(err, reader, seen = new Set()) {
50
+ const direct = reader(err);
51
+ if (direct !== undefined) {
52
+ return direct;
53
+ }
54
+ if (!err || typeof err !== "object") {
55
+ return undefined;
56
+ }
57
+ if (seen.has(err)) {
58
+ return undefined;
59
+ }
60
+ seen.add(err);
61
+ const candidate = err;
62
+ return (findErrorProperty(candidate.error, reader, seen) ??
63
+ findErrorProperty(candidate.cause, reader, seen));
64
+ }
65
+ function readDirectStatusCode(err) {
66
+ if (!err || typeof err !== "object") {
45
67
  return undefined;
68
+ }
46
69
  const candidate = err.status ??
47
70
  err.statusCode;
48
- if (typeof candidate === "number")
71
+ if (typeof candidate === "number") {
49
72
  return candidate;
73
+ }
50
74
  if (typeof candidate === "string" && /^\d+$/.test(candidate)) {
51
75
  return Number(candidate);
52
76
  }
53
77
  return undefined;
54
78
  }
55
- function getErrorName(err) {
56
- if (!err || typeof err !== "object")
57
- return "";
58
- return "name" in err ? String(err.name) : "";
79
+ function getStatusCode(err) {
80
+ return findErrorProperty(err, readDirectStatusCode);
59
81
  }
60
- function getErrorCode(err) {
61
- if (!err || typeof err !== "object")
82
+ function readDirectErrorCode(err) {
83
+ if (!err || typeof err !== "object") {
62
84
  return undefined;
63
- const candidate = err.code;
64
- if (typeof candidate !== "string")
85
+ }
86
+ const directCode = err.code;
87
+ if (typeof directCode === "string") {
88
+ const trimmed = directCode.trim();
89
+ return trimmed ? trimmed : undefined;
90
+ }
91
+ const status = err.status;
92
+ if (typeof status !== "string" || /^\d+$/.test(status)) {
65
93
  return undefined;
66
- const trimmed = candidate.trim();
94
+ }
95
+ const trimmed = status.trim();
67
96
  return trimmed ? trimmed : undefined;
68
97
  }
69
- function getErrorMessage(err) {
70
- if (err instanceof Error)
71
- return err.message;
72
- if (typeof err === "string")
73
- return err;
98
+ function getErrorCode(err) {
99
+ return findErrorProperty(err, readDirectErrorCode);
100
+ }
101
+ function readDirectErrorMessage(err) {
102
+ if (err instanceof Error) {
103
+ return err.message || undefined;
104
+ }
105
+ if (typeof err === "string") {
106
+ return err || undefined;
107
+ }
74
108
  if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") {
75
109
  return String(err);
76
110
  }
77
- if (typeof err === "symbol")
78
- return err.description ?? "";
111
+ if (typeof err === "symbol") {
112
+ return err.description ?? undefined;
113
+ }
79
114
  if (err && typeof err === "object") {
80
115
  const message = err.message;
81
- if (typeof message === "string")
82
- return message;
116
+ if (typeof message === "string") {
117
+ return message || undefined;
118
+ }
119
+ }
120
+ return undefined;
121
+ }
122
+ function getErrorMessage(err) {
123
+ return findErrorProperty(err, readDirectErrorMessage) ?? "";
124
+ }
125
+ function getErrorCause(err) {
126
+ if (!err || typeof err !== "object" || !("cause" in err)) {
127
+ return undefined;
128
+ }
129
+ return err.cause;
130
+ }
131
+ /** Classify rate-limit / overloaded from symbolic error codes like RESOURCE_EXHAUSTED. */
132
+ function classifyFailoverReasonFromSymbolicCode(raw) {
133
+ const normalized = raw?.trim().toUpperCase();
134
+ if (!normalized) {
135
+ return null;
136
+ }
137
+ switch (normalized) {
138
+ case "RESOURCE_EXHAUSTED":
139
+ case "RATE_LIMIT":
140
+ case "RATE_LIMITED":
141
+ case "RATE_LIMIT_EXCEEDED":
142
+ case "TOO_MANY_REQUESTS":
143
+ case "THROTTLED":
144
+ case "THROTTLING":
145
+ case "THROTTLINGEXCEPTION":
146
+ case "THROTTLING_EXCEPTION":
147
+ return "rate_limit";
148
+ case "OVERLOADED":
149
+ case "OVERLOADED_ERROR":
150
+ return "overloaded";
151
+ default:
152
+ return null;
83
153
  }
84
- return "";
85
154
  }
86
155
  function hasTimeoutHint(err) {
87
- if (!err)
156
+ if (!err) {
88
157
  return false;
89
- if (getErrorName(err) === "TimeoutError")
158
+ }
159
+ if (readErrorName(err) === "TimeoutError") {
90
160
  return true;
161
+ }
91
162
  const message = getErrorMessage(err);
92
- return Boolean(message && TIMEOUT_HINT_RE.test(message));
163
+ return Boolean(message && isTimeoutErrorMessage(message));
93
164
  }
94
165
  export function isTimeoutError(err) {
95
- if (hasTimeoutHint(err))
166
+ if (hasTimeoutHint(err)) {
96
167
  return true;
97
- if (!err || typeof err !== "object")
168
+ }
169
+ if (!err || typeof err !== "object") {
98
170
  return false;
99
- if (getErrorName(err) !== "AbortError")
171
+ }
172
+ if (readErrorName(err) !== "AbortError") {
100
173
  return false;
174
+ }
101
175
  const message = getErrorMessage(err);
102
- if (message && ABORT_TIMEOUT_RE.test(message))
176
+ if (message && ABORT_TIMEOUT_RE.test(message)) {
103
177
  return true;
178
+ }
104
179
  const cause = "cause" in err ? err.cause : undefined;
105
180
  const reason = "reason" in err ? err.reason : undefined;
106
181
  return hasTimeoutHint(cause) || hasTimeoutHint(reason);
107
182
  }
108
183
  export function resolveFailoverReasonFromError(err) {
109
- if (isFailoverError(err))
184
+ if (isFailoverError(err)) {
110
185
  return err.reason;
111
- const status = getStatusCode(err);
112
- if (status === 402)
113
- return "billing";
114
- if (status === 429)
115
- return "rate_limit";
116
- if (status === 401 || status === 403)
117
- return "auth";
118
- if (status === 408)
119
- return "timeout";
186
+ }
187
+ const message = getErrorMessage(err);
188
+ // Check symbolic error codes (e.g. RESOURCE_EXHAUSTED from Google APIs)
189
+ const symbolicCodeReason = classifyFailoverReasonFromSymbolicCode(getErrorCode(err));
190
+ if (symbolicCodeReason) {
191
+ return symbolicCodeReason;
192
+ }
120
193
  const code = (getErrorCode(err) ?? "").toUpperCase();
121
- if (["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "ECONNABORTED"].includes(code)) {
194
+ if ([
195
+ "ETIMEDOUT",
196
+ "ESOCKETTIMEDOUT",
197
+ "ECONNRESET",
198
+ "ECONNABORTED",
199
+ "ECONNREFUSED",
200
+ "ENETUNREACH",
201
+ "EHOSTUNREACH",
202
+ "EHOSTDOWN",
203
+ "ENETRESET",
204
+ "EPIPE",
205
+ "EAI_AGAIN",
206
+ ].includes(code)) {
122
207
  return "timeout";
123
208
  }
124
- if (isTimeoutError(err))
209
+ // Walk into error cause chain *before* timeout heuristics so that a specific
210
+ // cause (e.g. RESOURCE_EXHAUSTED wrapped in AbortError) overrides a parent
211
+ // message-based "timeout" guess from isTimeoutError.
212
+ const cause = getErrorCause(err);
213
+ if (cause && cause !== err) {
214
+ const causeReason = resolveFailoverReasonFromError(cause);
215
+ if (causeReason) {
216
+ return causeReason;
217
+ }
218
+ }
219
+ if (isTimeoutError(err)) {
125
220
  return "timeout";
126
- const message = getErrorMessage(err);
127
- if (!message)
221
+ }
222
+ if (!message) {
128
223
  return null;
224
+ }
129
225
  return classifyFailoverReason(message);
130
226
  }
131
227
  export function describeFailoverError(err) {
@@ -146,11 +242,13 @@ export function describeFailoverError(err) {
146
242
  };
147
243
  }
148
244
  export function coerceToFailoverError(err, context) {
149
- if (isFailoverError(err))
245
+ if (isFailoverError(err)) {
150
246
  return err;
247
+ }
151
248
  const reason = resolveFailoverReasonFromError(err);
152
- if (!reason)
249
+ if (!reason) {
153
250
  return null;
251
+ }
154
252
  const message = getErrorMessage(err) || String(err);
155
253
  const status = getStatusCode(err) ?? resolveFailoverStatus(reason);
156
254
  const code = getErrorCode(err);
@@ -0,0 +1,24 @@
1
+ import { normalizeFastMode } from "../auto-reply/thinking.js";
2
+ export function resolveFastModeParam(extraParams) {
3
+ return normalizeFastMode((extraParams?.fastMode ?? extraParams?.fast_mode));
4
+ }
5
+ function resolveConfiguredFastModeRaw(params) {
6
+ const modelKey = `${params.provider}/${params.model}`;
7
+ const modelConfig = params.cfg?.agents?.defaults?.models?.[modelKey];
8
+ return modelConfig?.params?.fastMode ?? modelConfig?.params?.fast_mode;
9
+ }
10
+ export function resolveConfiguredFastMode(params) {
11
+ return (normalizeFastMode(resolveConfiguredFastModeRaw(params)) ?? false);
12
+ }
13
+ export function resolveFastModeState(params) {
14
+ const sessionOverride = normalizeFastMode(params.sessionEntry?.fastMode);
15
+ if (sessionOverride !== undefined) {
16
+ return { enabled: sessionOverride, source: "session" };
17
+ }
18
+ const configuredRaw = resolveConfiguredFastModeRaw(params);
19
+ const configured = normalizeFastMode(configuredRaw);
20
+ if (configured !== undefined) {
21
+ return { enabled: configured, source: "config" };
22
+ }
23
+ return { enabled: false, source: "default" };
24
+ }
@@ -0,0 +1,23 @@
1
+ export function isModelNotFoundErrorMessage(raw) {
2
+ const msg = raw.trim();
3
+ if (!msg) {
4
+ return false;
5
+ }
6
+ if (/\b404\b/.test(msg) && /not(?:[_\-\s])?found/i.test(msg)) {
7
+ return true;
8
+ }
9
+ if (/not_found_error/i.test(msg)) {
10
+ return true;
11
+ }
12
+ if (/model:\s*[a-z0-9._-]+/i.test(msg) && /not(?:[_\-\s])?found/i.test(msg)) {
13
+ return true;
14
+ }
15
+ return false;
16
+ }
17
+ export function isMiniMaxModelNotFoundErrorMessage(raw) {
18
+ const msg = raw.trim();
19
+ if (!msg) {
20
+ return false;
21
+ }
22
+ return /\b404\b.*\bpage not found\b/i.test(msg);
23
+ }
@@ -0,0 +1,44 @@
1
+ export const PROVIDER_ENV_API_KEY_CANDIDATES = {
2
+ "github-copilot": ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"],
3
+ anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
4
+ chutes: ["CHUTES_OAUTH_TOKEN", "CHUTES_API_KEY"],
5
+ zai: ["ZAI_API_KEY", "Z_AI_API_KEY"],
6
+ opencode: ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"],
7
+ "opencode-go": ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"],
8
+ "qwen-portal": ["QWEN_OAUTH_TOKEN", "QWEN_PORTAL_API_KEY"],
9
+ volcengine: ["VOLCANO_ENGINE_API_KEY"],
10
+ "volcengine-plan": ["VOLCANO_ENGINE_API_KEY"],
11
+ byteplus: ["BYTEPLUS_API_KEY"],
12
+ "byteplus-plan": ["BYTEPLUS_API_KEY"],
13
+ "minimax-portal": ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"],
14
+ "kimi-coding": ["KIMI_API_KEY", "KIMICODE_API_KEY"],
15
+ huggingface: ["HUGGINGFACE_HUB_TOKEN", "HF_TOKEN"],
16
+ openai: ["OPENAI_API_KEY"],
17
+ google: ["GEMINI_API_KEY"],
18
+ voyage: ["VOYAGE_API_KEY"],
19
+ groq: ["GROQ_API_KEY"],
20
+ deepgram: ["DEEPGRAM_API_KEY"],
21
+ cerebras: ["CEREBRAS_API_KEY"],
22
+ xai: ["XAI_API_KEY"],
23
+ openrouter: ["OPENROUTER_API_KEY"],
24
+ litellm: ["LITELLM_API_KEY"],
25
+ "vercel-ai-gateway": ["AI_GATEWAY_API_KEY"],
26
+ "cloudflare-ai-gateway": ["CLOUDFLARE_AI_GATEWAY_API_KEY"],
27
+ moonshot: ["MOONSHOT_API_KEY"],
28
+ minimax: ["MINIMAX_API_KEY"],
29
+ nvidia: ["NVIDIA_API_KEY"],
30
+ xiaomi: ["XIAOMI_API_KEY"],
31
+ synthetic: ["SYNTHETIC_API_KEY"],
32
+ venice: ["VENICE_API_KEY"],
33
+ mistral: ["MISTRAL_API_KEY"],
34
+ together: ["TOGETHER_API_KEY"],
35
+ qianfan: ["QIANFAN_API_KEY"],
36
+ modelstudio: ["MODELSTUDIO_API_KEY"],
37
+ ollama: ["OLLAMA_API_KEY"],
38
+ sglang: ["SGLANG_API_KEY"],
39
+ vllm: ["VLLM_API_KEY"],
40
+ kilocode: ["KILOCODE_API_KEY"],
41
+ };
42
+ export function listKnownProviderEnvApiKeyNames() {
43
+ return [...new Set(Object.values(PROVIDER_ENV_API_KEY_CANDIDATES).flat())];
44
+ }
@@ -0,0 +1,69 @@
1
+ import { listKnownProviderEnvApiKeyNames } from "./model-auth-env-vars.js";
2
+ export const MINIMAX_OAUTH_MARKER = "minimax-oauth";
3
+ export const QWEN_OAUTH_MARKER = "qwen-oauth";
4
+ export const OLLAMA_LOCAL_AUTH_MARKER = "ollama-local";
5
+ export const CUSTOM_LOCAL_AUTH_MARKER = "custom-local";
6
+ export const NON_ENV_SECRETREF_MARKER = "secretref-managed"; // pragma: allowlist secret
7
+ export const SECRETREF_ENV_HEADER_MARKER_PREFIX = "secretref-env:"; // pragma: allowlist secret
8
+ const AWS_SDK_ENV_MARKERS = new Set([
9
+ "AWS_BEARER_TOKEN_BEDROCK",
10
+ "AWS_ACCESS_KEY_ID",
11
+ "AWS_PROFILE",
12
+ ]);
13
+ // Legacy marker names kept for backward compatibility with existing models.json files.
14
+ const LEGACY_ENV_API_KEY_MARKERS = [
15
+ "GOOGLE_API_KEY",
16
+ "DEEPSEEK_API_KEY",
17
+ "PERPLEXITY_API_KEY",
18
+ "FIREWORKS_API_KEY",
19
+ "NOVITA_API_KEY",
20
+ "AZURE_OPENAI_API_KEY",
21
+ "AZURE_API_KEY",
22
+ "MINIMAX_CODE_PLAN_KEY",
23
+ ];
24
+ const KNOWN_ENV_API_KEY_MARKERS = new Set([
25
+ ...listKnownProviderEnvApiKeyNames(),
26
+ ...LEGACY_ENV_API_KEY_MARKERS,
27
+ ...AWS_SDK_ENV_MARKERS,
28
+ ]);
29
+ export function isAwsSdkAuthMarker(value) {
30
+ return AWS_SDK_ENV_MARKERS.has(value.trim());
31
+ }
32
+ export function isKnownEnvApiKeyMarker(value) {
33
+ const trimmed = value.trim();
34
+ return KNOWN_ENV_API_KEY_MARKERS.has(trimmed) && !isAwsSdkAuthMarker(trimmed);
35
+ }
36
+ export function resolveNonEnvSecretRefApiKeyMarker(_source) {
37
+ return NON_ENV_SECRETREF_MARKER;
38
+ }
39
+ export function resolveNonEnvSecretRefHeaderValueMarker(_source) {
40
+ return NON_ENV_SECRETREF_MARKER;
41
+ }
42
+ export function resolveEnvSecretRefHeaderValueMarker(envVarName) {
43
+ return `${SECRETREF_ENV_HEADER_MARKER_PREFIX}${envVarName.trim()}`;
44
+ }
45
+ export function isSecretRefHeaderValueMarker(value) {
46
+ const trimmed = value.trim();
47
+ return (trimmed === NON_ENV_SECRETREF_MARKER || trimmed.startsWith(SECRETREF_ENV_HEADER_MARKER_PREFIX));
48
+ }
49
+ export function isNonSecretApiKeyMarker(value, opts) {
50
+ const trimmed = value.trim();
51
+ if (!trimmed) {
52
+ return false;
53
+ }
54
+ const isKnownMarker = trimmed === MINIMAX_OAUTH_MARKER ||
55
+ trimmed === QWEN_OAUTH_MARKER ||
56
+ trimmed === OLLAMA_LOCAL_AUTH_MARKER ||
57
+ trimmed === CUSTOM_LOCAL_AUTH_MARKER ||
58
+ trimmed === NON_ENV_SECRETREF_MARKER ||
59
+ isAwsSdkAuthMarker(trimmed);
60
+ if (isKnownMarker) {
61
+ return true;
62
+ }
63
+ if (opts?.includeEnvVarName === false) {
64
+ return false;
65
+ }
66
+ // Do not treat arbitrary ALL_CAPS values as markers; only recognize the
67
+ // known env-var markers we intentionally persist for compatibility.
68
+ return KNOWN_ENV_API_KEY_MARKERS.has(trimmed);
69
+ }
@@ -0,0 +1,180 @@
1
+ import { createSubsystemLogger } from "../logging/subsystem.js";
2
+ import { KILOCODE_BASE_URL } from "../providers/kilocode-shared.js";
3
+ import { discoverHuggingfaceModels, HUGGINGFACE_BASE_URL, HUGGINGFACE_MODEL_CATALOG, buildHuggingfaceModelDefinition, } from "./huggingface-models.js";
4
+ import { discoverKilocodeModels } from "./kilocode-models.js";
5
+ import { enrichOllamaModelsWithContext, OLLAMA_DEFAULT_CONTEXT_WINDOW, OLLAMA_DEFAULT_COST, OLLAMA_DEFAULT_MAX_TOKENS, isReasoningModelHeuristic, resolveOllamaApiBase, } from "./ollama-models.js";
6
+ import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js";
7
+ import { discoverVercelAiGatewayModels, VERCEL_AI_GATEWAY_BASE_URL } from "./vercel-ai-gateway.js";
8
+ export { resolveOllamaApiBase } from "./ollama-models.js";
9
+ const log = createSubsystemLogger("agents/model-providers");
10
+ const OLLAMA_SHOW_CONCURRENCY = 8;
11
+ const OLLAMA_SHOW_MAX_MODELS = 200;
12
+ const OPENAI_COMPAT_LOCAL_DEFAULT_CONTEXT_WINDOW = 128000;
13
+ const OPENAI_COMPAT_LOCAL_DEFAULT_MAX_TOKENS = 8192;
14
+ const OPENAI_COMPAT_LOCAL_DEFAULT_COST = {
15
+ input: 0,
16
+ output: 0,
17
+ cacheRead: 0,
18
+ cacheWrite: 0,
19
+ };
20
+ const SGLANG_BASE_URL = "http://127.0.0.1:30000/v1";
21
+ const VLLM_BASE_URL = "http://127.0.0.1:8000/v1";
22
+ async function discoverOllamaModels(baseUrl, opts) {
23
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
24
+ return [];
25
+ }
26
+ try {
27
+ const apiBase = resolveOllamaApiBase(baseUrl);
28
+ const response = await fetch(`${apiBase}/api/tags`, {
29
+ signal: AbortSignal.timeout(5000),
30
+ });
31
+ if (!response.ok) {
32
+ if (!opts?.quiet) {
33
+ log.warn(`Failed to discover Ollama models: ${response.status}`);
34
+ }
35
+ return [];
36
+ }
37
+ const data = (await response.json());
38
+ if (!data.models || data.models.length === 0) {
39
+ log.debug("No Ollama models found on local instance");
40
+ return [];
41
+ }
42
+ const modelsToInspect = data.models.slice(0, OLLAMA_SHOW_MAX_MODELS);
43
+ if (modelsToInspect.length < data.models.length && !opts?.quiet) {
44
+ log.warn(`Capping Ollama /api/show inspection to ${OLLAMA_SHOW_MAX_MODELS} models (received ${data.models.length})`);
45
+ }
46
+ const discovered = await enrichOllamaModelsWithContext(apiBase, modelsToInspect, {
47
+ concurrency: OLLAMA_SHOW_CONCURRENCY,
48
+ });
49
+ return discovered.map((model) => ({
50
+ id: model.name,
51
+ name: model.name,
52
+ reasoning: isReasoningModelHeuristic(model.name),
53
+ input: ["text"],
54
+ cost: OLLAMA_DEFAULT_COST,
55
+ contextWindow: model.contextWindow ?? OLLAMA_DEFAULT_CONTEXT_WINDOW,
56
+ maxTokens: OLLAMA_DEFAULT_MAX_TOKENS,
57
+ }));
58
+ }
59
+ catch (error) {
60
+ if (!opts?.quiet) {
61
+ log.warn(`Failed to discover Ollama models: ${String(error)}`);
62
+ }
63
+ return [];
64
+ }
65
+ }
66
+ async function discoverOpenAICompatibleLocalModels(params) {
67
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
68
+ return [];
69
+ }
70
+ const trimmedBaseUrl = params.baseUrl.trim().replace(/\/+$/, "");
71
+ const url = `${trimmedBaseUrl}/models`;
72
+ try {
73
+ const trimmedApiKey = params.apiKey?.trim();
74
+ const response = await fetch(url, {
75
+ headers: trimmedApiKey ? { Authorization: `Bearer ${trimmedApiKey}` } : undefined,
76
+ signal: AbortSignal.timeout(5000),
77
+ });
78
+ if (!response.ok) {
79
+ log.warn(`Failed to discover ${params.label} models: ${response.status}`);
80
+ return [];
81
+ }
82
+ const data = (await response.json());
83
+ const models = data.data ?? [];
84
+ if (models.length === 0) {
85
+ log.warn(`No ${params.label} models found on local instance`);
86
+ return [];
87
+ }
88
+ return models
89
+ .map((model) => ({ id: typeof model.id === "string" ? model.id.trim() : "" }))
90
+ .filter((model) => Boolean(model.id))
91
+ .map((model) => {
92
+ const modelId = model.id;
93
+ return {
94
+ id: modelId,
95
+ name: modelId,
96
+ reasoning: isReasoningModelHeuristic(modelId),
97
+ input: ["text"],
98
+ cost: OPENAI_COMPAT_LOCAL_DEFAULT_COST,
99
+ contextWindow: params.contextWindow ?? OPENAI_COMPAT_LOCAL_DEFAULT_CONTEXT_WINDOW,
100
+ maxTokens: params.maxTokens ?? OPENAI_COMPAT_LOCAL_DEFAULT_MAX_TOKENS,
101
+ };
102
+ });
103
+ }
104
+ catch (error) {
105
+ log.warn(`Failed to discover ${params.label} models: ${String(error)}`);
106
+ return [];
107
+ }
108
+ }
109
+ export async function buildVeniceProvider() {
110
+ const models = await discoverVeniceModels();
111
+ return {
112
+ baseUrl: VENICE_BASE_URL,
113
+ api: "openai-completions",
114
+ models,
115
+ };
116
+ }
117
+ export async function buildOllamaProvider(configuredBaseUrl, opts) {
118
+ const models = await discoverOllamaModels(configuredBaseUrl, opts);
119
+ return {
120
+ baseUrl: resolveOllamaApiBase(configuredBaseUrl),
121
+ api: "ollama",
122
+ models,
123
+ };
124
+ }
125
+ export async function buildHuggingfaceProvider(discoveryApiKey) {
126
+ const resolvedSecret = discoveryApiKey?.trim() ?? "";
127
+ const models = resolvedSecret !== ""
128
+ ? await discoverHuggingfaceModels(resolvedSecret)
129
+ : HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
130
+ return {
131
+ baseUrl: HUGGINGFACE_BASE_URL,
132
+ api: "openai-completions",
133
+ models,
134
+ };
135
+ }
136
+ export async function buildVercelAiGatewayProvider() {
137
+ return {
138
+ baseUrl: VERCEL_AI_GATEWAY_BASE_URL,
139
+ api: "anthropic-messages",
140
+ models: await discoverVercelAiGatewayModels(),
141
+ };
142
+ }
143
+ export async function buildVllmProvider(params) {
144
+ const baseUrl = (params?.baseUrl?.trim() || VLLM_BASE_URL).replace(/\/+$/, "");
145
+ const models = await discoverOpenAICompatibleLocalModels({
146
+ baseUrl,
147
+ apiKey: params?.apiKey,
148
+ label: "vLLM",
149
+ });
150
+ return {
151
+ baseUrl,
152
+ api: "openai-completions",
153
+ models,
154
+ };
155
+ }
156
+ export async function buildSglangProvider(params) {
157
+ const baseUrl = (params?.baseUrl?.trim() || SGLANG_BASE_URL).replace(/\/+$/, "");
158
+ const models = await discoverOpenAICompatibleLocalModels({
159
+ baseUrl,
160
+ apiKey: params?.apiKey,
161
+ label: "SGLang",
162
+ });
163
+ return {
164
+ baseUrl,
165
+ api: "openai-completions",
166
+ models,
167
+ };
168
+ }
169
+ /**
170
+ * Build the Kilocode provider with dynamic model discovery from the gateway
171
+ * API. Falls back to the static catalog on failure.
172
+ */
173
+ export async function buildKilocodeProviderWithDiscovery() {
174
+ const models = await discoverKilocodeModels();
175
+ return {
176
+ baseUrl: KILOCODE_BASE_URL,
177
+ api: "openai-completions",
178
+ models,
179
+ };
180
+ }