/** * @since 3.16.0 * @experimental */ import type { NonEmptyReadonlyArray } from "./Array.js" import type * as Context from "./Context.js" import * as Effect from "./Effect.js" import * as internal from "./internal/executionPlan.js" import * as Layer from "./Layer.js" import type { Pipeable } from "./Pipeable.js" import { pipeArguments } from "./Pipeable.js" import type * as Schedule from "./Schedule.js" /** * @since 3.16.0 * @category Symbols * @experimental */ export const TypeId: unique symbol = internal.TypeId /** * @since 3.16.0 * @category Symbols * @experimental */ export type TypeId = typeof TypeId /** * @since 3.16.0 * @category Guards * @experimental */ export const isExecutionPlan: (u: unknown) => u is ExecutionPlan = internal.isExecutionPlan /** * A `ExecutionPlan` can be used with `Effect.withExecutionPlan` or `Stream.withExecutionPlan`, allowing you to provide different resources for each step of execution until the effect succeeds or the plan is exhausted. * * ```ts * import type { LanguageModel } from "@effect/ai" * import type { Layer } from "effect" * import { Effect, ExecutionPlan, Schedule } from "effect" * * declare const layerBad: Layer.Layer * declare const layerGood: Layer.Layer * * const ThePlan = ExecutionPlan.make( * { * // First try with the bad layer 2 times with a 3 second delay between attempts * provide: layerBad, * attempts: 2, * schedule: Schedule.spaced(3000) * }, * // Then try with the bad layer 3 times with a 1 second delay between attempts * { * provide: layerBad, * attempts: 3, * schedule: Schedule.spaced(1000) * }, * // Finally try with the good layer. * // * // If `attempts` is omitted, the plan will only attempt once, unless a schedule is provided. * { * provide: layerGood * } * ) * * declare const effect: Effect.Effect< * void, * never, * LanguageModel.LanguageModel * > * const withPlan: Effect.Effect = Effect.withExecutionPlan(effect, ThePlan) * ``` * * @since 3.16.0 * @category Models * @experimental */ export interface ExecutionPlan< Types extends { provides: any input: any error: any requirements: any } > extends Pipeable { readonly [TypeId]: TypeId readonly steps: NonEmptyReadonlyArray<{ readonly provide: | Context.Context | Layer.Layer readonly attempts?: number | undefined readonly while?: | ((input: Types["input"]) => Effect.Effect) | undefined readonly schedule?: Schedule.Schedule | undefined }> /** * Returns an equivalent `ExecutionPlan` with the requirements satisfied, * using the current context. */ readonly withRequirements: Effect.Effect< ExecutionPlan<{ provides: Types["provides"] input: Types["input"] error: Types["error"] requirements: never }>, never, Types["requirements"] > } /** * @since 3.16.0 * @experimental */ export type TypesBase = { provides: any input: any error: any requirements: any } /** * Create an `ExecutionPlan`, which can be used with `Effect.withExecutionPlan` or `Stream.withExecutionPlan`, allowing you to provide different resources for each step of execution until the effect succeeds or the plan is exhausted. * * ```ts * import type { LanguageModel } from "@effect/ai" * import type { Layer } from "effect" * import { Effect, ExecutionPlan, Schedule } from "effect" * * declare const layerBad: Layer.Layer * declare const layerGood: Layer.Layer * * const ThePlan = ExecutionPlan.make( * { * // First try with the bad layer 2 times with a 3 second delay between attempts * provide: layerBad, * attempts: 2, * schedule: Schedule.spaced(3000) * }, * // Then try with the bad layer 3 times with a 1 second delay between attempts * { * provide: layerBad, * attempts: 3, * schedule: Schedule.spaced(1000) * }, * // Finally try with the good layer. * // * // If `attempts` is omitted, the plan will only attempt once, unless a schedule is provided. * { * provide: layerGood * } * ) * * declare const effect: Effect.Effect< * void, * never, * LanguageModel.LanguageModel * > * const withPlan: Effect.Effect = Effect.withExecutionPlan(effect, ThePlan) * ``` * * @since 3.16.0 * @category Constructors * @experimental */ export const make = >( ...steps: Steps & { [K in keyof Steps]: make.Step } ): ExecutionPlan<{ provides: make.StepProvides input: make.StepInput error: | (Steps[number]["provide"] extends Context.Context | Layer.Layer ? E : never) | (Steps[number]["while"] extends (input: infer _I) => Effect.Effect ? _E : never) requirements: | (Steps[number]["provide"] extends Layer.Layer ? R : never) | (Steps[number]["while"] extends (input: infer _I) => Effect.Effect ? R : never) | (Steps[number]["schedule"] extends Schedule.Schedule ? R : never) }> => makeProto(steps.map((options, i) => { if (options.attempts && options.attempts < 1) { throw new Error(`ExecutionPlan.make: step[${i}].attempts must be greater than 0`) } return { schedule: options.schedule, attempts: options.attempts, while: options.while ? (input: any) => Effect.suspend(() => { const result = options.while!(input) return typeof result === "boolean" ? Effect.succeed(result) : result }) : undefined, provide: options.provide } }) as any) /** * @since 3.16.0 * @experimental */ export declare namespace make { /** * @since 3.16.0 * @experimental */ export type Step = { readonly provide: Context.Context | Context.Context | Layer.Layer.Any readonly attempts?: number | undefined readonly while?: ((input: any) => boolean | Effect.Effect) | undefined readonly schedule?: Schedule.Schedule | undefined } /** * @since 3.16.1 * @experimental */ export type StepProvides, Out = unknown> = Steps extends readonly [infer Step, ...infer Rest] ? StepProvides< Rest, & Out & ( (Step extends { readonly provide: Context.Context | Layer.Layer } ? P : unknown) ) > : Out /** * @since 3.16.1 * @experimental */ export type PlanProvides, Out = unknown> = Plans extends readonly [infer Plan, ...infer Rest] ? PlanProvides ? T["provides"] : unknown)> : Out /** * @since 3.16.0 * @experimental */ export type StepInput, Out = unknown> = Steps extends readonly [infer Step, ...infer Rest] ? StepInput< Rest, & Out & ( & (Step extends { readonly while: (input: infer I) => infer _ } ? I : unknown) & (Step extends { readonly schedule: Schedule.Schedule } ? I : unknown) ) > : Out /** * @since 3.16.0 * @experimental */ export type PlanInput, Out = unknown> = Plans extends readonly [infer Plan, ...infer Rest] ? PlanInput ? T["input"] : unknown)> : Out } const Proto: Omit, "steps"> = { [TypeId]: TypeId, get withRequirements() { const self = this as any as ExecutionPlan return Effect.contextWith((context: Context.Context) => makeProto(self.steps.map((step) => ({ ...step, provide: Layer.isLayer(step.provide) ? Layer.provide(step.provide, Layer.succeedContext(context)) : step.provide })) as any) ) }, pipe() { return pipeArguments(this, arguments) } } const makeProto = ( steps: ExecutionPlan<{ provides: Provides input: In error: PlanE requirements: PlanR }>["steps"] ) => { const self = Object.create(Proto) self.steps = steps return self } /** * @since 3.16.0 * @category Combining * @experimental */ export const merge = >>( ...plans: Plans ): ExecutionPlan<{ provides: make.PlanProvides input: make.PlanInput error: Plans[number] extends ExecutionPlan ? T["error"] : never requirements: Plans[number] extends ExecutionPlan ? T["requirements"] : never }> => makeProto(plans.flatMap((plan) => plan.steps) as any)