@oh-my-pi/pi-coding-agent 16.0.5 → 16.0.6

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 (223) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/cli.js +1927 -1376
  3. package/dist/types/advisor/advise-tool.d.ts +22 -19
  4. package/dist/types/autoresearch/tools/init-experiment.d.ts +13 -17
  5. package/dist/types/autoresearch/tools/log-experiment.d.ts +17 -19
  6. package/dist/types/autoresearch/tools/run-experiment.d.ts +3 -4
  7. package/dist/types/autoresearch/tools/update-notes.d.ts +4 -5
  8. package/dist/types/cli/ttsr-cli.d.ts +39 -0
  9. package/dist/types/commands/ttsr.d.ts +57 -0
  10. package/dist/types/commit/agentic/tools/analyze-file.d.ts +4 -5
  11. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +4 -5
  12. package/dist/types/commit/agentic/tools/git-hunk.d.ts +5 -6
  13. package/dist/types/commit/agentic/tools/git-overview.d.ts +4 -5
  14. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +23 -24
  15. package/dist/types/commit/agentic/tools/propose-commit.d.ts +11 -32
  16. package/dist/types/commit/agentic/tools/recent-commits.d.ts +3 -4
  17. package/dist/types/commit/agentic/tools/schemas.d.ts +6 -27
  18. package/dist/types/commit/agentic/tools/split-commit.d.ts +28 -49
  19. package/dist/types/commit/changelog/generate.d.ts +12 -13
  20. package/dist/types/commit/shared-llm.d.ts +10 -37
  21. package/dist/types/config/config-file.d.ts +4 -4
  22. package/dist/types/config/keybindings.d.ts +5 -0
  23. package/dist/types/config/models-config-schema.d.ts +625 -990
  24. package/dist/types/config/models-config.d.ts +229 -217
  25. package/dist/types/config/settings-schema.d.ts +53 -23
  26. package/dist/types/edit/hashline/params.d.ts +7 -11
  27. package/dist/types/edit/index.d.ts +2 -1
  28. package/dist/types/edit/modes/apply-patch.d.ts +4 -5
  29. package/dist/types/edit/modes/patch.d.ts +15 -24
  30. package/dist/types/edit/modes/replace.d.ts +16 -17
  31. package/dist/types/eval/js/index.d.ts +1 -0
  32. package/dist/types/extensibility/custom-commands/types.d.ts +6 -3
  33. package/dist/types/extensibility/custom-tools/types.d.ts +8 -5
  34. package/dist/types/extensibility/extensions/types.d.ts +6 -3
  35. package/dist/types/extensibility/hooks/types.d.ts +7 -4
  36. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +13 -5
  37. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +17 -0
  38. package/dist/types/extensibility/typebox.d.ts +80 -58
  39. package/dist/types/goals/tools/goal-tool.d.ts +11 -24
  40. package/dist/types/index.d.ts +2 -0
  41. package/dist/types/lsp/index.d.ts +11 -26
  42. package/dist/types/lsp/types.d.ts +12 -28
  43. package/dist/types/mcp/client.d.ts +8 -0
  44. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  45. package/dist/types/modes/components/custom-editor.d.ts +3 -1
  46. package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
  47. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  48. package/dist/types/modes/interactive-mode.d.ts +3 -0
  49. package/dist/types/modes/setup-wizard/index.d.ts +1 -0
  50. package/dist/types/modes/setup-wizard/startup-splash.d.ts +7 -0
  51. package/dist/types/modes/theme/theme.d.ts +1 -1
  52. package/dist/types/modes/types.d.ts +3 -0
  53. package/dist/types/sdk.d.ts +5 -0
  54. package/dist/types/session/agent-session.d.ts +4 -0
  55. package/dist/types/startup-splash.d.ts +12 -0
  56. package/dist/types/task/types.d.ts +47 -48
  57. package/dist/types/tools/ask.d.ts +26 -27
  58. package/dist/types/tools/ast-edit.d.ts +17 -17
  59. package/dist/types/tools/ast-grep.d.ts +12 -13
  60. package/dist/types/tools/bash.d.ts +20 -17
  61. package/dist/types/tools/browser.d.ts +46 -71
  62. package/dist/types/tools/checkpoint.d.ts +14 -15
  63. package/dist/types/tools/debug.d.ts +82 -145
  64. package/dist/types/tools/eval.d.ts +30 -40
  65. package/dist/types/tools/find.d.ts +17 -18
  66. package/dist/types/tools/gh.d.ts +49 -78
  67. package/dist/types/tools/image-gen.d.ts +20 -36
  68. package/dist/types/tools/inspect-image.d.ts +10 -11
  69. package/dist/types/tools/irc.d.ts +22 -33
  70. package/dist/types/tools/job.d.ts +11 -12
  71. package/dist/types/tools/learn.d.ts +21 -28
  72. package/dist/types/tools/manage-skill.d.ts +13 -22
  73. package/dist/types/tools/memory-edit.d.ts +15 -24
  74. package/dist/types/tools/memory-recall.d.ts +7 -8
  75. package/dist/types/tools/memory-reflect.d.ts +9 -10
  76. package/dist/types/tools/memory-retain.d.ts +13 -14
  77. package/dist/types/tools/read.d.ts +7 -8
  78. package/dist/types/tools/resolve.d.ts +11 -18
  79. package/dist/types/tools/review.d.ts +9 -15
  80. package/dist/types/tools/search-tool-bm25.d.ts +9 -10
  81. package/dist/types/tools/search.d.ts +16 -17
  82. package/dist/types/tools/ssh.d.ts +14 -15
  83. package/dist/types/tools/todo.d.ts +27 -43
  84. package/dist/types/tools/tts.d.ts +8 -9
  85. package/dist/types/tools/write.d.ts +9 -10
  86. package/dist/types/tui/index.d.ts +1 -0
  87. package/dist/types/tui/width-aware-text.d.ts +23 -0
  88. package/dist/types/utils/markit.d.ts +10 -1
  89. package/dist/types/web/search/index.d.ts +17 -28
  90. package/dist/types/web/search/providers/perplexity.d.ts +0 -2
  91. package/dist/types/web/search/types.d.ts +32 -26
  92. package/package.json +14 -13
  93. package/scripts/omp +1 -1
  94. package/src/advisor/__tests__/advisor.test.ts +44 -1
  95. package/src/advisor/advise-tool.ts +34 -11
  96. package/src/autoresearch/tools/init-experiment.ts +13 -16
  97. package/src/autoresearch/tools/log-experiment.ts +15 -18
  98. package/src/autoresearch/tools/run-experiment.ts +3 -3
  99. package/src/autoresearch/tools/update-notes.ts +4 -4
  100. package/src/cli/ttsr-cli.ts +995 -0
  101. package/src/cli-commands.ts +1 -0
  102. package/src/cli.ts +7 -1
  103. package/src/commands/ttsr.ts +125 -0
  104. package/src/commit/agentic/tools/analyze-file.ts +4 -4
  105. package/src/commit/agentic/tools/git-file-diff.ts +4 -4
  106. package/src/commit/agentic/tools/git-hunk.ts +7 -5
  107. package/src/commit/agentic/tools/git-overview.ts +4 -4
  108. package/src/commit/agentic/tools/propose-changelog.ts +18 -15
  109. package/src/commit/agentic/tools/propose-commit.ts +6 -6
  110. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  111. package/src/commit/agentic/tools/schemas.ts +8 -20
  112. package/src/commit/agentic/tools/split-commit.ts +19 -23
  113. package/src/commit/analysis/summary.ts +7 -5
  114. package/src/commit/changelog/generate.ts +15 -11
  115. package/src/commit/shared-llm.ts +17 -24
  116. package/src/config/config-file.ts +13 -15
  117. package/src/config/keybindings.ts +6 -0
  118. package/src/config/models-config-schema.ts +206 -179
  119. package/src/config/settings-schema.ts +34 -0
  120. package/src/discovery/builtin-rules/index.ts +2 -0
  121. package/src/discovery/builtin-rules/ts-import-type.md +2 -2
  122. package/src/discovery/builtin-rules/ts-no-any.md +11 -2
  123. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
  124. package/src/edit/hashline/params.ts +12 -11
  125. package/src/edit/index.ts +5 -4
  126. package/src/edit/modes/apply-patch.ts +4 -4
  127. package/src/edit/modes/patch.ts +15 -18
  128. package/src/edit/modes/replace.ts +13 -17
  129. package/src/edit/renderer.ts +0 -1
  130. package/src/eval/agent-bridge.ts +11 -13
  131. package/src/eval/completion-bridge.ts +25 -17
  132. package/src/eval/js/context-manager.ts +17 -2
  133. package/src/eval/js/index.ts +1 -1
  134. package/src/eval/py/executor.ts +2 -2
  135. package/src/extensibility/custom-commands/loader.ts +5 -3
  136. package/src/extensibility/custom-commands/types.ts +6 -3
  137. package/src/extensibility/custom-tools/loader.ts +4 -2
  138. package/src/extensibility/custom-tools/types.ts +8 -5
  139. package/src/extensibility/extensions/loader.ts +4 -2
  140. package/src/extensibility/extensions/types.ts +6 -3
  141. package/src/extensibility/hooks/loader.ts +5 -2
  142. package/src/extensibility/hooks/types.ts +7 -4
  143. package/src/extensibility/legacy-pi-ai-shim.ts +42 -5
  144. package/src/extensibility/legacy-pi-coding-agent-shim.ts +113 -0
  145. package/src/extensibility/plugins/legacy-pi-compat.ts +13 -13
  146. package/src/extensibility/tool-proxy.ts +4 -1
  147. package/src/extensibility/typebox.ts +778 -251
  148. package/src/goals/guided-setup.ts +12 -3
  149. package/src/goals/tools/goal-tool.ts +6 -6
  150. package/src/index.ts +2 -0
  151. package/src/internal-urls/docs-index.generated.ts +11 -9
  152. package/src/lsp/types.ts +13 -27
  153. package/src/main.ts +19 -18
  154. package/src/mcp/client.ts +38 -13
  155. package/src/mcp/render.ts +102 -89
  156. package/src/modes/components/agent-hub.ts +11 -4
  157. package/src/modes/components/btw-panel.ts +5 -1
  158. package/src/modes/components/custom-editor.ts +18 -0
  159. package/src/modes/components/status-line/component.ts +8 -1
  160. package/src/modes/components/tool-execution.ts +17 -10
  161. package/src/modes/controllers/btw-controller.ts +69 -1
  162. package/src/modes/controllers/input-controller.ts +29 -0
  163. package/src/modes/interactive-mode.ts +38 -8
  164. package/src/modes/setup-wizard/index.ts +1 -0
  165. package/src/modes/setup-wizard/scenes/sign-in.ts +77 -5
  166. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  167. package/src/modes/theme/theme.ts +133 -143
  168. package/src/modes/types.ts +3 -0
  169. package/src/modes/utils/context-usage.ts +9 -5
  170. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  171. package/src/prompts/system/system-prompt.md +1 -0
  172. package/src/sdk.ts +21 -4
  173. package/src/session/agent-session.ts +160 -33
  174. package/src/session/session-history-format.ts +11 -2
  175. package/src/session/snapcompact-inline.ts +1 -1
  176. package/src/slash-commands/builtin-registry.ts +4 -11
  177. package/src/startup-splash.ts +19 -0
  178. package/src/task/executor.ts +11 -6
  179. package/src/task/types.ts +44 -41
  180. package/src/tool-discovery/tool-index.ts +17 -4
  181. package/src/tools/ask.ts +14 -14
  182. package/src/tools/ast-edit.ts +17 -14
  183. package/src/tools/ast-grep.ts +10 -9
  184. package/src/tools/bash.ts +15 -10
  185. package/src/tools/browser/launch.ts +13 -0
  186. package/src/tools/browser.ts +26 -32
  187. package/src/tools/checkpoint.ts +7 -7
  188. package/src/tools/debug.ts +72 -69
  189. package/src/tools/eval.ts +18 -19
  190. package/src/tools/find.ts +20 -13
  191. package/src/tools/gh.ts +29 -49
  192. package/src/tools/image-gen.ts +27 -32
  193. package/src/tools/inspect-image.ts +8 -9
  194. package/src/tools/irc.ts +12 -12
  195. package/src/tools/job.ts +6 -6
  196. package/src/tools/learn.ts +11 -14
  197. package/src/tools/manage-skill.ts +19 -23
  198. package/src/tools/memory-edit.ts +8 -8
  199. package/src/tools/memory-recall.ts +4 -4
  200. package/src/tools/memory-reflect.ts +5 -5
  201. package/src/tools/memory-retain.ts +9 -11
  202. package/src/tools/puppeteer/02_stealth_hairline.txt +1 -1
  203. package/src/tools/puppeteer/04_stealth_iframe.txt +4 -4
  204. package/src/tools/puppeteer/05_stealth_webgl.txt +1 -1
  205. package/src/tools/puppeteer/10_stealth_plugins.txt +6 -4
  206. package/src/tools/puppeteer/12_stealth_codecs.txt +2 -2
  207. package/src/tools/puppeteer/13_stealth_worker.txt +1 -1
  208. package/src/tools/read.ts +169 -13
  209. package/src/tools/report-tool-issue.ts +6 -6
  210. package/src/tools/resolve.ts +6 -6
  211. package/src/tools/review.ts +10 -12
  212. package/src/tools/search-tool-bm25.ts +5 -5
  213. package/src/tools/search.ts +20 -29
  214. package/src/tools/ssh.ts +8 -8
  215. package/src/tools/todo.ts +16 -19
  216. package/src/tools/tts.ts +16 -15
  217. package/src/tools/write.ts +5 -5
  218. package/src/tui/index.ts +1 -0
  219. package/src/tui/width-aware-text.ts +58 -0
  220. package/src/utils/markit.ts +17 -2
  221. package/src/web/search/index.ts +9 -9
  222. package/src/web/search/providers/perplexity.ts +373 -126
  223. package/src/web/search/types.ts +28 -48
