@effect-agent/testing 0.1.0-beta.42 → 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.
- package/dist/certification.mjs.map +1 -1
- package/dist/chaos.mjs.map +1 -1
- package/dist/code-executor.mjs.map +1 -1
- package/dist/deterministic-layers-CKyYxBhN.mjs.map +1 -1
- package/dist/docs-researcher.mjs +3 -2
- package/dist/docs-researcher.mjs.map +1 -1
- package/dist/scripted-model-C2y0ztuj.mjs.map +1 -1
- package/dist/travel-planner.mjs.map +1 -1
- package/package.json +1 -71
- package/src/certification.ts +78 -0
- package/src/chaos.ts +96 -0
- package/src/code-executor-conformance.ts +19 -0
- package/src/code-executor-substitute.ts +32 -0
- package/src/fixtures/docs-researcher/definition.ts +7 -0
- package/src/fixtures/docs-researcher/harness.ts +19 -0
- package/src/fixtures/docs-researcher/mcp.ts +14 -2
- package/src/fixtures/travel-planner/definition.ts +12 -0
- package/src/fixtures/travel-planner/deterministic-layers.ts +38 -0
- package/src/fixtures/travel-planner/phase3.ts +4 -0
- package/src/fixtures/travel-planner/phase4.ts +10 -0
- package/src/fixtures/travel-planner/phase5.ts +20 -0
- package/src/fixtures/travel-planner/phase6.ts +12 -0
- package/src/fixtures/travel-planner/scenarios.ts +2 -0
- package/src/fixtures/travel-planner/subagents-durable.ts +13 -0
- package/src/fixtures/travel-planner/subagents.ts +21 -0
- package/src/scripted-model.ts +18 -0
|
@@ -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
|
});
|