@plumbus/core 0.8.0-beta.0 → 0.8.0-beta.10

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 (275) hide show
  1. package/CHANGELOG.md +197 -0
  2. package/README.md +8 -1
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/ai/ai/cost-tracker.d.ts +22 -2
  5. package/dist/ai/ai/model-pricing.d.ts +8 -2
  6. package/dist/ai/ai/openai-strict-schema.d.ts +84 -0
  7. package/dist/ai/ai/provider.d.ts +90 -6
  8. package/dist/ai/ai-service.d.ts +53 -1
  9. package/dist/ai/ai-service.d.ts.map +1 -1
  10. package/dist/ai/ai-service.js +550 -119
  11. package/dist/ai/ai-service.js.map +1 -1
  12. package/dist/ai/cost-tracker.d.ts +22 -2
  13. package/dist/ai/cost-tracker.d.ts.map +1 -1
  14. package/dist/ai/cost-tracker.js +59 -20
  15. package/dist/ai/cost-tracker.js.map +1 -1
  16. package/dist/ai/explainability.d.ts +2 -1
  17. package/dist/ai/explainability.d.ts.map +1 -1
  18. package/dist/ai/explainability.js +1 -0
  19. package/dist/ai/explainability.js.map +1 -1
  20. package/dist/ai/index.d.ts +2 -2
  21. package/dist/ai/index.d.ts.map +1 -1
  22. package/dist/ai/index.js +1 -1
  23. package/dist/ai/index.js.map +1 -1
  24. package/dist/ai/model-pricing.d.ts +8 -2
  25. package/dist/ai/model-pricing.d.ts.map +1 -1
  26. package/dist/ai/model-pricing.js +113 -15
  27. package/dist/ai/model-pricing.js.map +1 -1
  28. package/dist/ai/openai-strict-schema.d.ts +85 -0
  29. package/dist/ai/openai-strict-schema.d.ts.map +1 -0
  30. package/dist/ai/openai-strict-schema.js +730 -0
  31. package/dist/ai/openai-strict-schema.js.map +1 -0
  32. package/dist/ai/prompt-overrides.d.ts +19 -0
  33. package/dist/ai/prompt-overrides.d.ts.map +1 -0
  34. package/dist/ai/prompt-overrides.js +60 -0
  35. package/dist/ai/prompt-overrides.js.map +1 -0
  36. package/dist/ai/provider-failure.d.ts +9 -0
  37. package/dist/ai/provider-failure.d.ts.map +1 -0
  38. package/dist/ai/provider-failure.js +112 -0
  39. package/dist/ai/provider-failure.js.map +1 -0
  40. package/dist/ai/provider.d.ts +90 -6
  41. package/dist/ai/provider.d.ts.map +1 -1
  42. package/dist/ai/provider.js +546 -97
  43. package/dist/ai/provider.js.map +1 -1
  44. package/dist/ai/rag/pipeline.d.ts +21 -1
  45. package/dist/ai/rag/pipeline.d.ts.map +1 -1
  46. package/dist/ai/rag/pipeline.js +17 -1
  47. package/dist/ai/rag/pipeline.js.map +1 -1
  48. package/dist/ai/tool-loop.d.ts +8 -1
  49. package/dist/ai/tool-loop.d.ts.map +1 -1
  50. package/dist/ai/tool-loop.js +7 -0
  51. package/dist/ai/tool-loop.js.map +1 -1
  52. package/dist/ai/types/config.d.ts +13 -0
  53. package/dist/ai/types/fields.d.ts +6 -0
  54. package/dist/ai/types/prompt.d.ts +9 -0
  55. package/dist/ai/usage-validation.d.ts.map +1 -1
  56. package/dist/ai/usage-validation.js +6 -1
  57. package/dist/ai/usage-validation.js.map +1 -1
  58. package/dist/ai/validation.d.ts +7 -3
  59. package/dist/ai/validation.d.ts.map +1 -1
  60. package/dist/ai/validation.js +38 -25
  61. package/dist/ai/validation.js.map +1 -1
  62. package/dist/ai/zod-to-provider-schema.d.ts.map +1 -1
  63. package/dist/ai/zod-to-provider-schema.js +246 -16
  64. package/dist/ai/zod-to-provider-schema.js.map +1 -1
  65. package/dist/api/route-generator.d.ts +2 -12
  66. package/dist/api/route-generator.d.ts.map +1 -1
  67. package/dist/api/route-generator.js +33 -41
  68. package/dist/api/route-generator.js.map +1 -1
  69. package/dist/approvals/index.d.ts +1 -1
  70. package/dist/approvals/index.d.ts.map +1 -1
  71. package/dist/approvals/index.js.map +1 -1
  72. package/dist/approvals/memory-store.d.ts.map +1 -1
  73. package/dist/approvals/memory-store.js +22 -0
  74. package/dist/approvals/memory-store.js.map +1 -1
  75. package/dist/approvals/schema.d.ts +34 -0
  76. package/dist/approvals/schema.d.ts.map +1 -1
  77. package/dist/approvals/schema.js +2 -0
  78. package/dist/approvals/schema.js.map +1 -1
  79. package/dist/approvals/service.d.ts.map +1 -1
  80. package/dist/approvals/service.js +36 -0
  81. package/dist/approvals/service.js.map +1 -1
  82. package/dist/approvals/sql-store.d.ts.map +1 -1
  83. package/dist/approvals/sql-store.js +42 -1
  84. package/dist/approvals/sql-store.js.map +1 -1
  85. package/dist/approvals/types.d.ts +11 -0
  86. package/dist/approvals/types.d.ts.map +1 -1
  87. package/dist/approvals/types.js +1 -0
  88. package/dist/approvals/types.js.map +1 -1
  89. package/dist/audit/service.d.ts.map +1 -1
  90. package/dist/audit/service.js +22 -2
  91. package/dist/audit/service.js.map +1 -1
  92. package/dist/cli/cli.d.ts.map +1 -1
  93. package/dist/cli/cli.js +2 -1
  94. package/dist/cli/cli.js.map +1 -1
  95. package/dist/cli/commands/browser-extension.d.ts +16 -0
  96. package/dist/cli/commands/browser-extension.d.ts.map +1 -1
  97. package/dist/cli/commands/browser-extension.js +16 -5
  98. package/dist/cli/commands/browser-extension.js.map +1 -1
  99. package/dist/cli/commands/dev.d.ts.map +1 -1
  100. package/dist/cli/commands/dev.js +7 -1
  101. package/dist/cli/commands/dev.js.map +1 -1
  102. package/dist/cli/commands/e2e.d.ts +12 -0
  103. package/dist/cli/commands/e2e.d.ts.map +1 -1
  104. package/dist/cli/commands/e2e.js +11 -0
  105. package/dist/cli/commands/e2e.js.map +1 -1
  106. package/dist/cli/commands/generate-openapi-from-zod.d.ts.map +1 -1
  107. package/dist/cli/commands/generate-openapi-from-zod.js +9 -1
  108. package/dist/cli/commands/generate-openapi-from-zod.js.map +1 -1
  109. package/dist/cli/commands/index.d.ts +1 -0
  110. package/dist/cli/commands/index.d.ts.map +1 -1
  111. package/dist/cli/commands/index.js +1 -0
  112. package/dist/cli/commands/index.js.map +1 -1
  113. package/dist/cli/commands/init.d.ts +1 -1
  114. package/dist/cli/commands/init.d.ts.map +1 -1
  115. package/dist/cli/commands/init.js +63 -3
  116. package/dist/cli/commands/init.js.map +1 -1
  117. package/dist/cli/commands/migrate.d.ts.map +1 -1
  118. package/dist/cli/commands/migrate.js +8 -14
  119. package/dist/cli/commands/migrate.js.map +1 -1
  120. package/dist/cli/commands/payments.d.ts +56 -0
  121. package/dist/cli/commands/payments.d.ts.map +1 -0
  122. package/dist/cli/commands/payments.js +184 -0
  123. package/dist/cli/commands/payments.js.map +1 -0
  124. package/dist/cli/commands/run.d.ts.map +1 -1
  125. package/dist/cli/commands/run.js +2 -7
  126. package/dist/cli/commands/run.js.map +1 -1
  127. package/dist/cli/commands/seed.d.ts.map +1 -1
  128. package/dist/cli/commands/seed.js +2 -7
  129. package/dist/cli/commands/seed.js.map +1 -1
  130. package/dist/cli/commands/start.d.ts.map +1 -1
  131. package/dist/cli/commands/start.js +9 -1
  132. package/dist/cli/commands/start.js.map +1 -1
  133. package/dist/cli/commands/worker.js +1 -1
  134. package/dist/cli/commands/worker.js.map +1 -1
  135. package/dist/cli/discover.d.ts +2 -0
  136. package/dist/cli/discover.d.ts.map +1 -1
  137. package/dist/cli/discover.js +39 -5
  138. package/dist/cli/discover.js.map +1 -1
  139. package/dist/config/loader.d.ts +4 -1
  140. package/dist/config/loader.d.ts.map +1 -1
  141. package/dist/config/loader.js +112 -24
  142. package/dist/config/loader.js.map +1 -1
  143. package/dist/data/connection.d.ts +23 -0
  144. package/dist/data/connection.d.ts.map +1 -1
  145. package/dist/data/connection.js +23 -6
  146. package/dist/data/connection.js.map +1 -1
  147. package/dist/data/schema-generator.d.ts.map +1 -1
  148. package/dist/data/schema-generator.js +2 -2
  149. package/dist/data/schema-generator.js.map +1 -1
  150. package/dist/define/definePrompt.d.ts.map +1 -1
  151. package/dist/define/definePrompt.js +6 -0
  152. package/dist/define/definePrompt.js.map +1 -1
  153. package/dist/durable/index.d.ts +2 -2
  154. package/dist/durable/index.d.ts.map +1 -1
  155. package/dist/durable/index.js +2 -2
  156. package/dist/durable/index.js.map +1 -1
  157. package/dist/durable/postgres-persist.d.ts +47 -2
  158. package/dist/durable/postgres-persist.d.ts.map +1 -1
  159. package/dist/durable/postgres-persist.js +152 -6
  160. package/dist/durable/postgres-persist.js.map +1 -1
  161. package/dist/durable/spine-claim.d.ts +22 -1
  162. package/dist/durable/spine-claim.d.ts.map +1 -1
  163. package/dist/durable/spine-claim.js +54 -3
  164. package/dist/durable/spine-claim.js.map +1 -1
  165. package/dist/errors/http.d.ts.map +1 -1
  166. package/dist/errors/http.js +9 -1
  167. package/dist/errors/http.js.map +1 -1
  168. package/dist/events/dispatcher.d.ts +20 -0
  169. package/dist/events/dispatcher.d.ts.map +1 -1
  170. package/dist/events/dispatcher.js +61 -26
  171. package/dist/events/dispatcher.js.map +1 -1
  172. package/dist/events/pipeline-audit.d.ts +14 -0
  173. package/dist/events/pipeline-audit.d.ts.map +1 -0
  174. package/dist/events/pipeline-audit.js +29 -0
  175. package/dist/events/pipeline-audit.js.map +1 -0
  176. package/dist/events/worker.d.ts +3 -0
  177. package/dist/events/worker.d.ts.map +1 -1
  178. package/dist/events/worker.js +14 -14
  179. package/dist/events/worker.js.map +1 -1
  180. package/dist/execution/capability-error-hook.d.ts +24 -0
  181. package/dist/execution/capability-error-hook.d.ts.map +1 -0
  182. package/dist/execution/capability-error-hook.js +17 -0
  183. package/dist/execution/capability-error-hook.js.map +1 -0
  184. package/dist/execution/capability-executor.d.ts +2 -2
  185. package/dist/execution/capability-executor.d.ts.map +1 -1
  186. package/dist/execution/capability-executor.js +45 -7
  187. package/dist/execution/capability-executor.js.map +1 -1
  188. package/dist/execution/context-factory.d.ts.map +1 -1
  189. package/dist/execution/context-factory.js +9 -1
  190. package/dist/execution/context-factory.js.map +1 -1
  191. package/dist/execution/deferred-flow-service.d.ts.map +1 -1
  192. package/dist/execution/deferred-flow-service.js +7 -0
  193. package/dist/execution/deferred-flow-service.js.map +1 -1
  194. package/dist/execution/index.d.ts +1 -0
  195. package/dist/execution/index.d.ts.map +1 -1
  196. package/dist/execution/index.js.map +1 -1
  197. package/dist/fields/index.d.ts +7 -0
  198. package/dist/fields/index.d.ts.map +1 -1
  199. package/dist/fields/index.js +10 -1
  200. package/dist/fields/index.js.map +1 -1
  201. package/dist/flows/dead-letter.d.ts +18 -1
  202. package/dist/flows/dead-letter.d.ts.map +1 -1
  203. package/dist/flows/dead-letter.js +17 -1
  204. package/dist/flows/dead-letter.js.map +1 -1
  205. package/dist/flows/engine.d.ts +16 -1
  206. package/dist/flows/engine.d.ts.map +1 -1
  207. package/dist/flows/engine.js +218 -29
  208. package/dist/flows/engine.js.map +1 -1
  209. package/dist/flows/flow-service.d.ts.map +1 -1
  210. package/dist/flows/flow-service.js +3 -0
  211. package/dist/flows/flow-service.js.map +1 -1
  212. package/dist/flows/step-executor.d.ts +8 -0
  213. package/dist/flows/step-executor.d.ts.map +1 -1
  214. package/dist/flows/step-executor.js.map +1 -1
  215. package/dist/index.d.ts +4 -4
  216. package/dist/index.d.ts.map +1 -1
  217. package/dist/index.js +1 -1
  218. package/dist/index.js.map +1 -1
  219. package/dist/runtime/bootstrap.d.ts +18 -1
  220. package/dist/runtime/bootstrap.d.ts.map +1 -1
  221. package/dist/runtime/bootstrap.js +36 -5
  222. package/dist/runtime/bootstrap.js.map +1 -1
  223. package/dist/runtime/load-extensions.d.ts +2 -1
  224. package/dist/runtime/load-extensions.d.ts.map +1 -1
  225. package/dist/runtime/load-extensions.js +49 -1
  226. package/dist/runtime/load-extensions.js.map +1 -1
  227. package/dist/runtime/register-consumers.d.ts +3 -0
  228. package/dist/runtime/register-consumers.d.ts.map +1 -1
  229. package/dist/runtime/register-consumers.js +27 -6
  230. package/dist/runtime/register-consumers.js.map +1 -1
  231. package/dist/runtime/start-worker-pool.d.ts.map +1 -1
  232. package/dist/runtime/start-worker-pool.js +36 -15
  233. package/dist/runtime/start-worker-pool.js.map +1 -1
  234. package/dist/server/bootstrap.d.ts +42 -16
  235. package/dist/server/bootstrap.d.ts.map +1 -1
  236. package/dist/server/bootstrap.js +91 -21
  237. package/dist/server/bootstrap.js.map +1 -1
  238. package/dist/testing/context.d.ts +2 -0
  239. package/dist/testing/context.d.ts.map +1 -1
  240. package/dist/testing/context.js +8 -3
  241. package/dist/testing/context.js.map +1 -1
  242. package/dist/testing/field-validation.d.ts.map +1 -1
  243. package/dist/testing/field-validation.js +3 -0
  244. package/dist/testing/field-validation.js.map +1 -1
  245. package/dist/types/config.d.ts +13 -0
  246. package/dist/types/config.d.ts.map +1 -1
  247. package/dist/types/context.d.ts +70 -4
  248. package/dist/types/context.d.ts.map +1 -1
  249. package/dist/types/fields.d.ts +6 -0
  250. package/dist/types/fields.d.ts.map +1 -1
  251. package/dist/types/index.d.ts +1 -1
  252. package/dist/types/index.d.ts.map +1 -1
  253. package/dist/types/index.js.map +1 -1
  254. package/dist/types/prompt.d.ts +9 -0
  255. package/dist/types/prompt.d.ts.map +1 -1
  256. package/dist/worker/bootstrap.d.ts +19 -0
  257. package/dist/worker/bootstrap.d.ts.map +1 -1
  258. package/dist/worker/bootstrap.js +156 -84
  259. package/dist/worker/bootstrap.js.map +1 -1
  260. package/instructions/README.md +4 -1
  261. package/instructions/ai-classification.md +132 -0
  262. package/instructions/ai.md +94 -15
  263. package/instructions/cli.md +19 -1
  264. package/instructions/entities.md +3 -1
  265. package/instructions/flows.md +6 -0
  266. package/instructions/framework.md +13 -3
  267. package/instructions/peer-dependencies.md +2 -1
  268. package/instructions/prompts.md +2 -1
  269. package/migrations/durable-tenant/0002_approval_cancellation.sql +3 -0
  270. package/migrations/durable-tenant/meta/_journal.json +7 -0
  271. package/package.json +5 -4
  272. package/dist/ai/__typecheck-fixtures__/legacy-cost-api.d.ts +0 -5
  273. package/dist/ai/__typecheck-fixtures__/legacy-cost-api.d.ts.map +0 -1
  274. package/dist/ai/__typecheck-fixtures__/legacy-cost-api.js +0 -15
  275. package/dist/ai/__typecheck-fixtures__/legacy-cost-api.js.map +0 -1
