@exvio/os-backend-core 0.4.0

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.
@@ -0,0 +1,163 @@
1
+ /**
2
+ * A deliberately small SSE decoder for OpenAI-compatible streaming APIs.
3
+ *
4
+ * It implements the pieces of the event-stream format those APIs rely on:
5
+ * CRLF/LF/CR line endings, comments, multi-line `data` fields and a final
6
+ * unterminated event. The decoder never includes provider data in thrown
7
+ * errors because a response can contain user input or other sensitive data.
8
+ */
9
+
10
+ export interface SseJsonOptions {
11
+ signal?: AbortSignal
12
+ /** Refuse an individual event larger than this many decoded characters. */
13
+ maxEventSize?: number
14
+ }
15
+
16
+ export type SseJsonEvent<T> =
17
+ | { type: 'data'; data: T }
18
+ | { type: 'done' }
19
+
20
+ export class SseDecodeError extends Error {
21
+ constructor(message: string, options?: ErrorOptions) {
22
+ super(message, options)
23
+ this.name = 'SseDecodeError'
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Decode JSON `data:` events from a fetch response body.
29
+ *
30
+ * The iterator yields a `done` event only for an explicit `[DONE]` marker.
31
+ * Consumers should still treat normal EOF as completion because a number of
32
+ * OpenAI-compatible servers omit that marker.
33
+ */
34
+ export async function* parseSseJson<T>(
35
+ body: ReadableStream<Uint8Array>,
36
+ options: SseJsonOptions = {},
37
+ ): AsyncGenerator<SseJsonEvent<T>, void, undefined> {
38
+ const { signal, maxEventSize = 1_048_576 } = options
39
+ if (!Number.isSafeInteger(maxEventSize) || maxEventSize <= 0) {
40
+ throw new TypeError('maxEventSize must be a positive safe integer')
41
+ }
42
+ throwIfAborted(signal)
43
+
44
+ const reader = body.getReader()
45
+ const decoder = new TextDecoder()
46
+ let buffer = ''
47
+ let sawExplicitDone = false
48
+ let finishedReading = false
49
+
50
+ const onAbort = () => {
51
+ // Cancelling unblocks a pending read. Ignore the returned promise here;
52
+ // the generator's finally block performs the awaited cleanup.
53
+ void reader.cancel(abortReason(signal))
54
+ }
55
+ signal?.addEventListener('abort', onAbort, { once: true })
56
+
57
+ try {
58
+ while (!sawExplicitDone) {
59
+ throwIfAborted(signal)
60
+ const result = await reader.read()
61
+ throwIfAborted(signal)
62
+
63
+ if (result.done) {
64
+ finishedReading = true
65
+ buffer += decoder.decode()
66
+ break
67
+ }
68
+
69
+ buffer += decoder.decode(result.value, { stream: true })
70
+ if (buffer.length > maxEventSize && findFrameBoundary(buffer) === undefined) {
71
+ throw new SseDecodeError('SSE event exceeded the configured size limit')
72
+ }
73
+
74
+ while (true) {
75
+ const boundary = findFrameBoundary(buffer)
76
+ if (!boundary) break
77
+
78
+ const frame = buffer.slice(0, boundary.index)
79
+ buffer = buffer.slice(boundary.index + boundary.length)
80
+ const event = decodeFrame<T>(frame, maxEventSize)
81
+ if (!event) continue
82
+
83
+ yield event
84
+ if (event.type === 'done') {
85
+ sawExplicitDone = true
86
+ break
87
+ }
88
+ }
89
+ }
90
+
91
+ // A valid server may close after its last `data:` line without sending a
92
+ // blank separator or [DONE]. Decode that final frame before returning.
93
+ if (!sawExplicitDone && finishedReading && buffer.length > 0) {
94
+ const event = decodeFrame<T>(buffer, maxEventSize)
95
+ if (event) yield event
96
+ }
97
+ } catch (error) {
98
+ if (signal?.aborted) throw abortReason(signal)
99
+ throw error
100
+ } finally {
101
+ signal?.removeEventListener('abort', onAbort)
102
+ // If the consumer stopped early, cancel the transport so its socket can
103
+ // be returned to the pool. Calling cancel after EOF is harmless in the
104
+ // web-stream implementations used by Bun and Node.
105
+ if (!finishedReading) {
106
+ try {
107
+ await reader.cancel()
108
+ } catch {
109
+ // Preserve the original iteration result/error.
110
+ }
111
+ }
112
+ reader.releaseLock()
113
+ }
114
+ }
115
+
116
+ function decodeFrame<T>(frame: string, maxEventSize: number): SseJsonEvent<T> | undefined {
117
+ if (frame.length > maxEventSize) {
118
+ throw new SseDecodeError('SSE event exceeded the configured size limit')
119
+ }
120
+
121
+ // A boundary can consist of mixed newline styles; splitting this way also
122
+ // accepts the final unterminated line at EOF.
123
+ const lines = frame.split(/\r\n|\n|\r/)
124
+ const dataLines: string[] = []
125
+
126
+ for (const line of lines) {
127
+ if (line.length === 0 || line.startsWith(':')) continue
128
+
129
+ const colon = line.indexOf(':')
130
+ const field = colon === -1 ? line : line.slice(0, colon)
131
+ if (field !== 'data') continue
132
+
133
+ let value = colon === -1 ? '' : line.slice(colon + 1)
134
+ if (value.startsWith(' ')) value = value.slice(1)
135
+ dataLines.push(value)
136
+ }
137
+
138
+ if (dataLines.length === 0) return undefined
139
+ const payload = dataLines.join('\n')
140
+ if (payload.trim() === '[DONE]') return { type: 'done' }
141
+
142
+ try {
143
+ return { type: 'data', data: JSON.parse(payload) as T }
144
+ } catch (cause) {
145
+ throw new SseDecodeError('Provider returned malformed JSON in an SSE event', { cause })
146
+ }
147
+ }
148
+
149
+ function findFrameBoundary(input: string): { index: number; length: number } | undefined {
150
+ // A blank line is two line endings. Each ending may independently be CRLF,
151
+ // LF, or CR, so this expression also handles mixed proxy-normalised input.
152
+ const match = /(?:\r\n|\n|\r)(?:\r\n|\n|\r)/.exec(input)
153
+ return match?.index === undefined ? undefined : { index: match.index, length: match[0].length }
154
+ }
155
+
156
+ function throwIfAborted(signal: AbortSignal | undefined): void {
157
+ if (signal?.aborted) throw abortReason(signal)
158
+ }
159
+
160
+ function abortReason(signal: AbortSignal | undefined): unknown {
161
+ if (signal?.reason !== undefined) return signal.reason
162
+ return new DOMException('The operation was aborted', 'AbortError')
163
+ }
@@ -0,0 +1,65 @@
1
+ import { AIError } from './errors.ts'
2
+ import type { AIProviderFactory, AIProviderName } from './types.ts'
3
+
4
+ export type AIProviderRegistry = ReadonlyMap<AIProviderName, AIProviderFactory>
5
+
6
+ /**
7
+ * Create a request-independent registry snapshot. There is deliberately no
8
+ * package-global registry: each application owns exactly which providers are
9
+ * available, and tests cannot leak registrations into one another.
10
+ */
11
+ export function createAIProviderRegistry(
12
+ entries: Iterable<readonly [AIProviderName, AIProviderFactory]> = [],
13
+ ): AIProviderRegistry {
14
+ const registry = new Map<AIProviderName, AIProviderFactory>()
15
+ for (const [name, factory] of entries) {
16
+ assertProviderRegistration(name, factory)
17
+ if (registry.has(name)) {
18
+ throw new AIError({
19
+ code: 'invalid_config',
20
+ message: 'AI provider registry contains a duplicate name',
21
+ })
22
+ }
23
+ registry.set(name, factory)
24
+ }
25
+ return registry
26
+ }
27
+
28
+ /** Return a new snapshot with one provider added; the input map is untouched. */
29
+ export function withAIProvider(
30
+ registry: AIProviderRegistry,
31
+ name: AIProviderName,
32
+ factory: AIProviderFactory,
33
+ ): AIProviderRegistry {
34
+ assertProviderRegistration(name, factory)
35
+ if (registry.has(name)) {
36
+ throw new AIError({
37
+ code: 'invalid_config',
38
+ message: 'AI provider is already registered',
39
+ provider: name,
40
+ })
41
+ }
42
+ const next = new Map(registry)
43
+ next.set(name, factory)
44
+ return next
45
+ }
46
+
47
+ function assertProviderRegistration(name: string, factory: AIProviderFactory): void {
48
+ if (!isNonEmptyString(name) || name.length > 128 || /\s/.test(name)) {
49
+ throw new AIError({
50
+ code: 'invalid_config',
51
+ message: 'AI provider registry name is invalid',
52
+ })
53
+ }
54
+ if (typeof factory !== 'function') {
55
+ throw new AIError({
56
+ code: 'invalid_config',
57
+ message: 'AI provider registry factory is invalid',
58
+ provider: name,
59
+ })
60
+ }
61
+ }
62
+
63
+ function isNonEmptyString(value: unknown): value is string {
64
+ return typeof value === 'string' && value.trim().length > 0
65
+ }
@@ -0,0 +1,382 @@
1
+ import { AIError } from './errors.ts'
2
+ import { assertActiveAIModelId } from './model-policy.ts'
3
+ import type {
4
+ AIConfig,
5
+ AIModelRef,
6
+ AIObjectSchema,
7
+ AIProviderConfig,
8
+ } from './types.ts'
9
+
10
+ /** Validate and copy application-owned config without retaining unknown fields. */
11
+ export function parseAIConfig(value: unknown): AIConfig {
12
+ if (!isRecord(value)) invalidConfig()
13
+
14
+ const rawProviders = value.providers
15
+ const rawLanes = value.lanes
16
+ if (!isRecord(rawProviders) || !isRecord(rawLanes)) invalidConfig()
17
+
18
+ const providers: Record<string, AIProviderConfig> = Object.create(null) as Record<string, AIProviderConfig>
19
+ for (const [name, rawProvider] of Object.entries(rawProviders)) {
20
+ if (!validName(name) || !isRecord(rawProvider)) invalidConfig()
21
+
22
+ const apiKey = optionalString(rawProvider.apiKey ?? rawProvider.api_key)
23
+ const endpoint = optionalString(rawProvider.endpoint)
24
+ const baseUrl = optionalString(rawProvider.baseUrl ?? rawProvider.base_url)
25
+ const settings = rawProvider.settings
26
+ if (settings !== undefined && !isRecord(settings)) invalidConfig()
27
+
28
+ providers[name] = Object.freeze({
29
+ ...(apiKey === undefined ? {} : { apiKey }),
30
+ ...(endpoint === undefined ? {} : { endpoint }),
31
+ ...(baseUrl === undefined ? {} : { baseUrl }),
32
+ ...(settings === undefined ? {} : { settings: Object.freeze({ ...settings }) }),
33
+ })
34
+ }
35
+
36
+ const lanes: Record<string, AIModelRef> = Object.create(null) as Record<string, AIModelRef>
37
+ for (const [name, rawLane] of Object.entries(rawLanes)) {
38
+ if (!validName(name) || !isRecord(rawLane)) invalidConfig()
39
+ const provider = requiredString(rawLane.provider)
40
+ const model = requiredString(rawLane.model ?? rawLane.default_model)
41
+ assertActiveAIModelId(model)
42
+ lanes[name] = Object.freeze({ provider, model })
43
+ }
44
+
45
+ return Object.freeze({
46
+ providers: Object.freeze(providers),
47
+ lanes: Object.freeze(lanes),
48
+ })
49
+ }
50
+
51
+ /** Parse an exact JSON value, allowing only a single surrounding markdown fence. */
52
+ export function parseAIJSON(text: string): unknown {
53
+ if (typeof text !== 'string') invalidResponse()
54
+ const trimmed = text.trim()
55
+ if (!trimmed) invalidResponse()
56
+
57
+ const fenced = /^```(?:json)?\s*\r?\n?([\s\S]*?)\r?\n?```$/i.exec(trimmed)
58
+ const source = (fenced?.[1] ?? trimmed).trim()
59
+ try {
60
+ return JSON.parse(source) as unknown
61
+ } catch {
62
+ invalidResponse()
63
+ }
64
+ }
65
+
66
+ export function validateAIObject<T>(value: unknown, schema: AIObjectSchema<T>): T {
67
+ assertAIObjectSchema(schema)
68
+ try {
69
+ return schema.validate(value)
70
+ } catch {
71
+ invalidResponse()
72
+ }
73
+ }
74
+
75
+ export function parseAIObject<T>(text: string, schema: AIObjectSchema<T>): T {
76
+ return validateAIObject(parseAIJSON(text), schema)
77
+ }
78
+
79
+ export function createAIObjectPrompt<T>(prompt: string, schema: AIObjectSchema<T>): string {
80
+ if (typeof prompt !== 'string' || prompt.length === 0) {
81
+ throw new AIError({
82
+ code: 'invalid_request',
83
+ message: 'AI object prompt must not be empty',
84
+ operation: 'object',
85
+ })
86
+ }
87
+ assertAIObjectSchema(schema)
88
+ return `${prompt}\n\nReturn only JSON matching this schema:\n${JSON.stringify(schema.jsonSchema)}`
89
+ }
90
+
91
+ /** Validate before any billable provider request is started. */
92
+ export function assertAIObjectSchema<T>(schema: AIObjectSchema<T>): void {
93
+ if (
94
+ !schema
95
+ || typeof schema.validate !== 'function'
96
+ || !isRecord(schema.jsonSchema)
97
+ || !isAIJSONValue(schema.jsonSchema)
98
+ || !isSupportedAIJSONSchema(schema.jsonSchema)
99
+ ) {
100
+ throw new AIError({
101
+ code: 'invalid_request',
102
+ message: 'AI object schema is invalid',
103
+ operation: 'object',
104
+ })
105
+ }
106
+ }
107
+
108
+ /** JSON-Schema subset validator used for untrusted provider tool arguments. */
109
+ export function matchesAIJSONSchema(
110
+ value: unknown,
111
+ schema: unknown,
112
+ root: unknown = schema,
113
+ depth = 0,
114
+ ): boolean {
115
+ if (depth > 64) return false
116
+ if (typeof schema === 'boolean') return schema
117
+ if (!isRecord(schema)) return false
118
+
119
+ if (typeof schema.$ref === 'string') {
120
+ const resolved = resolveLocalRef(root, schema.$ref)
121
+ return resolved !== undefined && matchesAIJSONSchema(value, resolved, root, depth + 1)
122
+ }
123
+ if (schema.nullable === true && value === null) return true
124
+ if (Array.isArray(schema.allOf) && !schema.allOf.every(item => matchesAIJSONSchema(value, item, root, depth + 1))) return false
125
+ if (Array.isArray(schema.anyOf) && !schema.anyOf.some(item => matchesAIJSONSchema(value, item, root, depth + 1))) return false
126
+ if (Array.isArray(schema.oneOf)) {
127
+ if (schema.oneOf.filter(item => matchesAIJSONSchema(value, item, root, depth + 1)).length !== 1) return false
128
+ }
129
+ if (schema.not !== undefined && matchesAIJSONSchema(value, schema.not, root, depth + 1)) return false
130
+ if (schema.const !== undefined && !jsonEquals(value, schema.const)) return false
131
+ if (Array.isArray(schema.enum) && !schema.enum.some(item => jsonEquals(value, item))) return false
132
+
133
+ const types = typeof schema.type === 'string'
134
+ ? [schema.type]
135
+ : Array.isArray(schema.type) && schema.type.every(item => typeof item === 'string')
136
+ ? schema.type as string[]
137
+ : []
138
+ if (types.length > 0 && !types.some(type => matchesJSONType(value, type))) return false
139
+
140
+ if (isRecord(value)) {
141
+ const properties = isRecord(schema.properties) ? schema.properties : {}
142
+ if (Array.isArray(schema.required)) {
143
+ for (const key of schema.required) {
144
+ if (typeof key !== 'string' || !Object.hasOwn(value, key)) return false
145
+ }
146
+ }
147
+ for (const [key, child] of Object.entries(value)) {
148
+ if (Object.hasOwn(properties, key)) {
149
+ if (!matchesAIJSONSchema(child, properties[key], root, depth + 1)) return false
150
+ } else if (schema.additionalProperties === false) {
151
+ return false
152
+ } else if (isRecord(schema.additionalProperties) || typeof schema.additionalProperties === 'boolean') {
153
+ if (!matchesAIJSONSchema(child, schema.additionalProperties, root, depth + 1)) return false
154
+ }
155
+ }
156
+ }
157
+
158
+ if (Array.isArray(value)) {
159
+ if (typeof schema.minItems === 'number' && value.length < schema.minItems) return false
160
+ if (typeof schema.maxItems === 'number' && value.length > schema.maxItems) return false
161
+ if (schema.items !== undefined && !value.every(item => matchesAIJSONSchema(item, schema.items, root, depth + 1))) return false
162
+ }
163
+
164
+ if (typeof value === 'string') {
165
+ if (typeof schema.minLength === 'number' && value.length < schema.minLength) return false
166
+ if (typeof schema.maxLength === 'number' && value.length > schema.maxLength) return false
167
+ if (typeof schema.pattern === 'string') {
168
+ try {
169
+ if (!new RegExp(schema.pattern, 'u').test(value)) return false
170
+ } catch {
171
+ return false
172
+ }
173
+ }
174
+ }
175
+
176
+ if (typeof value === 'number') {
177
+ if (typeof schema.minimum === 'number' && value < schema.minimum) return false
178
+ if (typeof schema.maximum === 'number' && value > schema.maximum) return false
179
+ if (typeof schema.exclusiveMinimum === 'number' && value <= schema.exclusiveMinimum) return false
180
+ if (typeof schema.exclusiveMaximum === 'number' && value >= schema.exclusiveMaximum) return false
181
+ if (typeof schema.multipleOf === 'number' && (
182
+ schema.multipleOf <= 0
183
+ || Math.abs(value / schema.multipleOf - Math.round(value / schema.multipleOf)) > Number.EPSILON * 16
184
+ )) return false
185
+ }
186
+ return true
187
+ }
188
+
189
+ export function isAIJSONValue(value: unknown, seen = new WeakSet<object>()): boolean {
190
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return true
191
+ if (typeof value === 'number') return Number.isFinite(value)
192
+ if (typeof value !== 'object') return false
193
+ if (seen.has(value)) return false
194
+ seen.add(value)
195
+ const valid = Array.isArray(value)
196
+ ? value.every(item => isAIJSONValue(item, seen))
197
+ : isRecord(value) && Object.values(value).every(item => isAIJSONValue(item, seen))
198
+ seen.delete(value)
199
+ return valid
200
+ }
201
+
202
+ const SUPPORTED_SCHEMA_KEYS = new Set([
203
+ '$ref', '$defs', 'definitions',
204
+ 'title', 'description', 'default', 'examples',
205
+ 'type', 'nullable', 'enum', 'const',
206
+ 'allOf', 'anyOf', 'oneOf', 'not',
207
+ 'properties', 'required', 'additionalProperties',
208
+ 'items', 'minItems', 'maxItems',
209
+ 'minLength', 'maxLength', 'pattern',
210
+ 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf',
211
+ ])
212
+
213
+ /**
214
+ * Core intentionally supports a strict JSON-Schema subset. Unknown validation
215
+ * keywords fail before a provider call rather than being silently ignored.
216
+ */
217
+ export function isSupportedAIJSONSchema(schema: unknown): boolean {
218
+ return isSupportedSchema(schema, schema, 0)
219
+ }
220
+
221
+ function isSupportedSchema(schema: unknown, root: unknown, depth: number): boolean {
222
+ if (depth > 64) return false
223
+ if (typeof schema === 'boolean') return true
224
+ if (!isRecord(schema)) return false
225
+ if (Object.keys(schema).some(key => !SUPPORTED_SCHEMA_KEYS.has(key))) return false
226
+
227
+ if (schema.$ref !== undefined) {
228
+ if (
229
+ typeof schema.$ref !== 'string'
230
+ || (schema.$ref !== '#' && !schema.$ref.startsWith('#/'))
231
+ || Object.keys(schema).some(key => !['$ref', 'title', 'description'].includes(key))
232
+ ) return false
233
+ const resolved = resolveLocalRef(root, schema.$ref)
234
+ return resolved !== undefined && isSupportedSchema(resolved, root, depth + 1)
235
+ }
236
+
237
+ for (const definitionsKey of ['$defs', 'definitions'] as const) {
238
+ const definitions = schema[definitionsKey]
239
+ if (definitions !== undefined) {
240
+ if (!isRecord(definitions)) return false
241
+ if (Object.values(definitions).some(child => !isSupportedSchema(child, root, depth + 1))) return false
242
+ }
243
+ }
244
+
245
+ const allowedTypes = new Set(['null', 'object', 'array', 'string', 'number', 'integer', 'boolean'])
246
+ if (schema.type !== undefined) {
247
+ const types = typeof schema.type === 'string' ? [schema.type] : schema.type
248
+ if (!Array.isArray(types) || types.length === 0 || types.some(type => typeof type !== 'string' || !allowedTypes.has(type))) {
249
+ return false
250
+ }
251
+ }
252
+ if (schema.nullable !== undefined && typeof schema.nullable !== 'boolean') return false
253
+
254
+ const properties = schema.properties
255
+ if (properties !== undefined) {
256
+ if (!isRecord(properties)) return false
257
+ if (Object.values(properties).some(child => !isSupportedSchema(child, root, depth + 1))) return false
258
+ }
259
+ if (schema.required !== undefined && (
260
+ !Array.isArray(schema.required)
261
+ || schema.required.some(key => typeof key !== 'string')
262
+ || new Set(schema.required).size !== schema.required.length
263
+ )) return false
264
+ if (
265
+ schema.additionalProperties !== undefined
266
+ && typeof schema.additionalProperties !== 'boolean'
267
+ && !isSupportedSchema(schema.additionalProperties, root, depth + 1)
268
+ ) return false
269
+ if (schema.items !== undefined && !isSupportedSchema(schema.items, root, depth + 1)) return false
270
+
271
+ for (const combinator of ['allOf', 'anyOf', 'oneOf'] as const) {
272
+ const entries = schema[combinator]
273
+ if (entries !== undefined && (
274
+ !Array.isArray(entries)
275
+ || entries.length === 0
276
+ || entries.some(child => !isSupportedSchema(child, root, depth + 1))
277
+ )) return false
278
+ }
279
+ if (schema.not !== undefined && !isSupportedSchema(schema.not, root, depth + 1)) return false
280
+
281
+ if (schema.enum !== undefined && (
282
+ !Array.isArray(schema.enum) || schema.enum.length === 0 || !isAIJSONValue(schema.enum)
283
+ )) return false
284
+ if (schema.const !== undefined && !isAIJSONValue(schema.const)) return false
285
+ for (const annotation of ['default', 'examples'] as const) {
286
+ if (schema[annotation] !== undefined && !isAIJSONValue(schema[annotation])) return false
287
+ }
288
+
289
+ for (const integerKeyword of ['minItems', 'maxItems', 'minLength', 'maxLength'] as const) {
290
+ const value = schema[integerKeyword]
291
+ if (value !== undefined && (!Number.isSafeInteger(value) || (value as number) < 0)) return false
292
+ }
293
+ for (const numberKeyword of [
294
+ 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf',
295
+ ] as const) {
296
+ const value = schema[numberKeyword]
297
+ if (value !== undefined && (!Number.isFinite(value) || (numberKeyword === 'multipleOf' && (value as number) <= 0))) {
298
+ return false
299
+ }
300
+ }
301
+ if (schema.pattern !== undefined) {
302
+ if (typeof schema.pattern !== 'string') return false
303
+ try { new RegExp(schema.pattern, 'u') } catch { return false }
304
+ }
305
+ return true
306
+ }
307
+
308
+ function matchesJSONType(value: unknown, type: string): boolean {
309
+ switch (type) {
310
+ case 'null': return value === null
311
+ case 'object': return isRecord(value)
312
+ case 'array': return Array.isArray(value)
313
+ case 'string': return typeof value === 'string'
314
+ case 'number': return typeof value === 'number' && Number.isFinite(value)
315
+ case 'integer': return typeof value === 'number' && Number.isSafeInteger(value)
316
+ case 'boolean': return typeof value === 'boolean'
317
+ default: return false
318
+ }
319
+ }
320
+
321
+ function resolveLocalRef(root: unknown, ref: string): unknown {
322
+ if (ref === '#') return root
323
+ if (!ref.startsWith('#/')) return undefined
324
+ let current: unknown = root
325
+ for (const segment of ref.slice(2).split('/')) {
326
+ if (!isRecord(current)) return undefined
327
+ const key = segment.replaceAll('~1', '/').replaceAll('~0', '~')
328
+ if (!Object.hasOwn(current, key)) return undefined
329
+ current = current[key]
330
+ }
331
+ return current
332
+ }
333
+
334
+ function jsonEquals(left: unknown, right: unknown): boolean {
335
+ if (Object.is(left, right)) return true
336
+ if (Array.isArray(left) && Array.isArray(right)) {
337
+ return left.length === right.length && left.every((value, index) => jsonEquals(value, right[index]))
338
+ }
339
+ if (isRecord(left) && isRecord(right)) {
340
+ const leftKeys = Object.keys(left)
341
+ const rightKeys = Object.keys(right)
342
+ return leftKeys.length === rightKeys.length
343
+ && leftKeys.every(key => Object.hasOwn(right, key) && jsonEquals(left[key], right[key]))
344
+ }
345
+ return false
346
+ }
347
+
348
+ function optionalString(value: unknown): string | undefined {
349
+ if (value === undefined || value === null) return undefined
350
+ if (typeof value === 'string' && value.trim().length === 0) return undefined
351
+ return requiredString(value)
352
+ }
353
+
354
+ function requiredString(value: unknown): string {
355
+ if (typeof value !== 'string' || value.trim().length === 0) invalidConfig()
356
+ return value.trim()
357
+ }
358
+
359
+ function validName(value: string): boolean {
360
+ return value.length > 0 && value.length <= 128 && !/\s/.test(value)
361
+ }
362
+
363
+ function isRecord(value: unknown): value is Record<string, unknown> {
364
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return false
365
+ const prototype = Object.getPrototypeOf(value)
366
+ return prototype === Object.prototype || prototype === null
367
+ }
368
+
369
+ function invalidConfig(): never {
370
+ throw new AIError({
371
+ code: 'invalid_config',
372
+ message: 'AI configuration is invalid',
373
+ })
374
+ }
375
+
376
+ function invalidResponse(): never {
377
+ throw new AIError({
378
+ code: 'invalid_response',
379
+ message: 'AI provider returned an invalid structured response',
380
+ operation: 'object',
381
+ })
382
+ }