@effect-agent/testing 0.1.0-beta.9 → 0.1.0-beta.91

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 (55) hide show
  1. package/dist/Certification.d.mts +106 -0
  2. package/dist/Certification.mjs +633 -0
  3. package/dist/Certification.mjs.map +1 -0
  4. package/dist/Chaos.d.mts +126 -0
  5. package/dist/Chaos.mjs +755 -0
  6. package/dist/Chaos.mjs.map +1 -0
  7. package/dist/CodeExecutorConformance.d.mts +33 -0
  8. package/dist/CodeExecutorConformance.mjs +231 -0
  9. package/dist/CodeExecutorConformance.mjs.map +1 -0
  10. package/dist/CodeExecutorSubstitute.d.mts +21 -0
  11. package/dist/CodeExecutorSubstitute.mjs +368 -0
  12. package/dist/CodeExecutorSubstitute.mjs.map +1 -0
  13. package/dist/DocsResearcher.d.mts +276 -0
  14. package/dist/DocsResearcher.mjs +490 -0
  15. package/dist/DocsResearcher.mjs.map +1 -0
  16. package/dist/ScriptedModel-DAvxIiud.d.mts +220 -0
  17. package/dist/ScriptedModel.d.mts +2 -0
  18. package/dist/ScriptedModel.mjs +155 -0
  19. package/dist/ScriptedModel.mjs.map +1 -0
  20. package/dist/TravelPlanner.d.mts +1680 -0
  21. package/dist/TravelPlanner.mjs +1963 -0
  22. package/dist/TravelPlanner.mjs.map +1 -0
  23. package/dist/deterministic-layers-D5owIoke.mjs +358 -0
  24. package/dist/deterministic-layers-D5owIoke.mjs.map +1 -0
  25. package/dist/index.d.mts +2 -3408
  26. package/dist/index.mjs +2 -4771
  27. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  28. package/package.json +1 -48
  29. package/src/{certification.ts → Certification.ts} +366 -171
  30. package/src/{chaos.ts → Chaos.ts} +241 -127
  31. package/src/{code-executor-conformance.ts → CodeExecutorConformance.ts} +30 -7
  32. package/src/{code-executor-substitute.ts → CodeExecutorSubstitute.ts} +113 -46
  33. package/src/{fixtures/docs-researcher/index.ts → DocsResearcher.ts} +68 -5
  34. package/src/{scripted-model.ts → ScriptedModel.ts} +25 -29
  35. package/src/TravelPlanner.ts +232 -0
  36. package/src/fixtures/docs-researcher/definition.ts +20 -11
  37. package/src/fixtures/docs-researcher/harness.ts +41 -34
  38. package/src/fixtures/docs-researcher/mcp.ts +49 -3
  39. package/src/fixtures/travel-planner/definition.ts +15 -2
  40. package/src/fixtures/travel-planner/deterministic-layers.ts +47 -4
  41. package/src/fixtures/travel-planner/phase2.ts +4 -3
  42. package/src/fixtures/travel-planner/phase3.ts +19 -42
  43. package/src/fixtures/travel-planner/phase4.ts +24 -37
  44. package/src/fixtures/travel-planner/phase5.ts +35 -42
  45. package/src/fixtures/travel-planner/phase6.ts +191 -88
  46. package/src/fixtures/travel-planner/phase7.ts +4 -102
  47. package/src/fixtures/travel-planner/scenarios.ts +3 -4
  48. package/src/fixtures/travel-planner/subagents-durable.ts +35 -61
  49. package/src/fixtures/travel-planner/subagents.ts +36 -14
  50. package/src/index.ts +1 -11
  51. package/src/internal/certification-report.ts +25 -0
  52. package/dist/index.mjs.map +0 -1
  53. package/src/code-executor-conformance.d.ts +0 -30
  54. package/src/fixtures/travel-planner/index.ts +0 -11
  55. package/src/fixtures/warehouse/index.ts +0 -412
