@gajae-code/ai 0.1.1

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 (349) hide show
  1. package/CHANGELOG.md +2644 -0
  2. package/README.md +1181 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth-broker/client.d.ts +66 -0
  5. package/dist/types/auth-broker/index.d.ts +5 -0
  6. package/dist/types/auth-broker/refresher.d.ts +25 -0
  7. package/dist/types/auth-broker/remote-store.d.ts +96 -0
  8. package/dist/types/auth-broker/server.d.ts +32 -0
  9. package/dist/types/auth-broker/types.d.ts +105 -0
  10. package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
  11. package/dist/types/auth-gateway/http.d.ts +39 -0
  12. package/dist/types/auth-gateway/index.d.ts +3 -0
  13. package/dist/types/auth-gateway/server.d.ts +17 -0
  14. package/dist/types/auth-gateway/types.d.ts +115 -0
  15. package/dist/types/auth-storage.d.ts +641 -0
  16. package/dist/types/cli.d.ts +2 -0
  17. package/dist/types/index.d.ts +49 -0
  18. package/dist/types/model-cache.d.ts +17 -0
  19. package/dist/types/model-manager.d.ts +62 -0
  20. package/dist/types/model-thinking.d.ts +71 -0
  21. package/dist/types/models.d.ts +12 -0
  22. package/dist/types/provider-details.d.ts +24 -0
  23. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  24. package/dist/types/provider-models/descriptors.d.ts +48 -0
  25. package/dist/types/provider-models/google.d.ts +20 -0
  26. package/dist/types/provider-models/index.d.ts +5 -0
  27. package/dist/types/provider-models/ollama.d.ts +7 -0
  28. package/dist/types/provider-models/openai-compat.d.ts +237 -0
  29. package/dist/types/provider-models/special.d.ts +16 -0
  30. package/dist/types/providers/amazon-bedrock.d.ts +36 -0
  31. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  32. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  33. package/dist/types/providers/anthropic.d.ts +188 -0
  34. package/dist/types/providers/aws-credentials.d.ts +43 -0
  35. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  36. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  37. package/dist/types/providers/azure-openai-responses.d.ts +15 -0
  38. package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
  39. package/dist/types/providers/cursor.d.ts +42 -0
  40. package/dist/types/providers/error-message.d.ts +27 -0
  41. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  42. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  43. package/dist/types/providers/google-auth.d.ts +24 -0
  44. package/dist/types/providers/google-gemini-cli.d.ts +72 -0
  45. package/dist/types/providers/google-gemini-headers.d.ts +18 -0
  46. package/dist/types/providers/google-shared.d.ts +163 -0
  47. package/dist/types/providers/google-types.d.ts +138 -0
  48. package/dist/types/providers/google-vertex.d.ts +7 -0
  49. package/dist/types/providers/google.d.ts +4 -0
  50. package/dist/types/providers/grammar.d.ts +1 -0
  51. package/dist/types/providers/kimi.d.ts +27 -0
  52. package/dist/types/providers/mock.d.ts +175 -0
  53. package/dist/types/providers/ollama.d.ts +6 -0
  54. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  55. package/dist/types/providers/openai-chat-server-schema.d.ts +814 -0
  56. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  57. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  58. package/dist/types/providers/openai-codex/request-transformer.d.ts +49 -0
  59. package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
  60. package/dist/types/providers/openai-codex-responses.d.ts +67 -0
  61. package/dist/types/providers/openai-completions-compat.d.ts +25 -0
  62. package/dist/types/providers/openai-completions.d.ts +33 -0
  63. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  64. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  65. package/dist/types/providers/openai-responses-shared.d.ts +89 -0
  66. package/dist/types/providers/openai-responses.d.ts +32 -0
  67. package/dist/types/providers/pi-native-client.d.ts +13 -0
  68. package/dist/types/providers/pi-native-server.d.ts +68 -0
  69. package/dist/types/providers/register-builtins.d.ts +31 -0
  70. package/dist/types/providers/synthetic.d.ts +26 -0
  71. package/dist/types/providers/transform-messages.d.ts +12 -0
  72. package/dist/types/providers/vision-guard.d.ts +8 -0
  73. package/dist/types/rate-limit-utils.d.ts +19 -0
  74. package/dist/types/stream.d.ts +24 -0
  75. package/dist/types/types.d.ts +746 -0
  76. package/dist/types/usage/claude.d.ts +3 -0
  77. package/dist/types/usage/gemini.d.ts +2 -0
  78. package/dist/types/usage/github-copilot.d.ts +7 -0
  79. package/dist/types/usage/google-antigravity.d.ts +2 -0
  80. package/dist/types/usage/kimi.d.ts +2 -0
  81. package/dist/types/usage/minimax-code.d.ts +2 -0
  82. package/dist/types/usage/openai-codex.d.ts +3 -0
  83. package/dist/types/usage/shared.d.ts +1 -0
  84. package/dist/types/usage/zai.d.ts +2 -0
  85. package/dist/types/usage.d.ts +258 -0
  86. package/dist/types/utils/abort.d.ts +19 -0
  87. package/dist/types/utils/anthropic-auth.d.ts +31 -0
  88. package/dist/types/utils/discovery/antigravity.d.ts +61 -0
  89. package/dist/types/utils/discovery/codex.d.ts +38 -0
  90. package/dist/types/utils/discovery/cursor.d.ts +23 -0
  91. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  92. package/dist/types/utils/discovery/index.d.ts +4 -0
  93. package/dist/types/utils/discovery/openai-compatible.d.ts +72 -0
  94. package/dist/types/utils/event-stream.d.ts +28 -0
  95. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  96. package/dist/types/utils/foundry.d.ts +1 -0
  97. package/dist/types/utils/h2-fetch.d.ts +22 -0
  98. package/dist/types/utils/http-inspector.d.ts +31 -0
  99. package/dist/types/utils/idle-iterator.d.ts +67 -0
  100. package/dist/types/utils/json-parse.d.ts +10 -0
  101. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
  102. package/dist/types/utils/oauth/anthropic.d.ts +22 -0
  103. package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
  104. package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
  105. package/dist/types/utils/oauth/callback-server.d.ts +57 -0
  106. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  107. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  108. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  109. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  110. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  111. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  112. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  113. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  114. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  115. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  116. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  117. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  118. package/dist/types/utils/oauth/index.d.ts +38 -0
  119. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  120. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  121. package/dist/types/utils/oauth/kimi.d.ts +21 -0
  122. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  123. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  124. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  125. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  126. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  127. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  128. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  129. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  130. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  131. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  132. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  133. package/dist/types/utils/oauth/perplexity.d.ts +9 -0
  134. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  135. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  136. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  137. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  138. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  139. package/dist/types/utils/oauth/together.d.ts +1 -0
  140. package/dist/types/utils/oauth/types.d.ts +44 -0
  141. package/dist/types/utils/oauth/venice.d.ts +18 -0
  142. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  143. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  144. package/dist/types/utils/oauth/xiaomi.d.ts +19 -0
  145. package/dist/types/utils/oauth/zai.d.ts +18 -0
  146. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  147. package/dist/types/utils/overflow.d.ts +54 -0
  148. package/dist/types/utils/parse-bind.d.ts +23 -0
  149. package/dist/types/utils/provider-response.d.ts +3 -0
  150. package/dist/types/utils/retry-after.d.ts +3 -0
  151. package/dist/types/utils/retry.d.ts +26 -0
  152. package/dist/types/utils/schema/adapt.d.ts +24 -0
  153. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  154. package/dist/types/utils/schema/dereference.d.ts +11 -0
  155. package/dist/types/utils/schema/draft.d.ts +10 -0
  156. package/dist/types/utils/schema/equality.d.ts +4 -0
  157. package/dist/types/utils/schema/fields.d.ts +49 -0
  158. package/dist/types/utils/schema/index.d.ts +13 -0
  159. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  160. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  161. package/dist/types/utils/schema/normalize.d.ts +93 -0
  162. package/dist/types/utils/schema/spill.d.ts +8 -0
  163. package/dist/types/utils/schema/stamps.d.ts +25 -0
  164. package/dist/types/utils/schema/types.d.ts +4 -0
  165. package/dist/types/utils/schema/wire.d.ts +54 -0
  166. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  167. package/dist/types/utils/sse-debug.d.ts +10 -0
  168. package/dist/types/utils/tool-call-healing.d.ts +71 -0
  169. package/dist/types/utils/tool-choice.d.ts +50 -0
  170. package/dist/types/utils/validation.d.ts +17 -0
  171. package/dist/types/utils.d.ts +28 -0
  172. package/package.json +146 -0
  173. package/src/api-registry.ts +96 -0
  174. package/src/auth-broker/client.ts +358 -0
  175. package/src/auth-broker/index.ts +5 -0
  176. package/src/auth-broker/refresher.ts +127 -0
  177. package/src/auth-broker/remote-store.ts +623 -0
  178. package/src/auth-broker/server.ts +644 -0
  179. package/src/auth-broker/types.ts +127 -0
  180. package/src/auth-broker/wire-schemas.ts +200 -0
  181. package/src/auth-gateway/http.ts +194 -0
  182. package/src/auth-gateway/index.ts +3 -0
  183. package/src/auth-gateway/server.ts +717 -0
  184. package/src/auth-gateway/types.ts +134 -0
  185. package/src/auth-storage.ts +4104 -0
  186. package/src/cli.ts +262 -0
  187. package/src/index.ts +54 -0
  188. package/src/model-cache.ts +129 -0
  189. package/src/model-manager.ts +450 -0
  190. package/src/model-thinking.ts +691 -0
  191. package/src/models.json +73853 -0
  192. package/src/models.json.d.ts +9 -0
  193. package/src/models.ts +56 -0
  194. package/src/prompts/turn-aborted-guidance.md +4 -0
  195. package/src/provider-details.ts +90 -0
  196. package/src/provider-models/bundled-references.ts +38 -0
  197. package/src/provider-models/descriptors.ts +308 -0
  198. package/src/provider-models/google.ts +91 -0
  199. package/src/provider-models/index.ts +5 -0
  200. package/src/provider-models/ollama.ts +153 -0
  201. package/src/provider-models/openai-compat.ts +2275 -0
  202. package/src/provider-models/special.ts +67 -0
  203. package/src/providers/amazon-bedrock.ts +849 -0
  204. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  205. package/src/providers/anthropic-messages-server.ts +677 -0
  206. package/src/providers/anthropic.ts +2696 -0
  207. package/src/providers/aws-credentials.ts +501 -0
  208. package/src/providers/aws-eventstream.ts +185 -0
  209. package/src/providers/aws-sigv4.ts +218 -0
  210. package/src/providers/azure-openai-responses.ts +337 -0
  211. package/src/providers/cursor/gen/agent_pb.ts +15274 -0
  212. package/src/providers/cursor/proto/agent.proto +3526 -0
  213. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  214. package/src/providers/cursor/proto/buf.yaml +17 -0
  215. package/src/providers/cursor.ts +2561 -0
  216. package/src/providers/error-message.ts +21 -0
  217. package/src/providers/github-copilot-headers.ts +140 -0
  218. package/src/providers/gitlab-duo.ts +372 -0
  219. package/src/providers/google-auth.ts +252 -0
  220. package/src/providers/google-gemini-cli.ts +795 -0
  221. package/src/providers/google-gemini-headers.ts +41 -0
  222. package/src/providers/google-shared.ts +902 -0
  223. package/src/providers/google-types.ts +167 -0
  224. package/src/providers/google-vertex.ts +88 -0
  225. package/src/providers/google.ts +41 -0
  226. package/src/providers/grammar.ts +70 -0
  227. package/src/providers/kimi.ts +52 -0
  228. package/src/providers/mock.ts +500 -0
  229. package/src/providers/ollama.ts +544 -0
  230. package/src/providers/openai-anthropic-shim.ts +138 -0
  231. package/src/providers/openai-chat-server-schema.ts +243 -0
  232. package/src/providers/openai-chat-server.ts +628 -0
  233. package/src/providers/openai-codex/constants.ts +43 -0
  234. package/src/providers/openai-codex/request-transformer.ts +161 -0
  235. package/src/providers/openai-codex/response-handler.ts +81 -0
  236. package/src/providers/openai-codex-responses.ts +2598 -0
  237. package/src/providers/openai-completions-compat.ts +279 -0
  238. package/src/providers/openai-completions.ts +1853 -0
  239. package/src/providers/openai-responses-server-schema.ts +290 -0
  240. package/src/providers/openai-responses-server.ts +1183 -0
  241. package/src/providers/openai-responses-shared.ts +800 -0
  242. package/src/providers/openai-responses.ts +621 -0
  243. package/src/providers/pi-native-client.ts +228 -0
  244. package/src/providers/pi-native-server.ts +210 -0
  245. package/src/providers/register-builtins.ts +412 -0
  246. package/src/providers/synthetic.ts +50 -0
  247. package/src/providers/transform-messages.ts +309 -0
  248. package/src/providers/vision-guard.ts +31 -0
  249. package/src/rate-limit-utils.ts +84 -0
  250. package/src/stream.ts +895 -0
  251. package/src/types.ts +884 -0
  252. package/src/usage/claude.ts +431 -0
  253. package/src/usage/gemini.ts +250 -0
  254. package/src/usage/github-copilot.ts +421 -0
  255. package/src/usage/google-antigravity.ts +201 -0
  256. package/src/usage/kimi.ts +271 -0
  257. package/src/usage/minimax-code.ts +31 -0
  258. package/src/usage/openai-codex.ts +503 -0
  259. package/src/usage/shared.ts +10 -0
  260. package/src/usage/zai.ts +247 -0
  261. package/src/usage.ts +183 -0
  262. package/src/utils/abort.ts +51 -0
  263. package/src/utils/anthropic-auth.ts +87 -0
  264. package/src/utils/discovery/antigravity.ts +261 -0
  265. package/src/utils/discovery/codex.ts +371 -0
  266. package/src/utils/discovery/cursor.ts +306 -0
  267. package/src/utils/discovery/gemini.ts +248 -0
  268. package/src/utils/discovery/index.ts +4 -0
  269. package/src/utils/discovery/openai-compatible.ts +224 -0
  270. package/src/utils/event-stream.ts +142 -0
  271. package/src/utils/fireworks-model-id.ts +30 -0
  272. package/src/utils/foundry.ts +8 -0
  273. package/src/utils/h2-fetch.ts +60 -0
  274. package/src/utils/http-inspector.ts +176 -0
  275. package/src/utils/idle-iterator.ts +250 -0
  276. package/src/utils/json-parse.ts +148 -0
  277. package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
  278. package/src/utils/oauth/anthropic.ts +200 -0
  279. package/src/utils/oauth/api-key-login.ts +87 -0
  280. package/src/utils/oauth/api-key-validation.ts +92 -0
  281. package/src/utils/oauth/callback-server.ts +276 -0
  282. package/src/utils/oauth/cerebras.ts +16 -0
  283. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  284. package/src/utils/oauth/cursor.ts +157 -0
  285. package/src/utils/oauth/deepseek.ts +53 -0
  286. package/src/utils/oauth/firepass.ts +24 -0
  287. package/src/utils/oauth/fireworks.ts +15 -0
  288. package/src/utils/oauth/github-copilot.ts +362 -0
  289. package/src/utils/oauth/gitlab-duo.ts +123 -0
  290. package/src/utils/oauth/google-antigravity.ts +200 -0
  291. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  292. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  293. package/src/utils/oauth/huggingface.ts +62 -0
  294. package/src/utils/oauth/index.ts +444 -0
  295. package/src/utils/oauth/kagi.ts +47 -0
  296. package/src/utils/oauth/kilo.ts +87 -0
  297. package/src/utils/oauth/kimi.ts +254 -0
  298. package/src/utils/oauth/litellm.ts +47 -0
  299. package/src/utils/oauth/lm-studio.ts +38 -0
  300. package/src/utils/oauth/minimax-code.ts +78 -0
  301. package/src/utils/oauth/moonshot.ts +16 -0
  302. package/src/utils/oauth/nanogpt.ts +15 -0
  303. package/src/utils/oauth/nvidia.ts +70 -0
  304. package/src/utils/oauth/oauth.html +199 -0
  305. package/src/utils/oauth/ollama-cloud.ts +28 -0
  306. package/src/utils/oauth/ollama.ts +47 -0
  307. package/src/utils/oauth/openai-codex.ts +299 -0
  308. package/src/utils/oauth/opencode.ts +49 -0
  309. package/src/utils/oauth/parallel.ts +46 -0
  310. package/src/utils/oauth/perplexity.ts +206 -0
  311. package/src/utils/oauth/pkce.ts +18 -0
  312. package/src/utils/oauth/qianfan.ts +58 -0
  313. package/src/utils/oauth/qwen-portal.ts +60 -0
  314. package/src/utils/oauth/synthetic.ts +16 -0
  315. package/src/utils/oauth/tavily.ts +46 -0
  316. package/src/utils/oauth/together.ts +16 -0
  317. package/src/utils/oauth/types.ts +94 -0
  318. package/src/utils/oauth/venice.ts +59 -0
  319. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  320. package/src/utils/oauth/vllm.ts +40 -0
  321. package/src/utils/oauth/xiaomi.ts +137 -0
  322. package/src/utils/oauth/zai.ts +60 -0
  323. package/src/utils/oauth/zenmux.ts +15 -0
  324. package/src/utils/overflow.ts +137 -0
  325. package/src/utils/parse-bind.ts +54 -0
  326. package/src/utils/provider-response.ts +30 -0
  327. package/src/utils/retry-after.ts +110 -0
  328. package/src/utils/retry.ts +54 -0
  329. package/src/utils/schema/CONSTRAINTS.md +164 -0
  330. package/src/utils/schema/adapt.ts +36 -0
  331. package/src/utils/schema/compatibility.ts +435 -0
  332. package/src/utils/schema/dereference.ts +98 -0
  333. package/src/utils/schema/draft.ts +341 -0
  334. package/src/utils/schema/equality.ts +97 -0
  335. package/src/utils/schema/fields.ts +190 -0
  336. package/src/utils/schema/index.ts +13 -0
  337. package/src/utils/schema/json-schema-validator.ts +577 -0
  338. package/src/utils/schema/meta-validator.ts +167 -0
  339. package/src/utils/schema/normalize.ts +1588 -0
  340. package/src/utils/schema/spill.ts +43 -0
  341. package/src/utils/schema/stamps.ts +97 -0
  342. package/src/utils/schema/types.ts +11 -0
  343. package/src/utils/schema/wire.ts +213 -0
  344. package/src/utils/schema/zod-decontaminate.ts +331 -0
  345. package/src/utils/sse-debug.ts +289 -0
  346. package/src/utils/tool-call-healing.ts +271 -0
  347. package/src/utils/tool-choice.ts +99 -0
  348. package/src/utils/validation.ts +1019 -0
  349. package/src/utils.ts +166 -0
