@juspay/neurolink 12.44.5 โ 12.44.7
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 +2 -2
- package/dist/browser/neurolink.min.js +272 -272
- package/dist/cli/commands/proxy.js +30 -13
- package/dist/cli/commands/proxyPolyfill.d.ts +4 -0
- package/dist/cli/commands/proxyPolyfill.js +55 -0
- package/dist/cli/parser.js +3 -1
- package/dist/cli/proxy-clients/registry.d.ts +2 -0
- package/dist/cli/proxy-clients/registry.js +4 -0
- package/dist/proxy/accountCooldown.js +5 -2
- package/dist/proxy/accountQuota.js +2 -0
- package/dist/proxy/codexFallback.js +3 -4
- package/dist/proxy/globalInstaller.d.ts +3 -1
- package/dist/proxy/globalInstaller.js +151 -4
- package/dist/proxy/proxyPackagePolyfills.d.ts +18 -0
- package/dist/proxy/proxyPackagePolyfills.js +334 -0
- package/dist/server/routes/claudeProxyRoutes.js +7 -2
- package/dist/server/routes/codexProxyRoutes.js +24 -8
- package/dist/types/proxy.d.ts +26 -0
- package/docs-site/static/search-index.json +4 -3
- package/package.json +5 -3
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, writeFileSync, renameSync, mkdirSync, } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { applyPatch, structuredPatch } from "diff";
|
|
5
|
+
const BASELINE_NAME = ".neurolink-proxy-baseline.json";
|
|
6
|
+
const MAX_FILES = 4096;
|
|
7
|
+
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
8
|
+
const MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
9
|
+
const MAX_CHANGED_FILES = 64;
|
|
10
|
+
/** Locate the package without importing its executable or provider graph. */
|
|
11
|
+
export function proxyPackageRoot(entryScript) {
|
|
12
|
+
let directory = dirname(realpathSync(entryScript));
|
|
13
|
+
for (;;) {
|
|
14
|
+
const manifest = join(directory, "package.json");
|
|
15
|
+
if (existsSync(manifest)) {
|
|
16
|
+
let value;
|
|
17
|
+
try {
|
|
18
|
+
value = JSON.parse(readFileSync(manifest, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new Error("Proxy package manifest is unreadable or invalid");
|
|
22
|
+
}
|
|
23
|
+
if (value &&
|
|
24
|
+
typeof value === "object" &&
|
|
25
|
+
"name" in value &&
|
|
26
|
+
value.name === "@juspay/neurolink") {
|
|
27
|
+
return directory;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const parent = dirname(directory);
|
|
31
|
+
if (parent === directory) {
|
|
32
|
+
throw new Error("Proxy package manifest not found");
|
|
33
|
+
}
|
|
34
|
+
directory = parent;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function sorted(value) {
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return value.map(sorted);
|
|
40
|
+
}
|
|
41
|
+
if (value && typeof value === "object") {
|
|
42
|
+
return Object.fromEntries(Object.entries(value)
|
|
43
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
44
|
+
.map(([key, item]) => [key, sorted(item)]));
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function safePath(path) {
|
|
49
|
+
return (!isAbsolute(path) &&
|
|
50
|
+
!path.includes("\\") &&
|
|
51
|
+
path
|
|
52
|
+
.split("/")
|
|
53
|
+
.every((part) => part !== "" && part !== "." && part !== ".."));
|
|
54
|
+
}
|
|
55
|
+
function runtimePath(path, entryRelative) {
|
|
56
|
+
return (safePath(path) &&
|
|
57
|
+
/\.(?:[cm]?js|json)$/.test(path) &&
|
|
58
|
+
(path === entryRelative ||
|
|
59
|
+
(path.startsWith("dist/") && !path.startsWith("dist/browser/")) ||
|
|
60
|
+
path.startsWith("scripts/observability/")));
|
|
61
|
+
}
|
|
62
|
+
/** Bounded baseline of shipped proxy code; operator config and dependencies are excluded. */
|
|
63
|
+
export function snapshotProxyPackage(selection) {
|
|
64
|
+
const root = proxyPackageRoot(selection.entryScript);
|
|
65
|
+
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
66
|
+
// Release numbers and npm bookkeeping are not runtime compatibility edits.
|
|
67
|
+
const comparable = Object.fromEntries(Object.entries(manifest).filter(([key]) => key !== "version" && key !== "gitHead" && !key.startsWith("_")));
|
|
68
|
+
const entryRelative = relative(root, realpathSync(selection.entryScript))
|
|
69
|
+
.split("\\")
|
|
70
|
+
.join("/");
|
|
71
|
+
if (!safePath(entryRelative)) {
|
|
72
|
+
throw new Error("Proxy executable escapes package");
|
|
73
|
+
}
|
|
74
|
+
const files = {};
|
|
75
|
+
let bytes = 0;
|
|
76
|
+
const read = (path) => {
|
|
77
|
+
if (!runtimePath(path, entryRelative) || Object.hasOwn(files, path)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const file = join(root, path);
|
|
81
|
+
const stat = lstatSync(file);
|
|
82
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
83
|
+
throw new Error(`Proxy runtime file is not a regular file: ${path}`);
|
|
84
|
+
}
|
|
85
|
+
if (stat.size > MAX_FILE_BYTES ||
|
|
86
|
+
bytes + stat.size > MAX_TOTAL_BYTES ||
|
|
87
|
+
Object.keys(files).length >= MAX_FILES) {
|
|
88
|
+
throw new Error("Proxy runtime baseline exceeds its size limit");
|
|
89
|
+
}
|
|
90
|
+
const data = readFileSync(file);
|
|
91
|
+
files[path] = new TextDecoder("utf-8", {
|
|
92
|
+
fatal: true,
|
|
93
|
+
ignoreBOM: true,
|
|
94
|
+
}).decode(data);
|
|
95
|
+
bytes += data.length;
|
|
96
|
+
};
|
|
97
|
+
const walk = (path) => {
|
|
98
|
+
if (!existsSync(join(root, path))) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (lstatSync(join(root, path)).isSymbolicLink()) {
|
|
102
|
+
throw new Error(`Proxy runtime directory is a symbolic link: ${path}`);
|
|
103
|
+
}
|
|
104
|
+
for (const item of readdirSync(join(root, path), {
|
|
105
|
+
withFileTypes: true,
|
|
106
|
+
}).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
107
|
+
const next = `${path}/${item.name}`;
|
|
108
|
+
if (next === "dist/browser" || item.name === "node_modules") {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (item.isSymbolicLink()) {
|
|
112
|
+
throw new Error(`Proxy runtime path is a symbolic link: ${next}`);
|
|
113
|
+
}
|
|
114
|
+
if (item.isDirectory()) {
|
|
115
|
+
walk(next);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
read(next);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
walk("dist");
|
|
123
|
+
walk("scripts/observability");
|
|
124
|
+
read(entryRelative);
|
|
125
|
+
return {
|
|
126
|
+
schemaVersion: 1,
|
|
127
|
+
version: selection.version,
|
|
128
|
+
manifest: JSON.stringify(sorted(comparable)),
|
|
129
|
+
entryRelative,
|
|
130
|
+
files,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Invalid or missing baseline must never be silently replaced with edited files. */
|
|
134
|
+
export function readProxyPackageBaseline(selection) {
|
|
135
|
+
const path = join(proxyPackageRoot(selection.entryScript), BASELINE_NAME);
|
|
136
|
+
if (!existsSync(path)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const stat = lstatSync(path);
|
|
140
|
+
if (!stat.isFile() ||
|
|
141
|
+
stat.isSymbolicLink() ||
|
|
142
|
+
stat.size > MAX_TOTAL_BYTES * 2) {
|
|
143
|
+
throw new Error("Proxy package baseline is invalid");
|
|
144
|
+
}
|
|
145
|
+
let value;
|
|
146
|
+
try {
|
|
147
|
+
value = JSON.parse(readFileSync(path, "utf8"));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
throw new Error("Proxy package baseline is unreadable or invalid");
|
|
151
|
+
}
|
|
152
|
+
if (value.schemaVersion !== 1 ||
|
|
153
|
+
typeof value.version !== "string" ||
|
|
154
|
+
typeof value.manifest !== "string" ||
|
|
155
|
+
typeof value.entryRelative !== "string" ||
|
|
156
|
+
!safePath(value.entryRelative) ||
|
|
157
|
+
!value.files ||
|
|
158
|
+
typeof value.files !== "object" ||
|
|
159
|
+
Array.isArray(value.files)) {
|
|
160
|
+
throw new Error("Proxy package baseline is invalid");
|
|
161
|
+
}
|
|
162
|
+
let bytes = 0;
|
|
163
|
+
const entries = Object.entries(value.files);
|
|
164
|
+
if (entries.length > MAX_FILES) {
|
|
165
|
+
throw new Error("Proxy package baseline exceeds its size limit");
|
|
166
|
+
}
|
|
167
|
+
for (const [name, content] of entries) {
|
|
168
|
+
if (!runtimePath(name, value.entryRelative) ||
|
|
169
|
+
typeof content !== "string" ||
|
|
170
|
+
Buffer.byteLength(content) > MAX_FILE_BYTES) {
|
|
171
|
+
throw new Error("Proxy package baseline contains an invalid file");
|
|
172
|
+
}
|
|
173
|
+
bytes += Buffer.byteLength(content);
|
|
174
|
+
}
|
|
175
|
+
if (bytes > MAX_TOTAL_BYTES) {
|
|
176
|
+
throw new Error("Proxy package baseline exceeds its size limit");
|
|
177
|
+
}
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
export function writeProxyPackageBaseline(selection, baseline) {
|
|
181
|
+
const path = join(proxyPackageRoot(selection.entryScript), BASELINE_NAME);
|
|
182
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
183
|
+
writeFileSync(temporary, JSON.stringify(baseline), {
|
|
184
|
+
mode: 0o600,
|
|
185
|
+
flag: "wx",
|
|
186
|
+
});
|
|
187
|
+
renameSync(temporary, path);
|
|
188
|
+
}
|
|
189
|
+
/** Operator supplies the pristine base for a legacy local build; this does not activate it. */
|
|
190
|
+
export function captureProxyPackageBaseline(base, patched) {
|
|
191
|
+
const original = snapshotProxyPackage(base);
|
|
192
|
+
const current = snapshotProxyPackage(patched);
|
|
193
|
+
if (original.manifest !== current.manifest ||
|
|
194
|
+
original.entryRelative !== current.entryRelative) {
|
|
195
|
+
throw new Error("Local package metadata differs from its base; runtime polyfills cannot change dependencies or the executable");
|
|
196
|
+
}
|
|
197
|
+
const existing = readProxyPackageBaseline(patched);
|
|
198
|
+
if (existing &&
|
|
199
|
+
(existing.manifest !== original.manifest ||
|
|
200
|
+
JSON.stringify(sorted(existing.files)) !==
|
|
201
|
+
JSON.stringify(sorted(original.files)))) {
|
|
202
|
+
throw new Error("A different proxy package baseline is already recorded");
|
|
203
|
+
}
|
|
204
|
+
const changed = changedProxyRuntimeFiles(original, current);
|
|
205
|
+
writeProxyPackageBaseline(patched, original);
|
|
206
|
+
return changed;
|
|
207
|
+
}
|
|
208
|
+
export function changedProxyRuntimeFiles(base, current) {
|
|
209
|
+
if (base.manifest !== current.manifest ||
|
|
210
|
+
base.entryRelative !== current.entryRelative) {
|
|
211
|
+
throw new Error("Local package metadata changed; refusing upgrade rather than discarding it");
|
|
212
|
+
}
|
|
213
|
+
return [
|
|
214
|
+
...new Set([...Object.keys(base.files), ...Object.keys(current.files)]),
|
|
215
|
+
]
|
|
216
|
+
.sort()
|
|
217
|
+
.filter((path) => base.files[path] !== current.files[path]);
|
|
218
|
+
}
|
|
219
|
+
function occurrences(content, lines) {
|
|
220
|
+
if (!lines.length) {
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
const haystack = content.split("\n");
|
|
224
|
+
let count = 0;
|
|
225
|
+
for (let start = 0; start + lines.length <= haystack.length; start++) {
|
|
226
|
+
if (lines.every((line, offset) => line === haystack[start + offset])) {
|
|
227
|
+
count++;
|
|
228
|
+
}
|
|
229
|
+
if (count > 1) {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return count;
|
|
234
|
+
}
|
|
235
|
+
/** Apply exact local hunks, leaving independent upstream changes intact. */
|
|
236
|
+
export function reconcileProxyPackagePolyfills(base, local, candidate) {
|
|
237
|
+
const changed = changedProxyRuntimeFiles(base, local);
|
|
238
|
+
if (changed.length > MAX_CHANGED_FILES) {
|
|
239
|
+
throw new Error("Proxy polyfill exceeds the 64-file compatibility diff limit");
|
|
240
|
+
}
|
|
241
|
+
const files = {};
|
|
242
|
+
const report = {
|
|
243
|
+
applied: [],
|
|
244
|
+
alreadyIncluded: [],
|
|
245
|
+
};
|
|
246
|
+
for (const path of changed) {
|
|
247
|
+
if (!runtimePath(path, candidate.entryRelative)) {
|
|
248
|
+
throw new Error(`Proxy polyfill path is outside the candidate runtime: ${path}`);
|
|
249
|
+
}
|
|
250
|
+
const before = base.files[path];
|
|
251
|
+
const after = local.files[path];
|
|
252
|
+
const upstream = candidate.files[path];
|
|
253
|
+
if (upstream === after) {
|
|
254
|
+
report.alreadyIncluded.push(path);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (upstream === before) {
|
|
258
|
+
files[path] = after ?? null;
|
|
259
|
+
report.applied.push(path);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (before === undefined ||
|
|
263
|
+
after === undefined ||
|
|
264
|
+
upstream === undefined ||
|
|
265
|
+
before === "" ||
|
|
266
|
+
after === "") {
|
|
267
|
+
throw new Error(`Proxy polyfill conflicts with the release: ${path}`);
|
|
268
|
+
}
|
|
269
|
+
const patch = structuredPatch(path, path, before, after, "", "", {
|
|
270
|
+
context: 3,
|
|
271
|
+
timeout: 1000,
|
|
272
|
+
maxEditLength: 20_000,
|
|
273
|
+
});
|
|
274
|
+
if (!patch || patch.hunks.length > 128) {
|
|
275
|
+
throw new Error(`Proxy polyfill exceeds its diff budget: ${path}`);
|
|
276
|
+
}
|
|
277
|
+
let content = upstream;
|
|
278
|
+
let applied = false;
|
|
279
|
+
for (const hunk of patch.hunks) {
|
|
280
|
+
const oldLines = hunk.lines
|
|
281
|
+
.filter((line) => line[0] === " " || line[0] === "-")
|
|
282
|
+
.map((line) => line.slice(1));
|
|
283
|
+
const newLines = hunk.lines
|
|
284
|
+
.filter((line) => line[0] === " " || line[0] === "+")
|
|
285
|
+
.map((line) => line.slice(1));
|
|
286
|
+
const oldMatches = occurrences(content, oldLines);
|
|
287
|
+
const newMatches = occurrences(content, newLines);
|
|
288
|
+
if (newMatches === 1 && oldMatches === 0) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (oldMatches !== 1 || newMatches !== 0) {
|
|
292
|
+
throw new Error(`Proxy polyfill conflicts with the release: ${path}`);
|
|
293
|
+
}
|
|
294
|
+
const result = applyPatch(content, { ...patch, hunks: [hunk] }, { fuzzFactor: 0 });
|
|
295
|
+
if (result === false) {
|
|
296
|
+
throw new Error(`Proxy polyfill conflicts with the release: ${path}`);
|
|
297
|
+
}
|
|
298
|
+
content = result;
|
|
299
|
+
applied = true;
|
|
300
|
+
}
|
|
301
|
+
if (applied) {
|
|
302
|
+
files[path] = content;
|
|
303
|
+
report.applied.push(path);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
report.alreadyIncluded.push(path);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return { files, report };
|
|
310
|
+
}
|
|
311
|
+
/** Writes are confined to a new staged copy, never the serving package. */
|
|
312
|
+
export function writeProxyPolyfilledFile(root, path, content) {
|
|
313
|
+
root = realpathSync(root);
|
|
314
|
+
if (!safePath(path)) {
|
|
315
|
+
throw new Error("Invalid proxy polyfill path");
|
|
316
|
+
}
|
|
317
|
+
const target = resolve(root, path);
|
|
318
|
+
if (!target.startsWith(`${realpathSync(root)}/`)) {
|
|
319
|
+
throw new Error("Proxy polyfill escapes its package");
|
|
320
|
+
}
|
|
321
|
+
// A candidate cannot redirect a patch through a symlinked parent.
|
|
322
|
+
let parent = dirname(target);
|
|
323
|
+
while (parent !== root) {
|
|
324
|
+
if (existsSync(parent) && lstatSync(parent).isSymbolicLink()) {
|
|
325
|
+
throw new Error("Proxy polyfill parent is a symbolic link");
|
|
326
|
+
}
|
|
327
|
+
parent = dirname(parent);
|
|
328
|
+
}
|
|
329
|
+
if (existsSync(target) && lstatSync(target).isSymbolicLink()) {
|
|
330
|
+
throw new Error("Proxy polyfill target is a symbolic link");
|
|
331
|
+
}
|
|
332
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
333
|
+
writeFileSync(target, content);
|
|
334
|
+
}
|
|
@@ -773,8 +773,13 @@ function hasScopedOnlyExhaustion(quota, now, policy = overagePolicy) {
|
|
|
773
773
|
isOverageUsable(quota, policy)) {
|
|
774
774
|
return false;
|
|
775
775
|
}
|
|
776
|
-
|
|
777
|
-
|
|
776
|
+
// Same notion of "scoped" as the request path: header windows (which carry
|
|
777
|
+
// both a display scope and the wire id) and usage-API windows (often only
|
|
778
|
+
// one of them) must classify alike, or a 429 is scoped when routed and
|
|
779
|
+
// account-wide when reconciled.
|
|
780
|
+
return (quota.windows ?? []).some((window) => (window.kind === "weekly_scoped" || window.kind === "session_scoped") &&
|
|
781
|
+
(typeof window.scopeModel === "string" ||
|
|
782
|
+
typeof window.scopeModelId === "string") &&
|
|
778
783
|
now - scopedWindowObservedAt(quota, window) <=
|
|
779
784
|
QUOTA_SNAPSHOT_FRESHNESS_MS &&
|
|
780
785
|
isScopedWindowExhausted(window, now));
|
|
@@ -151,21 +151,25 @@ function buildCodexErrorResponse(status, message, code) {
|
|
|
151
151
|
}), { status, headers: { "content-type": "application/json" } });
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
154
|
-
* Build a terminal Codex Responses SSE stream for a
|
|
155
|
-
*
|
|
154
|
+
* Build a terminal Codex Responses SSE stream for a failure that never reached
|
|
155
|
+
* an upstream request (every pooled account is cooling).
|
|
156
156
|
* A bare non-2xx status with no body reads to the Codex CLI as a dropped
|
|
157
157
|
* connection, so it shows "Reconnecting... waiting for network" forever
|
|
158
158
|
* instead of a real error. Emitting a well-formed `response.failed` event โ
|
|
159
159
|
* the same terminal shape the CLI already parses out of a live stream โ
|
|
160
160
|
* lets it render the actual failure instead.
|
|
161
|
+
*
|
|
162
|
+
* The code is what the client keys on: `insufficient_quota` tells it the plan
|
|
163
|
+
* is spent, so it is reserved for a pool that is out of quota. Accounts parked
|
|
164
|
+
* by a transient auth failure report `server_error` instead.
|
|
161
165
|
*/
|
|
162
|
-
function
|
|
166
|
+
function buildCodexPoolCoolingResponse(message, retryAfterSec, code) {
|
|
163
167
|
const payload = {
|
|
164
168
|
type: "response.failed",
|
|
165
169
|
response: {
|
|
166
170
|
error: {
|
|
167
|
-
type:
|
|
168
|
-
code
|
|
171
|
+
type: code,
|
|
172
|
+
code,
|
|
169
173
|
message,
|
|
170
174
|
},
|
|
171
175
|
},
|
|
@@ -1333,14 +1337,26 @@ loopbackPort, internalDispatch) {
|
|
|
1333
1337
|
});
|
|
1334
1338
|
return buildCodexErrorResponse(413, outbound.message, "request_too_large");
|
|
1335
1339
|
}
|
|
1340
|
+
const resetAt = soonest ? new Date(soonest).toISOString() : undefined;
|
|
1341
|
+
// A refresh that failed transiently, or a 401 with no refresh token to
|
|
1342
|
+
// retry with, parks the account as "auth". Reporting that as spent quota
|
|
1343
|
+
// would tell the user the plan is exhausted when it is not.
|
|
1344
|
+
if (ordered.every((account) => account.coolingReason === "auth")) {
|
|
1345
|
+
await recordFinalOutcome(undefined, 503, {
|
|
1346
|
+
errorType: "all_accounts_auth_cooling",
|
|
1347
|
+
errorMessage: "All Codex accounts are cooling after an auth failure",
|
|
1348
|
+
});
|
|
1349
|
+
return buildCodexPoolCoolingResponse(resetAt
|
|
1350
|
+
? `Codex authentication is temporarily unavailable for every account. Retry after ${resetAt}, or run \`neurolink auth login codex\` if it persists.`
|
|
1351
|
+
: "Codex authentication is temporarily unavailable for every account. Retry shortly, or run `neurolink auth login codex` if it persists.", retryAfterSec, "server_error");
|
|
1352
|
+
}
|
|
1336
1353
|
await recordFinalOutcome(undefined, 429, {
|
|
1337
1354
|
errorType: "all_accounts_cooling",
|
|
1338
1355
|
errorMessage: "All Codex accounts are rate-limited",
|
|
1339
1356
|
});
|
|
1340
|
-
|
|
1341
|
-
return buildCodexQuotaExhaustedResponse(resetAt
|
|
1357
|
+
return buildCodexPoolCoolingResponse(resetAt
|
|
1342
1358
|
? `Codex quota exhausted: all accounts are rate-limited. Resets at ${resetAt}.`
|
|
1343
|
-
: "Codex quota exhausted: all accounts are rate-limited.", retryAfterSec);
|
|
1359
|
+
: "Codex quota exhausted: all accounts are rate-limited.", retryAfterSec, "insufficient_quota");
|
|
1344
1360
|
}
|
|
1345
1361
|
let attempt = 0;
|
|
1346
1362
|
let lastErrorMessage = "All Codex accounts failed";
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -4437,6 +4437,32 @@ export type ProxyStagedInstallOptions = {
|
|
|
4437
4437
|
outputBytes: number;
|
|
4438
4438
|
}) => void;
|
|
4439
4439
|
};
|
|
4440
|
+
/** Original proxy runtime files retained before any local compatibility edits. */
|
|
4441
|
+
export type ProxyPackageBaseline = {
|
|
4442
|
+
schemaVersion: 1;
|
|
4443
|
+
version: string;
|
|
4444
|
+
manifest: string;
|
|
4445
|
+
entryRelative: string;
|
|
4446
|
+
files: Record<string, string>;
|
|
4447
|
+
};
|
|
4448
|
+
/** A candidate carries only local diff hunks that its release still needs. */
|
|
4449
|
+
export type ProxyPackagePolyfillReport = {
|
|
4450
|
+
applied: string[];
|
|
4451
|
+
alreadyIncluded: string[];
|
|
4452
|
+
};
|
|
4453
|
+
/** Staged upgrade that reconciles recorded local edits before publication. */
|
|
4454
|
+
export type ProxyPackageUpgradeOptions = ProxyStagedInstallOptions & {
|
|
4455
|
+
activePackage: ProxyPackageSelection;
|
|
4456
|
+
isCurrentOwner?: () => boolean;
|
|
4457
|
+
onPolyfills?: (report: ProxyPackagePolyfillReport) => void;
|
|
4458
|
+
};
|
|
4459
|
+
/** Capture a trusted baseline for a locally patched package without activation. */
|
|
4460
|
+
export type ProxyPolyfillArgs = {
|
|
4461
|
+
action: "capture";
|
|
4462
|
+
base: string;
|
|
4463
|
+
patched: string;
|
|
4464
|
+
format: "text" | "json";
|
|
4465
|
+
};
|
|
4440
4466
|
/** Saved launchd settings retained when installing the service again. */
|
|
4441
4467
|
export type ProxyServiceInstallSettings = {
|
|
4442
4468
|
envFile?: string;
|
|
@@ -2289,9 +2289,10 @@
|
|
|
2289
2289
|
{"objectID":"9d37e7d66f60fa6af16eaee381757f1d39b03c29d805748d72980fc766a80afc","title":"Acceptance evidence","url":"/docs/development/proxy-incident-verification#acceptance-evidence","content":"Local verification on macOS, Node 24.14.1, September 7, 2026. All network fixtures\nuse ephemeral loopback listeners and isolated storage. No installed proxy,\nprovider account, launchd service, or production request was used.\n\n| Requirement | Evidence |\n| --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Delayed acceptance gets its own full commit deadline | Actual child-process IPC fixture; no failed transfer or worker replacement |\n| A failed commit preserves unrelated streams | Actual shipped worker runtime; affected connection closes, other stream delivers every byte, replacement serves requests; commit timeouts and IPC write failures retain distinct reasons |\n| Accepted work remains identifiable after worker death | Confirmed admission append, fixture SIGKILL, independent parent exit journal, built analyzer joins by process instance |\n| Unknown outcomes remain unknown | Missing terminal plus worker exit is reported as unconfirmedAtWorkerExit; conflicting exit evidence is excluded |\n| Storage failures cannot dispatch unrecorded upstream work | HTTP fixture returns classified 503 after admission write failure; upstream invocation count stays zero |\n| Time-window analysis does not f","hierarchy":{"lvl0":"Development","lvl1":"Proxy incident reliability verification","lvl2":"Acceptance evidence","lvl3":""}},
|
|
2290
2290
|
{"objectID":"e445d51bde52184ea947c95c6b1f2105e1c1dd67b2ed01509cbfa320d3a396db","title":"Performance observations","url":"/docs/development/proxy-incident-verification#performance-observations","content":"The existing lifecycle gate wrote 100,000 events for 25,000 requests with zero\ndrops or uncertain writes. Enqueue p95 was 12 microseconds; added response-tracking\np95 was 49 microseconds. The statistics gate reconciled 50,000 requests exactly.\nThe loopback transport gate added 1.69 ms p95 against its 5 ms budget.\n\nThe first rolling gate preserved all traffic but failed the sustained latency\nbudget: 51.28 ms added p95 against 25 ms. We then ran three alternating comparisons\nagainst unchanged release 382217975c39433df058e2b2c2fb91b283230725, using the\nsame fixture, machine and background scheduling priority. No budgets were changed.\n\n| Round | Release added p95 under sustained load | Patched added p95 under sustained load |\n| ----- | -------------------------------------: | -------------------------------------: |\n| 1 | 6.042 ms | 24.840 ms |\n| 2 | 6.826 ms | 5.355 ms |\n| 3 | 9.827 ms | 4.811 ms |\n\nEvery comparison passed the existing budgets, preserved the active stream, and\nreported zero dropped requests. The fixture includes 100 concurrent handoff\nrequests and 64-way sustained traffic. These observations expose variability;\nthey do not certify a latency ceiling under arbitrary host contention.","hierarchy":{"lvl0":"Development","lvl1":"Proxy incident reliability verification","lvl2":"Performance observations","lvl3":""}},
|
|
2291
2291
|
{"objectID":"5cb2fe62f38e8943b94facdd1ddd6a4a968fbbb161744152fe47c3c269d575cf","title":"Interpretation limits","url":"/docs/development/proxy-incident-verification#interpretation-limits","content":"The CLI has one signal owner. Proxy workers and supervisors own their drain and\nexit; the ordinary CLI telemetry cleanup cannot exit alongside them. Repeated\nSIGINT/SIGTERM signals reuse that shutdown. Ordinary command cleanup catches\nflush failures and uses a five-second deadline. Worker and supervisor telemetry\ncleanup also has a deadline after request draining. Supervisor SIGHUP forwards a\nreload to the ready serving worker, while a candidate loads configuration during\nstartup. Isolated subprocess fixtures in test:proxy-restart exercise reload,\nduplicate signals, failed flushes, stalled flushes and drain failures. If startup\nregisters a shutdown owner during ordinary cleanup, the original signal is\ndelegated to that owner after cleanup settles, including rejection or timeout.\nSignal-triggered ordinary cleanup preserves an existing command failure code.\nSupervisor SIGHUP handling is installed before startup awaits, ignores reloads\nuntil a serving worker is ready, and forwards them during control-plane setup.\nSupervisor shutdown ownership is also registered before startup awaits. A stop\nprevents later setup stages, waits for already-started resource creation, and\ncloses the resulting resources before exiting. Cleanup continues after an\nindividual close failure and exits unsuccessfully when a resource cannot close.\nIsolated child-process regressions signal bootstrap, pending worker startup,\npending restart-control setup, and updater startup, including duplicate signals\nand a rejected control close.\nResource cleanup runs concurrently: worker creation and drain have a 35-second\ndeadline, and control cleanup has a 5-second deadline. Telemetry then has its\n5-second budget, leaving headroom inside the launchd service's 45-second stop\ntimeout. A stalled resource is reported and causes an unsuccessful exit; a late\nresource returned while telemetry is flushing is still closed without extending\nthe exit deadline. Fixtures cover permanently pending worker/control setup and a\n","hierarchy":{"lvl0":"Development","lvl1":"Proxy incident reliability verification","lvl2":"Interpretation limits","lvl3":""}},
|
|
2292
|
-
{"objectID":"e8a7f76ea9249969c4e7f7f8767fd2cac18478baca29ee921e7a7ca73825db69","title":"Proxy package updates and token budgets","url":"/docs/development/proxy-updates-and-token-budgets","content":"Proxy package updates and token budgets\n\nAutomatic updates prepare a separate package before asking the supervisor to\nactivate it. They do not run a global package replacement over files used by\nserving workers. These changes take effect after the release is installed and\nits supervisor is activated; merging a PR does not change a running service.\n\nUpdate and restart behavior\n\nThe updater installs the current running version, when needed for rollback, and\nthe candidate under ~/.neurolink/proxy-packages/<version>. Installation uses a\nprivate temporary directory and publishes the version directory only after the\npackage name, version, executable location, and JavaScript syntax validate.\nA candidate with missing dependencies can still fail startup: actual worker\nreadiness remains the activation gate, and the serving generation is retained\nwhen replacement fails.\n\nThe package-manager process runs asynchronously. Output progress renews a\ntwo-minute inactivity deadline; a fifteen-minute maximum still bounds an\ninstaller that emits output forever. A timed-out installer and its process group\nare stopped, its unpublished directory is removed, and transient errors receive\nthe updater's bounded retry schedule. Progress telemetry contains byte counts and\ntiming, not raw package-manager output that may contain authenticated URLs.\n\nThe stable launcher selects one validated package. Its --version check reads\npackage metadata in a minimal Node process; it does not initialize provider,\ntelemetry, or proxy code. Each worker is spawned from its expected package, so a\nnew candidate cannot silently change the recovery executable of an older worker.\nRollback restores the previous selection rather than reinstalling over live\nfiles. Proxy auto-update does not change the global CLI package; invoking an older\nglobal CLI to reinstall the service retains the selected proxy package instead\nof downgrading it or rejecting its version. Version directories are intentionally retained: they can still be needed\nby active/draining generations or rollback. This increases package disk usage;\nthere is currently no automatic garbage collection of retained versions.\n\nBefore refreshing a supervisor, all of the following must be settled:\nActive requests in the current worker.\nEvery draining worker generation.\nQueued sockets and pending socket transfers.\nAny candidate worker.\n\nIncomplete rolling status is not evidence of idleness. A drain attempt lasts at\nmost thirty seconds before deferral and admission recovery; it never kills an\nolder stream to make the update proceed. The worker also has a ninety-second\nadmission recovery lease if the updater disappears. Package installation and\nvalidation finish before a legacy service closes admission.\n\nUse neurolink proxy restart --check for a read-only restart preflight and\nneurolink proxy restart for a supervisor-owned rolling replacement. A failed\ncheck does not fall back to a forced process restart. The result distinguishes a\nverified replacement from an activated worker whose final verification failed.\nA worker restart does not, by itself, upgrade the supervisor.\n\nStatus separates diskPackageVersion (metadata at the executable used by the\nreporting process), selectedPackageVersion (next launcher selection),\nvalidatedVersion (last updater-validated package), candidateVersion, the\nserving worker version, and the supervisor version. A changed disk manifest or a\nvalidated package does not prove live adoption.\n\nReinstall first refuses any running worker, draining generation, or loaded launchd\njob before writing files or changing processes. Unknown/permission-denied process\nor launchd state also refuses. Use the rolling restart command for a live service;\nexplicitly stop and unload it before an intended reinstall.\n\nFor a stopped service, reinstall reads the saved definition before writing it. Existing\nhost/port, environment-file path, routing-config path, and operator OTel/proxy\nvariables become defaults; explicit arguments and current environment values\nwin. An unreadable service definition aborts reinstall instead of silently\nremoving its settings. Worker IPC identity is not persisted. This includes\nNEUROLINK_PROXY_OTLP_BODIES_ENDPOINT, NEUROLINK_PROXY_CONTEXT_POLICY, and\nNEUROLINK_PROXY_TOKEN_BUDGET.\n\
|
|
2292
|
+
{"objectID":"e8a7f76ea9249969c4e7f7f8767fd2cac18478baca29ee921e7a7ca73825db69","title":"Proxy package updates and token budgets","url":"/docs/development/proxy-updates-and-token-budgets","content":"Proxy package updates and token budgets\n\nAutomatic updates prepare a separate package before asking the supervisor to\nactivate it. They do not run a global package replacement over files used by\nserving workers. These changes take effect after the release is installed and\nits supervisor is activated; merging a PR does not change a running service.\n\nUpdate and restart behavior\n\nThe updater installs the current running version, when needed for rollback, and\nthe candidate under ~/.neurolink/proxy-packages/<version>. Installation uses a\nprivate temporary directory and publishes the version directory only after the\npackage name, version, executable location, and JavaScript syntax validate.\nA candidate with missing dependencies can still fail startup: actual worker\nreadiness remains the activation gate, and the serving generation is retained\nwhen replacement fails.\n\nThe package-manager process runs asynchronously. Output progress renews a\ntwo-minute inactivity deadline; a fifteen-minute maximum still bounds an\ninstaller that emits output forever. A timed-out installer and its process group\nare stopped, its unpublished directory is removed, and transient errors receive\nthe updater's bounded retry schedule. Progress telemetry contains byte counts and\ntiming, not raw package-manager output that may contain authenticated URLs.\n\nThe stable launcher selects one validated package. Its --version check reads\npackage metadata in a minimal Node process; it does not initialize provider,\ntelemetry, or proxy code. Each worker is spawned from its expected package, so a\nnew candidate cannot silently change the recovery executable of an older worker.\nRollback restores the previous selection rather than reinstalling over live\nfiles. Proxy auto-update does not change the global CLI package; invoking an older\nglobal CLI to reinstall the service retains the selected proxy package instead\nof downgrading it or rejecting its version. Version directories are intentionally retained: they can still be needed\nby active/draining generations or rollback. This increases package disk usage;\nthere is currently no automatic garbage collection of retained versions.\n\nBefore refreshing a supervisor, all of the following must be settled:\nActive requests in the current worker.\nEvery draining worker generation.\nQueued sockets and pending socket transfers.\nAny candidate worker.\n\nIncomplete rolling status is not evidence of idleness. A drain attempt lasts at\nmost thirty seconds before deferral and admission recovery; it never kills an\nolder stream to make the update proceed. The worker also has a ninety-second\nadmission recovery lease if the updater disappears. Package installation and\nvalidation finish before a legacy service closes admission.\n\nUse neurolink proxy restart --check for a read-only restart preflight and\nneurolink proxy restart for a supervisor-owned rolling replacement. A failed\ncheck does not fall back to a forced process restart. The result distinguishes a\nverified replacement from an activated worker whose final verification failed.\nA worker restart does not, by itself, upgrade the supervisor.\n\nStatus separates diskPackageVersion (metadata at the executable used by the\nreporting process), selectedPackageVersion (next launcher selection),\nvalidatedVersion (last updater-validated package), candidateVersion, the\nserving worker version, and the supervisor version. A changed disk manifest or a\nvalidated package does not prove live adoption.\n\nReinstall first refuses any running worker, draining generation, or loaded launchd\njob before writing files or changing processes. Unknown/permission-denied process\nor launchd state also refuses. Use the rolling restart command for a live service;\nexplicitly stop and unload it before an intended reinstall.\n\nFor a stopped service, reinstall reads the saved definition before writing it. Existing\nhost/port, environment-file path, routing-config path, and operator OTel/proxy\nvariables become defaults; explicit arguments and current environment values\nwin. An unreadable service definition aborts reinstall instead of silently\nremoving its settings. Worker IPC identity is not persisted. This includes\nNEUROLINK_PROXY_OTLP_BODIES_ENDPOINT, NEUROLINK_PROXY_CONTEXT_POLICY, and\nNEUROLINK_PROXY_TOKEN_BUDGET.\n\nLocal compatibility polyfills\n\nAn automatic package upgrade leaves the routing config, environment file, and\nlaunchd definition intact. It also preserves the exact previous package path,\nincluding when a local build and an official build share a version number.\nManaged service starts and worker replacements do not reapply CLI client\nconfiguration. User changes to Codex, Claude Code, and other client settings\nremain in place. Use explicit proxy setup or foreground proxy start to\nconfigure client routing.\n\nEach freshly staged package records its original proxy runtime files. When a\nlater upgrade is available, the updater compares the serving package with that\nbaseline and applies only the remaining local diff hunk","hierarchy":{"lvl0":"Development","lvl1":"Proxy package updates and token budgets","lvl2":"","lvl3":""}},
|
|
2293
2293
|
{"objectID":"d8b758e463be06d23598c7f1722e82d46f3d5f2df3c3c98058f400923f83ac8f","title":"Proxy package updates and token budgets","url":"/docs/development/proxy-updates-and-token-budgets#proxy-package-updates-and-token-budgets","content":"Automatic updates prepare a separate package before asking the supervisor to\nactivate it. They do not run a global package replacement over files used by\nserving workers. These changes take effect after the release is installed and\nits supervisor is activated; merging a PR does not change a running service.","hierarchy":{"lvl0":"Development","lvl1":"Proxy package updates and token budgets","lvl2":"Proxy package updates and token budgets","lvl3":""}},
|
|
2294
2294
|
{"objectID":"b31737c456bf0dd066337629bccccd40c4572b96ad3b51fdf48de2c44d45e82a","title":"Update and restart behavior","url":"/docs/development/proxy-updates-and-token-budgets#update-and-restart-behavior","content":"The updater installs the current running version, when needed for rollback, and\nthe candidate under ~/.neurolink/proxy-packages/<version>. Installation uses a\nprivate temporary directory and publishes the version directory only after the\npackage name, version, executable location, and JavaScript syntax validate.\nA candidate with missing dependencies can still fail startup: actual worker\nreadiness remains the activation gate, and the serving generation is retained\nwhen replacement fails.\n\nThe package-manager process runs asynchronously. Output progress renews a\ntwo-minute inactivity deadline; a fifteen-minute maximum still bounds an\ninstaller that emits output forever. A timed-out installer and its process group\nare stopped, its unpublished directory is removed, and transient errors receive\nthe updater's bounded retry schedule. Progress telemetry contains byte counts and\ntiming, not raw package-manager output that may contain authenticated URLs.\n\nThe stable launcher selects one validated package. Its --version check reads\npackage metadata in a minimal Node process; it does not initialize provider,\ntelemetry, or proxy code. Each worker is spawned from its expected package, so a\nnew candidate cannot silently change the recovery executable of an older worker.\nRollback restores the previous selection rather than reinstalling over live\nfiles. Proxy auto-update does not change the global CLI package; invoking an older\nglobal CLI to reinstall the service retains the selected proxy package instead\nof downgrading it or rejecting its version. Version directories are intentionally retained: they can still be needed\nby active/draining generations or rollback. This increases package disk usage;\nthere is currently no automatic garbage collection of retained versions.\n\nBefore refreshing a supervisor, all of the following must be settled:\nActive requests in the current worker.\nEvery draining worker generation.\nQueued sockets and pending socket transfers.\nAny candidate worker.\n\nIncom","hierarchy":{"lvl0":"Development","lvl1":"Proxy package updates and token budgets","lvl2":"Update and restart behavior","lvl3":""}},
|
|
2295
|
+
{"objectID":"69e1f9556ad4141f30fa78e44edc0297f70c1dcc1e775c5955e44c912f79a0b6","title":"Local compatibility polyfills","url":"/docs/development/proxy-updates-and-token-budgets#local-compatibility-polyfills","content":"An automatic package upgrade leaves the routing config, environment file, and\nlaunchd definition intact. It also preserves the exact previous package path,\nincluding when a local build and an official build share a version number.\nManaged service starts and worker replacements do not reapply CLI client\nconfiguration. User changes to Codex, Claude Code, and other client settings\nremain in place. Use explicit proxy setup or foreground proxy start to\nconfigure client routing.\n\nEach freshly staged package records its original proxy runtime files. When a\nlater upgrade is available, the updater compares the serving package with that\nbaseline and applies only the remaining local diff hunks to a new candidate.\nIndependent release changes remain intact. A fix already present in the release\nis skipped and disappears from the local diff on subsequent upgrades. The\nserving package is never edited during this reconciliation.\n\nAn overlapping change, ambiguous patch context, unsupported dependency change,\nor invalid JavaScript aborts candidate preparation. The current worker,\nlauncher, config, and rollback selection remain in place. Automatic staging\nlogs the number of files applied or already included without logging their\ncontents. Runtime JavaScript and JSON are supported; dependency trees and\nbrowser bundles are not local proxy polyfills.\n\nClean legacy installations bootstrap their baseline by comparing with a\nseparately staged registry package of the same version. An edited legacy build\ncannot be safely compared without its actual original base, so its upgrade is\nretained until that base is recorded explicitly:\n\nThis command records the original files; it does not apply edits, select a\npackage, or restart a service. Supply the pristine package from which the local\nedit was made. A different existing baseline is refused rather than overwritten.","hierarchy":{"lvl0":"Development","lvl1":"Proxy package updates and token budgets","lvl2":"Local compatibility polyfills","lvl3":""}},
|
|
2295
2296
|
{"objectID":"82a142fcb5a4a408bcc1ef4a742c15da227f7efe0ca8039f32786bd317776acc","title":"Optional token reservations","url":"/docs/development/proxy-updates-and-token-budgets#optional-token-reservations","content":"NEUROLINK_PROXY_TOKEN_BUDGET accepts a JSON object. No token cap is enabled by\ndefault. For example, an operator could configure:\n\nThese numbers are illustrative, not recommended account allowances. Supported\nfields must be positive safe integers; an invalid configuration fails closed.\nmaxInFlightTokens is per provider/account. accountWindowTokens caps charged\ntokens in that account's fixed window. sessionWindowTokens applies across\naccounts and providers for the same client session. The default window is one\nhour when a cap is set without windowMs.\n\nBefore each native upstream attempt or translated SDK invocation, the proxy\nreserves estimated input plus the output allowance. An SDK invocation can contain\nprovider-internal retries that the proxy cannot observe individually; its single\nreservation does not prove or cap the combined tokens of those hidden retries. Tool definitions and instructions contribute to the input\nestimate. This is an estimate with recorded provenance, not an exact provider\ntokenization or a guarantee of monetary cost. A configured estimated input maximum\nis not a hard maximum on actual provider input tokens, especially for images,\naudio, and files. Model/context policy is configured\nseparately with NEUROLINK_PROXY_CONTEXT_POLICY; it must use the actual serving\nmodel. Provider-reported total input plus output replaces the estimate once when\ncomplete usage is known. Reasoning is already part of output and is not added a\nsecond time. Missing or uncertain usage retains the estimate. Cancellation\nbefore dispatch refunds the unused reservation; cancellation after dispatch does\nnot presume that the provider billed zero.\n\nActive and draining workers reserve atomically against one in-memory supervisor\ncoordinator. Outstanding reservations remain charged across a window boundary;\na reset cannot create new capacity for requests that are still running. Worker\nexit retains estimated spending for its unresolved calls while releasing its\nin-flight occupancy.","hierarchy":{"lvl0":"Development","lvl1":"Proxy package updates and token budgets","lvl2":"Optional token reservations","lvl3":""}},
|
|
2296
2297
|
{"objectID":"183568ccdb77340136a3026e2df12e9e82c5722ba3ddbf3b5749ba6c62de231b","title":"OTLP acknowledgement boundaries","url":"/docs/development/proxy-updates-and-token-budgets#otlp-acknowledgement-boundaries","content":"Both metadata and body queues serialize through OpenTelemetry's official JSON\nserializer. A response-aware HTTP sender validates the complete, bounded response\nbefore reporting transport acknowledgement. HTTP 200 with {} or a zero-rejection\npartialSuccess warning is acknowledged. A known partial rejection leaves the\nwhole batch unconfirmed because the response does not identify the rejected\nrecords; that batch is not replayed. Empty, malformed, interrupted, oversized,\nunexpected-shape or ambiguous-alias success responses are also unconfirmed and\nare not retried. Empty JSON is not {}; the zero-byte protobuf convention does\nnot apply to this JSON sender. Canonical and proto snake_case response names are\naccepted, but duplicate aliases are conservatively rejected.\n\nTransient HTTP 429/502/503/504 and transport failures before a response may retry\nwithin the existing 30-second total export deadline, with at most five attempts\nand exponential backoff with jitter even when Retry-After is zero. Retries retain the same\nserialized event IDs and bytes, enabling query-side deduplication. Retry-After\ncannot extend the deadline. Response reads are capped at 64 KiB. The exporter\npreserves merged generic/log-specific OTLP headers, gzip request compression,\nand configured CA/client certificate/key files; invalid configuration fails\nexplicitly. It requests uncompressed JSON responses and treats unsupported\nresponse encodings as unconfirmed. Diagnostics omit collector response bodies\nand credentials. Transport acknowledgement remains distinct from backend\npersistence; query reconciliation is still needed to confirm ingestion.\n\nA staged update may finish after its original supervisor/updater has been\nreplaced. Before publishing, validation rollback, or activation, the updater\nrechecks that its recorded supervisor and updater PIDs still own the service\nand that the original parent is running. Unknown ownership defers mutation.\nStale update jobs cannot overwrite or roll back a replacemen","hierarchy":{"lvl0":"Development","lvl1":"Proxy package updates and token budgets","lvl2":"OTLP acknowledgement boundaries","lvl3":""}},
|
|
2297
2298
|
{"objectID":"3d5db69fb501ec2b11df1700773d0e7648ef30c85f643c3b54975e4f45a3a05b","title":"๐งช COMPREHENSIVE TESTING & VERIFICATION PLAN","url":"/docs/development/testing-plan","content":"๐งช COMPREHENSIVE TESTING & VERIFICATION PLAN\nTest Results Documentation:\nUpdated Documentation:\n\nLighthouse Integration Testing Strategy\nDate: 2025-07-06 02:55 AM\nEstimated Duration: 3 hours total\n\n๐ TESTING OVERVIEW\n\nWhat We're Testing:\nโ
Real-time WebSocket Infrastructure - New streaming services, WebSocket server, enhanced chat\nโ
Advanced Telemetry Integration - OpenTelemetry stack (15+ dependencies, optional by default)\nโ
Voice AI Removal - Complete cleanup of voice dependencies and code\nโ
Backward Compatibility - All existing functionality preserved\nโ
New API Surface - Factory methods, exports, TypeScript interfaces\n\nCritical Success Criteria:\nโ
Zero Breaking Changes: All existing code works unchanged\nโ
Build Success: TypeScript compilation with 0 errors\nโ
Performance: \\<5% overhead when new features disabled\nโ
Optional Features: Telemetry disabled by default, WebSocket services optional\nโ
Complete Integration: New features work with existing AI providers and MCP tools\n\n๐ PHASE A: IMMEDIATE VERIFICATION (30 minutes)\n\nPriority: CRITICAL | Blocking: Must pass before proceeding\n\nA.1 File System Verification (10 minutes)\n\nSuccess Criteria:\nโ
WebSocket infrastructure files exist\nโ
Streaming services files exist\nโ
Telemetry files exist\nโ
NO voice-related files remain\nโ
Enhanced chat files exist\n\nA.2 Build Validation (15 minutes)\n\nSuccess Criteria:\nโ
TypeScript compilation: 0 errors\nโ
Vite build: successful\nโ
CLI build: successful\nโ
publint: \"All good!\"\nโ
Package integrity: pnpm pack succeeds\n\nA.3 Dependency Verification (5 minutes)\n\nSuccess Criteria:\nโ
Voice AI dependencies: 0 found\nโ
OpenTelemetry dependencies: 15+ installed\nโ
No dependency conflicts\nโ
Package.json reflects changes\n\n๐ง PHASE B: CORE TESTING (1 hour)\n\nPriority: HIGH | Focus: New feature functionality\n\nB.1 WebSocket Infrastructure Testing (20 minutes)\n\nTests to Create:\ntest/websocket-server.test.ts\ntest/streaming-manager.test.ts\ntest/websocket-chat-handler.test.ts\n\nSuccess Criteria:\nโ
WebSocket server starts on specified port\nโ
Connection management works\nโ
Room creation/joining functional\nโ
Streaming channels operational\nโ
Error handling graceful\n\nB.2 Telemetry Integration Testing (20 minutes)\n\nTests to Create:\ntest/telemetryService.test.ts\ntest/ai-instrumentation.test.ts\ntest/mcp-instrumentation.test.ts\n\nSuccess Criteria:\nโ
Telemetry disabled by default\nโ
Telemetry enables when configured\nโ
AI operation tracking works\nโ
MCP tool instrumentation functional\nโ
Zero overhead when disabled\n\nB.3 Enhanced Chat Testing (20 minutes)\n\nTests to Create:\ntest/enhanced-chat.test.ts\ntest/chat-integration.test.ts\n\nSuccess Criteria:\nโ
Enhanced chat service creates successfully\nโ
SSE mode works\nโ
WebSocket mode works\nโ
Dual mode integration functional\nโ
Backward compatibility with existing chat\n\n๐ PHASE C: COMPREHENSIVE VALIDATION (1 hour)\n\nPriority: HIGH | Focus: Integration and performance\n\nC.1 Existing Functionality Regression Testing (20 minutes)\n\nSuccess Criteria:\nโ
All existing tests pass\nโ
CLI commands work unchanged\nโ
SDK methods work unchanged\nโ
AI providers function correctly\nโ
MCP tools continue working\n\nC.2 Performance Impact Testing (20 minutes)\n\nSuccess Criteria:\nโ
Default performance unchanged\nโ
Performance overhead \\<5% when features enabled\nโ
Memory usage remains stable\nโ
No performance regressions\n\nC.3 Real-World Scenario Testing (20 minutes)\n\nSuccess Criteria:\nโ
WebSocket chat works end-to-end\nโ
Telemetry collects accurate data\nโ
Multi-provider scenarios work\nโ
Streaming integrations functional\n\nโ
PHASE D: FINAL VALIDATION (30 minutes)\n\nPriority: CRITICAL | Focus: Production readiness\n\nD.1 API Surface Validation (10 minutes)\n\nSuccess Criteria:\nโ
All new exports importable\nโ
TypeScript types correct\nโ
No missing dependencies\nโ
API surface consistent\n\nD.2 Documentation Synchronization (10 minutes)\n\nSuccess Criteria:\nโ
Documentation reflects actual implementation\nโ
Voice references removed/minimal\nโ
New features documented\nโ
Examples are accurate\n\nD.3 Production Deployment Readiness (10 minutes)\n\nSuccess Criteria:\nโ
Package builds correctly\nโ
Installation works\nโ
Imports work after installation\nโ
No missing files\nโ
Ready for npm publish\n\n๐ SUCCESS CRITERIA SUMMARY\n\nCritical (Must Pass):\nโ
Build Success: 0 TypeScript errors, successful compilation\nโ
Backward Compatibility: All existing functionality works unchanged\nโ
Performance: \\<5% overhead when new features disabled\nโ
Voice AI Removal: No voice dependencies or code remaining\n\nImportant (Should Pass):\nโ
WebSocket Infrastructure: Real-time services operational\nโ
Telemetry Integration: Optional monitoring works when enabled\nโ
Enhanced Chat: Dual-mode chat capabilities functional\nโ
API Consistency: New exports and types work correctly\n\nNice to Have (Can Be Fixed):\nโ
Documentation Completeness: All features documented\nโ
Example Applications: Working demos available\nโ
Performance Optimization: Further optimization opportunities\n\n๐ฏ EXECUTION ORDER\n\nSequential Execution Required:\nPhase A โ Mu","hierarchy":{"lvl0":"Development","lvl1":"๐งช COMPREHENSIVE TESTING & VERIFICATION PLAN","lvl2":"","lvl3":""}},
|
|
@@ -2907,7 +2908,7 @@
|
|
|
2907
2908
|
{"objectID":"8ea0d60b8e33eeebfb825eb12ad9fcd0cdad610ee262ac5486db5d7fa3b8406b","title":"5xx / Transient Server Error","url":"/docs/features/claude-proxy#5xx-transient-server-error","content":"Transient errors (408, 500, 502, 503, 504, and Cloudflare 520-526/529).\nAlso matches 400 responses with api_error or overloaded_error types that wrap transient HTML content (e.g., Cloudflare error pages).\nApply bounded same-account retries, then rotate to the next account.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"5xx / Transient Server Error","lvl3":""}},
|
|
2908
2909
|
{"objectID":"195dae3c0ea28fdcfeb0fbf39c456fd580652b07fa5f2ce81b341d4279621cf8","title":"All Accounts Exhausted","url":"/docs/features/claude-proxy#all-accounts-exhausted","content":"When every account is in a cooling state:\nWalk the fallback chain (if configured).\nEach fallback uses NeuroLink's stream() pipeline with the specified provider/model.\nIf all fallbacks also fail, return a 429 with Retry-After set to the earliest account recovery time.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"All Accounts Exhausted","lvl3":""}},
|
|
2909
2910
|
{"objectID":"a9d69b153d2a12e50877017c5bfbcdb2ba932f1223183c8c311614898ae4a910","title":"Bootstrap Retry (Streaming)","url":"/docs/features/claude-proxy#bootstrap-retry-streaming","content":"For streaming requests, the proxy reads the first chunk from the upstream response before forwarding it to the client. If the first chunk is empty (indicating a failed stream), the proxy retries with the next account. This prevents Claude Code from receiving an empty SSE stream.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Bootstrap Retry (Streaming)","lvl3":""}},
|
|
2910
|
-
{"objectID":"98c795c3e6eec8f50fcb68adae0d6c62387d0ce83d06775eb4c32ff0698c7e7e","title":"Claude Code integration","url":"/docs/features/claude-proxy#claude-code-integration","content":"
|
|
2911
|
+
{"objectID":"98c795c3e6eec8f50fcb68adae0d6c62387d0ce83d06775eb4c32ff0698c7e7e","title":"Claude Code integration","url":"/docs/features/claude-proxy#claude-code-integration","content":"Foreground proxy start and explicit proxy setup configure ~/.claude/settings.json:\n\nWhen the proxy stops (Ctrl+C or SIGTERM), it removes these entries from the settings file. This means Claude Code automatically routes through the proxy when it is running and goes direct when it is not.\n\nNote: You must restart Claude Code after starting or stopping the proxy for the settings change to take effect.\n\nManaged service restarts and rolling upgrades preserve existing client settings.\nThey do not reapply routing or replace user edits. Run neurolink proxy setup\nwhen you want to configure clients explicitly, including after a standalone\nservice installation.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Claude Code integration","lvl3":""}},
|
|
2911
2912
|
{"objectID":"228facfde8c5dd950496365bfc28e6470b20eee6543317f0c3cc9b5e239a972e","title":"Proxy state file","url":"/docs/features/claude-proxy#proxy-state-file","content":"The proxy persists its running state to ~/.neurolink/proxy-state.json so that neurolink proxy status can report on it and neurolink proxy start can detect an already-running instance. The state includes PID, port, host, strategy, start time, fallback chain, enforced account allowlist, and the optional foreground fail-open guard PID.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Proxy state file","lvl3":""}},
|
|
2912
2913
|
{"objectID":"08047056092173a6d64c0fa57508b677157b3da499411f5a0783681010239ce9","title":"Fail-open guard","url":"/docs/features/claude-proxy#fail-open-guard","content":"A foreground proxy spawns one detached neurolink proxy guard that removes stale Claude Code settings after confirming its parent process has died. A launchd-managed proxy keeps restart ownership in launchd and starts a separate updater-only worker. Automatic package updates are enabled by default and can be disabled with NEUROLINK_PROXY_AUTO_UPDATE=off (also accepts 0 or false). The worker validates the global package root and executable directory and requires the package manager that owns the running installation. Rolling installations activate and health-check the new serving worker first. If the parent supervisor still reports the older version, the updater waits for a two-minute idle window, applies a bounded admission drain, and asks launchd to refresh the supervisor. Success requires both worker and supervisor to report the new version from a new parent process. The drain automatically expires if the updater disappears, and refresh failures retain the installed version for retry instead of repeatedly reinstalling it. Legacy services wait for every request and stream before their direct launchd restart. Post-install executable validation uses bounded retries, and the proxy supervises and replaces an updater worker that exits unexpectedly. Installed-but-not-running versions and stage-specific update failures remain persisted in ~/.neurolink/update-state.json. Updater diagnostics are sent through the configured proxy log sink and exposed in neurolink proxy status.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Fail-open guard","lvl3":""}},
|
|
2913
2914
|
{"objectID":"778246a1bbbc0afc1258a50c9117e61d795099421fbd7f634d960f73aa6d3fc4","title":"Endpoints","url":"/docs/features/claude-proxy#endpoints","content":"| Method | Path | Description |\n| ------ | --------------------------- | --------------------------------------- |\n| POST | /v1/messages | Claude Messages API (main endpoint) |\n| GET | /v1/models | List available Claude models |\n| POST | /v1/messages/count_tokens | Token counting |\n| GET | /health | Health check (status, strategy, uptime) |\n| GET | /status | Detailed proxy status |","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Endpoints","lvl3":""}},
|
|
@@ -3200,7 +3201,7 @@
|
|
|
3200
3201
|
{"objectID":"8f00ff46e651a81fa28b61fd20f66b0a93b72d2024b4da2f2e8fe661b32c4d9e","title":"<<< neurolink-proxy (managed) <<<","url":"/docs/features/codex-proxy-support#-neurolink-proxy-managed--1","content":"Your original model_provider value is snapshotted to ~/.neurolink/codex-proxy-snapshot.json and restored on shutdown, so the edit is fully reversible even if the proxy crashes. The managed block is delimited by markers and removed cleanly on clear. If ~/.codex/config.toml` doesn't exist, the step is skipped silently.\n\nRestart Codex after starting the proxy for it to pick up the new provider.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"<<< neurolink-proxy (managed) <<<","lvl3":""}},
|
|
3201
3202
|
{"objectID":"404cd8f6d8542fc84651326cf0b252e7c3c3098678409fecaf8c2ff1559906fd","title":"4. Quota and routing","url":"/docs/features/codex-proxy-support#4-quota-and-routing","content":"Codex reports two rate-limit windows โ primary (short) and secondary (weekly) โ which map onto the shared AccountQuota model as the session and weekly fields respectively. That means Codex reuses the existing cooldown, persistence, and display code rather than duplicating it.\n\nAccount ordering is fill-first and quota-aware:\nAccounts on cooldown sort last.\nWithin the same cooldown state, accounts with a rejected unified quota sort after accounts without known rejection, even when their session usage is low or unknown.\nWithin each group, accounts with no session quota measurement sort first โ they get probed so they become comparable, rather than being starved.\nOtherwise, least session utilization first. Rejected accounts remain eligible after preferred accounts so stale rejection evidence cannot permanently prevent a recovery probe.\n\nCooldown reasons map to the shared vocabulary: a rejected weekly window cools until its real reset (weekly), a rejected primary window until its reset (session), and a plain burst limit gets a bounded transient cooldown (60 s floor, 15 min ceiling).\n\nQuota and cooldown state are keyed by the full codex: account key, so a Codex account and an Anthropic account that share a bare label never collide.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"4. Quota and routing","lvl3":""}},
|
|
3202
3203
|
{"objectID":"acb592d28fcb90659fa175ac4f0d1e73cfb535d0aac2b63a101f0722e538eff7","title":"5. Response headers","url":"/docs/features/codex-proxy-support#5-response-headers","content":"A response served by the Codex pool carries these attribution headers. A\nresponse served by the outbound fallback\ncarries only x-neurolink-served-by: the account, attempt and quota headers\ndescribe a Codex account, and none served that turn.\n\n| Header | Meaning |\n| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| x-neurolink-account | Which pooled account served the request |\n| x-neurolink-account-type | Always codex-oauth |\n| x-neurolink-served-by | codex, or codex-outbound-fallback:anthropic / codex-outbound-fallback:vertex when the outbound fallback served the turn |\n| x-neurolink-attempt | Which attempt succeeded (1 = first account tried) |\n| x-neurolink-quota-source | live when the backend reported quota, else none |\n| x-neurolink-quota-session-left-pct | Remaining primary-window headroom |\n| x-neurolink-quota-weekly-left-pct | Canonical remaining","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"5. Response headers","lvl3":""}},
|
|
3203
|
-
{"objectID":"2b17d62c4d9cb65067100d6a90003bee2dce2bc06ede425f25f0b761aade359a","title":"6. Error handling","url":"/docs/features/codex-proxy-support#6-error-handling","content":"| Condition | Behaviour |\n| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| No Codex accounts configured | 401 with a message pointing at neurolink auth login codex |\n| All accounts cooling | Terminal response.failed SSE event (200, retry-after computed from the soonest recovery) instead of a bare 429, so the CLI shows a real error instead of \"Reconnecting...\" |\n| 401 / 403 from upstream | One forced token refresh, then rotate; a failed refresh disables the account until re-login |\n| 429 | Cool the account per its reported window, then rotate |\n| 5xx / network | Rotate to the next account |\n\nAccess tokens are refreshed proactively when within 5 minutes of expiry, and the rotated refresh token is written back to the store.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"6. Error handling","lvl3":""}},
|
|
3204
|
+
{"objectID":"2b17d62c4d9cb65067100d6a90003bee2dce2bc06ede425f25f0b761aade359a","title":"6. Error handling","url":"/docs/features/codex-proxy-support#6-error-handling","content":"| Condition | Behaviour |\n| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| No Codex accounts configured | 401 with a message pointing at neurolink auth login codex |\n| All accounts cooling | Terminal response.failed SSE event (200, retry-after computed from the soonest recovery) instead of a bare 429, so the CLI shows a real error instead of \"Reconnecting...\" |\n| All accounts auth-cooling | The same terminal event with code server_error instead of insufficient_quota (every account is parked by an auth failure), so it is not reported as spent quota |\n| 401 / 403 from upstream | One forced token refresh, then rotate; a failed refresh disables the account until re-login |\n| 429 | Cool the account per its reported window, then rotate |\n| 5xx / network | Rotate to the next account |\n\nAccess tokens are refreshed proactively when within 5 minutes of expiry, and the rotated refresh token is written back to the store.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"6. Error handling","lvl3":""}},
|
|
3204
3205
|
{"objectID":"9b1627d0fcff6a9e292b7f794fb47e62eb3da6b9c633e308da2b1de08a83368d","title":"7. Implementation map","url":"/docs/features/codex-proxy-support#7-implementation-map","content":"| File | Role |\n| ------------------------------------------- | ----------------------------------------------------------------------------------------- |\n| src/lib/types/codex.ts | Codex auth-file, token, and rate-limit types |\n| src/lib/auth/codexOAuth.ts | Endpoints/constants, auth.json import, token refresh, JWT decode, account-id resolution |\n| src/lib/proxy/codexAccountUsage.ts | Account enumeration, usage fetch, quota normalisation, header parsing |\n| src/lib/server/routes/codexProxyRoutes.ts | The pool engine: load โ order โ forward โ rotate |\n| src/cli/commands/auth.ts | auth login codex, Codex rows in auth list --refresh |\n| src/cli/commands/proxy.ts | Route registration, /backend-api/* request tracking, ~/.codex/config.toml management |\n\nThe Anthropic engine in claudeProxyRoutes.ts is untouched. The Codex engine is deliberately leaner: it does pre-commit rotation but not the full transient-retry-budget or admission-lease machinery.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"7. Implementation map","lvl3":""}},
|
|
3205
3206
|
{"objectID":"c90a5222434bc717385c13b9e9f9cb865d3ce71ed7a58c077a7b5241def7d086","title":"8. Caveats","url":"/docs/features/codex-proxy-support#8-caveats","content":"Model ids matter. The ChatGPT backend rejects models that aren't available to Codex-with-a-ChatGPT-account (e.g. gpt-5-codex returns a 400). Use the model your Codex config already uses.\nTerms of service. Pooling multiple personal ChatGPT subscriptions through one client fingerprint is the kind of pattern subscription anti-abuse systems are built to detect. The originator, installation_id, user-agent, and turn-metadata headers are all correlatable. Pooling your own accounts is materially different from sharing across people โ weigh the account-ban risk accordingly.\nNative browser login is not implemented. The verified path is importing an existing codex login credential. The OAuth constants (authorize URL, PKCE, scopes) are present in codexOAuth.ts for a future native flow.\nQuota-aware ordering is unverified against the live backend. The usage endpoint and the rate-limit header shape were reconstructed from a capture, not confirmed end to end. If either is wrong, fetchCodexAccountUsage returns no quota, x-neurolink-quota-source reads none, and ordering degenerates to insertion order while every 429 falls back to the 15-minute transient cooldown. Rotation still works; it is simply not quota-aware. Verify with neurolink auth list --refresh โ a codex usage โฆ error line per account means the quota path is not live.\nSSE usage-limit signals are not acted on. Only an HTTP 429 triggers a cooldown and rotation. A 200 response whose SSE stream carries usage_limit_reached (or the workspace-credit variants) is relayed to the client untouched, so the account is neither cooled nor rotated away from. HTTP-level exhaustion is handled; in-stream exhaustion is not.\nClient fingerprint is forwarded verbatim. The proxy replaces the caller's credentials but does not regenerate originator, installation_id, session-id, or turn metadata per account, so every pooled account shares the client's fingerprint. This is what makes the terms-of-service point above concrete.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"8. Caveats","lvl3":""}},
|
|
3206
3207
|
{"objectID":"607b92eb2c3b89a57438267a92b6212e616ad0dd106cb1c1a6bd0514c3446cd2","title":"Model discovery","url":"/docs/features/codex-proxy-support#model-discovery","content":"The Codex CLI refreshes its model list on every invocation:\n\nThe proxy relays that upstream to chatgpt.com/backend-api/codex/models using a\npooled account, forwarding the CLI's own query parameters.\n\nIt relays rather than synthesises, unlike the Claude and OpenAI /v1/models\nroutes, which build their lists locally from the model router. Which Codex\nmodels an account can reach is a property of that account โ plan tier, rollout\nstate โ not something this proxy knows, so a locally-built list would be a guess\nthat reads as authoritative.\n\nTwo details matter to anyone touching it:\nclient_version is required upstream. Omit it and ChatGPT answers 400\n with a pydantic Field required on ('query', 'client_version'). The query\n is rebuilt from ctx.query; ctx.path carries no query string, and reading it\n from there drops the parameter silently.\nDiscovery is side-effect free. No cooldown is recorded and no quota is\n consumed, so the once-per-invocation refresh cannot influence routing for real\n traffic. A cooling account is still allowed to answer it โ being rate-limited\n for completions does not make an account unable to say which models exist.\n\nBefore this route existed the request 404'd, and the CLI printed\nfailed to refresh available models: unexpected status 404 Not Found on every\nrun before silently falling back to a default model โ quietly ignoring the model\nthe user had configured.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"Model discovery","lvl3":""}},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.44.
|
|
3
|
+
"version": "12.44.7",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types โ generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI ยท Anthropic ยท Gemini ยท Bedrock ยท Azure ยท Ollama ยท TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|
|
@@ -188,6 +188,7 @@
|
|
|
188
188
|
"test:tool-routing-cli": "pnpm exec tsx test/continuous-test-suite-tool-routing-cli.ts",
|
|
189
189
|
"test:tool-dedup": "pnpm exec tsx test/continuous-test-suite-tool-dedup.ts",
|
|
190
190
|
"test:tools-manager-truncation": "pnpm exec tsx test/continuous-test-suite-tools-manager-truncation.ts",
|
|
191
|
+
"test:tooling-scripts": "pnpm exec tsx test/continuous-test-suite-tooling-scripts.ts",
|
|
191
192
|
"test:model-not-found-retryable": "pnpm exec tsx test/continuous-test-suite-model-not-found-retryable.ts",
|
|
192
193
|
"test:reasoning-parity-live": "pnpm exec tsx test/continuous-test-suite-reasoning-parity-live.ts",
|
|
193
194
|
"test:ci": "pnpm run test && pnpm run test:client && pnpm run test:hitl",
|
|
@@ -286,7 +287,8 @@
|
|
|
286
287
|
"test:native-failure-events": "pnpm exec tsx test/continuous-test-suite-native-failure-events.ts",
|
|
287
288
|
"test:native-audio-model-aware": "pnpm exec tsx test/continuous-test-suite-native-audio-model-aware.ts",
|
|
288
289
|
"test:cli-json-output": "pnpm exec tsx test/continuous-test-suite-cli-json-output.ts",
|
|
289
|
-
"test:logging-guidelines": "pnpm exec tsx test/continuous-test-suite-logging-guidelines.ts"
|
|
290
|
+
"test:logging-guidelines": "pnpm exec tsx test/continuous-test-suite-logging-guidelines.ts",
|
|
291
|
+
"test:proxy-polyfills": "pnpm exec tsx test/continuous-test-suite-proxy-polyfills.ts"
|
|
290
292
|
},
|
|
291
293
|
"files": [
|
|
292
294
|
"dist",
|
|
@@ -444,6 +446,7 @@
|
|
|
444
446
|
"chardet": "2.1.1",
|
|
445
447
|
"croner": "^9.1.0",
|
|
446
448
|
"csv-parser": "^3.2.0",
|
|
449
|
+
"diff": "^9.0.0",
|
|
447
450
|
"dotenv": "^17.3.1",
|
|
448
451
|
"eventsource-parser": "^3.0.8",
|
|
449
452
|
"fast-xml-parser": "^5.7.0",
|
|
@@ -565,7 +568,6 @@
|
|
|
565
568
|
"@vercel/ncc": "^0.38.4",
|
|
566
569
|
"concurrently": "^9.2.1",
|
|
567
570
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
568
|
-
"diff": "^9.0.0",
|
|
569
571
|
"esbuild": "^0.28.1",
|
|
570
572
|
"eslint": "^10.0.2",
|
|
571
573
|
"glob": "^13.0.6",
|