@@ -1,61 +1,176 @@
1
1
  /**
2
- * Minimal `@sinclair/typebox` runtime compatibility shim, backed by Zod.
2
+ * Minimal `@sinclair/typebox` runtime compatibility shim.
3
3
  *
4
4
  * Historically the coding agent injected the real `@sinclair/typebox` (~5MB
5
5
  * dependency) into extensions, hooks, custom tools, and custom commands so
6
6
  * they could author parameter schemas as `Type.Object({ name: Type.String() })`.
7
- * Internally everything already runs through Zod (`wire.ts`, `validation.ts`);
8
- * the only reason TypeBox remained was extension-author compat.
9
7
  *
10
- * This module replaces that injection with a tiny façade whose `Type` builders
11
- * return Zod schemas. Output is indistinguishable from hand-written Zod inside
12
- * the agent pipeline:
8
+ * This module provides the subset those integrations depend on:
13
9
  *
14
- * - `isZodSchema()` keys off the Zod `_zod` marker that every schema carries.
15
- * - `zodToWireSchema()` emits the same draft 2020-12 JSON Schema providers expect
16
- * from TypeBox-authored tools (defaulted fields treated as optional, etc.).
10
+ * - TypeBox-style `Type.*` builders.
11
+ * - Runtime validation through `schema.safeParse(input)` and `schema.__validator(input)`.
12
+ * - Enumerable JSON Schema keywords so `{ ...schema }`, `JSON.stringify(schema)`,
13
+ * and `toolWireSchema({ parameters: schema })` all see the same schema.
17
14
  *
18
- * The surface intentionally covers only the common TypeBox builders. Plugins
19
- * that reached for niche TypeBox-only APIs (`TypeCompiler`, the global
20
- * `TypeRegistry`, custom `Symbol(TypeBox.Kind)` introspection) must vendor
21
- * `@sinclair/typebox` directly in their own package.
15
+ * Internal validator fields and methods are intentionally non-enumerable. The
16
+ * object should look like JSON Schema at every serialization/wire boundary and
17
+ * like a small validator at runtime.
22
18
  */
23
19
 
