@linxiraos/pi-utils 1.1.11 → 1.1.13
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 +5 -3
- package/README.md +1 -2
- package/dist/types/dirs.d.ts +4 -4
- package/dist/types/fetch-retry.d.ts +4 -2
- package/dist/types/which.d.ts +3 -1
- package/dist/types/worker-host.d.ts +1 -1
- package/package.json +7 -4
- package/src/ar/entries.ts +1 -1
- package/src/ar/iso.ts +4 -4
- package/src/ar/rar/rar4-decoder.ts +4 -4
- package/src/ar/rar/rar5-decoder.ts +4 -4
- package/src/ar/unix-ar.ts +2 -2
- package/src/dates.ts +1 -1
- package/src/dirs.ts +6 -6
- package/src/docx/converter.ts +2 -2
- package/src/fetch-retry.ts +72 -15
- package/src/which.ts +7 -2
- package/src/worker-host.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [1.1.
|
|
5
|
+
## [1.1.13] - 2026-09-10
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- 上游 v18.1.16 同步:retry 提示解析升级为 longest-wins 合并(账号重置 + 追加 retry-after 同时出现时取更长窗口);`retry-after-ms` 支持 `:`/` =` 分隔形式;provider 明确要求立即重试(=0 或已过期时间戳)时返回 0 而非 undefined,避免误用启发式退避。
|
|
8
|
+
|
|
9
|
+
## [1.1.12] - 2026-09-10
|
|
8
10
|
|
|
9
|
-
-
|
|
11
|
+
- worker 宿主选择器族统一为 `__zeta_worker_*` 前缀(与主 CLI 对齐)。
|
|
10
12
|
|
|
11
13
|
## [1.1.10] - 2026-09-07
|
|
12
14
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @linxiraos/pi-utils
|
|
2
2
|
|
|
3
|
-
Shared utilities for
|
|
3
|
+
Shared utilities for Zeta packages. Zero ceremony, Bun-first.
|
|
4
4
|
|
|
5
5
|
## Notable modules
|
|
6
6
|
|
|
@@ -33,5 +33,4 @@ Ships TypeScript source directly (no build step); requires Bun ≥ 1.3.14.
|
|
|
33
33
|
|
|
34
34
|
## References
|
|
35
35
|
|
|
36
|
-
- [Monorepo README](https://github.com/can1357/oh-my-pi#readme)
|
|
37
36
|
- [CHANGELOG](./CHANGELOG.md)
|
package/dist/types/dirs.d.ts
CHANGED
|
@@ -119,7 +119,7 @@ export declare function getProjectAgentDir(cwd?: string): string;
|
|
|
119
119
|
export declare function getReportsDir(): string;
|
|
120
120
|
/** Get the logs directory (~/.zeta/logs). */
|
|
121
121
|
export declare function getLogsDir(): string;
|
|
122
|
-
/** Get this process's dated log path (~/.zeta/logs/
|
|
122
|
+
/** Get this process's dated log path (~/.zeta/logs/zeta.YYYY-MM-DD.PID.log). */
|
|
123
123
|
export declare function getLogPath(date?: Date, pid?: number): string;
|
|
124
124
|
/**
|
|
125
125
|
* Get the plugins directory (~/.zeta/plugins or its XDG equivalent).
|
|
@@ -263,9 +263,9 @@ export declare function getAgentModulesDir(agentDir?: string): string;
|
|
|
263
263
|
export declare function getMemoriesDir(agentDir?: string): string;
|
|
264
264
|
/** Get the terminal sessions directory (~/.zeta/agent/terminal-sessions). */
|
|
265
265
|
export declare function getTerminalSessionsDir(agentDir?: string): string;
|
|
266
|
-
/** Get the crash log path (~/.zeta/agent/
|
|
266
|
+
/** Get the crash log path (~/.zeta/agent/zeta-crash.log). */
|
|
267
267
|
export declare function getCrashLogPath(agentDir?: string): string;
|
|
268
|
-
/** Get the debug log path (~/.zeta/agent/
|
|
268
|
+
/** Get the debug log path (~/.zeta/agent/zeta-debug.log). */
|
|
269
269
|
export declare function getDebugLogPath(agentDir?: string): string;
|
|
270
270
|
/** Get the secret placeholder key path (~/.zeta/agent/secret-placeholder.key; XDG default: $XDG_STATE_HOME/zeta/secret-placeholder.key). Adopts a legacy key on first XDG resolution. */
|
|
271
271
|
export declare function getSecretPlaceholderKeyPath(): string;
|
|
@@ -295,7 +295,7 @@ export declare function getMCPConfigPath(scope: "user" | "project", cwd?: string
|
|
|
295
295
|
export declare function getSSHConfigPath(scope: "user" | "project", cwd?: string): string;
|
|
296
296
|
/**
|
|
297
297
|
* Application label for usage attribution (`OMP_APP_NAME`), defaulting to
|
|
298
|
-
* `
|
|
298
|
+
* `zeta`. Embedders that drive zeta programmatically (robomp, CI bots, …) set
|
|
299
299
|
* the env var so broker-side per-client burn tracking can answer "what did
|
|
300
300
|
* app X use" instead of folding everything into one install-wide bucket.
|
|
301
301
|
*/
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
* - `Please retry in 250ms` / `Please retry in 12s`
|
|
15
15
|
* - `"retryDelay": "34.074824224s"` (JSON error detail field)
|
|
16
16
|
* - `try again in 250ms` / `try again in 12s` / `try again in 5 min` / `try again in ~158 min`
|
|
17
|
-
* - `retry-after-ms=98497000`
|
|
17
|
+
* - `retry-after-ms=98497000` / `retry-after-ms: 7200000` / `retry-after-ms = 7200000`
|
|
18
18
|
* - `Your limit will reset at 2026-09-01 09:44:51` / `将在 2026-09-01 09:44:51 重置`
|
|
19
19
|
*
|
|
20
|
-
* Returns `undefined` if no signal is found
|
|
20
|
+
* Returns `undefined` if no signal is found, or `0` when the provider
|
|
21
|
+
* explicitly asks for an immediate retry (`retry-after…=0`, or an absolute
|
|
22
|
+
* reset timestamp that has already elapsed).
|
|
21
23
|
*/
|
|
22
24
|
export declare function extractRetryHint(source: Response | Headers | null | undefined, body?: string): number | undefined;
|
|
23
25
|
export interface FetchWithRetryOptions extends RequestInit {
|
package/dist/types/which.d.ts
CHANGED
|
@@ -26,7 +26,8 @@ export interface WhichOptions extends Bun.WhichOptions {
|
|
|
26
26
|
*/
|
|
27
27
|
cache?: WhichCachePolicy;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
declare function darwinWhich(command: string, options?: Bun.WhichOptions): string | null;
|
|
30
|
+
export declare const whichFresh: typeof darwinWhich;
|
|
30
31
|
/**
|
|
31
32
|
* Locate binary on PATH (with flexible caching).
|
|
32
33
|
*
|
|
@@ -35,3 +36,4 @@ export declare const whichFresh: typeof Bun.which;
|
|
|
35
36
|
* @returns Filesystem path if found, else null
|
|
36
37
|
*/
|
|
37
38
|
export declare function $which(command: string, options?: WhichOptions): string | null;
|
|
39
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Prefix reserved for argv selectors dispatched by the shared CLI worker host. */
|
|
2
|
-
export declare const WORKER_HOST_SELECTOR_PREFIX = "
|
|
2
|
+
export declare const WORKER_HOST_SELECTOR_PREFIX = "__zeta_worker_";
|
|
3
3
|
/** Whether an argv value selects a worker hosted by the shared CLI entrypoint. */
|
|
4
4
|
export declare function isWorkerHostSelector(value: string | undefined): value is string;
|
|
5
5
|
/** Called by CLI entrypoints whose main module dispatches worker argv selectors. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxiraos/pi-utils",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.13",
|
|
4
4
|
"description": "Shared utilities for pi packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"utilities",
|
|
@@ -46,12 +46,15 @@
|
|
|
46
46
|
"./*.js": "./src/*.ts"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
|
-
"check": "
|
|
49
|
+
"check": "oxlint . && oxfmt --check --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts' && bun run check:types",
|
|
50
50
|
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
51
|
-
"
|
|
51
|
+
"lint": "oxlint .",
|
|
52
|
+
"test": "bun test --parallel",
|
|
53
|
+
"fix": "oxlint --fix --fix-suggestions . && bun run fmt",
|
|
54
|
+
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
52
55
|
},
|
|
53
56
|
"dependencies": {
|
|
54
|
-
"@linxiraos/pi-natives": "1.1.
|
|
57
|
+
"@linxiraos/pi-natives": "1.1.13"
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
57
60
|
"@types/bun": "^1.3.14"
|
package/src/ar/entries.ts
CHANGED
|
@@ -73,7 +73,7 @@ export function resolveArchiveLinkPath(
|
|
|
73
73
|
): string {
|
|
74
74
|
let resolvedPath = archivePath;
|
|
75
75
|
const seen = new Set<string>();
|
|
76
|
-
for (let rewrites = 0; !seen.has(resolvedPath);
|
|
76
|
+
for (let rewrites = 0; !seen.has(resolvedPath);) {
|
|
77
77
|
seen.add(resolvedPath);
|
|
78
78
|
let replacement: string | undefined;
|
|
79
79
|
for (let end = resolvedPath.length; end > 0; end = resolvedPath.lastIndexOf("/", end - 1)) {
|
package/src/ar/iso.ts
CHANGED
|
@@ -324,7 +324,7 @@ function decodeIdentifier(identifier: Uint8Array, joliet: boolean): string {
|
|
|
324
324
|
|
|
325
325
|
function findSuspSkip(record: IsoRecord): number | undefined {
|
|
326
326
|
const bytes = record.systemUse;
|
|
327
|
-
for (let offset = 0; offset + 7 <= bytes.byteLength;
|
|
327
|
+
for (let offset = 0; offset + 7 <= bytes.byteLength;) {
|
|
328
328
|
const length = bytes[offset + 2]!;
|
|
329
329
|
if (length < 4 || offset + length > bytes.byteLength) return undefined;
|
|
330
330
|
if (
|
|
@@ -359,7 +359,7 @@ async function parseSusp(
|
|
|
359
359
|
let relocation: SuspData["relocation"];
|
|
360
360
|
for (let queueIndex = 0; queueIndex < queue.length; queueIndex++) {
|
|
361
361
|
const area = queue[queueIndex]!;
|
|
362
|
-
for (let offset = 0; offset + 4 <= area.byteLength;
|
|
362
|
+
for (let offset = 0; offset + 4 <= area.byteLength;) {
|
|
363
363
|
const signature = String.fromCharCode(area[offset]!, area[offset + 1]!);
|
|
364
364
|
const length = area[offset + 2]!;
|
|
365
365
|
const version = area[offset + 3]!;
|
|
@@ -466,7 +466,7 @@ async function readDirectoryRecords(
|
|
|
466
466
|
const end = safeAdd(start, record.size, "directory extent range");
|
|
467
467
|
const bytes = await budget.read(source, start, end, "directory extent");
|
|
468
468
|
const records: IsoRecord[] = [];
|
|
469
|
-
for (let offset = 0; offset < bytes.byteLength;
|
|
469
|
+
for (let offset = 0; offset < bytes.byteLength;) {
|
|
470
470
|
const sectorRemaining = blockSize - (offset % blockSize);
|
|
471
471
|
const length = bytes[offset]!;
|
|
472
472
|
if (length === 0) {
|
|
@@ -586,7 +586,7 @@ async function readIsoImpl(source: ByteSource, options: Parameters<FormatReader>
|
|
|
586
586
|
) {
|
|
587
587
|
suspSkip = findSuspSkip(records[0]!);
|
|
588
588
|
}
|
|
589
|
-
for (let index = 0; index < records.length;
|
|
589
|
+
for (let index = 0; index < records.length;) {
|
|
590
590
|
const record = records[index]!;
|
|
591
591
|
if (record.identifier.byteLength === 1 && (record.identifier[0] === 0 || record.identifier[0] === 1)) {
|
|
592
592
|
index++;
|
|
@@ -45,7 +45,7 @@ class Bits {
|
|
|
45
45
|
read(count: number): number {
|
|
46
46
|
if (count === 0) return 0;
|
|
47
47
|
let value = 0;
|
|
48
|
-
for (let left = count; left > 0;
|
|
48
|
+
for (let left = count; left > 0;) {
|
|
49
49
|
if (this.pos >= this.bytes.byteLength - 4) corrupt("truncated compressed data");
|
|
50
50
|
const take = Math.min(left, 8 - this.bit);
|
|
51
51
|
value = value * 2 ** take + ((this.bytes[this.pos]! >>> (8 - this.bit - take)) & (2 ** take - 1));
|
|
@@ -340,7 +340,7 @@ class VmBits {
|
|
|
340
340
|
|
|
341
341
|
read(count: number): number {
|
|
342
342
|
let value = 0;
|
|
343
|
-
for (let left = count; left > 0;
|
|
343
|
+
for (let left = count; left > 0;) {
|
|
344
344
|
const bytePosition = this.#position >>> 3;
|
|
345
345
|
if (bytePosition >= this.#bytes.byteLength) corrupt("truncated RarVM code");
|
|
346
346
|
const bitPosition = this.#position & 7;
|
|
@@ -393,7 +393,7 @@ function applyFilters(output: Uint8Array, filters: PendingFilter[]): void {
|
|
|
393
393
|
continue;
|
|
394
394
|
}
|
|
395
395
|
const compareOpcode = filter.type === 2 ? 0xe9 : 0xe8;
|
|
396
|
-
for (let position = 0; position + 4 < data.byteLength;
|
|
396
|
+
for (let position = 0; position + 4 < data.byteLength;) {
|
|
397
397
|
const opcode = data[position++]!;
|
|
398
398
|
if (opcode !== 0xe8 && opcode !== compareOpcode) continue;
|
|
399
399
|
const offset = position + (filter.fileOffset >>> 0);
|
|
@@ -437,7 +437,7 @@ function readLengthTable(bits: Bits, previous: Uint8Array, size: number): Uint8A
|
|
|
437
437
|
const preTable = new Huffman(20);
|
|
438
438
|
preTable.build(preLengths, 0, 20);
|
|
439
439
|
const lengths = new Uint8Array(size);
|
|
440
|
-
for (let position = 0; position < size;
|
|
440
|
+
for (let position = 0; position < size;) {
|
|
441
441
|
const symbol = preTable.decode(bits);
|
|
442
442
|
if (symbol < 16) {
|
|
443
443
|
lengths[position] = (symbol + previous[position]!) & 15;
|
|
@@ -33,7 +33,7 @@ class Bits {
|
|
|
33
33
|
if (n === 0) return 0;
|
|
34
34
|
if (n < 0 || n > 31) fail("invalid bit count");
|
|
35
35
|
let value = 0;
|
|
36
|
-
for (let left = n; left > 0;
|
|
36
|
+
for (let left = n; left > 0;) {
|
|
37
37
|
if (this.pos >= this.bytes.byteLength - 16) fail("truncated compressed data");
|
|
38
38
|
const take = Math.min(left, 8 - this.bit);
|
|
39
39
|
value = value * 2 ** take + ((this.bytes[this.pos]! >>> (8 - this.bit - take)) & (2 ** take - 1));
|
|
@@ -211,7 +211,7 @@ export class Rar5Decoder {
|
|
|
211
211
|
if ((flags & 0x80) === 0) return;
|
|
212
212
|
|
|
213
213
|
const levelLengths = new Uint8Array(20);
|
|
214
|
-
for (let i = 0; i < 20;
|
|
214
|
+
for (let i = 0; i < 20;) {
|
|
215
215
|
const length = bits.read(4);
|
|
216
216
|
if (length === 15) {
|
|
217
217
|
const zeros = bits.read(4);
|
|
@@ -226,7 +226,7 @@ export class Rar5Decoder {
|
|
|
226
226
|
level.build(levelLengths);
|
|
227
227
|
const tableLength = version === 1 ? TABLE_SIZE : TABLE_SIZE - 16;
|
|
228
228
|
const lengthsCompact = new Uint8Array(tableLength);
|
|
229
|
-
for (let i = 0; i < tableLength;
|
|
229
|
+
for (let i = 0; i < tableLength;) {
|
|
230
230
|
const symbol = level.decode(bits);
|
|
231
231
|
if (symbol < 16) {
|
|
232
232
|
lengthsCompact[i++] = symbol;
|
|
@@ -358,7 +358,7 @@ function applyFilters(output: Uint8Array, filters: Filter[]): void {
|
|
|
358
358
|
}
|
|
359
359
|
} else if (filter.type === 1 || filter.type === 2) {
|
|
360
360
|
const fileSize = 1 << 24;
|
|
361
|
-
for (let pos = 0; pos + 4 < data.byteLength;
|
|
361
|
+
for (let pos = 0; pos + 4 < data.byteLength;) {
|
|
362
362
|
const opcode = data[pos++]!;
|
|
363
363
|
if (opcode !== 0xe8 && (filter.type === 1 || opcode !== 0xe9)) continue;
|
|
364
364
|
const offset = (filter.start + pos) & (fileSize - 1);
|
package/src/ar/unix-ar.ts
CHANGED
|
@@ -197,7 +197,7 @@ export function readUnixArEntriesFromBuffer(bytes: Uint8Array, options: FormatRe
|
|
|
197
197
|
const records: RawArMember[] = [];
|
|
198
198
|
let longNames: Uint8Array | undefined;
|
|
199
199
|
let metadataSize = 0;
|
|
200
|
-
for (let position = SIGNATURE.length; position < bytes.byteLength;
|
|
200
|
+
for (let position = SIGNATURE.length; position < bytes.byteLength;) {
|
|
201
201
|
if (bytes.byteLength - position < HEADER_SIZE)
|
|
202
202
|
throw new ArchiveError("Invalid ar archive: truncated member header");
|
|
203
203
|
const header = parseHeader(readMemoryRange(bytes, position, position + HEADER_SIZE));
|
|
@@ -255,7 +255,7 @@ async function readUnixArImpl(source: ByteSource, options: FormatReadOptions): P
|
|
|
255
255
|
const records: RawArMember[] = [];
|
|
256
256
|
let longNames: Uint8Array | undefined;
|
|
257
257
|
let metadataSize = 0;
|
|
258
|
-
for (let position = SIGNATURE.length; position < source.size;
|
|
258
|
+
for (let position = SIGNATURE.length; position < source.size;) {
|
|
259
259
|
const headerBytes = await readExact(source, position, position + HEADER_SIZE, "member header");
|
|
260
260
|
const header = parseHeader(headerBytes);
|
|
261
261
|
metadataSize += HEADER_SIZE;
|
package/src/dates.ts
CHANGED
|
@@ -100,7 +100,7 @@ export function format(value: Date | number, pattern: string): string {
|
|
|
100
100
|
const date = asDate(value);
|
|
101
101
|
let result = "";
|
|
102
102
|
|
|
103
|
-
for (let index = 0; index < pattern.length;
|
|
103
|
+
for (let index = 0; index < pattern.length;) {
|
|
104
104
|
if (pattern[index] === "'") {
|
|
105
105
|
if (pattern[index + 1] === "'") {
|
|
106
106
|
result += "'";
|
package/src/dirs.ts
CHANGED
|
@@ -568,7 +568,7 @@ export function getLogsDir(): string {
|
|
|
568
568
|
return dirs.rootSubdir("logs", "state");
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
-
/** Get this process's dated log path (~/.zeta/logs/
|
|
571
|
+
/** Get this process's dated log path (~/.zeta/logs/zeta.YYYY-MM-DD.PID.log). */
|
|
572
572
|
export function getLogPath(date = new Date(), pid = process.pid): string {
|
|
573
573
|
return path.join(getLogsDir(), `${APP_NAME}.${date.toISOString().slice(0, 10)}.${pid}.log`);
|
|
574
574
|
}
|
|
@@ -893,12 +893,12 @@ export function getTerminalSessionsDir(agentDir?: string): string {
|
|
|
893
893
|
return dirs.agentSubdir(agentDir, "terminal-sessions", "state");
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
-
/** Get the crash log path (~/.zeta/agent/
|
|
896
|
+
/** Get the crash log path (~/.zeta/agent/zeta-crash.log). */
|
|
897
897
|
export function getCrashLogPath(agentDir?: string): string {
|
|
898
|
-
return dirs.agentSubdir(agentDir, "
|
|
898
|
+
return dirs.agentSubdir(agentDir, "zeta-crash.log", "state");
|
|
899
899
|
}
|
|
900
900
|
|
|
901
|
-
/** Get the debug log path (~/.zeta/agent/
|
|
901
|
+
/** Get the debug log path (~/.zeta/agent/zeta-debug.log). */
|
|
902
902
|
export function getDebugLogPath(agentDir?: string): string {
|
|
903
903
|
return dirs.agentSubdir(agentDir, `${APP_NAME}-debug.log`, "state");
|
|
904
904
|
}
|
|
@@ -1018,13 +1018,13 @@ let cachedInstallId: string | null = null;
|
|
|
1018
1018
|
const INSTALL_ID_FILE = "install-id";
|
|
1019
1019
|
/**
|
|
1020
1020
|
* Application label for usage attribution (`OMP_APP_NAME`), defaulting to
|
|
1021
|
-
* `
|
|
1021
|
+
* `zeta`. Embedders that drive zeta programmatically (robomp, CI bots, …) set
|
|
1022
1022
|
* the env var so broker-side per-client burn tracking can answer "what did
|
|
1023
1023
|
* app X use" instead of folding everything into one install-wide bucket.
|
|
1024
1024
|
*/
|
|
1025
1025
|
export function getAppName(): string {
|
|
1026
1026
|
const value = process.env.OMP_APP_NAME?.trim();
|
|
1027
|
-
return value ? value : "
|
|
1027
|
+
return value ? value : "zeta";
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
1030
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
package/src/docx/converter.ts
CHANGED
|
@@ -530,7 +530,7 @@ function renderList(
|
|
|
530
530
|
|
|
531
531
|
function renderBlocks(blocks: readonly Block[]): string {
|
|
532
532
|
let html = "";
|
|
533
|
-
for (let index = 0; index < blocks.length;
|
|
533
|
+
for (let index = 0; index < blocks.length;) {
|
|
534
534
|
const block = blocks[index];
|
|
535
535
|
if (block.kind === "html") {
|
|
536
536
|
html += block.html;
|
|
@@ -549,7 +549,7 @@ function renderBlocks(blocks: readonly Block[]): string {
|
|
|
549
549
|
listItems.push(candidate);
|
|
550
550
|
index++;
|
|
551
551
|
}
|
|
552
|
-
for (let itemIndex = 0; itemIndex < listItems.length;
|
|
552
|
+
for (let itemIndex = 0; itemIndex < listItems.length;) {
|
|
553
553
|
const rendered = renderList(listItems, itemIndex, listItems[itemIndex].list?.level ?? 0);
|
|
554
554
|
html += rendered.html;
|
|
555
555
|
itemIndex = rendered.next;
|
package/src/fetch-retry.ts
CHANGED
|
@@ -16,8 +16,8 @@ const WILL_RESET_IN_PATTERN = /(?:will\s+)?reset in\s+~?\s*([0-9.]+)\s*(ms|sec|s
|
|
|
16
16
|
const WILL_RESET_AT_PATTERN =
|
|
17
17
|
/(?:will\s+)?reset at\s+([0-9]{4}-[0-9]{2}-[0-9]{2}[ T][0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?(?:Z|[+-][0-9]{2}:?[0-9]{2})?)/i;
|
|
18
18
|
const CN_RESET_AT_PATTERN = /将在\s*([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2})\s*重置/;
|
|
19
|
-
// "retry-after-ms=98497000"
|
|
20
|
-
const RETRY_AFTER_MS_BODY_PATTERN = /\bretry-after-ms
|
|
19
|
+
// "retry-after-ms=98497000" / "retry-after-ms: 7200000" / "retry-after-ms = 7200000"
|
|
20
|
+
const RETRY_AFTER_MS_BODY_PATTERN = /\bretry-after-ms\s*[:=]\s*([0-9]+)\b/i;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Server-suggested retry delay extraction. Merges the patterns historically used
|
|
@@ -35,10 +35,12 @@ const RETRY_AFTER_MS_BODY_PATTERN = /\bretry-after-ms=([0-9]+)\b/i;
|
|
|
35
35
|
* - `Please retry in 250ms` / `Please retry in 12s`
|
|
36
36
|
* - `"retryDelay": "34.074824224s"` (JSON error detail field)
|
|
37
37
|
* - `try again in 250ms` / `try again in 12s` / `try again in 5 min` / `try again in ~158 min`
|
|
38
|
-
* - `retry-after-ms=98497000`
|
|
38
|
+
* - `retry-after-ms=98497000` / `retry-after-ms: 7200000` / `retry-after-ms = 7200000`
|
|
39
39
|
* - `Your limit will reset at 2026-09-01 09:44:51` / `将在 2026-09-01 09:44:51 重置`
|
|
40
40
|
*
|
|
41
|
-
* Returns `undefined` if no signal is found
|
|
41
|
+
* Returns `undefined` if no signal is found, or `0` when the provider
|
|
42
|
+
* explicitly asks for an immediate retry (`retry-after…=0`, or an absolute
|
|
43
|
+
* reset timestamp that has already elapsed).
|
|
42
44
|
*/
|
|
43
45
|
export function extractRetryHint(source: Response | Headers | null | undefined, body?: string): number | undefined {
|
|
44
46
|
const headers = source instanceof Headers ? source : (source?.headers ?? undefined);
|
|
@@ -83,6 +85,28 @@ export function extractRetryHint(source: Response | Headers | null | undefined,
|
|
|
83
85
|
|
|
84
86
|
if (!body) return undefined;
|
|
85
87
|
|
|
88
|
+
// A body can carry several timing signals at once: the account-reset
|
|
89
|
+
// window plus header timing folded into the message text (already the
|
|
90
|
+
// max across response headers — see getRetryAfterMsFromHeaders in
|
|
91
|
+
// pi-ai). Honor the longest: retrying before either window clears
|
|
92
|
+
// re-hits a still-blocked credential and burns the retry budget.
|
|
93
|
+
let longestMs: number | undefined;
|
|
94
|
+
// A parsed-but-non-positive signal is a provider "retry now": an explicit
|
|
95
|
+
// `retry-after…=0` or an absolute reset that already elapsed. It must
|
|
96
|
+
// survive as 0 rather than collapse into "no hint found" — consumers
|
|
97
|
+
// substitute a heuristic wait (30-minute quota guess, default backoff)
|
|
98
|
+
// when the parse returns undefined, which would sleep a session the
|
|
99
|
+
// provider told to retry immediately.
|
|
100
|
+
let retryNow = false;
|
|
101
|
+
const consider = (ms: number | undefined): void => {
|
|
102
|
+
if (ms !== undefined && ms > 0 && (longestMs === undefined || ms > longestMs)) longestMs = ms;
|
|
103
|
+
};
|
|
104
|
+
const considerClamped = (ms: number | undefined): void => {
|
|
105
|
+
if (ms === undefined) return;
|
|
106
|
+
if (ms > 0) consider(ms);
|
|
107
|
+
else retryNow = true;
|
|
108
|
+
};
|
|
109
|
+
|
|
86
110
|
const quotaMatch = QUOTA_RESET_PATTERN.exec(body);
|
|
87
111
|
if (quotaMatch) {
|
|
88
112
|
const hours = quotaMatch[1] ? Number.parseInt(quotaMatch[1], 10) : 0;
|
|
@@ -90,7 +114,7 @@ export function extractRetryHint(source: Response | Headers | null | undefined,
|
|
|
90
114
|
const seconds = Number.parseFloat(quotaMatch[3]!);
|
|
91
115
|
if (!Number.isNaN(seconds)) {
|
|
92
116
|
const totalMs = ((hours * 60 + minutes) * 60 + seconds) * 1000;
|
|
93
|
-
|
|
117
|
+
consider(totalMs > 0 ? totalMs : undefined);
|
|
94
118
|
}
|
|
95
119
|
}
|
|
96
120
|
for (const pattern of [WILL_RESET_AT_PATTERN, CN_RESET_AT_PATTERN]) {
|
|
@@ -101,27 +125,23 @@ export function extractRetryHint(source: Response | Headers | null | undefined,
|
|
|
101
125
|
const hasOffset = /(?:Z|[+-][0-9]{2}:?[0-9]{2})$/i.test(normalized);
|
|
102
126
|
const parsed = Date.parse(hasOffset ? normalized : `${normalized}Z`);
|
|
103
127
|
if (!Number.isNaN(parsed) && parsed > Date.now()) {
|
|
104
|
-
|
|
128
|
+
consider(parsed - Date.now());
|
|
105
129
|
}
|
|
106
130
|
}
|
|
107
131
|
}
|
|
108
|
-
// Account-reset hints ("will reset in …") take precedence over short
|
|
109
|
-
// retry hints ("please retry in 5s"): a body carrying both must honour the
|
|
110
|
-
// longer account window, not the shorter generic one. QUOTA_RESET_PATTERN
|
|
111
|
-
// ("reset after …") above already runs first and stays first.
|
|
112
132
|
const accountResetMatch = WILL_RESET_IN_PATTERN.exec(body);
|
|
113
133
|
if (accountResetMatch?.[1]) {
|
|
114
134
|
const value = Number.parseFloat(accountResetMatch[1]);
|
|
115
135
|
if (Number.isFinite(value) && value > 0) {
|
|
116
136
|
const unitMs = unitToMs(accountResetMatch[2]!);
|
|
117
|
-
if (unitMs !== undefined)
|
|
137
|
+
if (unitMs !== undefined) consider(value * unitMs);
|
|
118
138
|
}
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
const retryAfterMsMatch = RETRY_AFTER_MS_BODY_PATTERN.exec(body);
|
|
122
142
|
if (retryAfterMsMatch?.[1]) {
|
|
123
143
|
const ms = Number(retryAfterMsMatch[1]);
|
|
124
|
-
if (Number.isFinite(ms)
|
|
144
|
+
if (Number.isFinite(ms)) considerClamped(ms);
|
|
125
145
|
}
|
|
126
146
|
|
|
127
147
|
for (const pattern of [PLEASE_RETRY_PATTERN, RETRY_DELAY_FIELD_PATTERN, TRY_AGAIN_PATTERN]) {
|
|
@@ -130,11 +150,45 @@ export function extractRetryHint(source: Response | Headers | null | undefined,
|
|
|
130
150
|
const value = Number.parseFloat(match[1]);
|
|
131
151
|
if (Number.isFinite(value) && value > 0) {
|
|
132
152
|
const unitMs = unitToMs(match[2]!);
|
|
133
|
-
if (unitMs !== undefined)
|
|
153
|
+
if (unitMs !== undefined) consider(value * unitMs);
|
|
134
154
|
}
|
|
135
155
|
}
|
|
136
156
|
}
|
|
137
|
-
|
|
157
|
+
|
|
158
|
+
// Legacy text forms (also honored by older local parsers): a plain
|
|
159
|
+
// `retry-after` delta in seconds or as an HTTP date, and
|
|
160
|
+
// `x-ratelimit-reset[-ms]` counters. They compete in the same maximum —
|
|
161
|
+
// a longer legacy hint must not lose to a shorter reset phrase. Their
|
|
162
|
+
// non-positive readings (explicit zero, elapsed epoch/date) are the
|
|
163
|
+
// clamped retry-now signals above.
|
|
164
|
+
const retryAfterMatch = /retry-after\s*[:=]\s*([^\s,;]+)/i.exec(body);
|
|
165
|
+
if (retryAfterMatch) {
|
|
166
|
+
const value = retryAfterMatch[1]!;
|
|
167
|
+
const seconds = Number(value);
|
|
168
|
+
if (Number.isFinite(seconds)) {
|
|
169
|
+
considerClamped(seconds * 1000);
|
|
170
|
+
} else {
|
|
171
|
+
const dateMs = Date.parse(value);
|
|
172
|
+
if (!Number.isNaN(dateMs)) considerClamped(dateMs - Date.now());
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const resetMsMatch = /x-ratelimit-reset-ms\s*[:=]\s*(\d+)/i.exec(body);
|
|
177
|
+
if (resetMsMatch) {
|
|
178
|
+
const resetMs = Number(resetMsMatch[1]);
|
|
179
|
+
if (!Number.isNaN(resetMs)) {
|
|
180
|
+
considerClamped(resetMs > 1_000_000_000_000 ? resetMs - Date.now() : resetMs);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const resetMatch = /x-ratelimit-reset\s*[:=]\s*(\d+)/i.exec(body);
|
|
185
|
+
if (resetMatch) {
|
|
186
|
+
const resetSeconds = Number(resetMatch[1]);
|
|
187
|
+
if (!Number.isNaN(resetSeconds)) {
|
|
188
|
+
considerClamped(resetSeconds > 1_000_000_000 ? resetSeconds * 1000 - Date.now() : resetSeconds * 1000);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return longestMs ?? (retryNow ? 0 : undefined);
|
|
138
192
|
}
|
|
139
193
|
|
|
140
194
|
function unitToMs(unit: string): number | undefined {
|
|
@@ -391,7 +445,10 @@ export function isRetryableStatus(status: number): boolean {
|
|
|
391
445
|
* proxies surface these for any HTTP/2 stream reset.
|
|
392
446
|
*/
|
|
393
447
|
export function isUnexpectedSocketCloseMessage(message: string): boolean {
|
|
394
|
-
return
|
|
448
|
+
return (
|
|
449
|
+
/\b(?:the\s+)?socket connection (?:was )?closed unexpectedly\b/i.test(message) ||
|
|
450
|
+
/^(?:error:\s*)?socket is closed\.?$/i.test(message.trim())
|
|
451
|
+
);
|
|
395
452
|
}
|
|
396
453
|
|
|
397
454
|
const TRANSIENT_MESSAGE_PATTERN =
|
package/src/which.ts
CHANGED
|
@@ -192,8 +192,13 @@ function darwinWhich(command: string, options?: Bun.WhichOptions): string | null
|
|
|
192
192
|
return null;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
// Which function that incorporates Darwin Xcode logic if platform reports as 'darwin'
|
|
196
|
-
|
|
195
|
+
// Which function that incorporates Darwin Xcode logic if platform reports as 'darwin'.
|
|
196
|
+
// Look `Bun.which` up per call rather than capturing it at import, so a `Bun.which`
|
|
197
|
+
// stub installed later (the per-test seam) is honoured on every platform.
|
|
198
|
+
export const whichFresh =
|
|
199
|
+
os.platform() === "darwin"
|
|
200
|
+
? darwinWhich
|
|
201
|
+
: (command: string, options?: Bun.WhichOptions): string | null => Bun.which(command, options);
|
|
197
202
|
|
|
198
203
|
// Derive stable cache key from command and lookup options
|
|
199
204
|
function cacheKey(command: string, options?: Bun.WhichOptions): CacheKey {
|
package/src/worker-host.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { stripWindowsExtendedLengthPathPrefix } from "./path";
|
|
2
2
|
|
|
3
3
|
/** Prefix reserved for argv selectors dispatched by the shared CLI worker host. */
|
|
4
|
-
export const WORKER_HOST_SELECTOR_PREFIX = "
|
|
4
|
+
export const WORKER_HOST_SELECTOR_PREFIX = "__zeta_worker_";
|
|
5
5
|
|
|
6
6
|
/** Whether an argv value selects a worker hosted by the shared CLI entrypoint. */
|
|
7
7
|
export function isWorkerHostSelector(value: string | undefined): value is string {
|
|
@@ -10,7 +10,7 @@ export function isWorkerHostSelector(value: string | undefined): value is string
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Main-module path declared by self-dispatching CLI entrypoints — entries
|
|
13
|
-
* whose top-level argv handling routes hidden `
|
|
13
|
+
* whose top-level argv handling routes hidden `__zeta_**` worker selectors.
|
|
14
14
|
* Worker spawn sites re-enter this module via `new Worker(entry, { argv })`,
|
|
15
15
|
* so every distribution (source, npm bundle, compiled binary) needs exactly
|
|
16
16
|
* one JavaScript entrypoint. Never set under `bun test`, SDK embedding, or
|