@@ -1,33 +1,27 @@
1
- import {
2
- delegationAllocationFromPolicy,
3
- SubagentReservationsMemoryLive,
4
- SubagentRuntime,
5
- } from "@effect-agent/capabilities";
6
- import { Agent, type ConversationId } from "@effect-agent/core";
7
- import type { RuntimeBinding } from "@effect-agent/engine";
8
- import {
9
- DefinitionDigests,
10
- DeploymentId,
11
- Digest,
12
- DurableWorkerBinding,
13
- Principal,
14
- ProducerId,
15
- type DurableSubmitOptions,
16
- type IdempotencyKey,
17
- type ResolvedBinding,
18
- } from "@effect-agent/session";
19
1
  import { Effect, Layer, Ref, Schema, Stream } from "effect";
2
+ import * as Agent from "effect-agent/agent";
3
+ import { DurableWorkerBinding, type ResolvedBinding } from "effect-agent/agent-registration";
4
+ import { type RuntimeBinding } from "effect-agent/agent-runtime";
5
+ import { type DurableSubmitOptions } from "effect-agent/durable-agent-runtime";
6
+ import { type ThreadId } from "effect-agent/identifiers";
7
+ import { DefinitionDigests, DeploymentId, Digest, ProducerId } from "effect-agent/records";
8
+ import * as Subagent from "effect-agent/subagent";
9
+ import { delegationAllocationFromPolicy } from "effect-agent/subagent";
10
+ import { SubagentReservationsMemoryLive } from "effect-agent/subagent-reservations";
11
+ import { Principal, type IdempotencyKey } from "effect-agent/submission-ledger";
20
12
  import { LanguageModel, Model, type Response, type Toolkit } from "effect/unstable/ai";
21
13
 
22
14
  import { DeterministicIdGeneratorLayer } from "./deterministic-layers.ts";
