deepline 0.1.19 → 0.1.20

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.
@@ -156,6 +156,7 @@ interface CoordinatorEnv {
156
156
  DEEPLINE_API_BASE_URL: string;
157
157
  DEEPLINE_INTERNAL_TOKEN?: string;
158
158
  DEEPLINE_TAIL_LOG_TOKEN?: string;
159
+ DEEPLINE_COORDINATOR_DEPLOY_MARKER?: string;
159
160
  VERCEL_PROTECTION_BYPASS_TOKEN?: string;
160
161
  DEEPLINE_PLAY_PREVIEW_SLUG?: string;
161
162
  /**
@@ -369,6 +370,7 @@ const WORKFLOW_POOL_READY_TIMEOUT_MS = 1_500;
369
370
  const WORKFLOW_POOL_READY_POLL_MS = 250;
370
371
  const WORKFLOW_POOL_REFILL_ON_MISS_TIMEOUT_MS = 2_500;
371
372
  const WORKFLOW_POOL_REFILL_ON_MISS_MIN_AVAILABLE = 1;
373
+ const WORKFLOW_POOL_CONTROL_TIMEOUT_MS = 750;
372
374
 
373
375
  function buildDynamicWorkflowMetadata(
374
376
  params: PlayWorkflowParams,
@@ -469,26 +471,48 @@ function workflowPoolDurableObject(env: CoordinatorEnv): DurableObjectStub {
469
471
  async function callWorkflowPool<T>(
470
472
  env: CoordinatorEnv,
471
473
  path: string,
472
- init?: RequestInit,
474
+ init?: RequestInit & { timeoutMs?: number },
473
475
  ): Promise<T> {
474
- const response = await workflowPoolDurableObject(env).fetch(
475
- `https://deepline.workflow-pool.internal${path}`,
476
- {
477
- ...init,
478
- headers: {
479
- 'content-type': 'application/json',
480
- ...(init?.headers ?? {}),
481
- },
482
- },
476
+ const timeoutMs = Math.max(
477
+ 1,
478
+ Math.floor(init?.timeoutMs ?? WORKFLOW_POOL_CONTROL_TIMEOUT_MS),
483
479
  );
484
- if (!response.ok) {
485
- throw new Error(
486
- `workflow pool ${path} failed ${response.status}: ${(
487
- await response.text().catch(() => '')
488
- ).slice(0, 400)}`,
480
+ const controller = new AbortController();
481
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
482
+ try {
483
+ const fetchInit: RequestInit = { ...(init ?? {}) };
484
+ delete (fetchInit as { timeoutMs?: number }).timeoutMs;
485
+ delete fetchInit.signal;
486
+ const response = await workflowPoolDurableObject(env).fetch(
487
+ `https://deepline.workflow-pool.internal${path}`,
488
+ {
489
+ ...fetchInit,
490
+ signal: controller.signal,
491
+ headers: {
492
+ 'content-type': 'application/json',
493
+ ...(init?.headers ?? {}),
494
+ },
495
+ },
489
496
  );
497
+ if (!response.ok) {
498
+ throw new Error(
499
+ `workflow pool ${path} failed ${response.status}: ${(
500
+ await response.text().catch(() => '')
501
+ ).slice(0, 400)}`,
502
+ );
503
+ }
504
+ return (await response.json()) as T;
505
+ } catch (error) {
506
+ if (
507
+ error instanceof Error &&
508
+ (error.name === 'AbortError' || error.message.includes('aborted'))
509
+ ) {
510
+ throw new Error(`workflow pool ${path} timed out after ${timeoutMs}ms`);
511
+ }
512
+ throw error;
513
+ } finally {
514
+ clearTimeout(timer);
490
515
  }
491
- return (await response.json()) as T;
492
516
  }
493
517
 
494
518
  type WorkflowPoolCounts = {
@@ -903,7 +927,11 @@ async function submitViaPooledWorkflow(input: {
903
927
  return null;
904
928
  }
905
929
  const leaseStartedAt = Date.now();
906
- let pooledInstanceId = await leaseWorkflowPoolId(input.env);
930
+ let leaseError: string | null = null;
931
+ let pooledInstanceId = await leaseWorkflowPoolId(input.env).catch((error) => {
932
+ leaseError = error instanceof Error ? error.message : String(error);
933
+ return null;
934
+ });
907
935
  const missCounts = pooledInstanceId
908
936
  ? null
909
937
  : await workflowPoolCount(input.env).catch(() => null);
@@ -913,6 +941,7 @@ async function submitViaPooledWorkflow(input: {
913
941
  graphHash: input.params.graphHash ?? null,
914
942
  extra: {
915
943
  pooled: Boolean(pooledInstanceId),
944
+ ...(leaseError ? { error: leaseError } : {}),
916
945
  ...(missCounts
917
946
  ? {
918
947
  availableAfterMiss: missCounts.available,
@@ -952,12 +981,19 @@ async function submitViaPooledWorkflow(input: {
952
981
  });
953
982
  if (refillResult?.available) {
954
983
  const retryStartedAt = Date.now();
955
- pooledInstanceId = await leaseWorkflowPoolId(input.env);
984
+ let retryLeaseError: string | null = null;
985
+ pooledInstanceId = await leaseWorkflowPoolId(input.env).catch((error) => {
986
+ retryLeaseError = error instanceof Error ? error.message : String(error);
987
+ return null;
988
+ });
956
989
  input.recordSubmitTiming({
957
990
  phase: 'coordinator.workflow_pool_lease_retry',
958
991
  ms: Date.now() - retryStartedAt,
959
992
  graphHash: input.params.graphHash ?? null,
960
- extra: { pooled: Boolean(pooledInstanceId) },
993
+ extra: {
994
+ pooled: Boolean(pooledInstanceId),
995
+ ...(retryLeaseError ? { error: retryLeaseError } : {}),
996
+ },
961
997
  });
962
998
  }
963
999
  }
@@ -967,38 +1003,26 @@ async function submitViaPooledWorkflow(input: {
967
1003
  }
968
1004
 
969
1005
  const instance = await input.env.PLAY_WORKFLOW.get(pooledInstanceId);
1006
+ const readyCheckStartedAt = Date.now();
1007
+ const status = await instance.status().catch(() => null);
1008
+ const statusName = workflowStatusName(status);
1009
+ input.recordSubmitTiming({
1010
+ phase: 'coordinator.workflow_pool_ready_check',
1011
+ ms: Date.now() - readyCheckStartedAt,
1012
+ graphHash: input.params.graphHash ?? null,
1013
+ extra: { instanceId: pooledInstanceId, status: statusName },
1014
+ });
1015
+ if (!workflowPoolStatusIsReady(statusName)) {
1016
+ await instance.terminate().catch(() => undefined);
1017
+ disposeRpcStub(instance);
1018
+ return null;
1019
+ }
1020
+ const sendStartedAt = Date.now();
970
1021
  try {
971
- const readyCheckStartedAt = Date.now();
972
- const status = await instance.status().catch(() => null);
973
- const statusName = workflowStatusName(status);
974
- input.recordSubmitTiming({
975
- phase: 'coordinator.workflow_pool_ready_check',
976
- ms: Date.now() - readyCheckStartedAt,
977
- graphHash: input.params.graphHash ?? null,
978
- extra: { instanceId: pooledInstanceId, status: statusName },
979
- });
980
- if (!workflowPoolStatusIsReady(statusName)) {
981
- await instance.terminate().catch(() => undefined);
982
- disposeRpcStub(instance);
983
- return null;
984
- }
985
- const sendStartedAt = Date.now();
986
1022
  await instance.sendEvent({
987
1023
  type: WORKFLOW_POOL_START_EVENT_TYPE,
988
1024
  payload: buildDispatcherEnvelope(input.params),
989
1025
  });
990
- await mapRunToWorkflowInstance({
991
- env: input.env,
992
- runId: input.params.runId,
993
- instanceId: pooledInstanceId,
994
- });
995
- input.recordSubmitTiming({
996
- phase: 'coordinator.workflow_pool_send_event',
997
- ms: Date.now() - sendStartedAt,
998
- graphHash: input.params.graphHash ?? null,
999
- extra: { instanceId: pooledInstanceId },
1000
- });
1001
- return instance;
1002
1026
  } catch (error) {
1003
1027
  disposeRpcStub(instance);
1004
1028
  console.warn('[coordinator.workflow_pool] sendEvent failed; falling back', {
@@ -1008,6 +1032,27 @@ async function submitViaPooledWorkflow(input: {
1008
1032
  });
1009
1033
  return null;
1010
1034
  }
1035
+ try {
1036
+ await mapRunToWorkflowInstance({
1037
+ env: input.env,
1038
+ runId: input.params.runId,
1039
+ instanceId: pooledInstanceId,
1040
+ });
1041
+ } catch (error) {
1042
+ disposeRpcStub(instance);
1043
+ throw new Error(
1044
+ `workflow pool mapRunToWorkflowInstance failed after pooled workflow start for ${input.params.runId}: ${
1045
+ error instanceof Error ? error.message : String(error)
1046
+ }`,
1047
+ );
1048
+ }
1049
+ input.recordSubmitTiming({
1050
+ phase: 'coordinator.workflow_pool_send_event',
1051
+ ms: Date.now() - sendStartedAt,
1052
+ graphHash: input.params.graphHash ?? null,
1053
+ extra: { instanceId: pooledInstanceId },
1054
+ });
1055
+ return instance;
1011
1056
  }
1012
1057
 
1013
1058
  function readWorkflowPayload(event: unknown): Record<string, unknown> | null {
@@ -2029,6 +2074,29 @@ const coordinatorEntrypoint = {
2029
2074
  if (authError) return authError;
2030
2075
  return await handleCoordinatorWarmup(request, env, ctx);
2031
2076
  }
2077
+ if (url.pathname === '/tail-log-token/probe') {
2078
+ const authError = authorizeCoordinatorControlRequest({ request, env });
2079
+ if (authError) return authError;
2080
+ const expectedTailLogToken = env.DEEPLINE_TAIL_LOG_TOKEN?.trim();
2081
+ if (!expectedTailLogToken) {
2082
+ return Response.json(
2083
+ { ok: false, error: 'tail log token is not configured' },
2084
+ { status: 503 },
2085
+ );
2086
+ }
2087
+ const actualTailLogToken =
2088
+ request.headers.get('x-deepline-tail-log-token')?.trim() ?? '';
2089
+ if (actualTailLogToken !== expectedTailLogToken) {
2090
+ return Response.json(
2091
+ { ok: false, error: 'tail log token mismatch' },
2092
+ { status: 401 },
2093
+ );
2094
+ }
2095
+ return Response.json({
2096
+ ok: true,
2097
+ deployMarker: env.DEEPLINE_COORDINATOR_DEPLOY_MARKER ?? null,
2098
+ });
2099
+ }
2032
2100
  if (url.pathname === '/workflow-pool/refill') {
2033
2101
  const internalAuthError = authorizeCoordinatorControlRequest({
2034
2102
  request,
@@ -2798,7 +2866,7 @@ function loadDynamicPlayWorkerSync(
2798
2866
  }
2799
2867
  const artifactIdentity =
2800
2868
  metadata.artifactHash?.trim() || stableHash(artifactStorageKey);
2801
- const workerCacheKey = `play:${graphHash}:${artifactIdentity}:harness=h4-runtime-api`;
2869
+ const workerCacheKey = `play:${graphHash}:${artifactIdentity}:harness=h5-runtime-api-coordinator`;
2802
2870
  const runIdForTrace = metadata.runId ?? graphHash;
2803
2871
  const loaderGetStartedAt = Date.now();
2804
2872
  const stub = env.LOADER.get(workerCacheKey, async () => {
@@ -2829,6 +2897,10 @@ function loadDynamicPlayWorkerSync(
2829
2897
  // tool execution, DB session, and artifact callbacks. This avoids a
2830
2898
  // public fetch hop when Cloudflare exposes the RuntimeApi export.
2831
2899
  ...makeRuntimeApiEnvBinding(),
2900
+ // In-process coordinator control bridge used by ctx.runPlay and
2901
+ // parent terminal signals. This keeps scalar child plays inline with
2902
+ // the parent instead of round-tripping through nested Workflow waits.
2903
+ ...makeCoordinatorControlBinding(),
2832
2904
  // NOTE: We intentionally do NOT pass `env.PLAYS_BUCKET` (an R2Bucket
2833
2905
  // binding) through to the per-play Worker's env. Including a raw
2834
2906
  // R2Bucket in the dynamically-loaded Worker's env makes Cloudflare
@@ -2880,7 +2952,7 @@ async function loadDynamicPlayWorker(
2880
2952
  }
2881
2953
  const artifactIdentity =
2882
2954
  metadata.artifactHash?.trim() || stableHash(artifactStorageKey);
2883
- const workerCacheKey = `play:${graphHash}:${artifactIdentity}:harness=h4-runtime-api`;
2955
+ const workerCacheKey = `play:${graphHash}:${artifactIdentity}:harness=h5-runtime-api-coordinator`;
2884
2956
  const runIdForTrace = metadata.runId ?? graphHash;
2885
2957
  const loaderGetStartedAt = Date.now();
2886
2958
  const stub = env.LOADER.get(workerCacheKey, async () => {
@@ -2902,11 +2974,11 @@ async function loadDynamicPlayWorker(
2902
2974
  // Mirror of the sync loader (above) — see that copy for the
2903
2975
  // architectural rationale. The dynamic worker env is intentionally
2904
2976
  // minimal; runtime callbacks use RUNTIME_API, file reads go through
2905
- // HARNESS, and child workflow control uses the coordinator URL in the
2906
- // run request.
2977
+ // HARNESS, and child workflow control uses the COORDINATOR binding.
2907
2978
  HARNESS: env.HARNESS,
2908
2979
  VERCEL_PROTECTION_BYPASS_TOKEN: env.VERCEL_PROTECTION_BYPASS_TOKEN,
2909
2980
  ...makeRuntimeApiEnvBinding(),
2981
+ ...makeCoordinatorControlBinding(),
2910
2982
  },
2911
2983
  };
2912
2984
  });
@@ -3219,6 +3291,7 @@ async function handleCoordinatorWarmup(
3219
3291
  * worker uses its existing `fetch(req.baseUrl + path)` transport.
3220
3292
  */
3221
3293
  let loggedMissingRuntimeApiExport = false;
3294
+ let loggedMissingCoordinatorControlExport = false;
3222
3295
 
3223
3296
  function makeRuntimeApiEnvBinding():
3224
3297
  | { RUNTIME_API: { fetch(req: Request): Promise<Response> } }
@@ -3241,20 +3314,24 @@ function makeRuntimeApiEnvBinding():
3241
3314
  return { RUNTIME_API: ctor({ props: undefined }) };
3242
3315
  }