@@ -0,0 +1,577 @@
1
+ /**
2
+ * In-tree JSON Schema validator.
3
+ *
4
+ * Used by `validation.ts` for tools authored as plain JSON Schema (no Zod
5
+ * runtime). Covers the keyword set tool authors actually rely on — type,
6
+ * enum, const, combinators, if/then/else, object/array/string/number
7
+ * constraints, $ref, prefixItems/items, contains, propertyNames, pattern &
8
+ * dependent* — but treats `unevaluatedProperties` / `unevaluatedItems` as
9
+ * permissive (with a one-shot warning) since those require evaluation
10
+ * tracking we do not implement.
11
+ *
12
+ * Compared to AJV this is single-pass, synchronous, dependency-free, and
13
+ * tolerates non-standard shapes (`nullable`) that LLM-emitted schemas carry.
14
+ */
15
+ import { logger } from "@gajae-code/utils";
16
+ import { areJsonValuesEqual } from "./equality";
17
+
18
+ export interface JsonSchemaValidationIssue {
19
+ path: PropertyKey[];
20
+ message: string;
21
+ expectedTypes?: string[];
22
+ keyword?: string;
23
+ }
24
+
25
+ export interface JsonSchemaValidationResult {
26
+ success: boolean;
27
+ issues: JsonSchemaValidationIssue[];
28
+ }
29
+
30
+ /**
31
+ * Cycle bookkeeping for recursive `$ref` schemas. We track pairs of (resolved
32
+ * ref, value identity) rather than refs alone: returning `true` for every
33
+ * nested occurrence of a ref previously allowed recursive schemas to silently
34
+ * validate values they should have rejected. For primitive values we fall back
35
+ * to a depth counter capped at MAX_REF_DEPTH so a self-referential schema can
36
+ * still bottom out without infinite recursion.
37
+ */
38
+ interface ValidationContext {
39
+ root: unknown;
40
+ seenPairs: Set<string>;
41
+ objectIds: WeakMap<object, number>;
42
+ nextObjectId: { value: number };
43
+ refDepth: number;
44
+ }
45
+
46
+ const MAX_REF_DEPTH = 64;
47
+
48
+ /** Module-level guard so the unevaluatedItems/unevaluatedProperties warning fires once per process. */
49
+ let seenUnevaluatedWarning = false;
50
+
51
+ function getValueIdentity(ctx: ValidationContext, value: object): number {
52
+ let id = ctx.objectIds.get(value);
53
+ if (id !== undefined) return id;
54
+ id = ctx.nextObjectId.value;
55
+ ctx.nextObjectId.value += 1;
56
+ ctx.objectIds.set(value, id);
57
+ return id;
58
+ }
59
+
60
+ function isJsonObject(value: unknown): value is Record<string, unknown> {
61
+ return typeof value === "object" && value !== null && !Array.isArray(value);
62
+ }
63
+
64
+ function pushIssue(
65
+ issues: JsonSchemaValidationIssue[],
66
+ path: readonly PropertyKey[],
67
+ message: string,
68
+ options: { expectedTypes?: string[]; keyword?: string } = {},
69
+ ): void {
70
+ issues.push({ path: [...path], message, ...options });
71
+ }
72
+
73
+ function typeOfJsonValue(value: unknown): string {
74
+ if (value === null) return "null";
75
+ if (Array.isArray(value)) return "array";
76
+ if (typeof value === "number" && Number.isInteger(value)) return "integer";
77
+ return typeof value;
78
+ }
79
+
80
+ /** Push a validation issue with a copied path so later mutations to `path` do not corrupt earlier issues. */
81
+
82
+ function matchesJsonSchemaType(value: unknown, type: string): boolean {
83
+ switch (type) {
84
+ case "string":
85
+ return typeof value === "string";
86
+ case "number":
87
+ return typeof value === "number" && Number.isFinite(value);
88
+ case "integer":
89
+ return typeof value === "number" && Number.isInteger(value);
90
+ case "boolean":
91
+ return typeof value === "boolean";
92
+ case "object":
93
+ return isJsonObject(value);
94
+ case "array":
95
+ return Array.isArray(value);
96
+ case "null":
97
+ return value === null;
98
+ default:
99
+ return false;
100
+ }
101
+ }
102
+
103
+ /** Decide whether `value` satisfies a single JSON-Schema `type` keyword string. `integer` is a refinement of `number`. */
104
+
105
+ function schemaTypes(schema: Record<string, unknown>): string[] {
106
+ const raw = schema.type;
107
+ const types =
108
+ typeof raw === "string"
109
+ ? [raw]
110
+ : Array.isArray(raw)
111
+ ? raw.filter((entry): entry is string => typeof entry === "string")
112
+ : [];
113
+ if (schema.nullable === true && !types.includes("null")) {
114
+ return [...types, "null"];
115
+ }
116
+ return types;
117
+ }
118
+
119
+ /** Extract the effective `type` list from a schema, treating `nullable: true` as adding `"null"`. */
120
+
121
+ function decodePointerToken(token: string): string {
122
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
123
+ }
124
+
125
+ /** RFC 6901 token decode: `~1` → `/`, `~0` → `~`. */
126
+
127
+ function resolveLocalRef(root: unknown, ref: string): unknown | undefined {
128
+ if (ref === "#") return root;
129
+ if (!ref.startsWith("#/")) return undefined;
130
+ let current: unknown = root;
131
+ for (const rawToken of ref.slice(2).split("/")) {
132
+ const token = decodePointerToken(rawToken);
133
+ if (!isJsonObject(current) && !Array.isArray(current)) return undefined;
134
+ current = (current as Record<string, unknown>)[token];
135
+ }
136
+ return current;
137
+ }
138
+
139
+ /** Resolve a `#/path/to/node` pointer against the root schema. Returns `undefined` for external/unsupported refs. */
140
+
141
+ function isRequiredSet(value: unknown): value is string[] {
142
+ return Array.isArray(value) && value.every(entry => typeof entry === "string");
143
+ }
144
+
145
+ /** Narrow `required: unknown` to `required: string[]` — the spec allows it to be missing but rejects non-string entries. */
146
+
147
+ /**
148
+ * Core validator. Walks a schema node, applies every applicable keyword to
149
+ * `value`, and accumulates issues. Returns `true` only if no keyword
150
+ * rejected; combinators may add issues but still return true (e.g. `anyOf`
151
+ * succeeds if at least one branch matches).
152
+ */
153
+ function validateSchemaNode(
154
+ schema: unknown,
155
+ value: unknown,
156
+ path: readonly PropertyKey[],
157
+ ctx: ValidationContext,
158
+ issues: JsonSchemaValidationIssue[],
159
+ ): boolean {
160
+ if (schema === true) return true;
161
+ if (schema === false) {
162
+ pushIssue(issues, path, "must not match false schema", { keyword: "false" });
163
+ return false;
164
+ }
165
+ if (!isJsonObject(schema)) {
166
+ pushIssue(issues, path, "schema must be an object or boolean", { keyword: "schema" });
167
+ return false;
168
+ }
169
+
170
+ const ref = schema.$ref;
171
+ if (typeof ref === "string") {
172
+ const resolved = resolveLocalRef(ctx.root, ref);
173
+ if (resolved === undefined) {
174
+ pushIssue(issues, path, `unresolved reference ${ref}`, { keyword: "$ref" });
175
+ return false;
176
+ }
177
+ // Cycle detection: for object/array values we key on (ref, value-identity)
178
+ // so the same schema applied to a different value still recurses; only an
179
+ // exact (schema, value) repeat short-circuits as a true cycle. For
180
+ // primitives we fall back to a depth counter so self-referential schemas
181
+ // without a base case still terminate.
182
+ let pairKey: string | undefined;
183
+ if (value !== null && typeof value === "object") {
184
+ pairKey = `${ref}:${getValueIdentity(ctx, value)}`;
185
+ if (ctx.seenPairs.has(pairKey)) return true;
186
+ ctx.seenPairs.add(pairKey);
187
+ } else {
188
+ if (ctx.refDepth >= MAX_REF_DEPTH) {
189
+ pushIssue(issues, path, "reference depth exceeded", { keyword: "$ref" });
190
+ return false;
191
+ }
192
+ ctx.refDepth += 1;
193
+ }
194
+ const ok = validateSchemaNode(resolved, value, path, ctx, issues);
195
+ if (pairKey !== undefined) ctx.seenPairs.delete(pairKey);
196
+ else ctx.refDepth -= 1;
197
+ return ok;
198
+ }
199
+
200
+ if (value === null && schema.nullable === true) return true;
201
+
202
+ let valid = true;
203
+ const types = schemaTypes(schema);
204
+ if (types.length > 0 && !types.some(type => matchesJsonSchemaType(value, type))) {
205
+ pushIssue(issues, path, `expected ${types.join(" or ")}, received ${typeOfJsonValue(value)}`, {
206
+ keyword: "type",
207
+ expectedTypes: types,
208
+ });
209
+ return false;
210
+ }
211
+
212
+ if ("const" in schema && !areJsonValuesEqual(value, schema.const)) {
213
+ pushIssue(issues, path, "must equal const value", { keyword: "const" });
214
+ valid = false;
215
+ }
216
+
217
+ if (Array.isArray(schema.enum) && !schema.enum.some(entry => areJsonValuesEqual(entry, value))) {
218
+ pushIssue(issues, path, "must be one of the allowed enum values", { keyword: "enum" });
219
+ valid = false;
220
+ }
221
+
222
+ for (const keyword of ["anyOf", "oneOf", "allOf"] as const) {
223
+ const branches = schema[keyword];
224
+ if (!Array.isArray(branches)) continue;
225
+ if (keyword === "allOf") {
226
+ for (const branch of branches) {
227
+ valid = validateSchemaNode(branch, value, path, ctx, issues) && valid;
228
+ }
229
+ continue;
230
+ }
231
+
232
+ let matches = 0;
233
+ let firstIssues: JsonSchemaValidationIssue[] | undefined;
234
+ for (const branch of branches) {
235
+ const branchIssues: JsonSchemaValidationIssue[] = [];
236
+ if (validateSchemaNode(branch, value, path, ctx, branchIssues)) {
237
+ matches += 1;
238
+ } else if (!firstIssues) {
239
+ firstIssues = branchIssues;
240
+ }
241
+ }
242
+ const branchValid = keyword === "anyOf" ? matches > 0 : matches === 1;
243
+ if (!branchValid) {
244
+ if (matches === 0 && firstIssues && firstIssues.length > 0) {
245
+ issues.push(...firstIssues);
246
+ } else {
247
+ pushIssue(
248
+ issues,
249
+ path,
250
+ keyword === "anyOf" ? "must match at least one schema" : "must match exactly one schema",
251
+ {
252
+ keyword,
253
+ },
254
+ );
255
+ }
256
+ valid = false;
257
+ }
258
+ }
259
+
260
+ if ("not" in schema) {
261
+ const notIssues: JsonSchemaValidationIssue[] = [];
262
+ if (validateSchemaNode(schema.not, value, path, ctx, notIssues)) {
263
+ pushIssue(issues, path, "must not match excluded schema", { keyword: "not" });
264
+ valid = false;
265
+ }
266
+ }
267
+
268
+ // if/then/else: validate the if-branch silently; based on its outcome,
269
+ // validate against then/else. Each sub-schema is treated as a schema node
270
+ // (no requirement that branches be objects). This is a minimal correct
271
+ // semantic — schemas where the if-branch references properties only present
272
+ // after applying then will still resolve consistently for the LLM-emitted
273
+ // shapes we encounter.
274
+ if ("if" in schema) {
275
+ const ifIssues: JsonSchemaValidationIssue[] = [];
276
+ const ifOk = validateSchemaNode(schema.if, value, path, ctx, ifIssues);
277
+ const branch = ifOk ? schema.then : schema.else;
278
+ if (branch !== undefined) {
279
+ valid = validateSchemaNode(branch, value, path, ctx, issues) && valid;
280
+ }
281
+ }
282
+
283
+ // `unevaluatedProperties` / `unevaluatedItems` require tracking which
284
+ // keys/indices were "evaluated" by sibling keywords across composed
285
+ // schemas — expensive bookkeeping we do not implement. Warn once so tool
286
+ // authors who rely on them know the keyword is silently permissive in
287
+ // this validator.
288
+ if (("unevaluatedProperties" in schema || "unevaluatedItems" in schema) && !seenUnevaluatedWarning) {
289
+ seenUnevaluatedWarning = true;
290
+ logger.warn(
291
+ "JSON Schema unevaluatedProperties/unevaluatedItems are not enforced by the in-tree validator; treating as permissive",
292
+ );
293
+ }
294
+
295
+ if (isJsonObject(value)) {
296
+ valid = validateObjectKeywords(schema, value, path, ctx, issues) && valid;
297
+ }
298
+ if (Array.isArray(value)) {
299
+ valid = validateArrayKeywords(schema, value, path, ctx, issues) && valid;
300
+ }
301
+ if (typeof value === "string") {
302
+ valid = validateStringKeywords(schema, value, path, issues) && valid;
303
+ }
304
+ if (typeof value === "number" && Number.isFinite(value)) {
305
+ valid = validateNumberKeywords(schema, value, path, issues) && valid;
306
+ }
307
+
308
+ return valid;
309
+ }
310
+
311
+ /** Apply object-shaped JSON-Schema keywords: `required`, `properties`, `propertyNames`, `patternProperties`, `dependentRequired`, `dependentSchemas`, `additionalProperties`, and the `min/maxProperties` counts. */
312
+ function validateObjectKeywords(
313
+ schema: Record<string, unknown>,
314
+ value: Record<string, unknown>,
315
+ path: readonly PropertyKey[],
316
+ ctx: ValidationContext,
317
+ issues: JsonSchemaValidationIssue[],
318
+ ): boolean {
319
+ let valid = true;
320
+ const properties = isJsonObject(schema.properties) ? schema.properties : {};
321
+ if (isRequiredSet(schema.required)) {
322
+ for (const key of schema.required) {
323
+ if (!(key in value)) {
324
+ pushIssue(issues, [...path, key], "is required", { keyword: "required" });
325
+ valid = false;
326
+ }
327
+ }
328
+ }
329
+
330
+ for (const key in properties) {
331
+ if (!(key in value)) continue;
332
+ valid = validateSchemaNode(properties[key], value[key], [...path, key], ctx, issues) && valid;
333
+ }
334
+
335
+ if (schema.propertyNames !== undefined) {
336
+ for (const key of Object.keys(value)) {
337
+ valid = validateSchemaNode(schema.propertyNames, key, [...path, key], ctx, issues) && valid;
338
+ }
339
+ }
340
+
341
+ const known = new Set(Object.keys(properties));
342
+ if (isJsonObject(schema.patternProperties)) {
343
+ const patternProperties = schema.patternProperties;
344
+ for (const pattern in patternProperties) {
345
+ const patternSchema = patternProperties[pattern];
346
+ let re: RegExp;
347
+ try {
348
+ re = new RegExp(pattern);
349
+ } catch {
350
+ pushIssue(issues, path, `invalid patternProperties regex ${pattern}`, { keyword: "patternProperties" });
351
+ valid = false;
352
+ continue;
353
+ }
354
+ for (const key in value) {
355
+ if (!re.test(key)) continue;
356
+ known.add(key);
357
+ valid = validateSchemaNode(patternSchema, value[key], [...path, key], ctx, issues) && valid;
358
+ }
359
+ }
360
+ }
361
+
362
+ if (isJsonObject(schema.dependentRequired)) {
363
+ const dependentRequired = schema.dependentRequired;
364
+ for (const key in dependentRequired) {
365
+ const deps = dependentRequired[key];
366
+ if (!(key in value)) continue;
367
+ if (!Array.isArray(deps)) continue;
368
+ for (const dep of deps) {
369
+ if (typeof dep !== "string") continue;
370
+ if (!(dep in value)) {
371
+ pushIssue(issues, [...path, dep], `is required when "${key}" is present`, {
372
+ keyword: "dependentRequired",
373
+ });
374
+ valid = false;
375
+ }
376
+ }
377
+ }
378
+ }
379
+
380
+ if (isJsonObject(schema.dependentSchemas)) {
381
+ const dependentSchemas = schema.dependentSchemas;
382
+ for (const key in dependentSchemas) {
383
+ if (!(key in value)) continue;
384
+ valid = validateSchemaNode(dependentSchemas[key], value, path, ctx, issues) && valid;
385
+ }
386
+ }
387
+
388
+ // `known` includes property names and any keys matched by patternProperties
389
+ // above, so additionalProperties only governs the genuine leftovers.
390
+ const additional = schema.additionalProperties;
391
+ if (additional === false) {
392
+ for (const key of Object.keys(value)) {
393
+ if (known.has(key)) continue;
394
+ pushIssue(issues, [...path, key], "must not be present", { keyword: "additionalProperties" });
395
+ valid = false;
396
+ }
397
+ } else if (additional !== undefined && additional !== true) {
398
+ for (const key in value) {
399
+ if (known.has(key)) continue;
400
+ valid = validateSchemaNode(additional, value[key], [...path, key], ctx, issues) && valid;
401
+ }
402
+ }
403
+
404
+ if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
405
+ pushIssue(issues, path, `must have at least ${schema.minProperties} properties`, { keyword: "minProperties" });
406
+ valid = false;
407
+ }
408
+ if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
409
+ pushIssue(issues, path, `must have at most ${schema.maxProperties} properties`, { keyword: "maxProperties" });
410
+ valid = false;
411
+ }
412
+
413
+ return valid;
414
+ }
415
+
416
+ /** Apply array-shaped keywords: `min/maxItems`, `uniqueItems`, `prefixItems` + `items` tuple validation, and `contains` with `min/maxContains`. */
417
+ function validateArrayKeywords(
418
+ schema: Record<string, unknown>,
419
+ value: unknown[],
420
+ path: readonly PropertyKey[],
421
+ ctx: ValidationContext,
422
+ issues: JsonSchemaValidationIssue[],
423
+ ): boolean {
424
+ let valid = true;
425
+ if (typeof schema.minItems === "number" && value.length < schema.minItems) {
426
+ pushIssue(issues, path, `must have at least ${schema.minItems} items`, { keyword: "minItems" });
427
+ valid = false;
428
+ }
429
+ if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
430
+ pushIssue(issues, path, `must have at most ${schema.maxItems} items`, { keyword: "maxItems" });
431
+ valid = false;
432
+ }
433
+ if (schema.uniqueItems === true) {
434
+ for (let i = 0; i < value.length; i += 1) {
435
+ for (let j = i + 1; j < value.length; j += 1) {
436
+ if (!areJsonValuesEqual(value[i], value[j])) continue;
437
+ pushIssue(issues, [...path, j], "must be unique", { keyword: "uniqueItems" });
438
+ valid = false;
439
+ }
440
+ }
441
+ }
442
+
443
+ // Tuple validation uses JSON Schema 2020-12 `prefixItems` for per-index
444
+ // schemas. When present, `items` is the schema for every remaining element.
445
+ const prefixItems = Array.isArray(schema.prefixItems) ? schema.prefixItems : undefined;
446
+ const items = schema.items;
447
+ if (Array.isArray(items)) {
448
+ pushIssue(issues, path, "array-valued items is not valid in JSON Schema 2020-12; use prefixItems", {
449
+ keyword: "items",
450
+ });
451
+ valid = false;
452
+ } else if (prefixItems) {
453
+ const limit = Math.min(prefixItems.length, value.length);
454
+ for (let i = 0; i < limit; i += 1) {
455
+ valid = validateSchemaNode(prefixItems[i], value[i], [...path, i], ctx, issues) && valid;
456
+ }
457
+ if (items !== undefined) {
458
+ for (let i = prefixItems.length; i < value.length; i += 1) {
459
+ valid = validateSchemaNode(items, value[i], [...path, i], ctx, issues) && valid;
460
+ }
461
+ }
462
+ } else if (items !== undefined) {
463
+ for (let i = 0; i < value.length; i += 1) {
464
+ valid = validateSchemaNode(items, value[i], [...path, i], ctx, issues) && valid;
465
+ }
466
+ }
467
+
468
+ if (schema.contains !== undefined) {
469
+ const minContains = typeof schema.minContains === "number" ? schema.minContains : 1;
470
+ const maxContains = typeof schema.maxContains === "number" ? schema.maxContains : Infinity;
471
+ let count = 0;
472
+ for (let i = 0; i < value.length; i += 1) {
473
+ const containsIssues: JsonSchemaValidationIssue[] = [];
474
+ if (validateSchemaNode(schema.contains, value[i], [...path, i], ctx, containsIssues)) {
475
+ count += 1;
476
+ }
477
+ }
478
+ if (count < minContains) {
479
+ pushIssue(issues, path, `must contain at least ${minContains} matching item(s)`, { keyword: "contains" });
480
+ valid = false;
481
+ }
482
+ if (count > maxContains) {
483
+ pushIssue(issues, path, `must contain at most ${maxContains} matching item(s)`, { keyword: "maxContains" });
484
+ valid = false;
485
+ }
486
+ }
487
+
488
+ return valid;
489
+ }
490
+
491
+ /** Apply string-shaped keywords: `min/maxLength`, `pattern`. Invalid regexes flag the schema itself rather than the value. */
492
+ function validateStringKeywords(
493
+ schema: Record<string, unknown>,
494
+ value: string,
495
+ path: readonly PropertyKey[],
496
+ issues: JsonSchemaValidationIssue[],
497
+ ): boolean {
498
+ let valid = true;
499
+ if (typeof schema.minLength === "number" && value.length < schema.minLength) {
500
+ pushIssue(issues, path, `must be at least ${schema.minLength} characters`, { keyword: "minLength" });
501
+ valid = false;
502
+ }
503
+ if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
504
+ pushIssue(issues, path, `must be at most ${schema.maxLength} characters`, { keyword: "maxLength" });
505
+ valid = false;
506
+ }
507
+ if (typeof schema.pattern === "string") {
508
+ try {
509
+ if (!new RegExp(schema.pattern).test(value)) {
510
+ pushIssue(issues, path, "must match pattern", { keyword: "pattern" });
511
+ valid = false;
512
+ }
513
+ } catch {
514
+ pushIssue(issues, path, "schema pattern is invalid", { keyword: "pattern" });
515
+ valid = false;
516
+ }
517
+ }
518
+ return valid;
519
+ }
520
+
521
+ /** Apply number-shaped keywords: `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum` (both numeric draft 2020-12 and boolean draft-07 forms), and `multipleOf`. */
522
+ function validateNumberKeywords(
523
+ schema: Record<string, unknown>,
524
+ value: number,
525
+ path: readonly PropertyKey[],
526
+ issues: JsonSchemaValidationIssue[],
527
+ ): boolean {
528
+ let valid = true;
529
+ if (typeof schema.minimum === "number" && value < schema.minimum) {
530
+ pushIssue(issues, path, `must be >= ${schema.minimum}`, { keyword: "minimum" });
531
+ valid = false;
532
+ }
533
+ if (typeof schema.maximum === "number" && value > schema.maximum) {
534
+ pushIssue(issues, path, `must be <= ${schema.maximum}`, { keyword: "maximum" });
535
+ valid = false;
536
+ }
537
+ if (typeof schema.exclusiveMinimum === "number" && value <= schema.exclusiveMinimum) {
538
+ pushIssue(issues, path, `must be > ${schema.exclusiveMinimum}`, { keyword: "exclusiveMinimum" });
539
+ valid = false;
540
+ }
541
+ if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum) {
542
+ pushIssue(issues, path, `must be < ${schema.exclusiveMaximum}`, { keyword: "exclusiveMaximum" });
543
+ valid = false;
544
+ }
545
+ if (schema.exclusiveMinimum === true && typeof schema.minimum === "number" && value <= schema.minimum) {
546
+ pushIssue(issues, path, `must be > ${schema.minimum}`, { keyword: "exclusiveMinimum" });
547
+ valid = false;
548
+ }
549
+ if (schema.exclusiveMaximum === true && typeof schema.maximum === "number" && value >= schema.maximum) {
550
+ pushIssue(issues, path, `must be < ${schema.maximum}`, { keyword: "exclusiveMaximum" });
551
+ valid = false;
552
+ }
553
+ if (typeof schema.multipleOf === "number" && schema.multipleOf > 0) {
554
+ const quotient = value / schema.multipleOf;
555
+ if (Math.abs(quotient - Math.round(quotient)) > Number.EPSILON * 10) {
556
+ pushIssue(issues, path, `must be a multiple of ${schema.multipleOf}`, { keyword: "multipleOf" });
557
+ valid = false;
558
+ }
559
+ }
560
+ return valid;
561
+ }
562
+
563
+ export function validateJsonSchemaValue(schema: unknown, value: unknown): JsonSchemaValidationResult {
564
+ const issues: JsonSchemaValidationIssue[] = [];
565
+ const success = validateSchemaNode(
566
+ schema,
567
+ value,
568
+ [],
569
+ { root: schema, seenPairs: new Set(), objectIds: new WeakMap(), nextObjectId: { value: 0 }, refDepth: 0 },
570
+ issues,
571
+ );
572
+ return { success, issues };
573
+ }
574
+
575
+ export function isJsonSchemaValueValid(schema: unknown, value: unknown): boolean {
576
+ return validateJsonSchemaValue(schema, value).success;
577
+ }