23
- import {
15
+ import type {
24
16
  DestinationBrief,
17
+ DestinationReport,
18
+ DestinationResearcherToolkit,
19
+ } from "./subagents.ts";
20
+ import {
25
21
  DestinationFacts,
26
22
  DestinationGuide,
27
23
  DestinationRecommendation,
28
- DestinationReport,
29
24
  DestinationResearcher,
30
- DestinationResearcherToolkit,
31
25
  DestinationResearcherToolkitLayer,
32
26
  destinationLookup,
33
27
  destinationReportFor,
@@ -40,46 +34,15 @@ import {
40
34
  TravelCoordinator,
41
35
  } from "./subagents.ts";
42
36
 
43
- // ---------------------------------------------------------------------------
44
- // S2 durability profile (spec/subagents.md §17): the S1 coordinator →
45
- // destination-researcher delegation re-run as ACCEPTED WORK on the Node/SQLite
46
- // runtime. The claim is `DN` durable attached Subagents only: establishment
47
- // and join are replay-safe by construction (SUB-016/SUB-019), a completed
48
- // child is never re-executed on a lost join acknowledgment, and NO claim of
49
- // exactly-once child external effects is made (rule 8; an unresolved ordinary
50
- // child Tool blocks as an Unknown Outcome instead, SUB-021). Cloudflare
51
- // equivalence is P6 scope and explicitly not claimed here.
52
- // ---------------------------------------------------------------------------
53
-
54
- export class TravelPlannerSubagentDurabilityProfile extends Schema.Class<TravelPlannerSubagentDurabilityProfile>(
55
- "@effect-agent/testing/travel-planner/TravelPlannerSubagentDurabilityProfile",
56
- )({
57
- deploymentClass: Schema.Literal("DN"),
58
- durableAttachedSubagents: Schema.Literal(true),
59
- canonicalSchemaVersion: Schema.Literal(1),
60
- /** Establishment/join replay converges on one child Receipt, Conversation, and join batch. */
61
- subagentReplaySafe: Schema.Literal(true),
62
- /** Never claimed (rule 8): child ordinary Tools stop at Unknown Outcomes, they do not replay. */
63
- childExternalEffectsExactlyOnce: Schema.Literal(false),
64
- /** The same conformance suite under DO eviction/alarms is P6 scope (spec §17 `DC`). */
65
- cloudflareEquivalence: Schema.Literal(false),
66
- }) {}
67
-
68
- export const s2TravelPlannerProfile = TravelPlannerSubagentDurabilityProfile.make({
69
- deploymentClass: "DN",
70
- durableAttachedSubagents: true,
71
- canonicalSchemaVersion: 1,
72
- subagentReplaySafe: true,
73
- childExternalEffectsExactlyOnce: false,
74
- cloudflareEquivalence: false,
75
- });
76
-
37
+ /** Registered coordinator and researcher bindings for durable delegation tests. */
77
38
  export const s2TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)(
78
39
  "travel-planner-s2-deployment",
79
40
  );
41
+
80
42
  export const s2TravelPlannerProducerId = Schema.decodeSync(ProducerId)(
81
43
  "travel-planner-s2-producer",
82
44
  );
45
+
83
46
  export const s2TravelPlannerPrincipal = Schema.decodeSync(Principal)("travel-planner-s2-principal");
84
47
 
85
48
  const digestOf = (character: string) => Schema.decodeSync(Digest)(character.repeat(64));
@@ -94,7 +57,7 @@ export const s2CoordinatorDigests = DefinitionDigests.make({
94
57
  /**
95
58
  * The exact child Binding digest strings the application declares on
96
59
  * `SubagentRuntimeOptions.durable.targetDigests` AND the host registers with
97
- * the `AgentBindingResolver` for the researcher Binding. The coordinator
60
+ * the host's binding array for the researcher Binding. The coordinator
98
61
  * stores and verifies them byte-for-byte (SUB-023); a host registration under
99
62
  * different strings is a `ChildCompatibilityFailure`, never a substitution.
100
63
  */
@@ -112,10 +75,10 @@ export const s2ResearcherDigests = DefinitionDigests.make({
112
75
 
113
76
  /** Durable admission options for one coordinator Submission on one mission lane. */
114
77
  export const s2TravelPlannerSubmitOptions = (
115
- conversationId: ConversationId,
78
+ threadId: ThreadId,
116
79
  idempotencyKey: IdempotencyKey,
117
80
  ): DurableSubmitOptions => ({
118
- conversationId,
81
+ threadId,
119
82
  principal: s2TravelPlannerPrincipal,
120
83
  idempotencyKey,
121
84
  definitions: s2CoordinatorDigests,
@@ -166,6 +129,7 @@ export const durableResearchShortlist = (destination: string): DestinationShortl
166
129
  */
167
130
  export const encodedDestinationFacts = (destination: string): unknown => {
168
131
  const report = destinationReportFor(destination);
132
+
169
133
  return Schema.encodeSync(DestinationFacts)(
170
134
  DestinationFacts.make({
171
135
  destination: report.destination,
@@ -193,6 +157,7 @@ export const makeInvocationCountingModel = (
193
157
  Effect.gen(function* () {
194
158
  const calls = yield* Ref.make(0);
195
159
  const prompts = yield* Ref.make<ReadonlyArray<string>>([]);
160
+
196
161
  const model = Model.make(
197
162
  "scripted",
198
163
  name,
@@ -204,16 +169,19 @@ export const makeInvocationCountingModel = (
204
169
  Stream.unwrap(
205
170
  Effect.gen(function* () {
206
171
  const call = yield* Ref.getAndUpdate(calls, (value) => value + 1);
172
+
207
173
  yield* Ref.update(prompts, (previous) => [
208
174
  ...previous,
209
175
  JSON.stringify(request.prompt.content),
210
176
  ]);
177
+
211
178
  return Stream.fromIterable(script(call));
212
179
  }),
213
180
  ),
214
181
  }),
215
182
  ),
216
183
  );
184
+
217
185
  return { model, calls: Ref.get(calls), prompts: Ref.get(prompts) };
218
186
  });
219
187
 
@@ -283,7 +251,7 @@ export const durableDestinationResearchHandlersLayer = <Provider, ModelProvides,
283
251
  ModelRequires
284
252
  >,
285
253
  ) =>
286
- SubagentRuntime.layer(destinationResearchDelegation, childBinding, {
254
+ Subagent.layer(destinationResearchDelegation, childBinding, {
287
255
  mapChildFailure: mapResearchChildFailure,
288
256
  durable: { targetDigests: s2ResearcherDigestStrings },
289
257
  });
@@ -304,7 +272,7 @@ export interface DurableResearchHarnessOptions {
304
272
 
305
273
  /** One durable coordinator/researcher pair with observable invocation counters. */
306
274
  export interface DurableResearchHarness {
307
- /** Host registrations for `NodeDurableRuntimeOptions.bindings` (parent + child). */
275
+ /** Host registrations for `NodeDurableAgentRuntimeOptions.bindings` (parent + child). */
308
276
  readonly bindings: ReadonlyArray<ResolvedBinding>;
309
277
  /** Total coordinator model invocations across every Attempt and runtime handle. */
310
278
  readonly parentModelCalls: Effect.Effect<number>;
@@ -325,7 +293,7 @@ export interface DurableResearchHarness {
325
293
  * guide, Turn 2 writes the report), and both worker Bindings captured with
326
294
  * their requirement Contexts via `DurableWorkerBinding.make` under the exact
327
295
  * fixture digests. The returned `bindings` are plain values: they can be
328
- * registered with several `NodeDurableRuntime` stacks over the same SQLite
296
+ * registered with several `NodeDurableAgentRuntime` stacks over the same SQLite
329
297
  * file while the counters keep counting across all of them.
330
298
  */
331
299
  export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptions) =>
@@ -334,6 +302,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
334
302
  const focus = options?.focus ?? "museums";
335
303
 
336
304
  const guideInvocations = yield* Ref.make(0);
305
+
337
306
  const guideLayer = Layer.succeed(
338
307
  DestinationGuide,
339
308
  DestinationGuide.of({
@@ -343,6 +312,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
343
312
  ),
344
313
  }),
345
314
  );
315
+
346
316
  const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(
347
317
  Layer.provideMerge(guideLayer),
348
318
  );
@@ -350,6 +320,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
350
320
  const childModel = yield* makeInvocationCountingModel("destination-researcher-s2", (call) =>
351
321
  call === 0 ? researcherLookupParts(destination) : researcherReportParts(destination),
352
322
  );
323
+
353
324
  const childBinding = Agent.withModel(DestinationResearcher, childModel.model);
354
325
 
355
326
  const parentModel = yield* makeInvocationCountingModel("travel-coordinator-s2", (call) =>
@@ -357,6 +328,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
357
328
  ? delegationTurnParts(durableResearchCallId, destination, focus)
358
329
  : shortlistParts(durableResearchShortlist(destination)),
359
330
  );
331
+
360
332
  const parentBinding = Agent.withModel(TravelCoordinator, parentModel.model);
361
333
 
362
334
  const delegationLayer = durableDestinationResearchHandlersLayer(childBinding).pipe(
@@ -374,6 +346,7 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
374
346
  parentBinding,
375
347
  s2CoordinatorDigests,
376
348
  ).pipe(Effect.provide(delegationLayer));
349
+
377
350
  const childResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
378
351
  childBinding,
379
352
  options?.childRegistrationDigests ?? s2ResearcherDigests,
@@ -387,5 +360,6 @@ export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptio
387
360
  childPrompts: childModel.prompts,
388
361
  guideInvocations: Ref.get(guideInvocations),
389
362
  };
363
+
390
364
  return harness;
391
365
  });
@@ -1,17 +1,18 @@
1
- import { Subagent, SubagentPolicy, SubagentRuntime } from "@effect-agent/capabilities";
2
- import { Agent, AgentPolicy } from "@effect-agent/core";
3
- import type { RuntimeBinding } from "@effect-agent/engine";
4
1
  import { Context, Deferred, Effect, Layer, Ref, Schema, Stream } from "effect";
2
+ import * as Agent from "effect-agent/agent";
3
+ import { AgentPolicy } from "effect-agent/agent-policy";
4
+ import { type RuntimeBinding } from "effect-agent/agent-runtime";
5
+ import * as Subagent from "effect-agent/subagent";
6
+ import { SubagentPolicy } from "effect-agent/subagent";
5
7
  import { LanguageModel, Model, type Response, Tool, Toolkit } from "effect/unstable/ai";
6
8
 
7
- import type { ScriptedTurnInput } from "../../scripted-model.ts";
9
+ import type { ScriptedTurnInput } from "../../ScriptedModel.ts";
8
10
  import { AirportCode } from "./definition.ts";
9
11
  import { CatalogLifecycle } from "./deterministic-layers.ts";
10
12
 
11
13
  // ---------------------------------------------------------------------------
12
- // Destination Researcher: the S1 specialist child Agent
13
- // (spec/subagents.md §17 "deterministic scripted Travel Planner specialist
14
- // delegation"). The researcher is a normal Agent with one deterministic
14
+ // Destination Researcher: the S1 specialist child Agent.
15
+ // The researcher is a normal Agent with one deterministic
15
16
  // travel-service Tool; it never becomes a second child loop (SUB-001/003).
16
17
  // ---------------------------------------------------------------------------
17
18
 
@@ -51,6 +52,7 @@ export const LookupDestination = Tool.make("lookup_destination", {
51
52
  });
52
53
 
53
54
  export const DestinationResearcherToolkit = Toolkit.make(LookupDestination);
55
+
54
56
  export const DestinationResearcherToolkitLayer = DestinationResearcherToolkit.toLayer({
55
57
  lookup_destination: (query) => Effect.flatMap(DestinationGuide, (guide) => guide.lookup(query)),
56
58
  });
@@ -66,7 +68,7 @@ export class DestinationReport extends Schema.Class<DestinationReport>("Destinat
66
68
  advisory: Schema.NonEmptyString,
67
69
  }) {}
68
70
 
69
- export const DestinationResearcher = Agent.define("destination-researcher", {
71
+ export const DestinationResearcher = Agent.make("destination-researcher", {
70
72
  input: DestinationBrief,
71
73
  output: DestinationReport,
72
74
  instructions:
@@ -112,6 +114,7 @@ export const destinationLookup = (
112
114
  query: DestinationQuery,
113
115
  ): Effect.Effect<DestinationFacts, DestinationGuideUnavailable> => {
114
116
  const facts = guideFacts.get(query.destination);
117
+
115
118
  return facts === undefined
116
119
  ? Effect.fail(
117
120
  DestinationGuideUnavailable.make({
@@ -124,15 +127,18 @@ export const destinationLookup = (
124
127
 
125
128
  const requireDestinationFacts = (destination: string): DestinationFacts => {
126
129
  const facts = guideFacts.get(destination);
130
+
127
131
  if (facts === undefined) {
128
132
  throw new Error(`No deterministic guide entry exists for destination ${destination}`);
129
133
  }
134
+
130
135
  return facts;
131
136
  };
132
137
 
133
138
  /** The report the scripted researcher writes after consulting the guide. */
134
139
  export const destinationReportFor = (destination: string): DestinationReport => {
135
140
  const facts = requireDestinationFacts(destination);
141
+
136
142
  return DestinationReport.make({
137
143
  destination: facts.destination,
138
144
  highlights: facts.highlights,
@@ -147,7 +153,9 @@ export const DestinationGuideLayer = Layer.effect(
147
153
  DestinationGuide,
148
154
  Effect.gen(function* () {
149
155
  const lifecycle = yield* CatalogLifecycle;
156
+
150
157
  yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
158
+
151
159
  return DestinationGuide.of({ lookup: destinationLookup });
152
160
  }),
153
161
  );
@@ -159,7 +167,7 @@ export const DestinationResearchSupportLayer = Layer.mergeAll(
159
167
  );
160
168
 
161
169
  // ---------------------------------------------------------------------------
162
- // Delegation Definition (spec/subagents.md §4): the coordinator sees exactly
170
+ // Delegation Definition: the coordinator sees exactly
163
171
  // one Effect AI Tool with explicit input/result projections and finite bounds.
164
172
  // ---------------------------------------------------------------------------
165
173
 
@@ -189,7 +197,7 @@ export class DestinationResearchFailed extends Schema.TaggedError<DestinationRes
189
197
  * this gate before the handler reserves budget or spawns, so tests can order
190
198
  * concurrent delegation preflights without sleeps. It also keeps the
191
199
  * projection's construction requirements honestly visible in the handler
192
- * Layer's `R` (spec/subagents.md §4.1). The open Layer never waits.
200
+ * Layer's `R`. The open Layer never waits.
193
201
  */
194
202
  export class ResearchDispatchGate extends Context.Service<
195
203
  ResearchDispatchGate,
@@ -224,14 +232,16 @@ export const destinationResearchDelegation = Subagent.define("delegate_destinati
224
232
  prepareInput: (request) =>
225
233
  Effect.gen(function* () {
226
234
  const gate = yield* ResearchDispatchGate;
235
+
227
236
  yield* gate.awaitDispatch(request.destination);
237
+
228
238
  return DestinationBrief.make({
229
239
  destination: request.destination,
230
240
  focus: `research:${request.focus}`,
231
241
  });
232
242
  }),
233
243
  // The explicit declassification boundary (SUB-015): only the advisory
234
- // crosses to the parent; guide highlights stay in the child Conversation.
244
+ // crosses to the parent; guide highlights stay in the child Thread.
235
245
  projectResult: (report) =>
236
246
  Effect.succeed(
237
247
  DestinationResearchFindings.make({
@@ -259,7 +269,7 @@ export const destinationResearchHandlersLayer = <Provider, ModelProvides, ModelR
259
269
  ModelRequires
260
270
  >,
261
271
  ) =>
262
- SubagentRuntime.layer(destinationResearchDelegation, childBinding, {
272
+ Subagent.layer(destinationResearchDelegation, childBinding, {
263
273
  mapChildFailure: mapResearchChildFailure,
264
274
  });
265
275
 
@@ -292,7 +302,7 @@ export const missionConfidentialMarker = "traveler-dossier-19f";
292
302
 
293
303
  export const TravelCoordinatorToolkit = Toolkit.make(destinationResearchDelegation.tool);
294
304
 
295
- export const TravelCoordinator = Agent.define("travel-coordinator", {
305
+ export const TravelCoordinator = Agent.make("travel-coordinator", {
296
306
  input: ResearchMission,
297
307
  output: DestinationShortlist,
298
308
  instructions: [
@@ -314,7 +324,7 @@ export const TravelCoordinator = Agent.define("travel-coordinator", {
314
324
  });
315
325
 
316
326
  export const researchMission = Schema.decodeSync(ResearchMission)({
317
- request: `Shortlist one September culture city; keep ${missionConfidentialMarker} inside the coordinator conversation.`,
327
+ request: `Shortlist one September culture city; keep ${missionConfidentialMarker} inside the coordinator thread.`,
318
328
  candidates: ["LHR", "CDG"],
319
329
  });
320
330
 
@@ -461,19 +471,23 @@ export const makeDestinationResearcherModel = (destinations: ReadonlyArray<strin
461
471
  const lifecycle = yield* CatalogLifecycle;
462
472
  const prompts = yield* Ref.make<ReadonlyArray<string>>([]);
463
473
  const gates = new Map<string, ResearcherGates>();
474
+
464
475
  for (const destination of destinations) {
465
476
  gates.set(destination, {
466
477
  started: yield* Deferred.make<void>(),
467
478
  release: yield* Deferred.make<void>(),
468
479
  });
469
480
  }
481
+
470
482
  const gatesFor = (destination: string): Effect.Effect<ResearcherGates> =>
471
483
  Effect.suspend(() => {
472
484
  const entry = gates.get(destination);
485
+
473
486
  return entry === undefined
474
487
  ? Effect.die(new Error(`No researcher gates exist for destination ${destination}`))
475
488
  : Effect.succeed(entry);
476
489
  });
490
+
477
491
  const controls: DestinationResearcherControls = {
478
492
  awaitStarted: (destination) =>
479
493
  gatesFor(destination).pipe(Effect.flatMap((entry) => Deferred.await(entry.started))),
@@ -484,6 +498,7 @@ export const makeDestinationResearcherModel = (destinations: ReadonlyArray<strin
484
498
  ),
485
499
  prompts: Ref.get(prompts),
486
500
  };
501
+
487
502
  const model = Model.make(
488
503
  "scripted",
489
504
  "destination-researcher-scripted",
@@ -492,15 +507,18 @@ export const makeDestinationResearcherModel = (destinations: ReadonlyArray<strin
492
507
  Effect.gen(function* () {
493
508
  yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
494
509
  const turn = yield* Ref.make(0);
510
+
495
511
  return yield* LanguageModel.make({
496
512
  generateText: () => Effect.succeed([]),
497
513
  streamText: (options) =>
498
514
  Stream.unwrap(
499
515
  Effect.gen(function* () {
500
516
  const promptJson = JSON.stringify(options.prompt.content);
517
+
501
518
  const destination = destinations.find((candidate) =>
502
519
  promptJson.includes(candidate),
503
520
  );
521
+
504
522
  if (destination === undefined) {
505
523
  return yield* Effect.die(
506
524
  new Error("The researcher prompt names no scripted destination"),
@@ -508,12 +526,15 @@ export const makeDestinationResearcherModel = (destinations: ReadonlyArray<strin
508
526
  }
509
527
  const entry = yield* gatesFor(destination);
510
528
  const index = yield* Ref.getAndUpdate(turn, (value) => value + 1);
529
+
511
530
  if (index === 0) {
512
531
  yield* Ref.update(prompts, (previous) => [...previous, promptJson]);
513
532
  yield* Deferred.succeed(entry.started, undefined);
533
+
514
534
  return Stream.fromIterable(researcherLookupParts(destination));
515
535
  }
516
536
  yield* Deferred.await(entry.release);
537
+
517
538
  return Stream.fromIterable(researcherReportParts(destination));
518
539
  }),
519
540
  ),
@@ -521,5 +542,6 @@ export const makeDestinationResearcherModel = (destinations: ReadonlyArray<strin
521
542
  }),
522
543
  ),
523
544
  );
545
+
524
546
  return { controls, model };
525
547
  });
package/src/index.ts CHANGED
@@ -1,11 +1 @@
1
- export * from "./certification.ts";
2
- export * from "./chaos.ts";
3
- export * from "./code-executor-conformance.ts";
4
- export * from "./code-executor-substitute.ts";
5
- export * from "./fixtures/docs-researcher/index.ts";
6
- export * from "./fixtures/travel-planner/index.ts";
7
- // The warehouse fixture is NOT re-exported here: it depends on
8
- // `@effect/sql-sqlite-node` (`node:sqlite`), which workerd consumers of this
9
- // barrel (the Cloudflare certification suites) cannot load. Import it
10
- // directly from `@effect-agent/testing/src/fixtures/warehouse/index.ts`.
11
- export * from "./scripted-model.ts";
1
+ export * as ScriptedModel from "./ScriptedModel.ts";
@@ -0,0 +1,25 @@
1
+ import { CertificationReport } from "effect-agent/testing/certification";
2
+
3
+ /** Fold executed results without acquiring adapters or claiming coverage from citations. */
4
+ export const makeCertificationReport = (
5
+ results: Pick<CertificationReport, "adapter" | "generatedAt" | "tier1" | "tier2" | "tier3">,
6
+ ): CertificationReport => {
7
+ const { adapter, tier1, tier2, tier3 } = results;
8
+
9
+ const ok =
10
+ tier1.every((result) => result.status === "passed") &&
11
+ tier2.every((result) => result.status !== "failed") &&
12
+ tier3.cases.every((result) => result.status === "passed");
13
+
14
+ return CertificationReport.make({
15
+ format: "effect-agent/certification@2",
16
+ ...results,
17
+ ok,
18
+ fullyCertified:
19
+ ok &&
20
+ adapter.durability !== "non-durable" &&
21
+ tier3.status === "exercised" &&
22
+ tier3.cases.length > 0 &&
23
+ tier3.cases.every((result) => result.suite === "real-loss"),
24
+ });
25
+ };