24
- import { areJsonValuesEqual, zodToWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
25
- import {
26
- type ZodArray,
27
- type ZodEnum,
28
- type ZodObject,
29
- type ZodOptional,
30
- type ZodRawShape,
31
- type ZodType,
32
- z,
33
- } from "zod/v4";
20
+ import { areJsonValuesEqual } from "@oh-my-pi/pi-ai/utils/schema";
34
21
 
35
22
  // ---------------------------------------------------------------------------
36
23
  // Type aliases — exported so `import type { Static, TSchema } from "..."`
37
24
  // patterns keep compiling at the call site.
38
25
  // ---------------------------------------------------------------------------
39
26
 
40
- export type TSchema = ZodType;
41
- export type Static<T extends ZodType> = z.infer<T>;
42
- export type TAny = ZodType;
43
- export type TUnknown = ZodType;
44
- export type TNever = ZodType;
45
- export type TNull = ZodType;
46
- export type TString = z.ZodString;
47
- export type TNumber = z.ZodNumber;
48
- export type TInteger = z.ZodNumber;
49
- export type TBoolean = z.ZodBoolean;
50
- export type TLiteral<V extends string | number | boolean> = z.ZodLiteral<V>;
51
- export type TArray<E extends ZodType> = ZodArray<E>;
52
- export type TObject<P extends ZodRawShape = ZodRawShape> = ZodObject<P>;
53
- export type TOptional<E extends ZodType> = ZodOptional<E>;
54
- export type TUnion<_T extends readonly ZodType[] = readonly ZodType[]> = ZodType;
55
- export type TEnum<T extends readonly (string | number)[] = readonly (string | number)[]> = ZodEnum<{
56
- [K in T[number] as `${K}`]: K;
57
- }>;
58
- export type TRecord<_K extends ZodType, _V extends ZodType> = ZodType;
27
+ export type TSchema = ArkSchema;
28
+ export type Static<T extends ArkSchema> = T["__infer"];
29
+ export type TAny = ArkSchema;
30
+ export type TUnknown = ArkSchema;
31
+ export type TNever = ArkSchema;
32
+ export type TNull = ArkSchema;
33
+ export type TString = ArkSchema;
34
+ export type TNumber = ArkSchema;
35
+ export type TInteger = ArkSchema;
36
+ export type TBoolean = ArkSchema;
37
+ export type TLiteral<_V extends string | number | boolean> = ArkSchema;
38
+ export type TArray<_E extends ArkSchema> = ArkSchema;
39
+ export type TObject<_P extends Record<string, ArkSchema> = Record<string, ArkSchema>> = ArkSchema;
40
+ export type TOptional<_E extends ArkSchema> = ArkSchema;
41
+ export type TUnion<_T extends readonly ArkSchema[] = readonly ArkSchema[]> = ArkSchema;
42
+ export type TEnum<_T extends readonly (string | number)[] = readonly (string | number)[]> = ArkSchema;
43
+ export type TRecord<_K extends ArkSchema, _V extends ArkSchema> = ArkSchema;
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // ArkSchema wrapper — JSON Schema object with hidden validator metadata
47
+ // ---------------------------------------------------------------------------
48
+
49
+ const VALIDATION_FAILURE = Symbol("pi.typebox.validationFailure");
50
+
51
+ interface ValidationFailure {
52
+ message: string;
53
+ readonly [VALIDATION_FAILURE]: true;
54
+ }
55
+
56
+ interface SafeParseSuccess {
57
+ success: true;
58
+ data: unknown;
59
+ }
60
+
61
+ interface SafeParseFailure {
62
+ success: false;
63
+ error: ValidationFailure;
64
+ }
65
+
66
+ interface SchemaInternals {
67
+ optional?: boolean;
68
+ metadata?: Record<string, unknown>;
69
+ properties?: Record<string, ArkSchema>;
70
+ additionalProperties?: boolean | ArkSchema;
71
+ inner?: ArkSchema;
72
+ }
73
+
74
+ /**
75
+ * JSON-Schema-shaped object with non-enumerable runtime helpers.
76
+ * Validators return the validated data or a marked `{ message }` failure.
77
+ */
78
+ interface ArkSchema {
79
+ __validator: (data: unknown) => unknown;
80
+ __metadata?: Record<string, unknown>;
81
+ __optional?: true;
82
+ __properties?: Record<string, ArkSchema>;
83
+ __additionalProperties?: boolean | ArkSchema;
84
+ __infer?: unknown;
85
+ __inner?: ArkSchema;
86
+ safeParse(input: unknown): SafeParseSuccess | SafeParseFailure;
87
+ toJSON(): Record<string, unknown>;
88
+ [key: string]: unknown;
89
+ }
90
+
91
+ function defineHidden(target: object, key: PropertyKey, value: unknown): void {
92
+ Object.defineProperty(target, key, {
93
+ value,
94
+ writable: true,
95
+ configurable: true,
96
+ });
97
+ }
98
+
99
+ function validationFailure(message: string): ValidationFailure {
100
+ const failure = { message } as ValidationFailure;
101
+ defineHidden(failure, VALIDATION_FAILURE, true);
102
+ return failure;
103
+ }
104
+
105
+ function isValidationFailure(value: unknown): value is ValidationFailure {
106
+ if (value === null || typeof value !== "object") return false;
107
+ const candidate = value as { readonly [VALIDATION_FAILURE]?: unknown };
108
+ return candidate[VALIDATION_FAILURE] === true;
109
+ }
110
+
111
+ function schemaJsonValue(value: unknown): unknown {
112
+ if (Array.isArray(value)) return value.map(schemaJsonValue);
113
+ if (value === null || typeof value !== "object") return value;
114
+ const result: Record<string, unknown> = {};
115
+ for (const key in value) {
116
+ result[key] = schemaJsonValue((value as Record<string, unknown>)[key]);
117
+ }
118
+ return result;
119
+ }
120
+
121
+ function jsonSchemaOf(schema: ArkSchema): Record<string, unknown> {
122
+ const result: Record<string, unknown> = {};
123
+ for (const key in schema) result[key] = schemaJsonValue(schema[key]);
124
+ return result;
125
+ }
126
+
127
+ function createArkSchema(
128
+ validator: (data: unknown) => unknown,
129
+ jsonSchema: Record<string, unknown> = {},
130
+ internals: SchemaInternals = {},
131
+ ): ArkSchema {
132
+ const schema = { ...jsonSchema } as ArkSchema;
133
+ const metadata = internals.metadata ?? {};
134
+ defineHidden(schema, "__validator", validator);
135
+ defineHidden(schema, "__metadata", metadata);
136
+ if (internals.optional) defineHidden(schema, "__optional", true);
137
+ if (internals.properties) defineHidden(schema, "__properties", internals.properties);
138
+ if (internals.additionalProperties !== undefined) {
139
+ defineHidden(schema, "__additionalProperties", internals.additionalProperties);
140
+ }
141
+ if (internals.inner) defineHidden(schema, "__inner", internals.inner);
142
+ defineHidden(schema, "safeParse", (input: unknown): SafeParseSuccess | SafeParseFailure => {
143
+ const result = validator(input);
144
+ if (isValidationFailure(result)) return { success: false, error: result };
145
+ return { success: true, data: result };
146
+ });
147
+ defineHidden(schema, "toJSON", () => jsonSchemaOf(schema));
148
+ return schema;
149
+ }
150
+
151
+ function cloneSchemaWith(
152
+ schema: ArkSchema,
153
+ jsonSchema: Record<string, unknown>,
154
+ internals?: SchemaInternals,
155
+ ): ArkSchema {
156
+ return createArkSchema(schema.__validator, jsonSchema, {
157
+ metadata: internals?.metadata ?? schema.__metadata,
158
+ optional: internals?.optional ?? schema.__optional === true,
159
+ properties: internals?.properties ?? schema.__properties,
160
+ additionalProperties: internals?.additionalProperties ?? schema.__additionalProperties,
161
+ inner: internals?.inner ?? schema.__inner,
162
+ });
163
+ }
164
+
165
+ function withMetadata(schema: ArkSchema, newMeta: Record<string, unknown>): ArkSchema {
166
+ return cloneSchemaWith(
167
+ schema,
168
+ { ...jsonSchemaOf(schema), ...newMeta },
169
+ {
170
+ metadata: { ...(schema.__metadata ?? {}), ...newMeta },
171
+ },
172
+ );
173
+ }
59
174
 
60
175
  // ---------------------------------------------------------------------------
61
176
  // Option shapes — loose subset of JSON Schema metadata + per-type constraints.
@@ -97,288 +212,700 @@ interface ObjectOpts extends Meta {
97
212
  * TypeBox default: extra keys are preserved. Set `false` to reject unknowns,
98
213
  * `true` to allow any, or a schema to validate them.
99
214
  */
100
- additionalProperties?: boolean | ZodType;
215
+ additionalProperties?: boolean | ArkSchema;
101
216
  }
102
217
 
103
218
  // ---------------------------------------------------------------------------
104
219
  // Helpers
105
220
  // ---------------------------------------------------------------------------
106
221
 
107
- /**
108
- * Stamp a non-enumerable `toJSON()` on a schema so `JSON.stringify(schema)`
109
- * yields a clean draft 2020-12 JSON Schema — matching real TypeBox semantics
110
- * where the schema object IS already a JSON Schema. Without this, an extension
111
- * author who serialises the schema across any JSON boundary (worker
112
- * postMessage, MCP transport, config persistence, network hop, structuredClone
113
- * fallback) ships the raw Zod internals (`def`, `_zod`, object-shaped `enum`,
114
- * `"type":"enum"`) — neither valid JSON Schema nor parseable Zod. See
115
- * issue #1101 for the symptoms when this leaks into a tool's `input_schema`.
116
- *
117
- * Idempotent: re-stamping the same instance is a no-op.
118
- */
119
- function wire<T extends ZodType>(schema: T): T {
120
- if (!Object.hasOwn(schema as object, "toJSON")) {
121
- Object.defineProperty(schema as object, "toJSON", {
122
- value: function toJSON(this: ZodType) {
123
- return zodToWireSchema(this);
124
- },
125
- enumerable: false,
126
- writable: true,
127
- configurable: true,
128
- });
222
+ function applyMeta(schema: ArkSchema, opts: Meta | undefined): ArkSchema {
223
+ if (!opts) return schema;
224
+ const metadata: Record<string, unknown> = {};
225
+ for (const key in opts) {
226
+ if (key === "additionalProperties") continue;
227
+ metadata[key] = opts[key];
129
228
  }
130
- return schema;
229
+ return withMetadata(schema, metadata);
230
+ }
231
+
232
+ function createStringValidator(
233
+ baseValidator: (data: unknown) => unknown,
234
+ opts?: StringOpts,
235
+ ): (data: unknown) => unknown {
236
+ return (data: unknown) => {
237
+ const result = baseValidator(data);
238
+ if (isValidationFailure(result)) return result;
239
+ if (typeof result !== "string") return validationFailure("Expected string");
240
+ if (opts?.minLength !== undefined && result.length < opts.minLength) {
241
+ return validationFailure(`String must have at least ${opts.minLength} characters`);
242
+ }
243
+ if (opts?.maxLength !== undefined && result.length > opts.maxLength) {
244
+ return validationFailure(`String must have at most ${opts.maxLength} characters`);
245
+ }
246
+ if (opts?.pattern !== undefined) {
247
+ const regex = new RegExp(opts.pattern);
248
+ if (!regex.test(result)) return validationFailure(`String must match pattern ${opts.pattern}`);
249
+ }
250
+ return result;
251
+ };
252
+ }
253
+
254
+ function createFormatStringValidator(format: string): (data: unknown) => unknown {
255
+ return (data: unknown) => {
256
+ if (typeof data !== "string") return validationFailure("Expected string");
257
+ switch (format) {
258
+ case "email": {
259
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
260
+ return emailRegex.test(data) ? data : validationFailure("Invalid email format");
261
+ }
262
+ case "url":
263
+ case "uri":
264
+ try {
265
+ new URL(data);
266
+ return data;
267
+ } catch {
268
+ return validationFailure("Invalid URL format");
269
+ }
270
+ case "uuid": {
271
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
272
+ return uuidRegex.test(data) ? data : validationFailure("Invalid UUID format");
273
+ }
274
+ case "date": {
275
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
276
+ if (!dateRegex.test(data)) return validationFailure("Invalid date format (YYYY-MM-DD)");
277
+ const date = new Date(data);
278
+ return Number.isNaN(date.getTime()) ? validationFailure("Invalid date") : data;
279
+ }
280
+ case "date-time": {
281
+ const dateTime = new Date(data);
282
+ return Number.isNaN(dateTime.getTime()) ? validationFailure("Invalid date-time format") : data;
283
+ }
284
+ case "time": {
285
+ const timeRegex = /^\d{2}:\d{2}:\d{2}(.\d{3})?([+-]\d{2}:\d{2}|Z)?$/;
286
+ return timeRegex.test(data) ? data : validationFailure("Invalid time format");
287
+ }
288
+ case "ipv4": {
289
+ const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
290
+ if (!ipv4Regex.test(data)) return validationFailure("Invalid IPv4 format");
291
+ const parts = data.split(".").map(Number);
292
+ return parts.some(part => part > 255) ? validationFailure("Invalid IPv4 address") : data;
293
+ }
294
+ case "ipv6": {
295
+ const ipv6Regex = /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i;
296
+ return ipv6Regex.test(data) ? data : validationFailure("Invalid IPv6 format");
297
+ }
298
+ default:
299
+ return data;
300
+ }
301
+ };
131
302
  }
132
303
 
133
- function withMeta<T extends ZodType>(schema: T, opts: Meta | undefined): T {
134
- let out: ZodType = schema;
135
- if (opts) {
136
- if (typeof opts.description === "string") out = out.describe(opts.description);
137
- if ("default" in opts) out = out.default(opts.default as never) as unknown as ZodType;
304
+ function createNumberValidator(isInteger = false): (data: unknown) => unknown {
305
+ return (data: unknown) => {
306
+ if (typeof data !== "number" || Number.isNaN(data)) {
307
+ return validationFailure(`Expected ${isInteger ? "integer" : "number"}`);
308
+ }
309
+ if (isInteger && !Number.isInteger(data)) return validationFailure("Expected integer");
310
+ return data;
311
+ };
312
+ }
313
+
314
+ function createConstrainedNumberValidator(
315
+ baseValidator: (data: unknown) => unknown,
316
+ opts?: NumberOpts,
317
+ ): (data: unknown) => unknown {
318
+ return (data: unknown) => {
319
+ const result = baseValidator(data);
320
+ if (isValidationFailure(result)) return result;
321
+ if (typeof result !== "number") return validationFailure("Expected number");
322
+ if (opts?.minimum !== undefined && result < opts.minimum) {
323
+ return validationFailure(`Number must be at least ${opts.minimum}`);
324
+ }
325
+ if (opts?.maximum !== undefined && result > opts.maximum) {
326
+ return validationFailure(`Number must be at most ${opts.maximum}`);
327
+ }
328
+ if (opts?.exclusiveMinimum !== undefined && result <= opts.exclusiveMinimum) {
329
+ return validationFailure(`Number must be greater than ${opts.exclusiveMinimum}`);
330
+ }
331
+ if (opts?.exclusiveMaximum !== undefined && result >= opts.exclusiveMaximum) {
332
+ return validationFailure(`Number must be less than ${opts.exclusiveMaximum}`);
333
+ }
334
+ if (opts?.multipleOf !== undefined && result % opts.multipleOf !== 0) {
335
+ return validationFailure(`Number must be a multiple of ${opts.multipleOf}`);
336
+ }
337
+ return result;
338
+ };
339
+ }
138
340
 
139
- const metadata: Record<string, unknown> = {};
140
- for (const key in opts) {
141
- if (key === "description" || key === "default" || key === "additionalProperties") continue;
142
- metadata[key] = opts[key];
341
+ function createArrayValidator(itemValidator: ArkSchema, opts?: ArrayOpts): (data: unknown) => unknown {
342
+ return (data: unknown) => {
343
+ if (!Array.isArray(data)) return validationFailure("Expected array");
344
+ if (opts?.minItems !== undefined && data.length < opts.minItems) {
345
+ return validationFailure(`Array must have at least ${opts.minItems} items`);
143
346
  }
144
- if (Object.keys(metadata).length > 0) out = out.meta(metadata);
145
- }
146
- return wire(out as T);
347
+ if (opts?.maxItems !== undefined && data.length > opts.maxItems) {
348
+ return validationFailure(`Array must have at most ${opts.maxItems} items`);
349
+ }
350
+ if (opts?.uniqueItems === true) {
351
+ for (let i = 0; i < data.length; i++) {
352
+ for (let j = i + 1; j < data.length; j++) {
353
+ if (areJsonValuesEqual(data[i], data[j])) return validationFailure("Array items must be unique");
354
+ }
355
+ }
356
+ }
357
+ const itemValidatorFn = itemValidator.__validator;
358
+ for (let i = 0; i < data.length; i++) {
359
+ const itemResult = itemValidatorFn(data[i]);
360
+ if (isValidationFailure(itemResult)) {
361
+ return validationFailure(`Item at index ${i}: ${itemResult.message || "Invalid"}`);
362
+ }
363
+ }
364
+ return data;
365
+ };
147
366
  }
148
367
 
149
- // ---------------------------------------------------------------------------
150
- // Builders
151
- // ---------------------------------------------------------------------------
368
+ function createTupleValidator(itemSchemas: ArkSchema[]): (data: unknown) => unknown {
369
+ return (data: unknown) => {
370
+ if (!Array.isArray(data)) return validationFailure("Expected array");
371
+ if (data.length !== itemSchemas.length) {
372
+ return validationFailure(`Expected tuple of length ${itemSchemas.length}, got ${data.length}`);
373
+ }
374
+ for (let i = 0; i < itemSchemas.length; i++) {
375
+ const itemValidator = itemSchemas[i].__validator;
376
+ const itemResult = itemValidator(data[i]);
377
+ if (isValidationFailure(itemResult)) {
378
+ return validationFailure(`Item at index ${i}: ${itemResult.message || "Invalid"}`);
379
+ }
380
+ }
381
+ return data;
382
+ };
383
+ }
152
384
 
153
- function tString(opts?: StringOpts): ZodType {
154
- let s: ZodType = z.string();
155
- if (opts) {
156
- // Format selection swaps the base schema for a more specific Zod string
157
- // validator that emits the right `format` keyword in JSON Schema.
158
- switch (opts.format) {
159
- case "email":
160
- s = z.email();
161
- break;
162
- case "url":
163
- case "uri":
164
- s = z.url();
165
- break;
166
- case "uuid":
167
- s = z.uuid();
168
- break;
169
- case "date-time":
170
- s = z.iso.datetime();
171
- break;
172
- case "date":
173
- s = z.iso.date();
174
- break;
175
- case "time":
176
- s = z.iso.time();
177
- break;
178
- case "ipv4":
179
- s = z.ipv4();
180
- break;
181
- case "ipv6":
182
- s = z.ipv6();
183
- break;
184
- default:
185
- break;
186
- }
187
- // Length/pattern constraints live on the `_ZodString` base that every
188
- // format-specific schema (ZodEmail, ZodURL, ZodISODateTime, ...) extends,
189
- // so we apply them regardless of which concrete subclass `s` ended up as.
190
- const sf = s as z.ZodString;
191
- if (typeof opts.minLength === "number") s = sf.min(opts.minLength);
192
- if (typeof opts.maxLength === "number") s = (s as z.ZodString).max(opts.maxLength);
193
- if (typeof opts.pattern === "string") s = (s as z.ZodString).regex(new RegExp(opts.pattern));
385
+ function createObjectValidator(properties: Record<string, ArkSchema>, opts?: ObjectOpts): (data: unknown) => unknown {
386
+ return (data: unknown) => {
387
+ if (!data || typeof data !== "object") return validationFailure("Expected object");
388
+ const obj = data as Record<string, unknown>;
389
+ const result: Record<string, unknown> = {};
390
+
391
+ const keys = new Set<string>();
392
+ for (const key in obj) {
393
+ keys.add(key);
394
+ }
395
+
396
+ for (const key in properties) {
397
+ const schema = properties[key];
398
+ const validated = schema.__validator(obj[key]);
399
+ if (isValidationFailure(validated)) {
400
+ return validationFailure(`Property ${key}: ${validated.message || "Invalid"}`);
401
+ }
402
+ if (obj[key] !== undefined || schema.__optional !== true) {
403
+ result[key] = validated;
404
+ }
405
+ keys.delete(key);
406
+ }
407
+
408
+ const additionalProperties = opts?.additionalProperties;
409
+ if (additionalProperties === false) {
410
+ if (keys.size > 0) return validationFailure(`Unexpected properties: ${Array.from(keys).join(", ")}`);
411
+ } else if (additionalProperties === true || additionalProperties === undefined) {
412
+ for (const key of keys) result[key] = obj[key];
413
+ } else {
414
+ const additionalValidator = additionalProperties.__validator;
415
+ for (const key of keys) {
416
+ const validated = additionalValidator(obj[key]);
417
+ if (isValidationFailure(validated)) {
418
+ return validationFailure(`Property ${key}: ${validated.message || "Invalid"}`);
419
+ }
420
+ result[key] = validated;
421
+ }
422
+ }
423
+ return result;
424
+ };
425
+ }
426
+
427
+ function createUnionValidator(schemas: ArkSchema[]): (data: unknown) => unknown {
428
+ return (data: unknown) => {
429
+ if (schemas.length === 0) return validationFailure("Cannot validate empty union");
430
+ const errors: string[] = [];
431
+ for (const schema of schemas) {
432
+ const result = schema.__validator(data);
433
+ if (!isValidationFailure(result)) return result;
434
+ errors.push(result.message || "Invalid");
435
+ }
436
+ return validationFailure(`Failed all union options: ${errors.join("; ")}`);
437
+ };
438
+ }
439
+
440
+ function createIntersectionValidator(schemas: ArkSchema[]): (data: unknown) => unknown {
441
+ return (data: unknown) => {
442
+ let result = data;
443
+ for (const schema of schemas) {
444
+ result = schema.__validator(result);
445
+ if (isValidationFailure(result)) return result;
446
+ }
447
+ return result;
448
+ };
449
+ }
450
+
451
+ function createOptionalValidator(schema: ArkSchema): (data: unknown) => unknown {
452
+ const baseValidator = schema.__validator;
453
+ return (data: unknown) => (data === undefined ? undefined : baseValidator(data));
454
+ }
455
+
456
+ function createNullableValidator(schema: ArkSchema): (data: unknown) => unknown {
457
+ const baseValidator = schema.__validator;
458
+ return (data: unknown) => (data === null ? null : baseValidator(data));
459
+ }
460
+
461
+ function createRecordValidator(keySchema: ArkSchema, valueSchema: ArkSchema): (data: unknown) => unknown {
462
+ return (data: unknown) => {
463
+ if (!data || typeof data !== "object") return validationFailure("Expected object");
464
+ const obj = data as Record<string, unknown>;
465
+ const result: Record<string, unknown> = {};
466
+ const keyValidator = keySchema.__validator;
467
+ const valueValidator = valueSchema.__validator;
468
+ for (const key in obj) {
469
+ const value = obj[key];
470
+ const keyResult = keyValidator(key);
471
+ if (isValidationFailure(keyResult)) {
472
+ return validationFailure(`Key ${key}: ${keyResult.message || "Invalid"}`);
473
+ }
474
+ const valueResult = valueValidator(value);
475
+ if (isValidationFailure(valueResult)) {
476
+ return validationFailure(`Key ${key}: ${valueResult.message || "Invalid"}`);
477
+ }
478
+ result[key] = valueResult;
479
+ }
480
+ return result;
481
+ };
482
+ }
483
+
484
+ function isArrayIndexKey(key: string): boolean {
485
+ if (!/^(?:0|[1-9]\d*)$/.test(key)) return false;
486
+ const index = Number(key);
487
+ return Number.isSafeInteger(index) && index >= 0;
488
+ }
489
+
490
+ function uniqueLiteralValues(values: readonly (string | number | boolean)[]): Array<string | number | boolean> {
491
+ const unique: Array<string | number | boolean> = [];
492
+ for (const value of values) {
493
+ if (!unique.some(existing => existing === value)) unique.push(value);
194
494
  }
195
- return withMeta(s, opts);
495
+ return unique;
196
496
  }
197
497
 
198
- function applyNumberConstraints(base: z.ZodNumber, opts: NumberOpts | undefined): z.ZodNumber {
199
- if (!opts) return base;
200
- let out = base;
201
- if (typeof opts.minimum === "number") out = out.min(opts.minimum);
202
- if (typeof opts.maximum === "number") out = out.max(opts.maximum);
203
- if (typeof opts.exclusiveMinimum === "number") out = out.gt(opts.exclusiveMinimum);
204
- if (typeof opts.exclusiveMaximum === "number") out = out.lt(opts.exclusiveMaximum);
205
- if (typeof opts.multipleOf === "number") out = out.multipleOf(opts.multipleOf);
206
- return out;
498
+ function requiredKeys(properties: Record<string, ArkSchema>): string[] | undefined {
499
+ const required: string[] = [];
500
+ for (const key in properties) {
501
+ if (properties[key].__optional !== true) {
502
+ required.push(key);
503
+ }
504
+ }
505
+ return required.length === 0 ? undefined : required;
506
+ }
507
+
508
+ function objectJsonSchema(properties: Record<string, ArkSchema>, opts?: ObjectOpts): Record<string, unknown> {
509
+ const propertySchemas: Record<string, unknown> = {};
510
+ for (const key in properties) propertySchemas[key] = jsonSchemaOf(properties[key]);
511
+ const schema: Record<string, unknown> = { type: "object", properties: propertySchemas };
512
+ const required = requiredKeys(properties);
513
+ if (required) schema.required = required;
514
+ const additionalProperties = opts?.additionalProperties;
515
+ if (additionalProperties !== undefined) {
516
+ schema.additionalProperties =
517
+ typeof additionalProperties === "boolean" ? additionalProperties : jsonSchemaOf(additionalProperties);
518
+ }
519
+ return schema;
207
520
  }
208
521
 
209
- function tNumber(opts?: NumberOpts): ZodType {
210
- return withMeta(applyNumberConstraints(z.number(), opts), opts);
522
+ function schemaWithoutOptional(schema: ArkSchema): ArkSchema {
523
+ const base = schema.__inner ?? schema;
524
+ return createArkSchema(base.__validator, jsonSchemaOf(base), {
525
+ metadata: base.__metadata,
526
+ properties: base.__properties,
527
+ additionalProperties: base.__additionalProperties,
528
+ });
211
529
  }
212
530
 
213
- function tInteger(opts?: NumberOpts): ZodType {
214
- return withMeta(applyNumberConstraints(z.number().int(), opts), opts);
531
+ // ---------------------------------------------------------------------------
532
+ // Builders
533
+ // ---------------------------------------------------------------------------
534
+
535
+ function tString(opts?: StringOpts): ArkSchema {
536
+ let validator: (data: unknown) => unknown = opts?.format
537
+ ? createFormatStringValidator(opts.format)
538
+ : (data: unknown) => (typeof data === "string" ? data : validationFailure("Expected string"));
539
+ validator = createStringValidator(validator, opts);
540
+ return applyMeta(createArkSchema(validator, { type: "string" }), opts);
215
541
  }
216
542
 
217
- function tBoolean(opts?: Meta): ZodType {
218
- return withMeta(z.boolean(), opts);
543
+ function tNumber(opts?: NumberOpts): ArkSchema {
544
+ const validator = createConstrainedNumberValidator(createNumberValidator(false), opts);
545
+ return applyMeta(createArkSchema(validator, { type: "number" }), opts);
219
546
  }
220
547
 
221
- function tNull(opts?: Meta): ZodType {
222
- return withMeta(z.null(), opts);
548
+ function tInteger(opts?: NumberOpts): ArkSchema {
549
+ const validator = createConstrainedNumberValidator(createNumberValidator(true), opts);
550
+ return applyMeta(createArkSchema(validator, { type: "integer" }), opts);
223
551
  }
224
552
 
225
- function tAny(opts?: Meta): ZodType {
226
- return withMeta(z.any(), opts);
553
+ function tBoolean(opts?: Meta): ArkSchema {
554
+ const validator = (data: unknown) => (typeof data === "boolean" ? data : validationFailure("Expected boolean"));
555
+ return applyMeta(createArkSchema(validator, { type: "boolean" }), opts);
227
556
  }
228
557
 
229
- function tUnknown(opts?: Meta): ZodType {
230
- return withMeta(z.unknown(), opts);
558
+ function tNull(opts?: Meta): ArkSchema {
559
+ const validator = (data: unknown) => (data === null ? data : validationFailure("Expected null"));
560
+ return applyMeta(createArkSchema(validator, { type: "null" }), opts);
231
561
  }
232
562
 
233
- function tNever(opts?: Meta): ZodType {
234
- return withMeta(z.never(), opts);
563
+ function tAny(opts?: Meta): ArkSchema {
564
+ return applyMeta(
565
+ createArkSchema((data: unknown) => data, {}),
566
+ opts,
567
+ );
235
568
  }
236
569
 
237
- function tLiteral<V extends string | number | boolean>(value: V, opts?: Meta): ZodType {
238
- return withMeta(z.literal(value), opts);
570
+ function tUnknown(opts?: Meta): ArkSchema {
571
+ return applyMeta(
572
+ createArkSchema((data: unknown) => data, {}),
573
+ opts,
574
+ );
239
575
  }
240
576
 
241
- function tUnion<T extends readonly ZodType[]>(schemas: T, opts?: Meta): ZodType {
242
- if (schemas.length === 0) return withMeta(z.never(), opts);
243
- if (schemas.length === 1) return withMeta(schemas[0] as ZodType, opts);
244
- return withMeta(z.union(schemas as unknown as [ZodType, ZodType, ...ZodType[]]), opts);
577
+ function tNever(opts?: Meta): ArkSchema {
578
+ return applyMeta(
579
+ createArkSchema(() => validationFailure("Never type does not accept any value"), { not: {} }),
580
+ opts,
581
+ );
245
582
  }
246
583
 
247
- function tIntersect(schemas: readonly ZodType[], opts?: Meta): ZodType {
248
- if (schemas.length === 0) return withMeta(z.unknown(), opts);
249
- if (schemas.length === 1) return withMeta(schemas[0] as ZodType, opts);
250
- let out: ZodType = schemas[0] as ZodType;
251
- for (let i = 1; i < schemas.length; i++) out = z.intersection(out, schemas[i] as ZodType) as ZodType;
252
- return withMeta(out, opts);
584
+ function tLiteral<V extends string | number | boolean>(value: V, opts?: Meta): ArkSchema {
585
+ const validator = (data: unknown) =>
586
+ data === value ? data : validationFailure(`Expected literal ${JSON.stringify(value)}`);
587
+ return applyMeta(createArkSchema(validator, { const: value }), opts);
253
588
  }
254
589
 
255
- function isArrayIndexKey(key: string): boolean {
256
- if (!/^(?:0|[1-9]\\d*)$/.test(key)) return false;
257
- const index = Number(key);
258
- return Number.isSafeInteger(index) && index >= 0;
590
+ function tUnion<T extends readonly ArkSchema[]>(schemas: T, opts?: Meta): ArkSchema {
591
+ if (schemas.length === 0)
592
+ return applyMeta(
593
+ createArkSchema(() => validationFailure("Empty union"), { not: {} }),
594
+ opts,
595
+ );
596
+ if (schemas.length === 1) return applyMeta(schemas[0], opts);
597
+ const validator = createUnionValidator([...schemas]);
598
+ return applyMeta(createArkSchema(validator, { anyOf: schemas.map(jsonSchemaOf) }), opts);
259
599
  }
260
600
 
261
- function uniqueLiteralValues(values: readonly (string | number | boolean)[]): Array<string | number | boolean> {
262
- const unique: Array<string | number | boolean> = [];
263
- for (const value of values) {
264
- if (!unique.some(existing => existing === value)) unique.push(value);
265
- }
266
- return unique;
601
+ function tIntersect(schemas: readonly ArkSchema[], opts?: Meta): ArkSchema {
602
+ if (schemas.length === 0)
603
+ return applyMeta(
604
+ createArkSchema((data: unknown) => data, {}),
605
+ opts,
606
+ );
607
+ if (schemas.length === 1) return applyMeta(schemas[0], opts);
608
+ const validator = createIntersectionValidator([...schemas]);
609
+ return applyMeta(createArkSchema(validator, { allOf: schemas.map(jsonSchemaOf) }), opts);
267
610
  }
268
611
 
269
- function literalUnion(values: readonly (string | number | boolean)[], opts?: Meta): ZodType {
612
+ function literalUnion(values: readonly (string | number | boolean)[], opts?: Meta): ArkSchema {
270
613
  const unique = uniqueLiteralValues(values);
271
- if (unique.length === 0) return withMeta(z.never(), opts);
272
- if (unique.length === 1) return withMeta(z.literal(unique[0] as string | number | boolean), opts);
273
- const schemas = unique.map(value => z.literal(value as string | number | boolean)) as unknown as [
274
- ZodType,
275
- ZodType,
276
- ...ZodType[],
277
- ];
278
- return withMeta(z.union(schemas), opts);
614
+ if (unique.length === 0)
615
+ return applyMeta(
616
+ createArkSchema(() => validationFailure("Empty literal union"), { not: {} }),
617
+ opts,
618
+ );
619
+ if (unique.length === 1) return tLiteral(unique[0] as string | number | boolean, opts);
620
+ const validator = (data: unknown) => {
621
+ for (const value of unique) if (data === value) return data;
622
+ return validationFailure(`Expected one of: ${unique.join(", ")}`);
623
+ };
624
+ return applyMeta(createArkSchema(validator, { enum: unique }), opts);
279
625
  }
626
+
280
627
  function tEnum<T extends Record<string, string | number> | readonly (string | number)[]>(
281
628
  values: T,
282
629
  opts?: Meta,
283
- ): ZodType {
284
- const list = Array.isArray(values)
285
- ? values
286
- : Object.entries(values)
287
- .filter(([key, value]) => !(isArrayIndexKey(key) && typeof value === "string"))
288
- .map(([, value]) => value);
630
+ ): ArkSchema {
631
+ let list: (string | number)[];
632
+ if (Array.isArray(values)) {
633
+ list = values;
634
+ } else {
635
+ list = [];
636
+ for (const key in values) {
637
+ const value = values[key];
638
+ if (!(isArrayIndexKey(key) && typeof value === "string")) {
639
+ list.push(value as string | number);
640
+ }
641
+ }
642
+ }
289
643
  return literalUnion(list, opts);
290
644
  }
291
645
 
292
- function tArray<E extends ZodType>(item: E, opts?: ArrayOpts): ZodType {
293
- let arr: ZodType = z.array(item);
294
- if (opts) {
295
- if (typeof opts.minItems === "number") arr = (arr as ZodArray<E>).min(opts.minItems);
296
- if (typeof opts.maxItems === "number") arr = (arr as ZodArray<E>).max(opts.maxItems);
297
- if (opts.uniqueItems === true) {
298
- arr = arr.refine(items => {
299
- if (!Array.isArray(items)) return true;
300
- for (let i = 0; i < items.length; i += 1) {
301
- for (let j = i + 1; j < items.length; j += 1) {
302
- if (areJsonValuesEqual(items[i], items[j])) return false;
303
- }
304
- }
305
- return true;
306
- }, "Expected array items to be unique");
307
- }
646
+ function tArray<E extends ArkSchema>(item: E, opts?: ArrayOpts): ArkSchema {
647
+ const validator = createArrayValidator(item, opts);
648
+ return applyMeta(createArkSchema(validator, { type: "array", items: jsonSchemaOf(item) }), opts);
649
+ }
650
+
651
+ function tTuple(items: readonly ArkSchema[], opts?: Meta): ArkSchema {
652
+ const validator = createTupleValidator([...items]);
653
+ return applyMeta(
654
+ createArkSchema(validator, {
655
+ type: "array",
656
+ prefixItems: items.map(jsonSchemaOf),
657
+ minItems: items.length,
658
+ maxItems: items.length,
659
+ }),
660
+ opts,
661
+ );
662
+ }
663
+
664
+ function tObject<P extends Record<string, ArkSchema>>(properties: P, opts?: ObjectOpts): ArkSchema {
665
+ const props = properties as Record<string, ArkSchema>;
666
+ const validator = createObjectValidator(props, opts);
667
+ const schema = createArkSchema(validator, objectJsonSchema(props, opts), {
668
+ properties: props,
669
+ additionalProperties: opts?.additionalProperties,
670
+ });
671
+ return applyMeta(schema, opts);
672
+ }
673
+
674
+ function literalRecordKeys(keySchema: ArkSchema): string[] | null {
675
+ const json = jsonSchemaOf(keySchema);
676
+ if ("const" in json) return [String(json.const)];
677
+ const values = json.enum;
678
+ if (!Array.isArray(values)) return null;
679
+ const keys: string[] = [];
680
+ for (const value of values) {
681
+ const type = typeof value;
682
+ if (type !== "string" && type !== "number" && type !== "boolean") return null;
683
+ keys.push(String(value));
308
684
  }
309
- return withMeta(arr, opts);
685
+ return keys;
310
686
  }
311
687
 
312
- function tTuple(items: readonly ZodType[], opts?: Meta): ZodType {
313
- return withMeta(z.tuple(items as unknown as [ZodType, ...ZodType[]]) as unknown as ZodType, opts);
688
+ function createRecordJson(keySchema: ArkSchema, valueSchema: ArkSchema): Record<string, unknown> {
689
+ const valueJson = jsonSchemaOf(valueSchema);
690
+ const keys = literalRecordKeys(keySchema);
691
+ if (keys) {
692
+ const properties: Record<string, unknown> = {};
693
+ for (const key of keys) properties[key] = valueJson;
694
+ return { type: "object", properties, required: keys, additionalProperties: false };
695
+ }
696
+ const json: Record<string, unknown> = { type: "object", additionalProperties: valueJson };
697
+ const keyJson = jsonSchemaOf(keySchema);
698
+ if (Object.keys(keyJson).length > 0) json.propertyNames = keyJson;
699
+ return json;
314
700
  }
315
701
 
316
- function isOptional(schema: ZodType): boolean {
317
- const def = (schema as { _zod?: { def?: { type?: string } } })._zod?.def;
318
- return def?.type === "optional";
702
+ function tRecord<K extends ArkSchema, V extends ArkSchema>(key: K, value: V, opts?: Meta): ArkSchema {
703
+ const validator = createRecordValidator(key, value);
704
+ return applyMeta(createArkSchema(validator, createRecordJson(key, value)), opts);
319
705
  }
320
706
 
321
- function tObject<P extends ZodRawShape>(properties: P, opts?: ObjectOpts): ZodObject<P> {
322
- // `z.object` automatically derives `required` from non-optional entries,
323
- // so `Type.Optional(...)` flows through unchanged (Zod treats `.optional()`
324
- // and `Type.Optional`-style wrappers identically).
325
- let obj = z.object(properties);
326
- const ap = opts?.additionalProperties;
327
- if (ap === false) {
328
- obj = obj.strict() as unknown as ZodObject<P>;
329
- } else if (ap === undefined || ap === true) {
330
- // TypeBox preserves unknown keys by default; Zod's default is `.strip()`.
331
- obj = obj.loose() as unknown as ZodObject<P>;
332
- } else {
333
- obj = obj.catchall(ap) as unknown as ZodObject<P>;
334
- }
335
- return withMeta(obj, opts);
707
+ function tOptional<E extends ArkSchema>(schema: E, opts?: Meta): ArkSchema {
708
+ const validator = createOptionalValidator(schema);
709
+ const optional = createArkSchema(validator, jsonSchemaOf(schema), {
710
+ optional: true,
711
+ inner: schema,
712
+ });
713
+ return applyMeta(optional, opts);
336
714
  }
337
715
 
338
- function tRecord<V extends ZodType>(key: ZodType, value: V, opts?: Meta): ZodType {
339
- return withMeta(z.record(key as never, value as never) as unknown as ZodType, opts);
716
+ function tNullable<E extends ArkSchema>(schema: E, opts?: Meta): ArkSchema {
717
+ const validator = createNullableValidator(schema);
718
+ return applyMeta(createArkSchema(validator, { anyOf: [jsonSchemaOf(schema), { type: "null" }] }), opts);
340
719
  }
341
720
 
342
- function tOptional<E extends ZodType>(schema: E, _opts?: Meta): ZodOptional<E> {
343
- if (isOptional(schema)) return wire(schema as unknown as ZodOptional<E>);
344
- return wire(schema.optional() as ZodOptional<E>);
721
+ function tReadonly<E extends ArkSchema>(schema: E): ArkSchema {
722
+ // TypeBox's `Type.Readonly` is purely a marker; runtime validation is identical.
723
+ return schema;
345
724
  }
346
725
 
347
- function tNullable<E extends ZodType>(schema: E, opts?: Meta): ZodType {
348
- return withMeta(schema.nullable() as ZodType, opts);
726
+ function tPartial<_P extends Record<string, ArkSchema>>(obj: ArkSchema): ArkSchema {
727
+ if (obj.__properties) {
728
+ const properties: Record<string, ArkSchema> = {};
729
+ for (const key in obj.__properties) {
730
+ const schema = obj.__properties[key];
731
+ properties[key] = schema.__optional === true ? schema : tOptional(schema);
732
+ }
733
+ return tObject(properties, { additionalProperties: obj.__additionalProperties });
734
+ }
735
+ const objValidator = obj.__validator;
736
+ const metadata = jsonSchemaOf(obj);
737
+ delete metadata.required;
738
+ return createArkSchema(objValidator, metadata);
349
739
  }
350
740
 
351
- function tReadonly<E extends ZodType>(schema: E): E {
352
- // TypeBox's `Type.Readonly` is purely a marker; runtime parsing is identical.
353
- return wire(schema);
741
+ function tRequired<_P extends Record<string, ArkSchema>>(obj: ArkSchema): ArkSchema {
742
+ if (obj.__properties) {
743
+ const properties: Record<string, ArkSchema> = {};
744
+ for (const key in obj.__properties) {
745
+ properties[key] = schemaWithoutOptional(obj.__properties[key]);
746
+ }
747
+ return tObject(properties, { additionalProperties: obj.__additionalProperties });
748
+ }
749
+ const metadata = jsonSchemaOf(obj);
750
+ if (metadata.properties && typeof metadata.properties === "object" && !Array.isArray(metadata.properties)) {
751
+ const properties = metadata.properties as Record<string, unknown>;
752
+ const required: string[] = [];
753
+ for (const key in properties) {
754
+ required.push(key);
755
+ }
756
+ metadata.required = required;
757
+ }
758
+ return createArkSchema(obj.__validator, metadata);
354
759
  }
355
760
 
356
- function tPartial<P extends ZodRawShape>(obj: ZodObject<P>): ZodObject<P> {
357
- return wire(obj.partial() as unknown as ZodObject<P>);
358
- }
761
+ function tPick<P extends Record<string, ArkSchema>, K extends keyof P>(obj: ArkSchema, keys: readonly K[]): ArkSchema {
762
+ const keySet = new Set([...keys].map(String));
763
+ if (obj.__properties) {
764
+ const properties: Record<string, ArkSchema> = {};
765
+ for (const key of keySet) {
766
+ const schema = obj.__properties[key];
767
+ if (schema) properties[key] = schema;
768
+ }
769
+ return tObject(properties, { additionalProperties: obj.__additionalProperties });
770
+ }
771
+ const validator = (data: unknown) => {
772
+ if (!data || typeof data !== "object") {
773
+ return validationFailure("Expected object");
774
+ }
359
775
 
360
- function tRequired<P extends ZodRawShape>(obj: ZodObject<P>): ZodObject<P> {
361
- return wire(obj.required() as unknown as ZodObject<P>);
362
- }
776
+ const result: Record<string, unknown> = {};
777
+ const obj_data = data as Record<string, unknown>;
363
778
 
364
- function tPick<P extends ZodRawShape, K extends keyof P>(obj: ZodObject<P>, keys: readonly K[]): ZodObject<Pick<P, K>> {
365
- const mask = Object.fromEntries(keys.map(k => [k as string, true]));
366
- return wire(obj.pick(mask as never) as unknown as ZodObject<Pick<P, K>>);
367
- }
779
+ for (const key of keySet) {
780
+ if (key in obj_data) {
781
+ result[key] = obj_data[key];
782
+ }
783
+ }
368
784
 
369
- function tOmit<P extends ZodRawShape, K extends keyof P>(obj: ZodObject<P>, keys: readonly K[]): ZodObject<Omit<P, K>> {
370
- const mask = Object.fromEntries(keys.map(k => [k as string, true]));
371
- return wire(obj.omit(mask as never) as unknown as ZodObject<Omit<P, K>>);
785
+ return result;
786
+ };
787
+
788
+ const metadata = jsonSchemaOf(obj);
789
+ if (metadata.properties && typeof metadata.properties === "object" && !Array.isArray(metadata.properties)) {
790
+ const properties = metadata.properties as Record<string, unknown>;
791
+ const filteredProps: Record<string, unknown> = {};
792
+ for (const key in properties) {
793
+ if (keySet.has(key)) {
794
+ filteredProps[key] = properties[key];
795
+ }
796
+ }
797
+ metadata.properties = filteredProps;
798
+ }
799
+ if (Array.isArray(metadata.required)) {
800
+ metadata.required = metadata.required.filter(key => typeof key === "string" && keySet.has(key));
801
+ }
802
+ return createArkSchema(validator, metadata);
372
803
  }
373
- function tComposite(objects: readonly ZodObject<ZodRawShape>[], opts?: Meta): ZodObject<ZodRawShape> {
374
- // `Type.Composite([...])` flattens every object schema into one object schema
375
- // rather than producing an intersection. Mirror that via repeated `extend`.
376
- if (objects.length === 0) return withMeta(z.object({}), opts) as ZodObject<ZodRawShape>;
377
- let out = objects[0] as ZodObject<ZodRawShape>;
378
- for (let i = 1; i < objects.length; i += 1) {
379
- out = out.extend(objects[i].shape) as ZodObject<ZodRawShape>;
804
+
805
+ function tOmit<P extends Record<string, ArkSchema>, K extends keyof P>(obj: ArkSchema, keys: readonly K[]): ArkSchema {
806
+ const keySet = new Set([...keys].map(String));
807
+ if (obj.__properties) {
808
+ const properties: Record<string, ArkSchema> = {};
809
+ for (const key in obj.__properties) {
810
+ if (!keySet.has(key)) {
811
+ properties[key] = obj.__properties[key];
812
+ }
813
+ }
814
+ return tObject(properties, { additionalProperties: obj.__additionalProperties });
815
+ }
816
+ const validator = (data: unknown) => {
817
+ if (!data || typeof data !== "object") {
818
+ return validationFailure("Expected object");
819
+ }
820
+
821
+ const result: Record<string, unknown> = {};
822
+ const obj_data = data as Record<string, unknown>;
823
+
824
+ for (const key in obj_data) {
825
+ if (!keySet.has(key)) {
826
+ result[key] = obj_data[key];
827
+ }
828
+ }
829
+
830
+ return result;
831
+ };
832
+
833
+ const metadata = jsonSchemaOf(obj);
834
+ if (metadata.properties && typeof metadata.properties === "object" && !Array.isArray(metadata.properties)) {
835
+ const properties = metadata.properties as Record<string, unknown>;
836
+ const filteredProps: Record<string, unknown> = {};
837
+ for (const key in properties) {
838
+ if (!keySet.has(key)) {
839
+ filteredProps[key] = properties[key];
840
+ }
841
+ }
842
+ metadata.properties = filteredProps;
843
+ }
844
+ if (Array.isArray(metadata.required)) {
845
+ metadata.required = metadata.required.filter(key => typeof key === "string" && !keySet.has(key));
846
+ }
847
+ return createArkSchema(validator, metadata);
848
+ }
849
+
850
+ function tComposite(objects: readonly ArkSchema[], opts?: Meta): ArkSchema {
851
+ // Composite flattens object schemas into one
852
+ if (objects.length === 0) {
853
+ return applyMeta(
854
+ createArkSchema(
855
+ (data: unknown) => (data && typeof data === "object" ? data : validationFailure("Expected object")),
856
+ {
857
+ type: "object",
858
+ },
859
+ ),
860
+ opts,
861
+ );
862
+ }
863
+
864
+ if (objects.length === 1) {
865
+ return applyMeta(objects[0], opts);
866
+ }
867
+
868
+ let canFlatten = true;
869
+ const properties: Record<string, ArkSchema> = {};
870
+ for (const schema of objects) {
871
+ if (!schema.__properties) {
872
+ canFlatten = false;
873
+ break;
874
+ }
875
+ for (const key in schema.__properties) {
876
+ properties[key] = schema.__properties[key];
877
+ }
380
878
  }
381
- return withMeta(out, opts) as ZodObject<ZodRawShape>;
879
+ if (canFlatten) return tObject(properties, opts as ObjectOpts | undefined);
880
+
881
+ // Merge all object validators
882
+ const validator = (data: unknown) => {
883
+ if (!data || typeof data !== "object") {
884
+ return validationFailure("Expected object");
885
+ }
886
+
887
+ const result = {} as Record<string, unknown>;
888
+ const obj_data = data as Record<string, unknown>;
889
+
890
+ for (const schema of objects) {
891
+ const schemaValidator = schema.__validator;
892
+ const schemaResult = schemaValidator(obj_data);
893
+
894
+ if (isValidationFailure(schemaResult)) {
895
+ return schemaResult;
896
+ }
897
+
898
+ if (typeof schemaResult === "object" && schemaResult !== null) {
899
+ for (const key in schemaResult) {
900
+ result[key] = (schemaResult as Record<string, unknown>)[key];
901
+ }
902
+ }
903
+ }
904
+
905
+ return result;
906
+ };
907
+
908
+ return applyMeta(createArkSchema(validator, { allOf: objects.map(jsonSchemaOf) }), opts);
382
909
  }
383
910
 
384
911
  // ---------------------------------------------------------------------------