deepline 0.1.199 → 0.1.200

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +5 -1
  2. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +51 -17
  3. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +1 -1
  4. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +16 -0
  5. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +40 -9
  6. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +108 -39
  7. package/dist/bundling-sources/sdk/src/http.ts +5 -2
  8. package/dist/bundling-sources/sdk/src/play.ts +1 -1
  9. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +7 -0
  10. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  11. package/dist/bundling-sources/sdk/src/worker-play-entry.ts +11 -55
  12. package/dist/bundling-sources/shared_libs/play-runtime/adaptive-tool-dispatcher.ts +355 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +172 -73
  14. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1794 -377
  15. package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +3 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +67 -4
  17. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +161 -39
  18. package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +218 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +1 -1
  20. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +115 -25
  21. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +17 -17
  22. package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +22 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +2 -1
  24. package/dist/bundling-sources/shared_libs/play-runtime/live-events.ts +0 -1
  25. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +265 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +135 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/play-input.ts +17 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +0 -3
  29. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +10 -1
  30. package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +9 -15
  31. package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +262 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/receipt-heartbeat-supervisor.ts +64 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +76 -8
  34. package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +59 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +3 -2
  36. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +269 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +2 -3
  38. package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +0 -1
  39. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +433 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +227 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +882 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +573 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +264 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +33 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runner-events.ts +35 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +53 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +0 -1
  48. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +459 -103
  49. package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +12 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/runtime-infra-mode.ts +23 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +23 -2
  52. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +16 -24
  53. package/dist/bundling-sources/shared_libs/play-runtime/serial-task-queue.ts +26 -0
  54. package/dist/bundling-sources/shared_libs/play-runtime/submit-limits.ts +1 -1
  55. package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +81 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +54 -9
  57. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +17 -3
  58. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +0 -20
  59. package/dist/bundling-sources/shared_libs/plays/dataset.ts +105 -4
  60. package/dist/bundling-sources/shared_libs/plays/row-identity.ts +4 -11
  61. package/dist/cli/index.js +167 -54
  62. package/dist/cli/index.mjs +167 -54
  63. package/dist/index.d.mts +1 -1
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.js +8 -8
  66. package/dist/index.mjs +8 -8
  67. package/dist/plays/bundle-play-file.mjs +15 -20
  68. package/package.json +1 -1
  69. package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-diagnosis.ts +0 -321
  70. package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-target.ts +0 -158
  71. package/dist/bundling-sources/shared_libs/temporal/constants.ts +0 -39
  72. package/dist/bundling-sources/shared_libs/temporal/preview-config.ts +0 -150
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
106
106
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
107
107
  // fields shipped in 0.1.153.
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
- version: '0.1.199',
109
+ version: '0.1.200',
110
110
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
111
111
  supportPolicy: {
112
- latest: '0.1.199',
112
+ latest: '0.1.200',
113
113
  minimumSupported: '0.1.53',
114
114
  deprecatedBelow: '0.1.53',
115
115
  commandMinimumSupported: [
@@ -112,9 +112,6 @@ class WorkerStepProgram<Input, Output, ReturnValue> implements StepProgram<
112
112
  | StepProgramResolver<Output, Value>,
113
113
  options?: StepOptions<Output, Value>,
114
114
  ): StepProgram<Input, Output & Record<Name, Value | null>, ReturnValue> {
115
- if (!name.trim()) {
116
- throw new Error('Step name required.');
117
- }
118
115
  const stepResolver =
119
116
  options?.runIf && !isConditionalStepResolver(resolver)
120
117
  ? new WorkerConditionalStepResolver(
@@ -155,11 +152,7 @@ class WorkerStepProgram<Input, Output, ReturnValue> implements StepProgram<
155
152
  function isConditionalStepResolver(
156
153
  value: unknown,
157
154
  ): value is ConditionalStepResolver<unknown, unknown> {
158
- return (
159
- value !== null &&
160
- typeof value === 'object' &&
161
- (value as { kind?: unknown }).kind === 'conditional'
162
- );
155
+ return (value as { kind?: unknown } | null)?.kind === 'conditional';
163
156
  }
164
157
 
165
158
  export function steps<TInput>(): StepProgram<TInput, TInput, TInput> {
@@ -176,9 +169,6 @@ export function runIf<Row, Value>(
176
169
  export function defineInput<TInput>(
177
170
  schema: Record<string, unknown>,
178
171
  ): PlayInputContract<TInput> {
179
- if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
180
- throw new Error('defineInput schema must be an object');
181
- }
182
172
  return { schema };
183
173
  }
184
174
 
@@ -220,24 +210,6 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
220
210
  const name = config.name;
221
211
  const fn = config.fn;
222
212
  const description = config.description?.trim();
223
- if (typeof fn !== 'function') {
224
- throw new Error('definePlay run must be async');
225
- }
226
- if (name.includes('/')) {
227
- throw new Error('Play name cannot contain /');
228
- }
229
- const normalizedName = name
230
- .trim()
231
- .replace(/[^a-z0-9]+/gi, '_')
232
- .replace(/_+/g, '_')
233
- .replace(/^_+|_+$/g, '')
234
- .toLowerCase();
235
- if (!normalizedName) {
236
- throw new Error('Play name required');
237
- }
238
- if (normalizedName.length > 63) {
239
- throw new Error('Play name >63');
240
- }
241
213
 
242
214
  const metadata: PlayMetadata = {
243
215
  name,
@@ -248,37 +220,21 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
248
220
  };
249
221
  const play = fn as DefinedPlay<TInput, TOutput>;
250
222
 
251
- Object.defineProperty(play, PLAY_METADATA_SYMBOL, {
252
- value: metadata,
253
- });
254
- Object.defineProperty(play, 'playName', {
255
- value: name,
256
- enumerable: true,
223
+ Object.defineProperties(play, {
224
+ [PLAY_METADATA_SYMBOL]: { value: metadata },
225
+ playName: { value: name, enumerable: true },
226
+ bindings: { value: config.bindings, enumerable: true },
227
+ name: { value: name },
257
228
  });
258
- Object.defineProperty(play, 'bindings', {
259
- value: config.bindings,
260
- enumerable: true,
261
- });
262
-
263
- Object.defineProperty(play, 'name', { value: name });
264
229
  return play;
265
230
  }
266
231
 
267
232
  export const defineWorkflow = definePlay;
268
233
 
269
234
  export function getDefinedPlayMetadata(value: unknown): PlayMetadata | null {
270
- if (typeof value !== 'function') {
271
- return null;
272
- }
273
- const metadata = (value as unknown as Record<PropertyKey, unknown>)[
274
- PLAY_METADATA_SYMBOL
275
- ];
276
- if (!metadata || typeof metadata !== 'object') {
277
- return null;
278
- }
279
- const candidate = metadata as PlayMetadata;
280
- if (!candidate.name || typeof candidate.name !== 'string') {
281
- return null;
282
- }
283
- return candidate;
235
+ return typeof value === 'function'
236
+ ? (((value as unknown as Record<PropertyKey, unknown>)[
237
+ PLAY_METADATA_SYMBOL
238
+ ] as PlayMetadata | undefined) ?? null)
239
+ : null;
284
240
  }
@@ -0,0 +1,355 @@
1
+ export type AdaptiveToolDispatchErrorKind = 'rate_limit' | 'other';
2
+
3
+ export type AdaptiveToolDispatchError = {
4
+ kind: AdaptiveToolDispatchErrorKind;
5
+ retryAfterMs?: number | null;
6
+ };
7
+
8
+ export type AdaptiveToolDispatcherOptions = {
9
+ initialRequestsPerSecond: number;
10
+ maxRequestsPerSecond: number;
11
+ maxConcurrency: number;
12
+ minConcurrency?: number;
13
+ additiveIncreasePerWindow?: number;
14
+ cleanWindowMs?: number;
15
+ defaultLatencyMs?: number;
16
+ safetyFactor?: number;
17
+ now?: () => number;
18
+ sleep?: (ms: number) => Promise<void>;
19
+ classifyError?: (error: unknown) => AdaptiveToolDispatchError | null;
20
+ };
21
+
22
+ export type AdaptiveToolDispatchResult<TJob, TResult> = {
23
+ job: TJob;
24
+ index: number;
25
+ result: TResult | null;
26
+ latencyMs: number | null;
27
+ error?: unknown;
28
+ };
29
+
30
+ export type AdaptiveToolDispatcherSnapshot = {
31
+ currentRequestsPerSecond: number;
32
+ targetConcurrency: number;
33
+ inFlight: number;
34
+ queued: number;
35
+ maxObservedInFlight: number;
36
+ successCount: number;
37
+ errorCount: number;
38
+ rateLimitCount: number;
39
+ latencyP95Ms: number | null;
40
+ cooldownUntilMs: number | null;
41
+ };
42
+
43
+ type QueuedJob<TJob> = {
44
+ job: TJob;
45
+ index: number;
46
+ };
47
+
48
+ function positiveInteger(value: number, fallback: number): number {
49
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
50
+ }
51
+
52
+ function clamp(value: number, min: number, max: number): number {
53
+ return Math.max(min, Math.min(max, value));
54
+ }
55
+
56
+ function percentile(
57
+ values: readonly number[],
58
+ percentileValue: number,
59
+ ): number {
60
+ if (values.length === 0) return 0;
61
+ const sorted = [...values].sort((a, b) => a - b);
62
+ const index = clamp(
63
+ Math.ceil(sorted.length * percentileValue) - 1,
64
+ 0,
65
+ sorted.length - 1,
66
+ );
67
+ return sorted[index] ?? 0;
68
+ }
69
+
70
+ export class AdaptiveToolDispatcher<TJob, TResult> {
71
+ private readonly maxRequestsPerSecond: number;
72
+ private readonly maxConcurrency: number;
73
+ private readonly minConcurrency: number;
74
+ private readonly additiveIncreasePerWindow: number;
75
+ private readonly cleanWindowMs: number;
76
+ private readonly defaultLatencyMs: number;
77
+ private readonly safetyFactor: number;
78
+ private readonly now: () => number;
79
+ private readonly sleep: (ms: number) => Promise<void>;
80
+ private readonly classifyError: (
81
+ error: unknown,
82
+ ) => AdaptiveToolDispatchError | null;
83
+ private readonly latencySamples: number[] = [];
84
+ private readonly launchTimestamps: number[] = [];
85
+ private readonly progressWaiters: Array<() => void> = [];
86
+ private currentRequestsPerSecond: number;
87
+ private lastIncreaseAtMs: number;
88
+ private successCountAtLastIncrease = 0;
89
+ private successCount = 0;
90
+ private errorCount = 0;
91
+ private rateLimitCount = 0;
92
+ private inFlight = 0;
93
+ private queued = 0;
94
+ private maxObservedInFlight = 0;
95
+ private cooldownUntilMs: number | null = null;
96
+
97
+ constructor(options: AdaptiveToolDispatcherOptions) {
98
+ this.maxRequestsPerSecond = positiveInteger(
99
+ options.maxRequestsPerSecond,
100
+ 1,
101
+ );
102
+ this.minConcurrency = positiveInteger(options.minConcurrency ?? 1, 1);
103
+ this.maxConcurrency = Math.max(
104
+ this.minConcurrency,
105
+ positiveInteger(options.maxConcurrency, this.minConcurrency),
106
+ );
107
+ this.currentRequestsPerSecond = clamp(
108
+ positiveInteger(options.initialRequestsPerSecond, 1),
109
+ 1,
110
+ this.maxRequestsPerSecond,
111
+ );
112
+ this.additiveIncreasePerWindow = positiveInteger(
113
+ options.additiveIncreasePerWindow ?? 5,
114
+ 5,
115
+ );
116
+ this.cleanWindowMs = positiveInteger(options.cleanWindowMs ?? 1_000, 1_000);
117
+ this.defaultLatencyMs = positiveInteger(
118
+ options.defaultLatencyMs ?? 1_000,
119
+ 1_000,
120
+ );
121
+ this.safetyFactor =
122
+ Number.isFinite(options.safetyFactor) && options.safetyFactor! > 0
123
+ ? options.safetyFactor!
124
+ : 1.5;
125
+ this.now = options.now ?? Date.now;
126
+ this.sleep =
127
+ options.sleep ??
128
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
129
+ this.classifyError = options.classifyError ?? (() => null);
130
+ this.lastIncreaseAtMs = this.now();
131
+ }
132
+
133
+ async dispatch(
134
+ jobs: readonly TJob[],
135
+ execute: (job: TJob) => Promise<TResult>,
136
+ ): Promise<Array<AdaptiveToolDispatchResult<TJob, TResult>>> {
137
+ const queue = jobs.map((job, index) => ({ job, index }));
138
+ const results = new Array<AdaptiveToolDispatchResult<TJob, TResult>>(
139
+ jobs.length,
140
+ );
141
+ this.queued = queue.length;
142
+
143
+ while (queue.length > 0 || this.inFlight > 0) {
144
+ let launched = false;
145
+ while (queue.length > 0 && this.canLaunch()) {
146
+ const next = queue.shift();
147
+ if (!next) break;
148
+ this.launch(next, execute, results);
149
+ launched = true;
150
+ }
151
+ this.queued = queue.length;
152
+ if (queue.length === 0 && this.inFlight === 0) break;
153
+ if (!launched) {
154
+ await this.waitForProgress(this.nextLaunchDelayMs());
155
+ }
156
+ }
157
+
158
+ return results.filter(
159
+ (result): result is AdaptiveToolDispatchResult<TJob, TResult> =>
160
+ result !== undefined,
161
+ );
162
+ }
163
+
164
+ snapshot(): AdaptiveToolDispatcherSnapshot {
165
+ return {
166
+ currentRequestsPerSecond: this.currentRequestsPerSecond,
167
+ targetConcurrency: this.targetConcurrency(),
168
+ inFlight: this.inFlight,
169
+ queued: this.queued,
170
+ maxObservedInFlight: this.maxObservedInFlight,
171
+ successCount: this.successCount,
172
+ errorCount: this.errorCount,
173
+ rateLimitCount: this.rateLimitCount,
174
+ latencyP95Ms: this.latencyP95Ms(),
175
+ cooldownUntilMs: this.cooldownUntilMs,
176
+ };
177
+ }
178
+
179
+ private canLaunch(): boolean {
180
+ return (
181
+ this.inFlight < this.targetConcurrency() &&
182
+ this.now() >= (this.cooldownUntilMs ?? 0) &&
183
+ this.startsAvailable() > 0
184
+ );
185
+ }
186
+
187
+ private launch(
188
+ queued: QueuedJob<TJob>,
189
+ execute: (job: TJob) => Promise<TResult>,
190
+ results: Array<AdaptiveToolDispatchResult<TJob, TResult> | undefined>,
191
+ ): void {
192
+ const startedAt = this.now();
193
+ this.launchTimestamps.push(startedAt);
194
+ this.inFlight += 1;
195
+ this.maxObservedInFlight = Math.max(
196
+ this.maxObservedInFlight,
197
+ this.inFlight,
198
+ );
199
+
200
+ let execution: Promise<TResult>;
201
+ try {
202
+ execution = Promise.resolve(execute(queued.job));
203
+ } catch (error) {
204
+ execution = Promise.reject(error);
205
+ }
206
+
207
+ void execution
208
+ .then((result) => {
209
+ const latencyMs = Math.max(0, this.now() - startedAt);
210
+ this.observeSuccess(latencyMs);
211
+ results[queued.index] = {
212
+ job: queued.job,
213
+ index: queued.index,
214
+ result,
215
+ latencyMs,
216
+ };
217
+ })
218
+ .catch((error) => {
219
+ this.observeError(error);
220
+ results[queued.index] = {
221
+ job: queued.job,
222
+ index: queued.index,
223
+ result: null,
224
+ latencyMs: Math.max(0, this.now() - startedAt),
225
+ error,
226
+ };
227
+ })
228
+ .finally(() => {
229
+ this.inFlight = Math.max(0, this.inFlight - 1);
230
+ this.signalProgress();
231
+ });
232
+ }
233
+
234
+ private observeSuccess(latencyMs: number): void {
235
+ this.successCount += 1;
236
+ this.latencySamples.push(latencyMs);
237
+ if (this.latencySamples.length > 128) {
238
+ this.latencySamples.splice(0, this.latencySamples.length - 128);
239
+ }
240
+ const currentTime = this.now();
241
+ if (this.cooldownUntilMs != null && this.cooldownUntilMs > currentTime) {
242
+ this.successCountAtLastIncrease = this.successCount;
243
+ return;
244
+ }
245
+ this.cooldownUntilMs = null;
246
+ const successesSinceIncrease =
247
+ this.successCount - this.successCountAtLastIncrease;
248
+ if (
249
+ currentTime - this.lastIncreaseAtMs >= this.cleanWindowMs &&
250
+ successesSinceIncrease >= this.currentRequestsPerSecond
251
+ ) {
252
+ this.currentRequestsPerSecond = Math.min(
253
+ this.maxRequestsPerSecond,
254
+ this.currentRequestsPerSecond + this.additiveIncreasePerWindow,
255
+ );
256
+ this.lastIncreaseAtMs = currentTime;
257
+ this.successCountAtLastIncrease = this.successCount;
258
+ }
259
+ }
260
+
261
+ private observeError(error: unknown): void {
262
+ this.errorCount += 1;
263
+ let classified: AdaptiveToolDispatchError | null = null;
264
+ try {
265
+ classified = this.classifyError(error);
266
+ } catch {
267
+ classified = null;
268
+ }
269
+ if (classified?.kind !== 'rate_limit') return;
270
+ this.rateLimitCount += 1;
271
+ this.currentRequestsPerSecond = Math.max(
272
+ 1,
273
+ Math.floor(this.currentRequestsPerSecond / 2),
274
+ );
275
+ const retryAfterMs = Math.max(0, classified.retryAfterMs ?? 0);
276
+ const currentTime = this.now();
277
+ const cooldownUntilMs = currentTime + retryAfterMs;
278
+ this.cooldownUntilMs =
279
+ this.cooldownUntilMs == null
280
+ ? cooldownUntilMs
281
+ : Math.max(this.cooldownUntilMs, cooldownUntilMs);
282
+ this.lastIncreaseAtMs = this.cooldownUntilMs;
283
+ this.successCountAtLastIncrease = this.successCount;
284
+ }
285
+
286
+ private targetConcurrency(): number {
287
+ const latencyMs = this.latencyP95Ms() ?? this.defaultLatencyMs;
288
+ const target = Math.ceil(
289
+ this.currentRequestsPerSecond *
290
+ (Math.max(1, latencyMs) / 1_000) *
291
+ this.safetyFactor,
292
+ );
293
+ return clamp(target, this.minConcurrency, this.maxConcurrency);
294
+ }
295
+
296
+ private latencyP95Ms(): number | null {
297
+ return this.latencySamples.length > 0
298
+ ? percentile(this.latencySamples, 0.95)
299
+ : null;
300
+ }
301
+
302
+ private startsAvailable(): number {
303
+ this.pruneLaunchTimestamps();
304
+ return Math.max(
305
+ 0,
306
+ this.currentRequestsPerSecond - this.launchTimestamps.length,
307
+ );
308
+ }
309
+
310
+ private nextLaunchDelayMs(): number {
311
+ const currentTime = this.now();
312
+ const cooldownWait =
313
+ this.cooldownUntilMs != null
314
+ ? Math.max(0, this.cooldownUntilMs - currentTime)
315
+ : 0;
316
+ this.pruneLaunchTimestamps();
317
+ const oldest = this.launchTimestamps[0];
318
+ const tokenWait =
319
+ oldest == null ||
320
+ this.launchTimestamps.length < this.currentRequestsPerSecond
321
+ ? 0
322
+ : Math.max(1, oldest + 1_000 - currentTime);
323
+ return Math.max(1, cooldownWait, tokenWait);
324
+ }
325
+
326
+ private pruneLaunchTimestamps(): void {
327
+ const cutoff = this.now() - 1_000;
328
+ while (
329
+ this.launchTimestamps.length > 0 &&
330
+ (this.launchTimestamps[0] ?? 0) <= cutoff
331
+ ) {
332
+ this.launchTimestamps.shift();
333
+ }
334
+ }
335
+
336
+ private async waitForProgress(delayMs: number): Promise<void> {
337
+ let waiter: (() => void) | null = null;
338
+ await Promise.race([
339
+ new Promise<void>((resolve) => {
340
+ waiter = resolve;
341
+ this.progressWaiters.push(resolve);
342
+ }),
343
+ this.sleep(delayMs),
344
+ ]);
345
+ if (waiter) {
346
+ const index = this.progressWaiters.indexOf(waiter);
347
+ if (index >= 0) this.progressWaiters.splice(index, 1);
348
+ }
349
+ }
350
+
351
+ private signalProgress(): void {
352
+ const waiters = this.progressWaiters.splice(0);
353
+ for (const waiter of waiters) waiter();
354
+ }
355
+ }