@centerforagenticai/pi-multi-account 0.1.1
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/LICENSE +21 -0
- package/NOTICE +29 -0
- package/README.md +999 -0
- package/config/models/pi-multi-account.v1.json +32 -0
- package/config/subscription-plans.v1.json +122 -0
- package/package.json +76 -0
- package/packages/pi-anthropic-oauth/LICENSE +21 -0
- package/packages/pi-anthropic-oauth/package.json +54 -0
- package/packages/pi-anthropic-oauth/src/auth.ts +396 -0
- package/packages/pi-anthropic-oauth/src/context.ts +116 -0
- package/packages/pi-anthropic-oauth/src/convert.ts +303 -0
- package/packages/pi-anthropic-oauth/src/index.ts +37 -0
- package/packages/pi-anthropic-oauth/src/prompt.ts +137 -0
- package/packages/pi-anthropic-oauth/src/stream.ts +476 -0
- package/packages/pi-antigravity/LICENSE +21 -0
- package/packages/pi-antigravity/package.json +77 -0
- package/packages/pi-antigravity/src/auth/index.ts +14 -0
- package/packages/pi-antigravity/src/auth/oauth.ts +442 -0
- package/packages/pi-antigravity/src/client/client.ts +561 -0
- package/packages/pi-antigravity/src/client/index.ts +1 -0
- package/packages/pi-antigravity/src/context.ts +110 -0
- package/packages/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
- package/packages/pi-antigravity/src/diagnostics/index.ts +1 -0
- package/packages/pi-antigravity/src/image/image.ts +336 -0
- package/packages/pi-antigravity/src/image/index.ts +1 -0
- package/packages/pi-antigravity/src/index.ts +280 -0
- package/packages/pi-antigravity/src/models/discovery.ts +154 -0
- package/packages/pi-antigravity/src/models/grouping.ts +424 -0
- package/packages/pi-antigravity/src/models/index.ts +3 -0
- package/packages/pi-antigravity/src/models/models.ts +500 -0
- package/packages/pi-antigravity/src/stream/index.ts +1 -0
- package/packages/pi-antigravity/src/stream/stream.ts +1478 -0
- package/packages/pi-antigravity/src/types/enums.ts +42 -0
- package/packages/pi-antigravity/src/types/index.ts +2 -0
- package/packages/pi-antigravity/src/types/types.ts +292 -0
- package/packages/pi-antigravity/src/usage/index.ts +1 -0
- package/packages/pi-antigravity/src/usage/usage.ts +416 -0
- package/packages/pi-antigravity/src/utils/http.ts +91 -0
- package/packages/pi-antigravity/src/utils/index.ts +3 -0
- package/packages/pi-antigravity/src/utils/security.ts +73 -0
- package/packages/pi-antigravity/src/utils/util.ts +132 -0
- package/scripts/multi-account.mjs +44 -0
- package/src/account-labels.ts +223 -0
- package/src/account-plan-assignment.ts +340 -0
- package/src/account-rate-history.ts +372 -0
- package/src/anthropic-adaptive-stream.ts +531 -0
- package/src/anthropic-alias-stream.ts +140 -0
- package/src/anthropic-context-compat.ts +80 -0
- package/src/api-pricing.ts +579 -0
- package/src/bounded-file-lines.ts +97 -0
- package/src/catalog-rebinding.ts +177 -0
- package/src/catalog-registration-probe.ts +111 -0
- package/src/codex-adapter.ts +345 -0
- package/src/codex-model-defaults.ts +785 -0
- package/src/command-completions.ts +404 -0
- package/src/commands.ts +2000 -0
- package/src/compaction.ts +14 -0
- package/src/config.ts +1317 -0
- package/src/continuation.ts +569 -0
- package/src/cooldowns.ts +110 -0
- package/src/cost-digest-store.ts +332 -0
- package/src/cost-digest.ts +1044 -0
- package/src/cost-history.ts +251 -0
- package/src/cost-period-closer.ts +160 -0
- package/src/cost-report-json.ts +318 -0
- package/src/cost-report-reader.ts +368 -0
- package/src/cost-report-render.ts +207 -0
- package/src/cost-report.ts +1104 -0
- package/src/coverage-attestation.ts +397 -0
- package/src/credential-lifecycle.ts +169 -0
- package/src/credential-refresh.ts +248 -0
- package/src/declaration-notice-marker.ts +238 -0
- package/src/diagnostic-store.ts +276 -0
- package/src/diagnostics.ts +309 -0
- package/src/discovery.ts +471 -0
- package/src/duration.ts +13 -0
- package/src/error-classification.ts +256 -0
- package/src/fuzzy.ts +15 -0
- package/src/group-policy.ts +81 -0
- package/src/history-store.ts +897 -0
- package/src/index.ts +5572 -0
- package/src/lifecycle.ts +378 -0
- package/src/logical-dispatch.ts +279 -0
- package/src/logical-model-selector.ts +254 -0
- package/src/logical-model-switcher.ts +430 -0
- package/src/logical-provider-attribution.ts +544 -0
- package/src/logical-provider.ts +1237 -0
- package/src/logical-route-indicator.ts +215 -0
- package/src/machine-lease.ts +445 -0
- package/src/model-support.ts +66 -0
- package/src/models-declaration.ts +1091 -0
- package/src/openai-adapter.ts +117 -0
- package/src/openrouter-budget.ts +304 -0
- package/src/openrouter-fallback.ts +146 -0
- package/src/period-boundaries.ts +376 -0
- package/src/pi-anthropic-oauth.d.ts +6 -0
- package/src/preflight.ts +253 -0
- package/src/pricing-cache.ts +235 -0
- package/src/project-identity.ts +100 -0
- package/src/provider-registration.ts +942 -0
- package/src/rate-formula.ts +163 -0
- package/src/recovery-engine.ts +853 -0
- package/src/recovery-output.ts +837 -0
- package/src/recovery-plan.ts +239 -0
- package/src/report-range.ts +203 -0
- package/src/route-resolver.ts +789 -0
- package/src/routing-config-transaction.ts +232 -0
- package/src/routing.ts +1163 -0
- package/src/runtime-state.ts +630 -0
- package/src/session-account-groups.ts +284 -0
- package/src/session-restore.ts +287 -0
- package/src/shared-usage.ts +1392 -0
- package/src/standalone-cli.ts +720 -0
- package/src/status-view.ts +578 -0
- package/src/subscription-plan-catalog.ts +346 -0
- package/src/tier-model-resolver.ts +46 -0
- package/src/upstream-anthropic.ts +315 -0
- package/src/upstream-antigravity.ts +327 -0
- package/src/usage-fetch.ts +1634 -0
- package/src/usage.ts +1026 -0
- package/src/vendor.ts +87 -0
- package/src/warmer.ts +231 -0
- package/src/watchdog.ts +219 -0
- package/src/window-history.ts +270 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
closeSync,
|
|
4
|
+
fsyncSync,
|
|
5
|
+
ftruncateSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
openSync,
|
|
8
|
+
readSync,
|
|
9
|
+
statSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { MAX_METHOD_LIMITS_LENGTH } from "./api-pricing.js";
|
|
15
|
+
import { iterateBoundedFileLines } from "./bounded-file-lines.js";
|
|
16
|
+
import {
|
|
17
|
+
costDigestId,
|
|
18
|
+
type ApiEquivalentMethod,
|
|
19
|
+
type CostDigestRow,
|
|
20
|
+
} from "./cost-digest.js";
|
|
21
|
+
import type { MachineLeaseHandle } from "./machine-lease.js";
|
|
22
|
+
import { PERIOD_TYPES, type PeriodType } from "./period-boundaries.js";
|
|
23
|
+
import { isProjectKey } from "./project-identity.js";
|
|
24
|
+
|
|
25
|
+
const MAX_ROW_BYTES = 8_192;
|
|
26
|
+
const MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
27
|
+
const ID_PATTERN = /^cost-digest-[0-9a-f]{32}$/;
|
|
28
|
+
|
|
29
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
30
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function finiteNonNegative(value: unknown): value is number {
|
|
34
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function safeCount(value: unknown): value is number {
|
|
38
|
+
return Number.isSafeInteger(value) && finiteNonNegative(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function boundedString(value: unknown, maxLength: number): value is string {
|
|
42
|
+
return (
|
|
43
|
+
typeof value === "string" &&
|
|
44
|
+
value.length > 0 &&
|
|
45
|
+
value.length <= maxLength &&
|
|
46
|
+
!value.includes("\0")
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function validNumberArray(value: unknown): value is readonly number[] {
|
|
51
|
+
return (
|
|
52
|
+
Array.isArray(value) &&
|
|
53
|
+
value.length <= 512 &&
|
|
54
|
+
value.every(finiteNonNegative)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function validStringArray(value: unknown): value is readonly string[] {
|
|
59
|
+
return (
|
|
60
|
+
Array.isArray(value) &&
|
|
61
|
+
value.length <= 512 &&
|
|
62
|
+
value.every((entry) => boundedString(entry, 200))
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const METHOD_SOURCES = [
|
|
67
|
+
"pi-installed-catalog",
|
|
68
|
+
"openrouter",
|
|
69
|
+
"legacy-day-aggregate",
|
|
70
|
+
] as const;
|
|
71
|
+
|
|
72
|
+
function validMethod(value: unknown): boolean {
|
|
73
|
+
return (
|
|
74
|
+
isRecord(value) &&
|
|
75
|
+
(METHOD_SOURCES as readonly string[]).includes(value.source as string) &&
|
|
76
|
+
boundedString(value.catalogVersion, 200) &&
|
|
77
|
+
(value.tierInputTokensAbove === undefined ||
|
|
78
|
+
finiteNonNegative(value.tierInputTokensAbove)) &&
|
|
79
|
+
boundedString(value.limits, MAX_METHOD_LIMITS_LENGTH)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Absent on every row persisted before per-response tier pricing existed. */
|
|
84
|
+
function validMethods(value: unknown): boolean {
|
|
85
|
+
return (
|
|
86
|
+
value === undefined ||
|
|
87
|
+
(Array.isArray(value) && value.length <= 16 && value.every(validMethod))
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function validApiEquivalent(value: unknown): boolean {
|
|
92
|
+
if (
|
|
93
|
+
!isRecord(value) ||
|
|
94
|
+
(value.status !== "priced" && value.status !== "unpriced") ||
|
|
95
|
+
!validNumberArray(value.rateAsOfMs) ||
|
|
96
|
+
!validMethods(value.methods)
|
|
97
|
+
) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return value.status === "priced"
|
|
101
|
+
? finiteNonNegative(value.estimatedUsd) &&
|
|
102
|
+
validStringArray(value.sourceModelIds)
|
|
103
|
+
: boundedString(value.reason, 128);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function validRow(value: unknown): value is CostDigestRow {
|
|
107
|
+
if (!isRecord(value) || !isRecord(value.tokens)) return false;
|
|
108
|
+
return (
|
|
109
|
+
value.schemaVersion === 1 &&
|
|
110
|
+
value.kind === "cost-period-digest" &&
|
|
111
|
+
typeof value.id === "string" &&
|
|
112
|
+
ID_PATTERN.test(value.id) &&
|
|
113
|
+
typeof value.projectKey === "string" &&
|
|
114
|
+
isProjectKey(value.projectKey) &&
|
|
115
|
+
boundedString(value.canonicalAccountId, 128) &&
|
|
116
|
+
boundedString(value.requestedModel, 200) &&
|
|
117
|
+
typeof value.periodType === "string" &&
|
|
118
|
+
(PERIOD_TYPES as readonly string[]).includes(value.periodType) &&
|
|
119
|
+
finiteNonNegative(value.periodStartMs) &&
|
|
120
|
+
finiteNonNegative(value.periodEndMs) &&
|
|
121
|
+
value.periodEndMs > value.periodStartMs &&
|
|
122
|
+
finiteNonNegative(value.closedAtMs) &&
|
|
123
|
+
(value.source === "raw" ||
|
|
124
|
+
value.source === "day-rollup" ||
|
|
125
|
+
value.source === "month-rollup") &&
|
|
126
|
+
safeCount(value.childRowCount) &&
|
|
127
|
+
safeCount(value.observationCount) &&
|
|
128
|
+
(value.coverage === "complete" ||
|
|
129
|
+
value.coverage === "partial" ||
|
|
130
|
+
value.coverage === "unknown") &&
|
|
131
|
+
safeCount(value.tokens.input) &&
|
|
132
|
+
safeCount(value.tokens.output) &&
|
|
133
|
+
safeCount(value.tokens.cacheRead) &&
|
|
134
|
+
safeCount(value.tokens.cacheWrite) &&
|
|
135
|
+
safeCount(value.tokens.cacheWrite1h) &&
|
|
136
|
+
value.tokens.cacheWrite1h <= value.tokens.cacheWrite &&
|
|
137
|
+
finiteNonNegative(value.retainedCostUsd) &&
|
|
138
|
+
validApiEquivalent(value.apiEquivalent) &&
|
|
139
|
+
value.id ===
|
|
140
|
+
costDigestId(
|
|
141
|
+
value.projectKey,
|
|
142
|
+
value.canonicalAccountId,
|
|
143
|
+
value.requestedModel,
|
|
144
|
+
value.periodType as PeriodType,
|
|
145
|
+
value.periodStartMs,
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function readRows(path: string): CostDigestRow[] {
|
|
151
|
+
const rows: CostDigestRow[] = [];
|
|
152
|
+
const ids = new Set<string>();
|
|
153
|
+
for (const line of iterateBoundedFileLines(path, {
|
|
154
|
+
maxLineBytes: MAX_ROW_BYTES,
|
|
155
|
+
includeIncompleteFinalLine: false,
|
|
156
|
+
})) {
|
|
157
|
+
if (line.length === 0) continue;
|
|
158
|
+
try {
|
|
159
|
+
const candidate = JSON.parse(line) as unknown;
|
|
160
|
+
if (!validRow(candidate) || ids.has(candidate.id)) continue;
|
|
161
|
+
rows.push(candidate);
|
|
162
|
+
ids.add(candidate.id);
|
|
163
|
+
} catch {
|
|
164
|
+
// A malformed complete row is isolated from every other row.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return rows;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function repairTornTail(path: string): boolean {
|
|
171
|
+
let descriptor: number | undefined;
|
|
172
|
+
try {
|
|
173
|
+
const stats = statSync(path);
|
|
174
|
+
if (!stats.isFile() || stats.size > MAX_FILE_BYTES) return false;
|
|
175
|
+
if (stats.size === 0) return true;
|
|
176
|
+
descriptor = openSync(path, "r+");
|
|
177
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
178
|
+
let cursor = stats.size;
|
|
179
|
+
let completeBytes = 0;
|
|
180
|
+
while (cursor > 0) {
|
|
181
|
+
const requested = Math.min(buffer.length, cursor);
|
|
182
|
+
const start = cursor - requested;
|
|
183
|
+
const bytesRead = readSync(descriptor, buffer, 0, requested, start);
|
|
184
|
+
for (let index = bytesRead - 1; index >= 0; index -= 1) {
|
|
185
|
+
if (buffer[index] === 0x0a) {
|
|
186
|
+
completeBytes = start + index + 1;
|
|
187
|
+
cursor = 0;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (completeBytes > 0) break;
|
|
192
|
+
cursor = start;
|
|
193
|
+
}
|
|
194
|
+
if (completeBytes === stats.size) return true;
|
|
195
|
+
ftruncateSync(descriptor, completeBytes);
|
|
196
|
+
fsyncSync(descriptor);
|
|
197
|
+
return true;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
200
|
+
} finally {
|
|
201
|
+
if (descriptor !== undefined) closeSync(descriptor);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function defaultCostDigestPaths(): {
|
|
206
|
+
readonly path: string;
|
|
207
|
+
readonly lockPath: string;
|
|
208
|
+
} {
|
|
209
|
+
const agentDirectory =
|
|
210
|
+
process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
211
|
+
// Package storage identity stays decoupled from the logical provider ID.
|
|
212
|
+
const directory = join(agentDirectory, "pi-multi-account");
|
|
213
|
+
return {
|
|
214
|
+
path: join(directory, "cost-period-digest.ndjson"),
|
|
215
|
+
lockPath: join(directory, "cost-period-digest.lock"),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Disclosed for every digest row persisted before per-response tier pricing
|
|
221
|
+
* existed: those rows have no `apiEquivalent.methods` on disk. This constant
|
|
222
|
+
* is never written back; it only labels a legacy row when it is read.
|
|
223
|
+
*/
|
|
224
|
+
export const LEGACY_DAY_AGGREGATE_METHOD: ApiEquivalentMethod = Object.freeze({
|
|
225
|
+
source: "legacy-day-aggregate",
|
|
226
|
+
catalogVersion: "legacy",
|
|
227
|
+
tierInputTokensAbove: undefined,
|
|
228
|
+
limits:
|
|
229
|
+
"Priced by summing this day's tokens under one rate lookup before per-response tier pricing existed; a day mixing tier thresholds may be priced less precisely than tier-aware rows. This closed row is retained unchanged.",
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Read-only disclosure of the pricing method(s) behind one persisted row.
|
|
234
|
+
* Never recomputes or overwrites `row.apiEquivalent`; a row missing
|
|
235
|
+
* `methods` (every row closed before this metadata existed) is labeled
|
|
236
|
+
* with the fixed legacy descriptor above instead.
|
|
237
|
+
*/
|
|
238
|
+
export function digestRowMethods(
|
|
239
|
+
row: CostDigestRow,
|
|
240
|
+
): readonly ApiEquivalentMethod[] {
|
|
241
|
+
const methods = row.apiEquivalent.methods;
|
|
242
|
+
return methods !== undefined && methods.length > 0
|
|
243
|
+
? methods
|
|
244
|
+
: [LEGACY_DAY_AGGREGATE_METHOD];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface DisclosedCostDigestRow {
|
|
248
|
+
readonly row: CostDigestRow;
|
|
249
|
+
readonly methods: readonly ApiEquivalentMethod[];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Pair a persisted row with its disclosed method(s) without touching its stored amount. */
|
|
253
|
+
export function describeCostDigestRow(
|
|
254
|
+
row: CostDigestRow,
|
|
255
|
+
): DisclosedCostDigestRow {
|
|
256
|
+
return { row, methods: digestRowMethods(row) };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export class CostDigestStore {
|
|
260
|
+
readonly #path: string;
|
|
261
|
+
|
|
262
|
+
constructor(options: { readonly path: string }) {
|
|
263
|
+
this.#path = options.path;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
read(): readonly CostDigestRow[] {
|
|
267
|
+
try {
|
|
268
|
+
const stats = statSync(this.#path);
|
|
269
|
+
if (!stats.isFile() || stats.size < 1 || stats.size > MAX_FILE_BYTES) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
return readRows(this.#path);
|
|
273
|
+
} catch {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** `read()` paired with each row's disclosed pricing method(s); never recomputes a stored amount. */
|
|
279
|
+
readDisclosed(): readonly DisclosedCostDigestRow[] {
|
|
280
|
+
return this.read().map(describeCostDigestRow);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
appendImmutable(
|
|
284
|
+
rows: readonly CostDigestRow[],
|
|
285
|
+
lease: MachineLeaseHandle,
|
|
286
|
+
): number {
|
|
287
|
+
if (rows.length === 0 || !lease.renew()) return 0;
|
|
288
|
+
if (!repairTornTail(this.#path)) return 0;
|
|
289
|
+
const existingIds = new Set(this.read().map((row) => row.id));
|
|
290
|
+
const pending: CostDigestRow[] = [];
|
|
291
|
+
for (const row of rows) {
|
|
292
|
+
if (!validRow(row) || existingIds.has(row.id)) continue;
|
|
293
|
+
const encoded = JSON.stringify(row);
|
|
294
|
+
if (Buffer.byteLength(encoded, "utf8") > MAX_ROW_BYTES) continue;
|
|
295
|
+
pending.push(row);
|
|
296
|
+
existingIds.add(row.id);
|
|
297
|
+
}
|
|
298
|
+
if (pending.length === 0 || !lease.renew()) return 0;
|
|
299
|
+
const raw = pending.map((row) => JSON.stringify(row)).join("\n") + "\n";
|
|
300
|
+
try {
|
|
301
|
+
const currentBytes = statSync(this.#path).size;
|
|
302
|
+
if (currentBytes + Buffer.byteLength(raw, "utf8") > MAX_FILE_BYTES) {
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_FILE_BYTES) return 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const directory = dirname(this.#path);
|
|
310
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
311
|
+
chmodSync(directory, 0o700);
|
|
312
|
+
let descriptor: number | undefined;
|
|
313
|
+
try {
|
|
314
|
+
descriptor = openSync(this.#path, "a", 0o600);
|
|
315
|
+
writeFileSync(descriptor, raw, { encoding: "utf8" });
|
|
316
|
+
fsyncSync(descriptor);
|
|
317
|
+
closeSync(descriptor);
|
|
318
|
+
descriptor = undefined;
|
|
319
|
+
chmodSync(this.#path, 0o600);
|
|
320
|
+
return pending.length;
|
|
321
|
+
} catch {
|
|
322
|
+
if (descriptor !== undefined) {
|
|
323
|
+
try {
|
|
324
|
+
closeSync(descriptor);
|
|
325
|
+
} catch {
|
|
326
|
+
// The original append failure remains authoritative.
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return 0;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|