@@ -0,0 +1,730 @@
1
+ // ── OpenAI strict structured-output schema projection ──
2
+ // OpenAI's strict mode (Chat Completions `response_format` json_schema, strict function tools,
3
+ // Responses `text.format`) refuses any object whose `required` does not list every key in
4
+ // `properties`. The provider-neutral schema from `zodToProviderJsonSchema` keeps optional
5
+ // properties optional, because Anthropic and Bedrock take them as they are (and Anthropic counts
6
+ // them against its limit of 24). OpenAI expresses "optional" as "required, but may be null", so the
7
+ // OpenAI adapter sends `toOpenAIStrictJsonSchema(schema)` (inside `toOpenAIStrictOutputSchema`)
8
+ // and the AI service turns those nulls back into absent properties with
9
+ // `restoreOmittedOptionalProperties` before the Zod output schema sees the answer.
10
+ //
11
+ // Strict mode also accepts fewer string formats than Anthropic and Bedrock: an unsupported one
12
+ // (`uri`, from `z.string().url()`) fails the whole request with "'uri' is not a valid format".
13
+ // The projection drops such a format and names it in the property's description instead; the
14
+ // Zod output schema still checks it.
15
+ //
16
+ // Strict mode does not support `allOf` ("Composition: allOf, not, ... are not yet supported").
17
+ // zod-to-json-schema emits it for intersections (`a.and(b)`) and for a string with more than one
18
+ // pattern (`.regex(a).regex(b)`, `.startsWith(a).endsWith(b)`). `zodToProviderJsonSchema` already
19
+ // merges intersections of objects (with `structuralSchema`, since their members' own
20
+ // `additionalProperties: false` would refuse every answer on any provider) and turns the two
21
+ // patterns of `z.string().cidr()` into an `anyOf`; what reaches this projection is a string's
22
+ // pattern list, or a schema built by hand. The projection merges the members into one schema and
23
+ // names in the description what that schema cannot carry (`mergeAllOf`); the Zod output schema
24
+ // still checks the original. Keywords next to an `anyOf` (an object intersected with a union, the
25
+ // `type` of `z.string().ip()` or `z.string().cidr()`) go into each branch (`distributeUnion`), and
26
+ // `restoreOmittedOptionalProperties` reads the schema the same way.
27
+ //
28
+ // Strict mode has no `default` either (it is not among the supported keywords): the projection
29
+ // names the value in the description (`Defaults to "x".`). An optional property with a default is
30
+ // sent nullable like any other optional property, its null is dropped before validation, and Zod
31
+ // fills in the default.
32
+ //
33
+ // Strict mode also needs an object at the root ("Root objects must not be anyOf and must be an
34
+ // object"), which a `z.array(...)`, union, enum or nullable output is not.
35
+ // `toOpenAIStrictOutputSchema` sends such a schema as the single property `value` of an object,
36
+ // and the adapter takes the answer back out (`unwrapOpenAIStrictOutput`, or
37
+ // `createOpenAIStrictOutputStreamUnwrapper` for a stream) before anything else sees it.
38
+ /**
39
+ * The string `format` values OpenAI accepts in strict mode, from the "Supported properties"
40
+ * section of https://developers.openai.com/api/docs/guides/structured-outputs.
41
+ */
42
+ const OPENAI_STRICT_STRING_FORMATS = new Set([
43
+ 'date-time',
44
+ 'time',
45
+ 'date',
46
+ 'duration',
47
+ 'email',
48
+ 'hostname',
49
+ 'ipv4',
50
+ 'ipv6',
51
+ 'uuid',
52
+ ]);
53
+ function isRecord(value) {
54
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
55
+ }
56
+ const scalarTypes = new Set(['string', 'number', 'integer', 'boolean']);
57
+ // Keys that describe a union without constraining it, so a null branch can join its `anyOf`.
58
+ const annotationKeys = new Set(['description', 'title']);
59
+ function isScalarType(value) {
60
+ return typeof value === 'string' && scalarTypes.has(value);
61
+ }
62
+ function matchesType(value, type) {
63
+ const types = Array.isArray(type) ? type : [type];
64
+ return types.some((candidate) => {
65
+ switch (candidate) {
66
+ case 'null':
67
+ return value === null;
68
+ case 'string':
69
+ return typeof value === 'string';
70
+ case 'number':
71
+ return typeof value === 'number';
72
+ case 'integer':
73
+ return Number.isInteger(value);
74
+ case 'boolean':
75
+ return typeof value === 'boolean';
76
+ case 'object':
77
+ return isRecord(value);
78
+ case 'array':
79
+ return Array.isArray(value);
80
+ default:
81
+ return true;
82
+ }
83
+ });
84
+ }
85
+ function sameJsonValue(left, right) {
86
+ if (left === right)
87
+ return true;
88
+ if (typeof left !== 'object' || typeof right !== 'object' || left === null || right === null) {
89
+ return false;
90
+ }
91
+ return JSON.stringify(left) === JSON.stringify(right);
92
+ }
93
+ function ownSchema(properties, key) {
94
+ return Object.hasOwn(properties, key) ? properties[key] : undefined;
95
+ }
96
+ function requiredKeys(schema) {
97
+ return new Set(Array.isArray(schema.required)
98
+ ? schema.required.filter((key) => typeof key === 'string')
99
+ : []);
100
+ }
101
+ /**
102
+ * Structural check of a value against the provider JSON Schema subset (`type`, `const`, `enum`,
103
+ * `anyOf`, `allOf`, `properties`/`required`/`additionalProperties: false`, `items`,
104
+ * `prefixItems`). String formats, patterns and numeric bounds are not checked: the Zod output
105
+ * schema still validates the answer, and this check only has to tell union branches apart and
106
+ * whether a property's schema admits `null`. An `allOf` is checked as the one schema strict mode
107
+ * was sent (`structuralSchema`): its members each carry `additionalProperties: false`, so taken
108
+ * one by one they would refuse every answer to an intersection of objects.
109
+ */
110
+ function matchesSchema(value, rawSchema) {
111
+ if (!isRecord(rawSchema))
112
+ return rawSchema !== false;
113
+ const schema = structuralSchema(rawSchema);
114
+ if ('const' in schema && !sameJsonValue(value, schema.const))
115
+ return false;
116
+ if (Array.isArray(schema.enum) && !schema.enum.some((option) => sameJsonValue(value, option))) {
117
+ return false;
118
+ }
119
+ if (schema.type !== undefined && !matchesType(value, schema.type))
120
+ return false;
121
+ if (Array.isArray(schema.anyOf) && !schema.anyOf.some((branch) => matchesSchema(value, branch))) {
122
+ return false;
123
+ }
124
+ if (isRecord(value) && isRecord(schema.properties)) {
125
+ const properties = schema.properties;
126
+ for (const key of requiredKeys(schema)) {
127
+ if (!Object.hasOwn(value, key))
128
+ return false;
129
+ }
130
+ for (const [key, child] of Object.entries(value)) {
131
+ const propertySchema = ownSchema(properties, key);
132
+ if (propertySchema === undefined) {
133
+ if (schema.additionalProperties === false)
134
+ return false;
135
+ continue;
136
+ }
137
+ if (!matchesSchema(child, propertySchema))
138
+ return false;
139
+ }
140
+ }
141
+ if (Array.isArray(value)) {
142
+ const prefixItems = Array.isArray(schema.prefixItems) ? schema.prefixItems : [];
143
+ for (const [index, item] of value.entries()) {
144
+ const itemSchema = index < prefixItems.length ? prefixItems[index] : schema.items;
145
+ if (itemSchema !== undefined && !matchesSchema(item, itemSchema))
146
+ return false;
147
+ }
148
+ }
149
+ return true;
150
+ }
151
+ function acceptsNull(schema) {
152
+ return matchesSchema(null, schema);
153
+ }
154
+ /**
155
+ * Widen a property schema so it also accepts `null`. Scalars use a type array
156
+ * (`["string", "null"]`), the form OpenAI documents for optional fields and the form
157
+ * zod-to-json-schema already emits for `.nullable()` scalars; everything else (enums, consts,
158
+ * objects, arrays, unions) gets a `{ type: "null" }` branch, flattened into an existing `anyOf`.
159
+ */
160
+ function withNull(schema) {
161
+ if (!isRecord(schema) || acceptsNull(schema))
162
+ return schema;
163
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
164
+ if (!('enum' in schema) && !('const' in schema) && types.every(isScalarType)) {
165
+ return { ...schema, type: [...types, 'null'] };
166
+ }
167
+ const { anyOf, ...rest } = schema;
168
+ if (Array.isArray(anyOf) && Object.keys(rest).every((key) => annotationKeys.has(key))) {
169
+ return { ...rest, anyOf: [...anyOf, { type: 'null' }] };
170
+ }
171
+ return { anyOf: [schema, { type: 'null' }] };
172
+ }
173
+ /** Append `hint` to a schema's description unless it already says it (mutates `schema`). */
174
+ function appendDescription(schema, hint) {
175
+ if (!hint)
176
+ return;
177
+ const existing = typeof schema.description === 'string' ? schema.description.trim() : '';
178
+ if (existing.includes(hint))
179
+ return;
180
+ schema.description = existing ? `${existing} ${hint}` : hint;
181
+ }
182
+ /**
183
+ * Replace a string `format` OpenAI strict mode rejects with a description hint, the way
184
+ * `zodToProviderJsonSchema` treats formats no provider supports.
185
+ */
186
+ function dropUnsupportedFormat(strict) {
187
+ const format = strict.format;
188
+ if (typeof format !== 'string' || OPENAI_STRICT_STRING_FORMATS.has(format))
189
+ return;
190
+ delete strict.format;
191
+ appendDescription(strict, `Expected string format: ${format}.`);
192
+ }
193
+ // ── allOf ──
194
+ /** The description text for a constraint the merged schema cannot carry. */
195
+ function constraintHint(key, value) {
196
+ if (key === 'pattern' && typeof value === 'string')
197
+ return `Must match pattern: ${value}.`;
198
+ if (key === 'format' && typeof value === 'string')
199
+ return `Expected string format: ${value}.`;
200
+ return `Must also satisfy ${JSON.stringify({ [key]: value })}.`;
201
+ }
202
+ function typeList(type) {
203
+ if (typeof type === 'string')
204
+ return [type];
205
+ if (Array.isArray(type) && type.every((entry) => typeof entry === 'string'))
206
+ return type;
207
+ return undefined;
208
+ }
209
+ /** The JSON types both `type` keywords accept (an integer is a number), or undefined if none. */
210
+ function intersectTypes(left, right) {
211
+ const leftTypes = typeList(left);
212
+ const rightTypes = typeList(right);
213
+ if (!leftTypes || !rightTypes)
214
+ return undefined;
215
+ const accepts = (types, type) => types.includes(type) || (type === 'integer' && types.includes('number'));
216
+ const common = [...new Set([...leftTypes, ...rightTypes])].filter((type) => accepts(leftTypes, type) && accepts(rightTypes, type));
217
+ if (common.length === 0)
218
+ return undefined;
219
+ return common.length === 1 ? common[0] : common;
220
+ }
221
+ function stringList(value) {
222
+ return Array.isArray(value)
223
+ ? value.filter((entry) => typeof entry === 'string')
224
+ : [];
225
+ }
226
+ /** `{ not: {} }` (zod-to-json-schema's `.optional()` outside an object property): matches nothing. */
227
+ function matchesNothing(schema) {
228
+ return isRecord(schema.not) && Object.keys(schema.not).length === 0;
229
+ }
230
+ /**
231
+ * False when no value can satisfy both schemas, judged from what each says outright: one of them
232
+ * matches nothing (`{ not: {} }`), their types share nothing, their `const` / `enum` values do not
233
+ * meet, or a property one of them requires has two such schemas. Merging such a pair would leave a
234
+ * union branch that only its description calls impossible (`a.and(b.nullable())` gave a branch
235
+ * `{a}` that Zod refuses, `a.and(b.optional())` one with `not: {}`), so the merge drops it.
236
+ */
237
+ function canBothHold(left, right) {
238
+ if (matchesNothing(left) || matchesNothing(right))
239
+ return false;
240
+ if (typeList(left.type) && typeList(right.type) && !intersectTypes(left.type, right.type)) {
241
+ return false;
242
+ }
243
+ const values = (schema) => Object.hasOwn(schema, 'const')
244
+ ? [schema.const]
245
+ : Array.isArray(schema.enum)
246
+ ? schema.enum
247
+ : undefined;
248
+ const leftValues = values(left);
249
+ const rightValues = values(right);
250
+ if (leftValues &&
251
+ rightValues &&
252
+ !leftValues.some((option) => rightValues.some((other) => sameJsonValue(option, other)))) {
253
+ return false;
254
+ }
255
+ if (isRecord(left.properties) && isRecord(right.properties)) {
256
+ const required = new Set([...requiredKeys(left), ...requiredKeys(right)]);
257
+ for (const key of required) {
258
+ const first = ownSchema(left.properties, key);
259
+ const second = ownSchema(right.properties, key);
260
+ if (isRecord(first) && isRecord(second) && !canBothHold(first, second))
261
+ return false;
262
+ }
263
+ }
264
+ return true;
265
+ }
266
+ /** Both objects' properties; a key both declare gets the merge of its two schemas. */
267
+ function mergeProperties(left, right) {
268
+ const entries = Object.entries(left);
269
+ for (const [key, schema] of Object.entries(right)) {
270
+ const index = entries.findIndex(([existing]) => existing === key);
271
+ const current = index >= 0 ? entries[index] : undefined;
272
+ if (!current) {
273
+ entries.push([key, schema]);
274
+ }
275
+ else if (isRecord(current[1]) && isRecord(schema)) {
276
+ current[1] = mergeSchemas([current[1], schema]);
277
+ }
278
+ }
279
+ // fromEntries defines own data properties, so a `__proto__` property name stays data.
280
+ return Object.fromEntries(entries);
281
+ }
282
+ /**
283
+ * One schema that requires what `left` and `right` both require. Keywords only one side has are
284
+ * kept; keywords both have are combined where JSON Schema allows it (`type` and `enum`
285
+ * intersected, `properties` and `required` joined, `minItems` the larger, `items` and union
286
+ * branches merged in turn); where it does not (two different patterns or formats), the first is
287
+ * kept and the other is named in the description. Annotations (`title`, `default`) keep the
288
+ * first; descriptions are joined. Also used by `zodToProviderJsonSchema` to fold a union's only
289
+ * reachable branch into the node that holds it.
290
+ */
291
+ export function mergeTwoSchemas(left, right) {
292
+ const merged = { ...left };
293
+ const hints = [];
294
+ for (const [key, value] of Object.entries(right)) {
295
+ if (!Object.hasOwn(merged, key)) {
296
+ merged[key] = value;
297
+ continue;
298
+ }
299
+ const current = merged[key];
300
+ if (sameJsonValue(current, value))
301
+ continue;
302
+ switch (key) {
303
+ case 'description':
304
+ if (typeof value === 'string')
305
+ hints.push(value.trim());
306
+ break;
307
+ case 'title':
308
+ case 'default':
309
+ case 'examples':
310
+ break;
311
+ case 'type': {
312
+ const type = intersectTypes(current, value);
313
+ if (type === undefined)
314
+ hints.push(constraintHint(key, value));
315
+ else
316
+ merged.type = type;
317
+ break;
318
+ }
319
+ case 'properties':
320
+ if (isRecord(current) && isRecord(value)) {
321
+ merged.properties = mergeProperties(current, value);
322
+ }
323
+ break;
324
+ case 'required':
325
+ merged.required = [...new Set([...stringList(current), ...stringList(value)])];
326
+ break;
327
+ case 'additionalProperties':
328
+ if (value === false)
329
+ merged.additionalProperties = false;
330
+ break;
331
+ case 'items':
332
+ if (isRecord(current) && isRecord(value))
333
+ merged.items = mergeSchemas([current, value]);
334
+ break;
335
+ case 'prefixItems':
336
+ if (Array.isArray(current) && Array.isArray(value)) {
337
+ merged.prefixItems = Array.from({ length: Math.max(current.length, value.length) }, (_, index) => {
338
+ const [first, second] = [current[index], value[index]];
339
+ return isRecord(first) && isRecord(second)
340
+ ? mergeSchemas([first, second])
341
+ : (first ?? second);
342
+ });
343
+ }
344
+ break;
345
+ case 'anyOf':
346
+ // (A1 | A2) & (B1 | B2) = (A1 & B1) | (A1 & B2) | (A2 & B1) | (A2 & B2), without the
347
+ // pairs no value satisfies (unless that is every pair: then nothing is dropped).
348
+ if (Array.isArray(current) && Array.isArray(value)) {
349
+ const pairs = current.flatMap((first) => value.map((second) => [first, second]));
350
+ const possible = pairs.filter(([first, second]) => !isRecord(first) ||
351
+ !isRecord(second) ||
352
+ canBothHold(mergeAllOf(first), mergeAllOf(second)));
353
+ merged.anyOf = (possible.length > 0 ? possible : pairs).map(([first, second]) => isRecord(first) && isRecord(second) ? mergeSchemas([first, second]) : first);
354
+ }
355
+ break;
356
+ case 'enum': {
357
+ const common = Array.isArray(current)
358
+ ? current.filter((option) => Array.isArray(value) && value.some((other) => sameJsonValue(option, other)))
359
+ : [];
360
+ if (common.length > 0)
361
+ merged.enum = common;
362
+ else
363
+ hints.push(constraintHint(key, value));
364
+ break;
365
+ }
366
+ case 'minItems':
367
+ if (typeof current === 'number' && typeof value === 'number') {
368
+ merged.minItems = Math.max(current, value);
369
+ }
370
+ break;
371
+ default:
372
+ hints.push(constraintHint(key, value));
373
+ }
374
+ }
375
+ for (const hint of hints)
376
+ appendDescription(merged, hint);
377
+ return merged;
378
+ }
379
+ /** Fold `schemas` (each with its own `allOf` merged first) into one. */
380
+ function mergeSchemas(schemas) {
381
+ const [first, ...rest] = schemas.map(mergeAllOf);
382
+ return rest.reduce(mergeTwoSchemas, first ?? {});
383
+ }
384
+ /**
385
+ * Replace a node's `allOf` with one schema that says the same: the node's own keywords merged with
386
+ * every member (`mergeTwoSchemas`). A member's `pattern` is not kept but named in the description
387
+ * (`"Must match pattern: …."`): zod-to-json-schema lists every pattern of a string in `allOf` when
388
+ * it has more than one, and strict mode takes one `pattern` per schema. The Zod output schema
389
+ * still checks every pattern. (The IPv4 and IPv6 alternatives of `z.string().cidr()`, which
390
+ * zod-to-json-schema also lists in `allOf`, arrive here as an `anyOf`: `zodToProviderJsonSchema`
391
+ * turns them into one.)
392
+ */
393
+ function mergeAllOf(node) {
394
+ if (!Array.isArray(node.allOf))
395
+ return node;
396
+ const { allOf, ...base } = node;
397
+ const members = allOf.filter(isRecord).map((member) => {
398
+ const { pattern, ...rest } = mergeAllOf(member);
399
+ if (pattern !== undefined)
400
+ appendDescription(rest, constraintHint('pattern', pattern));
401
+ return rest;
402
+ });
403
+ return members.reduce(mergeTwoSchemas, base);
404
+ }
405
+ /**
406
+ * Move a union's sibling keywords into each of its branches: `{ A, anyOf: [B1, B2] }` becomes
407
+ * `{ anyOf: [A & B1, A & B2] }` (a `description` or `title` stays on the union). Merging an
408
+ * intersection with a union (`z.object({...}).and(z.discriminatedUnion(...))`) leaves the object's
409
+ * `properties` next to the union's `anyOf`, and the branches' `additionalProperties: false` would
410
+ * then refuse the object's properties; `z.string().ip()` has `type` next to branches that have none.
411
+ * A branch `A` rules out (`canBothHold`: the `{ not: {} }` of `a.and(b.optional())`, the null of
412
+ * `a.and(b.nullable())`) is dropped, and a union left with one branch is that branch.
413
+ */
414
+ function distributeUnion(node) {
415
+ if (!Array.isArray(node.anyOf))
416
+ return node;
417
+ const { anyOf, ...rest } = node;
418
+ const annotations = {};
419
+ const shared = {};
420
+ for (const [key, value] of Object.entries(rest)) {
421
+ if (annotationKeys.has(key))
422
+ annotations[key] = value;
423
+ else
424
+ shared[key] = value;
425
+ }
426
+ if (Object.keys(shared).length === 0)
427
+ return node;
428
+ const branches = anyOf.map((branch) => (isRecord(branch) ? structuralSchema(branch) : branch));
429
+ const possible = branches.filter((branch) => !isRecord(branch) || canBothHold(shared, branch));
430
+ const merged = (possible.length > 0 ? possible : branches).map((branch) => isRecord(branch) ? mergeTwoSchemas(shared, branch) : branch);
431
+ const [only] = merged;
432
+ if (merged.length === 1 && isRecord(only))
433
+ return mergeTwoSchemas(annotations, only);
434
+ return { ...annotations, anyOf: merged };
435
+ }
436
+ /**
437
+ * The schema as strict mode is sent it, at this node: `allOf` merged, union siblings distributed.
438
+ * `zodToProviderJsonSchema` merges an intersection of objects with it too, for every provider.
439
+ */
440
+ export function structuralSchema(node) {
441
+ return distributeUnion(mergeAllOf(node));
442
+ }
443
+ /**
444
+ * Replace a `default` (not a strict-mode keyword) with a description hint (mutates `strict`). The
445
+ * value still applies: the property it sits on is optional, so strict mode sends it nullable,
446
+ * `restoreOmittedOptionalProperties` drops the null, and Zod fills in the default.
447
+ */
448
+ function dropDefault(strict) {
449
+ if (!Object.hasOwn(strict, 'default'))
450
+ return;
451
+ const value = strict.default;
452
+ delete strict.default;
453
+ appendDescription(strict, `Defaults to ${JSON.stringify(value)}.`);
454
+ }
455
+ function toStrictNode(value) {
456
+ if (Array.isArray(value))
457
+ return value.map(toStrictNode);
458
+ if (!isRecord(value))
459
+ return value;
460
+ const node = structuralSchema(value);
461
+ const strict = { ...node };
462
+ dropUnsupportedFormat(strict);
463
+ dropDefault(strict);
464
+ if (isRecord(node.properties)) {
465
+ const required = requiredKeys(node);
466
+ const properties = [];
467
+ for (const [key, child] of Object.entries(node.properties)) {
468
+ const converted = toStrictNode(child);
469
+ properties.push([key, required.has(key) ? converted : withNull(converted)]);
470
+ }
471
+ strict.properties = Object.fromEntries(properties);
472
+ strict.required = properties.map(([key]) => key);
473
+ }
474
+ if ('items' in node)
475
+ strict.items = toStrictNode(node.items);
476
+ if (Array.isArray(node.prefixItems))
477
+ strict.prefixItems = node.prefixItems.map(toStrictNode);
478
+ if (Array.isArray(node.anyOf))
479
+ strict.anyOf = node.anyOf.map(toStrictNode);
480
+ return strict;
481
+ }
482
+ /**
483
+ * Project a provider-neutral structured-output schema (from `zodToProviderJsonSchema`) onto
484
+ * OpenAI's strict subset: every object lists all of its properties in `required`, and each
485
+ * property that was optional also accepts `null` (unless it already did). A string `format`
486
+ * outside `OPENAI_STRICT_STRING_FORMATS` (such as `uri`) is removed and named in the
487
+ * description, and so is a `default` (`dropDefault`). Every `allOf` is merged into one schema
488
+ * (`mergeAllOf`), since strict mode has no `allOf`. Nested objects, array items and every `anyOf`
489
+ * branch are projected too.
490
+ * The input is not mutated — `createAIService` caches it and shares it across providers.
491
+ */
492
+ export function toOpenAIStrictJsonSchema(schema) {
493
+ return toStrictNode(schema);
494
+ }
495
+ // ── Root envelope ──
496
+ /** The property a structured output whose root is not an object is sent and answered under. */
497
+ export const OPENAI_STRICT_OUTPUT_ENVELOPE_KEY = 'value';
498
+ /** OpenAI's rule for the root: an object, and not a union, enum, const or nullable object. */
499
+ function isStrictObjectRoot(schema) {
500
+ return (schema.type === 'object' &&
501
+ !['anyOf', 'oneOf', 'allOf', 'not', 'enum', 'const'].some((key) => key in schema));
502
+ }
503
+ /**
504
+ * The schema an OpenAI strict structured output sends for `schema`. An object root goes as
505
+ * `toOpenAIStrictJsonSchema(schema)`. Any other root (`z.array(...)` — `classify()`, an
506
+ * `extract()` or prompt output that is an array, a union, an enum, a number, a nullable object)
507
+ * goes as the single required property `value` of an object, because OpenAI refuses a schema
508
+ * whose root is not an object; the adapter unwraps the answer, so callers never see the envelope.
509
+ */
510
+ export function toOpenAIStrictOutputSchema(schema) {
511
+ const strict = toOpenAIStrictJsonSchema(schema);
512
+ if (isStrictObjectRoot(strict))
513
+ return { schema: strict, enveloped: false };
514
+ return {
515
+ schema: {
516
+ type: 'object',
517
+ properties: { [OPENAI_STRICT_OUTPUT_ENVELOPE_KEY]: strict },
518
+ required: [OPENAI_STRICT_OUTPUT_ENVELOPE_KEY],
519
+ additionalProperties: false,
520
+ },
521
+ enveloped: true,
522
+ };
523
+ }
524
+ /** True when `text` is one complete JSON value. */
525
+ function isJsonText(text) {
526
+ try {
527
+ JSON.parse(text);
528
+ return true;
529
+ }
530
+ catch {
531
+ return false;
532
+ }
533
+ }
534
+ /**
535
+ * The answer inside an enveloped strict output (`{"value": [...]}` → `[...]`), as JSON text. An
536
+ * answer cut off inside the envelope's value (`max_tokens`) comes back as the part of the value
537
+ * it holds (`{"value":[{"a":"x"` → `[{"a":"x"`), as a stream of it would, so an
538
+ * `AIIncompleteOutputError`'s `partialText` does not show the envelope. Any other text (not the
539
+ * envelope, not JSON, or a complete value with something after it) comes back unchanged, for
540
+ * validation to report.
541
+ */
542
+ export function unwrapOpenAIStrictOutput(content) {
543
+ let parsed;
544
+ try {
545
+ parsed = JSON.parse(content);
546
+ }
547
+ catch {
548
+ const unwrapper = createOpenAIStrictOutputStreamUnwrapper();
549
+ const inner = unwrapper.push(content) + unwrapper.end();
550
+ return inner !== content && !isJsonText(inner) ? inner : content;
551
+ }
552
+ if (!isRecord(parsed) ||
553
+ Object.keys(parsed).length !== 1 ||
554
+ !Object.hasOwn(parsed, OPENAI_STRICT_OUTPUT_ENVELOPE_KEY)) {
555
+ return content;
556
+ }
557
+ return JSON.stringify(parsed[OPENAI_STRICT_OUTPUT_ENVELOPE_KEY]);
558
+ }
559
+ const JSON_WHITESPACE = new Set([' ', '\t', '\n', '\r']);
560
+ /**
561
+ * `unwrapOpenAIStrictOutput` for a streamed answer: the opening `{"value":` is held back until it
562
+ * is complete, the value is passed on as it arrives, and what follows it (the closing `}`) is
563
+ * dropped. A stream that does not open with the envelope is passed on unchanged, so validation
564
+ * sees exactly what the model wrote.
565
+ */
566
+ export function createOpenAIStrictOutputStreamUnwrapper() {
567
+ const keyText = `${OPENAI_STRICT_OUTPUT_ENVELOPE_KEY}"`;
568
+ let state = 'open';
569
+ let held = '';
570
+ let keyIndex = 0;
571
+ let kind = 'literal';
572
+ let depth = 0;
573
+ let inString = false;
574
+ let escaped = false;
575
+ /** Holds `char` back while it may be part of the envelope's opening. */
576
+ const hold = (char, next = state) => {
577
+ held += char;
578
+ state = next;
579
+ return '';
580
+ };
581
+ /** Not the envelope after all: everything held back is passed on, and the rest as it comes. */
582
+ const giveUp = (char) => {
583
+ const text = held + char;
584
+ held = '';
585
+ state = 'raw';
586
+ return text;
587
+ };
588
+ const step = (char) => {
589
+ switch (state) {
590
+ case 'raw':
591
+ return char;
592
+ case 'after':
593
+ return '';
594
+ case 'open':
595
+ if (JSON_WHITESPACE.has(char))
596
+ return hold(char);
597
+ return char === '{' ? hold(char, 'key') : giveUp(char);
598
+ case 'key':
599
+ if (JSON_WHITESPACE.has(char))
600
+ return hold(char);
601
+ if (char !== '"')
602
+ return giveUp(char);
603
+ keyIndex = 0;
604
+ return hold(char, 'keyText');
605
+ case 'keyText':
606
+ if (char !== keyText[keyIndex])
607
+ return giveUp(char);
608
+ keyIndex += 1;
609
+ return hold(char, keyIndex === keyText.length ? 'colon' : 'keyText');
610
+ case 'colon':
611
+ if (JSON_WHITESPACE.has(char))
612
+ return hold(char);
613
+ return char === ':' ? hold(char, 'valueStart') : giveUp(char);
614
+ case 'valueStart':
615
+ if (JSON_WHITESPACE.has(char))
616
+ return hold(char);
617
+ held = '';
618
+ state = 'value';
619
+ if (char === '{' || char === '[') {
620
+ kind = 'container';
621
+ depth = 1;
622
+ }
623
+ else if (char === '"') {
624
+ kind = 'string';
625
+ inString = true;
626
+ }
627
+ else {
628
+ kind = 'literal';
629
+ }
630
+ return char;
631
+ case 'value':
632
+ if (kind === 'literal') {
633
+ if (JSON_WHITESPACE.has(char) || char === ',' || char === '}' || char === ']') {
634
+ state = 'after';
635
+ return '';
636
+ }
637
+ return char;
638
+ }
639
+ if (inString) {
640
+ if (escaped)
641
+ escaped = false;
642
+ else if (char === '\\')
643
+ escaped = true;
644
+ else if (char === '"') {
645
+ inString = false;
646
+ if (kind === 'string')
647
+ state = 'after';
648
+ }
649
+ return char;
650
+ }
651
+ if (char === '"')
652
+ inString = true;
653
+ else if (char === '{' || char === '[')
654
+ depth += 1;
655
+ else if (char === '}' || char === ']') {
656
+ depth -= 1;
657
+ if (depth === 0)
658
+ state = 'after';
659
+ }
660
+ return char;
661
+ }
662
+ };
663
+ return {
664
+ push(delta) {
665
+ let text = '';
666
+ for (const char of delta)
667
+ text += step(char);
668
+ return text;
669
+ },
670
+ end() {
671
+ const text = state === 'valueStart' ? '' : held;
672
+ held = '';
673
+ return text;
674
+ },
675
+ };
676
+ }
677
+ /**
678
+ * Undo `toOpenAIStrictJsonSchema` on a parsed answer: drop a property whose value is `null` when
679
+ * `schema` (the provider-neutral schema the request carried) marks it optional and its own schema
680
+ * does not admit `null`. A property that admits `null` (`.nullable()`, `.nullish()`) keeps it.
681
+ *
682
+ * Union branches are chosen deterministically: the first branch the answer already satisfies
683
+ * keeps it unchanged; otherwise the first branch it satisfies once that branch's optional nulls
684
+ * are dropped. An answer that already satisfies `schema` comes back unchanged, so providers that
685
+ * honour the optional properties as they are (Anthropic, Bedrock) are unaffected. An answer that
686
+ * fits no branch is left for the Zod output schema to report.
687
+ *
688
+ * An `allOf` is read as the one schema strict mode was sent (merged, with an object's properties
689
+ * moved into the branches of a union it is intersected with), so an intersection's optionals are
690
+ * found whichever member declares them.
691
+ */
692
+ export function restoreOmittedOptionalProperties(value, rawSchema) {
693
+ if (!isRecord(rawSchema))
694
+ return value;
695
+ const schema = structuralSchema(rawSchema);
696
+ let current = value;
697
+ if (Array.isArray(schema.anyOf) &&
698
+ !schema.anyOf.some((branch) => matchesSchema(current, branch))) {
699
+ for (const branch of schema.anyOf) {
700
+ const restored = restoreOmittedOptionalProperties(current, branch);
701
+ if (matchesSchema(restored, branch)) {
702
+ current = restored;
703
+ break;
704
+ }
705
+ }
706
+ }
707
+ if (isRecord(current) && isRecord(schema.properties)) {
708
+ const properties = schema.properties;
709
+ const required = requiredKeys(schema);
710
+ const entries = [];
711
+ for (const [key, child] of Object.entries(current)) {
712
+ const propertySchema = ownSchema(properties, key);
713
+ if (propertySchema === undefined) {
714
+ entries.push([key, child]);
715
+ continue;
716
+ }
717
+ if (child === null && !required.has(key) && !acceptsNull(propertySchema))
718
+ continue;
719
+ entries.push([key, restoreOmittedOptionalProperties(child, propertySchema)]);
720
+ }
721
+ // fromEntries defines own data properties, so a `__proto__` key in the answer stays data.
722
+ return Object.fromEntries(entries);
723
+ }
724
+ if (Array.isArray(current)) {
725
+ const prefixItems = Array.isArray(schema.prefixItems) ? schema.prefixItems : [];
726
+ return current.map((item, index) => restoreOmittedOptionalProperties(item, index < prefixItems.length ? prefixItems[index] : schema.items));
727
+ }
728
+ return current;
729
+ }
730
+ //# sourceMappingURL=openai-strict-schema.js.map