@effect-agent/testing 0.1.0-beta.41 → 0.1.0-beta.44

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.
@@ -24,6 +24,7 @@ export class CatalogLifecycleCounts extends Schema.Class<CatalogLifecycleCounts>
24
24
  acquired: Schema.Natural,
25
25
  finalized: Schema.Natural,
26
26
  }) {}
27
+
27
28
  export class CatalogLifecycle extends Context.Service<
28
29
  CatalogLifecycle,
29
30
  {
@@ -37,6 +38,7 @@ export class CatalogLifecycle extends Context.Service<
37
38
  Effect.gen(function* () {
38
39
  const acquired = yield* Ref.make(0);
39
40
  const finalized = yield* Ref.make(0);
41
+
40
42
  return CatalogLifecycle.of({
41
43
  markAcquired: Ref.update(acquired, (n) => n + 1),
42
44
  markFinalized: Ref.update(finalized, (n) => n + 1),
@@ -54,11 +56,13 @@ const flight = FlightOption.make({
54
56
  estimatedCents: 180_000,
55
57
  currency: "USD",
56
58
  });
59
+
57
60
  const lodging = LodgingOption.make({
58
61
  lodging: "Bloomsbury House · refundable studio · 4 nights",
59
62
  estimatedCents: 104_000,
60
63
  currency: "USD",
61
64
  });
65
+
62
66
  const activities = ActivitySearchResult.make({
63
67
  activities: ["British Museum timed entry", "Thames evening walk"],
64
68
  });
@@ -85,6 +89,7 @@ export const ReverseCompletionToolkitLayer = Effect.gen(function* () {
85
89
  const releaseFlight = yield* Deferred.make<void>();
86
90
  const releaseLodging = yield* Deferred.make<void>();
87
91
  const releaseActivity = yield* Deferred.make<void>();
92
+
88
93
  const awaitRelease = <A>(
89
94
  started: Deferred.Deferred<void>,
90
95
  release: Deferred.Deferred<void>,
@@ -94,6 +99,7 @@ export const ReverseCompletionToolkitLayer = Effect.gen(function* () {
94
99
  Effect.andThen(Deferred.await(release)),
95
100
  Effect.as(value),
96
101
  );
102
+
97
103
  return {
98
104
  controls: {
99
105
  flightStarted: Deferred.await(flightStarted),
@@ -115,7 +121,9 @@ export const FlightCatalogLayer = Layer.effect(
115
121
  FlightCatalog,
116
122
  Effect.gen(function* () {
117
123
  const lifecycle = yield* CatalogLifecycle;
124
+
118
125
  yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
126
+
119
127
  return FlightCatalog.of({
120
128
  search: (query) =>
121
129
  query.origin === query.destination
@@ -129,11 +137,14 @@ export const FlightCatalogLayer = Layer.effect(
129
137
  });
130
138
  }),
131
139
  );
140
+
132
141
  export const LodgingCatalogLayer = Layer.effect(
133
142
  LodgingCatalog,
134
143
  Effect.gen(function* () {
135
144
  const lifecycle = yield* CatalogLifecycle;
145
+
136
146
  yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
147
+
137
148
  return LodgingCatalog.of({
138
149
  search: (query) =>
139
150
  query.nights < 1
@@ -147,11 +158,14 @@ export const LodgingCatalogLayer = Layer.effect(
147
158
  });
148
159
  }),
149
160
  );
161
+
150
162
  export const ActivityCatalogLayer = Layer.effect(
151
163
  ActivityCatalog,
152
164
  Effect.gen(function* () {
153
165
  const lifecycle = yield* CatalogLifecycle;
166
+
154
167
  yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
168
+
155
169
  return ActivityCatalog.of({
156
170
  search: (query) =>
157
171
  query.destination === ""
@@ -165,10 +179,12 @@ export const ActivityCatalogLayer = Layer.effect(
165
179
  });
166
180
  }),
167
181
  );
182
+
168
183
  /** Stable supplier-side booking identity, minted deterministically from the idempotency key. */
169
184
  export const BookingRef = Schema.NonEmptyString.pipe(
170
185
  Schema.brand("@effect-agent/testing/travel-planner/BookingRef"),
171
186
  );
187
+
172
188
  export type BookingRef = typeof BookingRef.Type;
173
189
 
174
190
  /** The supplier desk operations the P5 booking Tools and Steps invoke. */
@@ -179,6 +195,7 @@ export const SupplierOperation = Schema.Literals([
179
195
  "reserve-lodging",
180
196
  "issue-confirmation",
181
197
  ]);
198
+
182
199
  export type SupplierOperation = typeof SupplierOperation.Type;
183
200
 
184
201
  /**
@@ -287,7 +304,9 @@ export class SupplierBookingDesk extends Context.Service<
287
304
  request.idempotencyKey,
288
305
  (current.counts.get(request.idempotencyKey) ?? 0) + 1,
289
306
  );
307
+
290
308
  const existing = current.bookings.get(request.idempotencyKey);
309
+
291
310
  const record =
292
311
  existing ??
293
312
  SupplierBookingRecord.make({
@@ -297,18 +316,24 @@ export class SupplierBookingDesk extends Context.Service<
297
316
  detail: request.detail,
298
317
  status: "confirmed",
299
318
  });
319
+
300
320
  const bookings =
301
321
  existing === undefined
302
322
  ? new Map(current.bookings).set(request.idempotencyKey, record)
303
323
  : current.bookings;
324
+
304
325
  const hold = Option.fromNullishOr(current.holds.get(request.idempotencyKey));
326
+
305
327
  const holds = Option.isSome(hold)
306
328
  ? (() => {
307
329
  const next = new Map(current.holds);
330
+
308
331
  next.delete(request.idempotencyKey);
332
+
309
333
  return next;
310
334
  })()
311
335
  : current.holds;
336
+
312
337
  return [
313
338
  { record, hold },
314
339
  { bookings, counts, holds },
@@ -319,9 +344,11 @@ export class SupplierBookingDesk extends Context.Service<
319
344
  Ref.modify(state, (current) => {
320
345
  const key = cancelBookingIdempotencyKey(bookingRef);
321
346
  const counts = new Map(current.counts).set(key, (current.counts.get(key) ?? 0) + 1);
347
+
322
348
  const existingEntry = [...current.bookings.entries()].find(
323
349
  ([, record]) => record.bookingRef === bookingRef,
324
350
  );
351
+
325
352
  if (existingEntry === undefined) {
326
353
  return [
327
354
  { record: Option.none<SupplierBookingRecord>(), hold: Option.none() },
@@ -329,19 +356,25 @@ export class SupplierBookingDesk extends Context.Service<
329
356
  ] as const;
330
357
  }
331
358
  const [storeKey, existing] = existingEntry;
359
+
332
360
  const cancelled =
333
361
  existing.status === "cancelled"
334
362
  ? existing
335
363
  : SupplierBookingRecord.make({ ...existing, status: "cancelled" });
364
+
336
365
  const bookings = new Map(current.bookings).set(storeKey, cancelled);
337
366
  const hold = Option.fromNullishOr(current.holds.get(key));
367
+
338
368
  const holds = Option.isSome(hold)
339
369
  ? (() => {
340
370
  const next = new Map(current.holds);
371
+
341
372
  next.delete(key);
373
+
342
374
  return next;
343
375
  })()
344
376
  : current.holds;
377
+
345
378
  return [
346
379
  { record: Option.some(cancelled), hold },
347
380
  { bookings, counts, holds },
@@ -372,10 +405,12 @@ export class SupplierBookingDesk extends Context.Service<
372
405
  Effect.gen(function* () {
373
406
  const held = yield* Deferred.make<void>();
374
407
  const release = yield* Deferred.make<void>();
408
+
375
409
  yield* Ref.update(state, (current) => ({
376
410
  ...current,
377
411
  holds: new Map(current.holds).set(idempotencyKey, { held, release }),
378
412
  }));
413
+
379
414
  return {
380
415
  held: Deferred.await(held),
381
416
  release: Deferred.succeed(release, undefined).pipe(Effect.asVoid),
@@ -403,12 +438,14 @@ export const TravelGuidanceLayer = Layer.succeed(
403
438
  ),
404
439
  }),
405
440
  );
441
+
406
442
  export const DeterministicIdGeneratorLayer = Layer.effect(
407
443
  IdGenerator,
408
444
  Effect.gen(function* () {
409
445
  const thread = yield* Ref.make(0);
410
446
  const run = yield* Ref.make(0);
411
447
  const turn = yield* Ref.make(0);
448
+
412
449
  return IdGenerator.of({
413
450
  nextThreadId: Ref.updateAndGet(thread, (n) => n + 1).pipe(
414
451
  Effect.map((n) => Schema.decodeSync(ThreadId)(`thread-${n}`)),
@@ -422,6 +459,7 @@ export const DeterministicIdGeneratorLayer = Layer.effect(
422
459
  });
423
460
  }),
424
461
  );
462
+
425
463
  export const TravelPlannerRuntimeLayer = Layer.mergeAll(
426
464
  RunContextPreparationPassthrough,
427
465
  ThreadHistory.layerTransient,
@@ -35,9 +35,11 @@ export const phase3TravelPlannerProfile = TravelPlannerPersistenceProfile.make({
35
35
  });
36
36
 
37
37
  export const phase3TravelPlannerThreadId = Schema.decodeSync(ThreadId)("travel-planner-p3-thread");
38
+
38
39
  export const phase3TravelPlannerProducerId = Schema.decodeSync(ProducerId)(
39
40
  "travel-planner-p3-producer",
40
41
  );
42
+
41
43
  export const phase3TravelPlannerRunId = Schema.decodeSync(RunId)("travel-planner-p3-run");
42
44
 
43
45
  const deploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p3-scripted");
@@ -126,6 +128,7 @@ export const travelPlanFromProjection = (
126
128
  projection: ThreadProjection,
127
129
  ): Effect.Effect<TravelPlan, TravelPlannerProjectionError> => {
128
130
  const output = projection.modelOutputs.at(-1);
131
+
129
132
  if (output === undefined) {
130
133
  return Effect.fail(
131
134
  TravelPlannerProjectionError.make({
@@ -133,6 +136,7 @@ export const travelPlanFromProjection = (
133
136
  }),
134
137
  );
135
138
  }
139
+
136
140
  return Schema.decodeUnknownEffect(TravelPlan)(output).pipe(
137
141
  Effect.mapError((error) => TravelPlannerProjectionError.make({ message: error.message })),
138
142
  );
@@ -67,9 +67,11 @@ export const phase4TravelPlannerProfile = TravelPlannerDurabilityProfile.make({
67
67
  export const phase4TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)(
68
68
  "travel-planner-p4-deployment",
69
69
  );
70
+
70
71
  export const phase4TravelPlannerProducerId = Schema.decodeSync(ProducerId)(
71
72
  "travel-planner-p4-producer",
72
73
  );
74
+
73
75
  export const phase4TravelPlannerPrincipal = Schema.decodeSync(Principal)(
74
76
  "travel-planner-p4-principal",
75
77
  );
@@ -112,6 +114,7 @@ export const DurableSearchFlights = Tool.make("search_flights", {
112
114
  failureMode: "error",
113
115
  dependencies: [FlightCatalog],
114
116
  }).annotate(ToolExecutionClass, "readonly");
117
+
115
118
  export const DurableSearchLodging = Tool.make("search_lodging", {
116
119
  parameters: Schema.Struct(LodgingQuery.fields),
117
120
  success: LodgingOption,
@@ -119,6 +122,7 @@ export const DurableSearchLodging = Tool.make("search_lodging", {
119
122
  failureMode: "error",
120
123
  dependencies: [LodgingCatalog],
121
124
  }).annotate(ToolExecutionClass, "readonly");
125
+
122
126
  export const DurableSearchActivities = Tool.make("search_activities", {
123
127
  parameters: Schema.Struct(ActivityQuery.fields),
124
128
  success: ActivitySearchResult,
@@ -208,7 +212,9 @@ export const travelPlanFromDurableSettlement = Effect.fn(
208
212
  const settlements = records.flatMap((envelope) =>
209
213
  envelope.record.payload._tag === "SubmissionSettled" ? [envelope.record.payload] : [],
210
214
  );
215
+
211
216
  const settled = settlements.at(0);
217
+
212
218
  if (settled === undefined) {
213
219
  return yield* TravelPlannerDurableEvidenceError.make({
214
220
  message: "The canonical Thread Log has no SubmissionSettled record.",
@@ -219,6 +225,7 @@ export const travelPlanFromDurableSettlement = Effect.fn(
219
225
  message: `The Submission settled ${settled.outcome} without a completed itinerary result.`,
220
226
  });
221
227
  }
228
+
222
229
  return yield* Schema.decodeUnknownEffect(TravelPlan)(settled.result).pipe(
223
230
  Effect.mapError((error) =>
224
231
  TravelPlannerDurableEvidenceError.make({
@@ -252,16 +259,19 @@ export const normalizeDurableTravelPlannerEvidence = Effect.fn(
252
259
  }),
253
260
  ),
254
261
  );
262
+
255
263
  const comparable = encoded.map((envelope) => ({
256
264
  batchId: envelope.batchId,
257
265
  sequence: envelope.sequence,
258
266
  record: envelope.record,
259
267
  }));
268
+
260
269
  const substituted: unknown = JSON.parse(
261
270
  JSON.stringify(comparable)
262
271
  .replaceAll(receipt.submissionId, "{submissionId}")
263
272
  .replaceAll(receipt.receiptId, "{receiptId}"),
264
273
  );
274
+
265
275
  return yield* decodeComparableJson(substituted).pipe(
266
276
  Effect.mapError((error) =>
267
277
  TravelPlannerDurableEvidenceError.make({
@@ -75,9 +75,11 @@ export const phase5TravelPlannerProfile = TravelPlannerBookingProfile.make({
75
75
  export const phase5TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)(
76
76
  "travel-planner-p5-deployment",
77
77
  );
78
+
78
79
  export const phase5TravelPlannerProducerId = Schema.decodeSync(ProducerId)(
79
80
  "travel-planner-p5-producer",
80
81
  );
82
+
81
83
  export const phase5TravelPlannerPrincipal = Schema.decodeSync(Principal)(
82
84
  "travel-planner-p5-principal",
83
85
  );
@@ -105,6 +107,7 @@ export const phase5TravelPlannerSubmitOptions = (
105
107
  export const TravelerRef = Schema.NonEmptyString.pipe(
106
108
  Schema.brand("@effect-agent/testing/travel-planner/TravelerRef"),
107
109
  );
110
+
108
111
  export type TravelerRef = typeof TravelerRef.Type;
109
112
 
110
113
  /**
@@ -171,6 +174,7 @@ export class TravelBookingReport extends Schema.Class<TravelBookingReport>("Trav
171
174
  * to query external truth — both sides are exported so they cannot drift.
172
175
  */
173
176
  export const bookFlightIdempotencyKey = (toolCallId: string): string => `book-flight:${toolCallId}`;
177
+
174
178
  export const itineraryStepIdempotencyKey = (toolCallId: string, stepName: string): string =>
175
179
  `${toolCallId}:${stepName}`;
176
180
 
@@ -252,11 +256,13 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
252
256
  Effect.gen(function* () {
253
257
  const desk = yield* SupplierBookingDesk;
254
258
  const toolCallId = yield* requireToolCallId("book_flight", context.toolCallId);
259
+
255
260
  const record = yield* desk.book({
256
261
  operation: "book-flight",
257
262
  idempotencyKey: bookFlightIdempotencyKey(toolCallId),
258
263
  detail: `flight ${request.quoteId} for ${request.travelerRef} on ${request.departOn}`,
259
264
  });
265
+
260
266
  return SupplierBookingConfirmation.make({
261
267
  bookingRef: record.bookingRef,
262
268
  status: "confirmed",
@@ -267,6 +273,7 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
267
273
  Effect.gen(function* () {
268
274
  const desk = yield* SupplierBookingDesk;
269
275
  const record = yield* desk.cancel(request.bookingRef);
276
+
270
277
  return CancellationConfirmation.make({
271
278
  bookingRef: record.bookingRef,
272
279
  status: "cancelled",
@@ -277,6 +284,7 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
277
284
  const desk = yield* SupplierBookingDesk;
278
285
  const step = yield* DurableStep;
279
286
  const toolCallId = yield* requireToolCallId("book_itinerary", context.toolCallId);
287
+
280
288
  const bookStep = (
281
289
  stepName: "reserve-flight" | "reserve-lodging" | "issue-confirmation",
282
290
  detail: string,
@@ -290,18 +298,22 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
290
298
  detail,
291
299
  }),
292
300
  );
301
+
293
302
  const flight = yield* bookStep(
294
303
  "reserve-flight",
295
304
  `flight ${request.quoteId} for ${request.travelerRef}`,
296
305
  );
306
+
297
307
  const lodging = yield* bookStep(
298
308
  "reserve-lodging",
299
309
  `lodging ${request.destination} for ${request.nights} nights (${request.travelerRef})`,
300
310
  );
311
+
301
312
  const confirmation = yield* bookStep(
302
313
  "issue-confirmation",
303
314
  `itinerary confirmation for ${request.travelerRef}`,
304
315
  );
316
+
305
317
  return ItineraryConfirmation.make({
306
318
  flightBookingRef: flight.bookingRef,
307
319
  lodgingBookingRef: lodging.bookingRef,
@@ -336,6 +348,7 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
336
348
  ToolReconciler,
337
349
  Effect.gen(function* () {
338
350
  const desk = yield* SupplierBookingDesk;
351
+
339
352
  return ToolReconciler.of({
340
353
  reconcile: (evidence) =>
341
354
  Effect.gen(function* () {
@@ -343,6 +356,7 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
343
356
  case "book_flight": {
344
357
  const key = bookFlightIdempotencyKey(evidence.toolCallId);
345
358
  const booking = yield* desk.lookup(key);
359
+
346
360
  if (Option.isSome(booking) && booking.value.status === "confirmed") {
347
361
  const confirmation = yield* encodeConfirmation(
348
362
  SupplierBookingConfirmation.make({
@@ -351,8 +365,10 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
351
365
  detail: booking.value.detail,
352
366
  }),
353
367
  ).pipe(Effect.flatMap(decodePersistedJson));
368
+
354
369
  return ReconciliationCompleted.make({ result: confirmation, isFailure: false });
355
370
  }
371
+
356
372
  return ReconciliationUncertain.make({
357
373
  reason: `The supplier desk shows no confirmed booking under ${key}; a write may still be in flight.`,
358
374
  });
@@ -413,6 +429,7 @@ export class TravelPlannerBookingEvidenceError extends Schema.TaggedError<Travel
413
429
  const bookingResultRefs = (result: unknown): ReadonlyArray<string> => {
414
430
  if (typeof result !== "object" || result === null) return [];
415
431
  const refs: Array<string> = [];
432
+
416
433
  for (const [field, value] of Object.entries(result)) {
417
434
  if (
418
435
  typeof value === "string" &&
@@ -424,6 +441,7 @@ const bookingResultRefs = (result: unknown): ReadonlyArray<string> => {
424
441
  refs.push(value);
425
442
  }
426
443
  }
444
+
427
445
  return refs;
428
446
  };
429
447
 
@@ -443,8 +461,10 @@ export const assertSettledBookingsExistAtSupplier = Effect.fn(
443
461
  const desk = yield* SupplierBookingDesk;
444
462
  const bookings = yield* desk.bookings;
445
463
  const knownRefs = new Set<string>(bookings.map((booking) => booking.bookingRef));
464
+
446
465
  for (const envelope of records) {
447
466
  const payload = envelope.record.payload;
467
+
448
468
  if (
449
469
  payload._tag !== "ToolCallSettled" ||
450
470
  payload.isFailure ||
@@ -101,8 +101,10 @@ export const phase6TravelPlannerProfile = TravelPlannerCloudflareProfile.make({
101
101
  export const phase6TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)(
102
102
  "travel-planner-p6-deployment",
103
103
  );
104
+
104
105
  /** Producer prefix of the DC host; each Object mints `{prefix}:{threadId}`. */
105
106
  export const phase6TravelPlannerProducerPrefix = "travel-planner-p6-producer";
107
+
106
108
  /** The full producer identity one DC Thread Object mints for itself. */
107
109
  export const phase6TravelPlannerProducerId = (threadId: string): ProducerId =>
108
110
  Schema.decodeSync(ProducerId)(`${phase6TravelPlannerProducerPrefix}:${threadId}`);
@@ -143,6 +145,7 @@ const ComparableEnvelope = Schema.Struct({
143
145
  sequence: Schema.Number,
144
146
  record: Schema.Json,
145
147
  });
148
+
146
149
  const decodeComparableEnvelopes = Schema.decodeUnknownEffect(Schema.Array(ComparableEnvelope));
147
150
 
148
151
  /**
@@ -180,7 +183,9 @@ export const normalizeCrossPlatformTravelPlannerEvidence = Effect.fn(
180
183
  const canonical = records.filter(
181
184
  (envelope) => envelope.record.payload._tag !== "RepairAnnotated",
182
185
  );
186
+
183
187
  const base = yield* normalizeDurableTravelPlannerEvidence(canonical, receipt);
188
+
184
189
  const scrubbed: unknown = JSON.parse(
185
190
  JSON.stringify(base)
186
191
  .replaceAll(identity.producerId, "{producerId}")
@@ -189,6 +194,7 @@ export const normalizeCrossPlatformTravelPlannerEvidence = Effect.fn(
189
194
  .replaceAll(/\d{4}-\d{2}-\d{2}T[0-9:.]+Z/g, "{timestamp}")
190
195
  .replaceAll(/"[0-9a-f]{64}"/g, '"{digest}"'),
191
196
  );
197
+
192
198
  const comparable = yield* decodeComparableEnvelopes(scrubbed).pipe(
193
199
  Effect.mapError((error) =>
194
200
  TravelPlannerDurableEvidenceError.make({
@@ -196,11 +202,13 @@ export const normalizeCrossPlatformTravelPlannerEvidence = Effect.fn(
196
202
  }),
197
203
  ),
198
204
  );
205
+
199
206
  const renumbered = comparable.map((entry, index) => ({
200
207
  batchId: entry.batchId,
201
208
  sequence: index + 1,
202
209
  record: entry.record,
203
210
  }));
211
+
204
212
  return yield* decodeComparableJson(renumbered).pipe(
205
213
  Effect.mapError((error) =>
206
214
  TravelPlannerDurableEvidenceError.make({
@@ -345,6 +353,7 @@ export const phase6GatedPlannerModel = Model.make(
345
353
  Stream.unwrap(
346
354
  Effect.sync(() => {
347
355
  const promptJson = JSON.stringify(options.prompt);
356
+
348
357
  return promptJson.includes(phase6FlightCallId)
349
358
  ? plannerDecide(promptJson)
350
359
  : Stream.fromEffectDrain(awaitPlannerGate(gateMarkerFromPrompt(promptJson))).pipe(
@@ -448,6 +457,7 @@ const bookingReportParts = (marker: string): ReadonlyArray<Response.StreamPartEn
448
457
  */
449
458
  export const phase6BookingModel = promptAwareModel("travel-planner-phase-5", (promptJson) => {
450
459
  const marker = bookingMarkerFromPrompt(promptJson);
460
+
451
461
  return promptJson.includes(phase6BookingToolCallId(marker))
452
462
  ? Stream.fromIterable(bookingReportParts(marker))
453
463
  : Stream.fromIterable(bookingCallParts(marker));
@@ -471,6 +481,7 @@ const countingGuideLayer = Layer.succeed(
471
481
  lookup: (query) =>
472
482
  Effect.suspend(() => {
473
483
  guideInvocations += 1;
484
+
474
485
  return destinationLookup(query);
475
486
  }),
476
487
  }),
@@ -609,6 +620,7 @@ export const makePhase6TravelPlannerBindings: Effect.Effect<ReadonlyArray<Resolv
609
620
  );
610
621
 
611
622
  const researcherBinding = Agent.withModel(DestinationResearcher, phase6ResearcherModel);
623
+
612
624
  const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(
613
625
  Layer.provideMerge(countingGuideLayer),
614
626
  );
@@ -4,6 +4,7 @@ import type { ScriptedTurnInput } from "../../scripted-model.ts";
4
4
  import { TravelPlan, TripRequest, type TravelPlan as TravelPlanValue } from "./definition.ts";
5
5
 
6
6
  const usage = { inputTokens: { total: 128 }, outputTokens: { total: 96 } };
7
+
7
8
  export const phase1Trip = Schema.decodeSync(TripRequest)({
8
9
  request:
9
10
  "Plan a review-only London trip using the deterministic flight, lodging, and activity searches.",
@@ -15,6 +16,7 @@ export const phase1Trip = Schema.decodeSync(TripRequest)({
15
16
  budgetCents: 350_000,
16
17
  currency: "USD",
17
18
  });
19
+
18
20
  export const expectedTravelPlan: TravelPlanValue = Schema.decodeSync(TravelPlan)({
19
21
  itineraries: [
20
22
  {
@@ -79,9 +79,11 @@ export const s2TravelPlannerProfile = TravelPlannerSubagentDurabilityProfile.mak
79
79
  export const s2TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)(
80
80
  "travel-planner-s2-deployment",
81
81
  );
82
+
82
83
  export const s2TravelPlannerProducerId = Schema.decodeSync(ProducerId)(
83
84
  "travel-planner-s2-producer",
84
85
  );
86
+
85
87
  export const s2TravelPlannerPrincipal = Schema.decodeSync(Principal)("travel-planner-s2-principal");
86
88
 
87
89
  const digestOf = (character: string) => Schema.decodeSync(Digest)(character.repeat(64));
@@ -168,6 +170,7 @@ export const durableResearchShortlist = (destination: string): DestinationShortl
168
170
  */
169
171
  export const encodedDestinationFacts = (destination: string): unknown => {
170
172
  const report = destinationReportFor(destination);
173
+
171
174
  return Schema.encodeSync(DestinationFacts)(
172
175
  DestinationFacts.make({
173
176
  destination: report.destination,
@@ -195,6 +198,7 @@ export const makeInvocationCountingModel = (
195
198
  Effect.gen(function* () {
196
199
  const calls = yield* Ref.make(0);
197
200
  const prompts = yield* Ref.make<ReadonlyArray<string>>([]);
201
+
198
202
  const model = Model.make(
199
203
  "scripted",
200
204
  name,
@@ -206,16 +210,19 @@ export const makeInvocationCountingModel = (
206
210
  Stream.unwrap(
207
211
  Effect.gen(function* () {
208
212
  const call = yield* Ref.getAndUpdate(calls, (value) => value + 1);
213
+
209
214
  yield* Ref.update(prompts, (previous) => [
210
215
  ...previous,
211
216
  JSON.stringify(request.prompt.content),
212
217
  ]);
218
+
213
219
  return Stream.fromIterable(script(call));
214
220
  }),
215
221
  ),
216
222
  }),
217
223
  ),
218
224
  );
225
+
219
226
  return { model, calls: Ref.get(calls), prompts: Ref.get(prompts) };
220
227
  });
221
228
 
@@ -336,6 +343,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
336
343
  const focus = options?.focus ?? "museums";
337
344
 
338
345
  const guideInvocations = yield* Ref.make(0);
346
+
339
347
  const guideLayer = Layer.succeed(
340
348
  DestinationGuide,
341
349
  DestinationGuide.of({
@@ -345,6 +353,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
345
353
  ),
346
354
  }),
347
355
  );
356
+
348
357
  const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(
349
358
  Layer.provideMerge(guideLayer),
350
359
  );
@@ -352,6 +361,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
352
361
  const childModel = yield* makeInvocationCountingModel("destination-researcher-s2", (call) =>
353
362
  call === 0 ? researcherLookupParts(destination) : researcherReportParts(destination),
354
363
  );
364
+
355
365
  const childBinding = Agent.withModel(DestinationResearcher, childModel.model);
356
366
 
357
367
  const parentModel = yield* makeInvocationCountingModel("travel-coordinator-s2", (call) =>
@@ -359,6 +369,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
359
369
  ? delegationTurnParts(durableResearchCallId, destination, focus)
360
370
  : shortlistParts(durableResearchShortlist(destination)),
361
371
  );
372
+
362
373
  const parentBinding = Agent.withModel(TravelCoordinator, parentModel.model);
363
374
 
364
375
  const delegationLayer = durableDestinationResearchHandlersLayer(childBinding).pipe(
@@ -376,6 +387,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
376
387
  parentBinding,
377
388
  s2CoordinatorDigests,
378
389
  ).pipe(Effect.provide(delegationLayer));
390
+
379
391
  const childResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
380
392
  childBinding,
381
393
  options?.childRegistrationDigests ?? s2ResearcherDigests,
@@ -389,5 +401,6 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
389
401
  childPrompts: childModel.prompts,
390
402
  guideInvocations: Ref.get(guideInvocations),
391
403
  };
404
+
392
405
  return harness;
393
406
  });