1/**
2 * Wraps an AI model layer with provider and model metadata.
3 *
4 * A `Model` can be used anywhere its underlying `Layer` can be used. It also
5 * provides the current provider name and model name through the Effect context.
6 * This module includes the `Model` interface, the `ProviderName` and
7 * `ModelName` service tags, and the `make` constructor. Models can also capture
8 * their required services from the current context when they need to be used
9 * inside another Effect service.
10 *
11 * @stability unstable
12 * @since 4.0.0
13 */
14import * as Context from "../Context.ts"
15import * as Effect from "../Effect.ts"
16import { identity } from "../Function.ts"
17import { PipeInspectableProto } from "../internal/core.ts"
18import * as Layer from "../Layer.ts"
19
20const TypeId = "~effect/ai/Model" as const
21
22/**
23 * A Model represents a provider-specific AI service.
24 *
25 * **When to use**
26 *
27 * Use when you use a Model directly as a Layer to provide a particular model implementation
28 * to an Effect program, or use it as an Effect to "lift" dependencies of the
29 * Model constructor into the parent Effect when you want to use a Model from
30 * within an Effect service.
31 *
32 * @stability unstable
33 * @category models
34 * @since 4.0.0
35 */
36export interface Model<in out Provider, in out Provides, in out Requires>
37 extends Layer.Layer<Provides | ProviderName | ModelName, never, Requires>
38{
39 readonly [TypeId]: typeof TypeId
40
41 /**
42 * The provider identifier (e.g., "openai", "anthropic", "amazon-bedrock").
43 */
44 readonly provider: Provider
45
46 /**
47 * Returns a `Layer` with the requirements satisfied, using the current context.
48 */
49 readonly captureRequirements: Effect.Effect<
50 Layer.Layer<Provides | ProviderName | ModelName>,
51 never,
52 Requires
53 >
54}
55
56/**
57 * Service tag that provides the current large language model provider name.
58 *
59 * **Details**
60 *
61 * This tag is automatically provided by Model instances and can be used to
62 * access the name of the provider that is currently in use within a given
63 * Effect program.
64 *
65 * @stability unstable
66 * @category services
67 * @since 4.0.0
68 */
69export class ProviderName extends Context.Service<ProviderName, string>()(
70 "effect/ai/Model/ProviderName"
71) {}
72
73/**
74 * Service tag that provides the current large language model name.
75 *
76 * **Details**
77 *
78 * This tag is automatically provided by Model instances and can be used to
79 * access the name of the model that is currently in use within a given Effect
80 * program.
81 *
82 * @stability unstable
83 * @category services
84 * @since 4.0.0
85 */
86export class ModelName extends Context.Service<ModelName, string>()(
87 "effect/ai/Model/ModelName"
88) {}
89
90const Proto = {
91 [TypeId]: TypeId,
92 ["~effect/Layer"]: {
93 _ROut: identity,
94 _E: identity,
95 _RIn: identity
96 },
97 get captureRequirements() {
98 const self = this as any as Model<any, any, any>
99 return Effect.contextWith((context: Context.Context<never>) =>
100 Effect.succeed(Layer.provide(self, Layer.succeedContext(context)))
101 )
102 },
103 ...PipeInspectableProto,
104 toJSON(this: Model<any, any, any>): unknown {
105 return {
106 _id: "effect/ai/Model",
107 provider: this.provider
108 }
109 }
110}
111
112/**
113 * Creates a Model from a provider name and a Layer that constructs AI services.
114 *
115 * **Example** (Providing model metadata)
116 *
117 * ```ts import.meta.vitest
118 * import { Effect, Layer } from "effect"
119 * import { Model } from "effect/ai"
120 *
121 * const model = Model.make("amazon-bedrock", "claude-3-5-haiku", Layer.empty)
122 * const program = Effect.gen(function*() {
123 * const provider = yield* Model.ProviderName
124 * const modelName = yield* Model.ModelName
125 * return { provider, modelName }
126 * }).pipe(Effect.provide(model))
127 *
128 * await Effect.runPromise(program) // => { provider: "amazon-bedrock", modelName: "claude-3-5-haiku" }
129 * ```
130 *
131 * @stability unstable
132 * @category constructors
133 * @since 4.0.0
134 */
135export const make = <const Provider extends string, const Name extends string, Provides, Requires>(
136 /**
137 * Provider identifier (e.g., "openai", "anthropic", "amazon-bedrock").
138 */
139 provider: Provider,
140 /**
141 * Model identifier (e.g., "gpt-5", "claude-3-5-haiku").
142 */
143 modelName: Name,
144 /**
145 * Layer that provides the AI services for this provider.
146 */
147 layer: Layer.Layer<Provides, never, Requires>
148): Model<Provider, Provides, Requires> =>
149 Object.assign(
150 Object.create(Proto),
151 { provider },
152 Layer.merge(
153 layer,
154 Layer.succeedContext(
155 ProviderName.context(provider).pipe(
156 Context.add(ModelName, modelName)
157 )
158 )
159 )
160 )