1/**
2 * Configuration schema and types for AI provider code generation.
3 *
4 * @since 4.0.0
5 */
6import * as Data from "effect/Data"
7import type * as Path from "effect/Path"
8import * as Schema from "effect/Schema"
9
10/**
11 * A text replacement to apply to generated code.
12 *
13 * @category models
14 * @since 4.0.0
15 */
16export class Replacement extends Schema.Class<Replacement>("Replacement")({
17 from: Schema.String,
18 to: Schema.String
19}) {}
20
21/**
22 * Structured spec source configuration for Stainless stats indirection.
23 *
24 * @category schemas
25 * @since 4.0.0
26 */
27export const SpecSourceConfig = Schema.Struct({
28 type: Schema.Literal("stainless-stats"),
29 statsUrl: Schema.String
30})
31
32/**
33 * Configuration for AI provider code generation.
34 *
35 * **Example** (Decoding a codegen configuration)
36 *
37 * ```ts import.meta.vitest
38 * import * as Config from "@effect/ai-codegen/Config"
39 * import { Schema } from "effect"
40 *
41 * const config = Schema.decodeUnknownSync(Config.CodegenConfig)({
42 * spec: "https://example.com/openapi.json",
43 * output: "Generated.ts",
44 * name: "MyClient"
45 * })
46 *
47 * config.spec // => "https://example.com/openapi.json"
48 * ```
49 *
50 * @category models
51 * @since 4.0.0
52 */
53export class CodegenConfig extends Schema.Class<CodegenConfig>("CodegenConfig")({
54 spec: Schema.Union([Schema.String, SpecSourceConfig]),
55 output: Schema.String,
56 name: Schema.optional(Schema.String),
57 typeOnly: Schema.optional(Schema.Boolean),
58 header: Schema.optional(Schema.String),
59 patches: Schema.optional(Schema.Array(Schema.String)),
60 replacements: Schema.optional(Schema.Array(Replacement)),
61 excludeAnnotations: Schema.optional(Schema.Array(Schema.String)),
62 disableAdditionalProperties: Schema.optional(Schema.Boolean)
63}) {
64 /**
65 * Get the client name, defaulting to "Client" if not specified.
66 *
67 * @since 4.0.0
68 */
69 get clientName(): string {
70 return this.name ?? "Client"
71 }
72
73 /**
74 * Check if type-only generation is enabled.
75 *
76 * @since 4.0.0
77 */
78 get isTypeOnly(): boolean {
79 return this.typeOnly ?? false
80 }
81
82 /**
83 * Get the list of patch files/strings to apply.
84 *
85 * @since 4.0.0
86 */
87 get patchList(): ReadonlyArray<string> {
88 return this.patches ?? []
89 }
90
91 /**
92 * Get the list of text replacements to apply.
93 *
94 * @since 4.0.0
95 */
96 get replacementList(): ReadonlyArray<Replacement> {
97 return this.replacements ?? []
98 }
99
100 /**
101 * Get the header content to prepend to generated files.
102 *
103 * @since 4.0.0
104 */
105 get headerContent(): string | undefined {
106 return this.header
107 }
108
109 /**
110 * Get the list of annotation keys to exclude from generated code.
111 *
112 * @since 4.0.0
113 */
114 get excludeAnnotationsList(): ReadonlyArray<string> | undefined {
115 return this.excludeAnnotations
116 }
117
118 /**
119 * Check if additionalProperties should be forced to false on all object schemas.
120 *
121 * @since 4.0.0
122 */
123 get shouldDisableAdditionalProperties(): boolean {
124 return this.disableAdditionalProperties ?? false
125 }
126}
127
128/**
129 * Represents the source of an OpenAPI specification.
130 *
131 * @category models
132 * @since 4.0.0
133 */
134export type SpecSource = SpecSource.Url | SpecSource.File | SpecSource.StainlessStats
135
136/**
137 * Namespace containing the supported OpenAPI specification source variants.
138 *
139 * @since 4.0.0
140 */
141export declare namespace SpecSource {
142 /**
143 * A URL-based spec source.
144 *
145 * @category models
146 * @since 4.0.0
147 */
148 export interface Url {
149 readonly _tag: "Url"
150 readonly url: string
151 }
152
153 /**
154 * A file-based spec source.
155 *
156 * @category models
157 * @since 4.0.0
158 */
159 export interface File {
160 readonly _tag: "File"
161 readonly path: string
162 }
163
164 /**
165 * Stainless SDK stats.yml indirection - fetches stats file and extracts openapi_spec_url.
166 *
167 * @category models
168 * @since 4.0.0
169 */
170 export interface StainlessStats {
171 readonly _tag: "StainlessStats"
172 readonly statsUrl: string
173 }
174}
175
176/**
177 * Constructors and utilities for `SpecSource`.
178 *
179 * **Example** (Creating spec sources)
180 *
181 * ```ts import.meta.vitest
182 * import * as Config from "@effect/ai-codegen/Config"
183 *
184 * // Create a URL-based source
185 * const urlSource = Config.SpecSource.Url("https://example.com/openapi.json")
186 *
187 * // Create a file-based source
188 * const fileSource = Config.SpecSource.File("/path/to/spec.json")
189 *
190 * urlSource._tag // => "Url"
191 * fileSource._tag // => "File"
192 * ```
193 *
194 * @category constructors
195 * @since 4.0.0
196 */
197export const SpecSource = {
198 /**
199 * Create a URL-based spec source.
200 *
201 * @since 4.0.0
202 */
203 Url: (url: string): SpecSource => ({ _tag: "Url", url }),
204
205 /**
206 * Create a file-based spec source.
207 *
208 * @since 4.0.0
209 */
210 File: (path: string): SpecSource => ({ _tag: "File", path }),
211
212 /**
213 * Create a Stainless stats-based spec source.
214 *
215 * @since 4.0.0
216 */
217 StainlessStats: (statsUrl: string): SpecSource => ({ _tag: "StainlessStats", statsUrl }),
218
219 /**
220 * Parse a spec string into a `SpecSource`.
221 * URLs (http:// or https://) become `Url`, otherwise `File`.
222 *
223 * @since 4.0.0
224 */
225 fromString: (spec: string, packagePath: string, pathService: Path.Path): SpecSource => {
226 if (spec.startsWith("http://") || spec.startsWith("https://")) {
227 return SpecSource.Url(spec)
228 }
229 return SpecSource.File(pathService.join(packagePath, spec))
230 },
231
232 /**
233 * Parse a spec config (string or object) into a `SpecSource`.
234 *
235 * @since 4.0.0
236 */
237 fromConfig: (
238 spec: string | { readonly type: string; readonly statsUrl?: string },
239 packagePath: string,
240 pathService: Path.Path
241 ): SpecSource => {
242 if (typeof spec === "string") {
243 return SpecSource.fromString(spec, packagePath, pathService)
244 }
245 if (spec.type === "stainless-stats" && spec.statsUrl) {
246 return SpecSource.StainlessStats(spec.statsUrl)
247 }
248 throw new Error(`Unknown spec type: ${spec.type}`)
249 }
250}
251
252/**
253 * Error when parsing a codegen configuration file fails.
254 *
255 * **Example** (Creating a config parse error)
256 *
257 * ```ts import.meta.vitest
258 * import * as Config from "@effect/ai-codegen/Config"
259 *
260 * const error = new Config.ConfigParseError({
261 * path: "/path/to/codegen.json",
262 * cause: new Error("Invalid JSON")
263 * })
264 *
265 * error._tag // => "ConfigParseError"
266 * error.path // => "/path/to/codegen.json"
267 * ```
268 *
269 * @category errors
270 * @since 4.0.0
271 */
272export class ConfigParseError extends Data.TaggedError("ConfigParseError")<{
273 readonly path: string
274 readonly cause: unknown
275}> {}
276
277/**
278 * Error when a codegen configuration file is not found.
279 *
280 * **Example** (Creating a config not found error)
281 *
282 * ```ts import.meta.vitest
283 * import * as Config from "@effect/ai-codegen/Config"
284 *
285 * const error = new Config.ConfigNotFoundError({
286 * provider: "openai",
287 * expectedPath: "/path/to/packages/ai/openai/codegen.json"
288 * })
289 *
290 * error._tag // => "ConfigNotFoundError"
291 * error.provider // => "openai"
292 * ```
293 *
294 * @category errors
295 * @since 4.0.0
296 */
297export class ConfigNotFoundError extends Data.TaggedError("ConfigNotFoundError")<{
298 readonly provider: string
299 readonly expectedPath: string
300}> {}