3243
3316
 
3244
- function makeCoordinatorControlBinding(): {
3245
- submitChild(
3246
- parentRunId: string,
3247
- body: Record<string, unknown>,
3248
- ): Promise<{ workflowId?: string; runId?: string; error?: unknown }>;
3249
- signal(
3250
- runId: string,
3251
- body: Record<string, unknown>,
3252
- ): Promise<Record<string, unknown>>;
3253
- recordPerfTrace(
3254
- runId: string,
3255
- payload: CoordinatorPerfTracePayload,
3256
- ): Promise<void>;
3257
- } {
3317
+ function makeCoordinatorControlBinding():
3318
+ | {
3319
+ COORDINATOR: {
3320
+ submitChild(
3321
+ parentRunId: string,
3322
+ body: Record<string, unknown>,
3323
+ ): Promise<{ workflowId?: string; runId?: string; error?: unknown }>;
3324
+ signal(
3325
+ runId: string,
3326
+ body: Record<string, unknown>,
3327
+ ): Promise<Record<string, unknown>>;
3328
+ recordPerfTrace(
3329
+ runId: string,
3330
+ payload: CoordinatorPerfTracePayload,
3331
+ ): Promise<void>;
3332
+ };
3333
+ }
3334
+ | Record<string, never> {
3258
3335
  const exports = workersExports as unknown as {
3259
3336
  CoordinatorControl?: (init: { props: undefined }) => {
3260
3337
  submitChild(
@@ -3273,11 +3350,15 @@ function makeCoordinatorControlBinding(): {
3273
3350
  };
3274
3351
  const ctor = exports.CoordinatorControl;
3275
3352
  if (typeof ctor !== 'function') {
3276
- throw new Error(
3277
- 'CoordinatorControl is not registered on cloudflare:workers exports.',
3278
- );
3353
+ if (!loggedMissingCoordinatorControlExport) {
3354
+ loggedMissingCoordinatorControlExport = true;
3355
+ console.warn(
3356
+ '[coordinator] CoordinatorControl is not registered on cloudflare:workers exports; using public coordinator transport.',
3357
+ );
3358
+ }
3359
+ return {};
3279
3360
  }
3280
- return ctor({ props: undefined });
3361
+ return { COORDINATOR: ctor({ props: undefined }) };
3281
3362
  }
3282
3363
 
3283
3364
  async function loadStoredPlayArtifactFromR2(
@@ -87,6 +87,7 @@ const COORDINATOR_TRACE_MAX_ENTRIES = 200;
87
87
  const FINISH_ALARM_DELAY_MS = 60_000; // self-evict 1 min after finish() called
88
88
  const WORKFLOW_POOL_DEFAULT_TTL_MS = 8 * 60 * 1000;
89
89
  const WORKFLOW_POOL_RUN_MAPPING_TTL_MS = 60 * 60 * 1000;
90
+ const WORKFLOW_POOL_READY_MAX_AGE_MS = 20_000;
90
91
 
91
92
  interface DedupEnv {
92
93
  PLAY_DEDUP: DurableObjectNamespace;
@@ -248,7 +249,9 @@ export class PlayDedup implements DurableObject {
248
249
  }
249
250
  }
250
251
 
251
- return new Response('{}', { headers: { 'content-type': 'application/json' } });
252
+ return new Response('{}', {
253
+ headers: { 'content-type': 'application/json' },
254
+ });
252
255
  }
253
256
 
254
257
  private async handleAwait(req: Request): Promise<Response> {
@@ -313,7 +316,9 @@ export class PlayDedup implements DurableObject {
313
316
  // Schedule alarm to evict after a grace period (any straggler awaits
314
317
  // get a chance to resolve). Grace = 60s.
315
318
  await this.state.storage.setAlarm(Date.now() + FINISH_ALARM_DELAY_MS);
316
- return new Response('{}', { headers: { 'content-type': 'application/json' } });
319
+ return new Response('{}', {
320
+ headers: { 'content-type': 'application/json' },
321
+ });
317
322
  }
318
323
 
319
324
  private async handleDebug(_req: Request): Promise<Response> {
@@ -321,7 +326,11 @@ export class PlayDedup implements DurableObject {
321
326
  const dump: Record<string, DedupEntry> = {};
322
327
  for (const [k, v] of all) dump[k] = v as DedupEntry;
323
328
  return new Response(
324
- JSON.stringify({ size: all.size, entries: dump, waiters: this.waiters.size }),
329
+ JSON.stringify({
330
+ size: all.size,
331
+ entries: dump,
332
+ waiters: this.waiters.size,
333
+ }),
325
334
  { headers: { 'content-type': 'application/json' } },
326
335
  );
327
336
  }
@@ -339,7 +348,8 @@ export class PlayDedup implements DurableObject {
339
348
  value.entry !== undefined &&
340
349
  value.entry.version === version &&
341
350
  value.entry.expiresAt > now &&
342
- value.entry.readyAt !== null
351
+ value.entry.readyAt !== null &&
352
+ now - value.entry.readyAt <= WORKFLOW_POOL_READY_MAX_AGE_MS
343
353
  );
344
354
  }
345
355
 
@@ -360,6 +370,8 @@ export class PlayDedup implements DurableObject {
360
370
  if (
361
371
  !entry ||
362
372
  entry.expiresAt <= now ||
373
+ (entry.readyAt !== null &&
374
+ now - entry.readyAt > WORKFLOW_POOL_READY_MAX_AGE_MS) ||
363
375
  (version && entry.version !== version)
364
376
  ) {
365
377
  expiredKeys.push(key);
@@ -380,16 +392,15 @@ export class PlayDedup implements DurableObject {
380
392
  }
381
393
 
382
394
  private async handlePoolAdd(req: Request): Promise<Response> {
383
- const body = (await req.json().catch(() => null)) as
384
- | {
385
- ids?: unknown;
386
- ttlMs?: unknown;
387
- version?: unknown;
388
- readyAt?: unknown;
389
- ready?: unknown;
390
- }
391
- | null;
392
- const version = typeof body?.version === 'string' ? body.version.trim() : '';
395
+ const body = (await req.json().catch(() => null)) as {
396
+ ids?: unknown;
397
+ ttlMs?: unknown;
398
+ version?: unknown;
399
+ readyAt?: unknown;
400
+ ready?: unknown;
401
+ } | null;
402
+ const version =
403
+ typeof body?.version === 'string' ? body.version.trim() : '';
393
404
  if (!version) {
394
405
  return new Response('version is required', { status: 400 });
395
406
  }
@@ -445,7 +456,9 @@ export class PlayDedup implements DurableObject {
445
456
  const sorted = [...entries.entries()]
446
457
  .map(([key, entry]) => ({ key, entry }))
447
458
  .filter((entry) => this.isReadyWorkflowPoolEntry(entry, version, now))
448
- .sort((a, b) => a.entry.readyAt - b.entry.readyAt);
459
+ // Lease the freshest ready instance. Older waiting Workflows are more
460
+ // likely to be hibernated, so "pooled" otherwise still pays a wake tax.
461
+ .sort((a, b) => b.entry.readyAt - a.entry.readyAt);
449
462
  const selected = sorted[0];
450
463
  if (!selected) return;
451
464
  leasedId = selected.entry.id;
@@ -461,7 +474,8 @@ export class PlayDedup implements DurableObject {
461
474
  if (!version) {
462
475
  return new Response('version is required', { status: 400 });
463
476
  }
464
- await this.gcWorkflowPool(Date.now(), version);
477
+ const now = Date.now();
478
+ await this.gcWorkflowPool(now, version);
465
479
  const entries = await this.state.storage.list<WorkflowPoolEntry>({
466
480
  prefix: WORKFLOW_POOL_KEY_PREFIX,
467
481
  });
@@ -469,8 +483,14 @@ export class PlayDedup implements DurableObject {
469
483
  let warming = 0;
470
484
  for (const entry of entries.values()) {
471
485
  if (entry.version !== version) continue;
472
- if (entry.readyAt === null) warming += 1;
473
- else available += 1;
486
+ if (
487
+ entry.readyAt === null ||
488
+ now - entry.readyAt > WORKFLOW_POOL_READY_MAX_AGE_MS
489
+ ) {
490
+ warming += 1;
491
+ } else {
492
+ available += 1;
493
+ }
474
494
  }
475
495
  return new Response(JSON.stringify({ available, warming }), {
476
496
  headers: { 'content-type': 'application/json' },
@@ -502,13 +522,12 @@ export class PlayDedup implements DurableObject {
502
522
  }
503
523
 
504
524
  private async handlePoolPromote(req: Request): Promise<Response> {
505
- const body = (await req.json().catch(() => null)) as
506
- | {
507
- ids?: unknown;
508
- version?: unknown;
509
- }
510
- | null;
511
- const version = typeof body?.version === 'string' ? body.version.trim() : '';
525
+ const body = (await req.json().catch(() => null)) as {
526
+ ids?: unknown;
527
+ version?: unknown;
528
+ } | null;
529
+ const version =
530
+ typeof body?.version === 'string' ? body.version.trim() : '';
512
531
  if (!version) {
513
532
  return new Response('version is required', { status: 400 });
514
533
  }
@@ -536,19 +555,21 @@ export class PlayDedup implements DurableObject {
536
555
  await this.state.storage.put(writes);
537
556
  }
538
557
  });
539
- return new Response(JSON.stringify({ promoted: Object.keys(writes).length }), {
540
- headers: { 'content-type': 'application/json' },
541
- });
558
+ return new Response(
559
+ JSON.stringify({ promoted: Object.keys(writes).length }),
560
+ {
561
+ headers: { 'content-type': 'application/json' },
562
+ },
563
+ );
542
564
  }
543
565
 
544
566
  private async handlePoolDelete(req: Request): Promise<Response> {
545
- const body = (await req.json().catch(() => null)) as
546
- | {
547
- ids?: unknown;
548
- version?: unknown;
549
- }
550
- | null;
551
- const version = typeof body?.version === 'string' ? body.version.trim() : '';
567
+ const body = (await req.json().catch(() => null)) as {
568
+ ids?: unknown;
569
+ version?: unknown;
570
+ } | null;
571
+ const version =
572
+ typeof body?.version === 'string' ? body.version.trim() : '';
552
573
  if (!version) {
553
574
  return new Response('version is required', { status: 400 });
554
575
  }
@@ -575,34 +596,32 @@ export class PlayDedup implements DurableObject {
575
596
  }
576
597
 
577
598
  private async handlePoolMapRun(req: Request): Promise<Response> {
578
- const body = (await req.json().catch(() => null)) as
579
- | {
580
- runId?: unknown;
581
- instanceId?: unknown;
582
- version?: unknown;
583
- }
584
- | null;
599
+ const body = (await req.json().catch(() => null)) as {
600
+ runId?: unknown;
601
+ instanceId?: unknown;
602
+ version?: unknown;
603
+ } | null;
585
604
  const runId = typeof body?.runId === 'string' ? body.runId : '';
586
605
  const instanceId =
587
606
  typeof body?.instanceId === 'string' ? body.instanceId : '';
588
- const version = typeof body?.version === 'string' ? body.version.trim() : '';
607
+ const version =
608
+ typeof body?.version === 'string' ? body.version.trim() : '';
589
609
  if (!runId || !instanceId || !version) {
590
610
  return new Response('runId, instanceId, and version are required', {
591
611
  status: 400,
592
612
  });
593
613
  }
594
614
  const now = Date.now();
595
- await this.state.storage.put(
596
- `${WORKFLOW_POOL_RUN_KEY_PREFIX}${runId}`,
597
- {
598
- runId,
599
- instanceId,
600
- version,
601
- createdAt: now,
602
- expiresAt: now + WORKFLOW_POOL_RUN_MAPPING_TTL_MS,
603
- } satisfies WorkflowRunMapping,
604
- );
605
- return new Response('{}', { headers: { 'content-type': 'application/json' } });
615
+ await this.state.storage.put(`${WORKFLOW_POOL_RUN_KEY_PREFIX}${runId}`, {
616
+ runId,
617
+ instanceId,
618
+ version,
619
+ createdAt: now,
620
+ expiresAt: now + WORKFLOW_POOL_RUN_MAPPING_TTL_MS,
621
+ } satisfies WorkflowRunMapping);
622
+ return new Response('{}', {
623
+ headers: { 'content-type': 'application/json' },
624
+ });
606
625
  }
607
626
 
608
627
  private async handlePoolResolveRun(req: Request): Promise<Response> {
@@ -620,7 +639,9 @@ export class PlayDedup implements DurableObject {
620
639
  `${WORKFLOW_POOL_RUN_KEY_PREFIX}${runId}`,
621
640
  );
622
641
  if (mapping && mapping.version !== version) {
623
- await this.state.storage.delete(`${WORKFLOW_POOL_RUN_KEY_PREFIX}${runId}`);
642
+ await this.state.storage.delete(
643
+ `${WORKFLOW_POOL_RUN_KEY_PREFIX}${runId}`,
644
+ );
624
645
  return new Response(JSON.stringify({ instanceId: null }), {
625
646
  headers: { 'content-type': 'application/json' },
626
647
  });
@@ -658,9 +679,9 @@ export class PlayDedup implements DurableObject {
658
679
  }
659
680
 
660
681
  private async handleTraceAdd(req: Request): Promise<Response> {
661
- const body = (await req.json().catch(() => null)) as
662
- | Partial<CoordinatorTraceEntry>
663
- | null;
682
+ const body = (await req
683
+ .json()
684
+ .catch(() => null)) as Partial<CoordinatorTraceEntry> | null;
664
685
  if (
665
686
  !body ||
666
687
  (body.source !== 'coordinator' && body.source !== 'dynamic_worker') ||
@@ -693,7 +714,9 @@ export class PlayDedup implements DurableObject {
693
714
  await this.state.storage.delete([...entries.keys()].slice(0, overflow));
694
715
  }
695
716
  });
696
- return new Response('{}', { headers: { 'content-type': 'application/json' } });
717
+ return new Response('{}', {
718
+ headers: { 'content-type': 'application/json' },
719
+ });
697
720
  }
698
721
 
699
722
  private async handleTraceList(): Promise<Response> {
@@ -702,7 +725,9 @@ export class PlayDedup implements DurableObject {
702
725
  });
703
726
  return new Response(
704
727
  JSON.stringify({
705
- entries: [...entries.values()].sort((left, right) => left.ts - right.ts),
728
+ entries: [...entries.values()].sort(
729
+ (left, right) => left.ts - right.ts,
730
+ ),
706
731
  }),
707
732
  { headers: { 'content-type': 'application/json' } },
708
733
  );