@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.
- package/CHANGELOG.md +111 -0
- package/dist/.buildstamp +1 -1
- package/dist/acp/bindings-store.js +209 -0
- package/dist/acp/control-plane/runtime-cache.js +54 -0
- package/dist/acp/control-plane/runtime-options.js +215 -0
- package/dist/acp/control-plane/session-actor-queue.js +36 -0
- package/dist/acp/policy.js +52 -0
- package/dist/acp/runtime/errors.js +47 -0
- package/dist/acp/runtime/registry.js +86 -0
- package/dist/acp/runtime/types.js +1 -0
- package/dist/acp/translator.js +97 -0
- package/dist/agents/btw.js +280 -0
- package/dist/agents/failover-error.js +145 -47
- package/dist/agents/fast-mode.js +24 -0
- package/dist/agents/live-model-errors.js +23 -0
- package/dist/agents/model-auth-env-vars.js +44 -0
- package/dist/agents/model-auth-markers.js +69 -0
- package/dist/agents/models-config.providers.discovery.js +180 -0
- package/dist/agents/models-config.providers.static.js +480 -0
- package/dist/auto-reply/reply/typing-policy.js +15 -0
- package/dist/browser/browser-profile-manager.js +319 -0
- package/dist/browser/cdp-proxy-bypass.js +129 -0
- package/dist/browser/cdp-timeouts.js +41 -0
- package/dist/browser/chrome-extension-validator.js +406 -0
- package/dist/browser/chrome-mcp-snapshot.js +222 -0
- package/dist/browser/chrome-mcp.js +421 -0
- package/dist/browser/chrome-mcp.snapshot.js +133 -0
- package/dist/browser/errors.js +67 -0
- package/dist/browser/form-fields.js +22 -0
- package/dist/browser/output-atomic.js +44 -0
- package/dist/browser/profile-capabilities.js +47 -0
- package/dist/browser/safe-filename.js +25 -0
- package/dist/browser/snapshot-roles.js +60 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/account-snapshot-fields.js +176 -0
- package/dist/channels/draft-stream-controls.js +89 -0
- package/dist/channels/inbound-debounce-policy.js +28 -0
- package/dist/channels/typing-lifecycle.js +39 -0
- package/dist/cli/program/command-registry.js +52 -0
- package/dist/commands/agent-binding.js +123 -0
- package/dist/commands/agents.commands.bind.js +280 -0
- package/dist/commands/backup-shared.js +186 -0
- package/dist/commands/backup-verify.js +236 -0
- package/dist/commands/backup.js +166 -0
- package/dist/commands/channel-account-context.js +15 -0
- package/dist/commands/channel-account.js +190 -0
- package/dist/commands/gateway-install-token.js +117 -0
- package/dist/commands/oauth-tls-preflight.js +121 -0
- package/dist/commands/ollama-setup.js +402 -0
- package/dist/commands/security-owner-only.js +86 -0
- package/dist/commands/self-hosted-provider-setup.js +207 -0
- package/dist/commands/session-store-targets.js +12 -0
- package/dist/commands/sessions-cleanup.js +97 -0
- package/dist/control-ui/assets/{index-Dvkl4Xlx.js → index-D7shnQwQ.js} +404 -388
- package/dist/control-ui/assets/index-D7shnQwQ.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/cron/cron-filters.js +150 -0
- package/dist/cron/heartbeat-policy.js +26 -0
- package/dist/gateway/device-pairing-security.js +197 -0
- package/dist/gateway/event-deduplication.js +167 -0
- package/dist/gateway/hooks-mapping.js +46 -7
- package/dist/gateway/run-tracker.js +253 -0
- package/dist/gateway/server-methods/nodes.js +14 -0
- package/dist/gateway/websocket-preauth-security.js +188 -0
- package/dist/hooks/module-loader.js +28 -0
- package/dist/infra/agent-command-binding.js +144 -0
- package/dist/infra/backup.js +328 -0
- package/dist/infra/channel-account-context.js +173 -0
- package/dist/infra/errors.js +53 -13
- package/dist/infra/exec-approvals-security.js +217 -0
- package/dist/infra/security/command-analyzer.js +257 -0
- package/dist/infra/session-cleanup.js +143 -0
- package/dist/plugins/loader.js +16 -8
- package/dist/security/external-content.js +51 -1
- package/dist/sessions/session-costs.js +228 -0
- package/dist/shared/param-key.js +16 -0
- package/dist/shared/poll-params.js +58 -0
- package/dist/shared/polls.js +55 -0
- package/docs/DASHBOARD-GAP-ANALYSIS-AND-PLAN.md +430 -0
- package/docs/FEATURES.md +523 -0
- package/docs/FINAL-IMPLEMENTATION-REVIEW.md +274 -0
- package/docs/FINAL-IMPLEMENTATION-SUMMARY.md +356 -0
- package/docs/FINAL-PROFESSIONAL-EVALUATION.md +312 -0
- package/docs/IMPLEMENTATION-PRIORITY-EVALUATION.md +298 -0
- package/docs/IMPLEMENTATION-PROGRESS.md +237 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE1-2.md +381 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE4.md +389 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE5.md +420 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE6.md +422 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE7-FINAL.md +184 -0
- package/docs/MIKRODASH-ANALYSIS.md +412 -0
- package/docs/OPENCLAW-GAP-ANALYSIS-FINAL.md +431 -0
- package/docs/OPENCLAW-VS-POOLBOT-ANALYSIS.md +351 -0
- package/docs/PHASE-7-SUMMARY.md +144 -0
- package/docs/POOLBOT-OFFICE-PLAN.md +697 -0
- package/docs/PROJECT-FINAL-STATUS.md +237 -0
- package/docs/README.md +116 -0
- package/docs/REAL-IMPROVEMENTS-EVALUATION.md +477 -0
- package/docs/SECURITY-HARDENING-IMPLEMENTATION.md +161 -0
- package/docs/channels/googlechat.md +235 -206
- package/docs/channels/irc.md +332 -0
- package/docs/channels/nostr.md +255 -168
- package/docs/components/command-palette.md +166 -0
- package/docs/components/login-gate.md +219 -0
- package/docs/getting-started/installation.md +191 -0
- package/docs/getting-started/introduction.md +120 -0
- package/docs/improvements/USAGE-GUIDE.md +359 -0
- package/docs/plans/2026-03-15-openclaw-features-implementation.md +1632 -0
- package/docs/reference/deadcode-detection.md +72 -0
- package/extensions/acpx/node_modules/.bin/acpx +21 -0
- package/extensions/agency-agents/node_modules/.bin/vite +4 -4
- package/extensions/agency-agents/node_modules/.bin/vitest +2 -2
- package/extensions/googlechat/node_modules/.bin/tsc +21 -0
- package/extensions/googlechat/node_modules/.bin/tsserver +21 -0
- package/extensions/googlechat/node_modules/.bin/vitest +21 -0
- package/extensions/googlechat/package.json +11 -28
- package/extensions/googlechat/src/googlechat-channel.test.ts +60 -0
- package/extensions/googlechat/src/googlechat-channel.ts +120 -0
- package/extensions/googlechat/src/index.ts +14 -0
- package/extensions/irc/node_modules/.bin/tsc +21 -0
- package/extensions/irc/node_modules/.bin/tsserver +21 -0
- package/extensions/irc/node_modules/.bin/vitest +21 -0
- package/extensions/irc/package.json +16 -8
- package/extensions/irc/src/index.ts +14 -0
- package/extensions/irc/src/irc-channel.test.ts +43 -0
- package/extensions/irc/src/irc-channel.ts +191 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsc +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsserver +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/vitest +21 -0
- package/extensions/keyed-async-queue/package.json +20 -0
- package/extensions/keyed-async-queue/src/index.ts +14 -0
- package/extensions/keyed-async-queue/src/queue.test.ts +135 -0
- package/extensions/keyed-async-queue/src/queue.ts +200 -0
- package/extensions/memory-core/node_modules/.bin/tsc +21 -0
- package/extensions/memory-core/node_modules/.bin/tsserver +21 -0
- package/extensions/memory-core/node_modules/.bin/vitest +21 -0
- package/extensions/memory-core/package.json +11 -8
- package/extensions/memory-core/src/index.ts +14 -0
- package/extensions/memory-core/src/memory-manager.test.ts +124 -0
- package/extensions/memory-core/src/memory-manager.ts +186 -0
- package/extensions/nostr/node_modules/.bin/tsc +2 -2
- package/extensions/nostr/node_modules/.bin/tsserver +2 -2
- package/extensions/nostr/node_modules/.bin/vitest +21 -0
- package/extensions/nostr/package.json +15 -24
- package/extensions/nostr/src/index.ts +14 -0
- package/extensions/nostr/src/nostr-channel.test.ts +55 -0
- package/extensions/nostr/src/nostr-channel.ts +228 -0
- package/extensions/page-agent/node_modules/.bin/vitest +2 -2
- package/extensions/test-utils/node_modules/.bin/jiti +21 -0
- package/extensions/test-utils/node_modules/.bin/playwright +21 -0
- package/extensions/test-utils/node_modules/.bin/tsx +21 -0
- package/extensions/test-utils/node_modules/.bin/vite +21 -0
- package/extensions/test-utils/node_modules/.bin/vitest +21 -0
- package/extensions/test-utils/node_modules/.bin/yaml +21 -0
- package/extensions/xyops/node_modules/.bin/vitest +2 -2
- package/package.json +2 -1
- package/dist/control-ui/assets/index-Dvkl4Xlx.js.map +0 -1
- package/extensions/googlechat/node_modules/.bin/poolbot +0 -21
- package/extensions/memory-core/node_modules/.bin/poolbot +0 -21
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
|
44
|
-
|
|
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
|
|
56
|
-
|
|
57
|
-
return "";
|
|
58
|
-
return "name" in err ? String(err.name) : "";
|
|
79
|
+
function getStatusCode(err) {
|
|
80
|
+
return findErrorProperty(err, readDirectStatusCode);
|
|
59
81
|
}
|
|
60
|
-
function
|
|
61
|
-
if (!err || typeof err !== "object")
|
|
82
|
+
function readDirectErrorCode(err) {
|
|
83
|
+
if (!err || typeof err !== "object") {
|
|
62
84
|
return undefined;
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
94
|
+
}
|
|
95
|
+
const trimmed = status.trim();
|
|
67
96
|
return trimmed ? trimmed : undefined;
|
|
68
97
|
}
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
158
|
+
}
|
|
159
|
+
if (readErrorName(err) === "TimeoutError") {
|
|
90
160
|
return true;
|
|
161
|
+
}
|
|
91
162
|
const message = getErrorMessage(err);
|
|
92
|
-
return Boolean(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
|
-
|
|
168
|
+
}
|
|
169
|
+
if (!err || typeof err !== "object") {
|
|
98
170
|
return false;
|
|
99
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 ([
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|