@oh-my-pi/pi-coding-agent 15.0.1 → 15.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/examples/custom-tools/README.md +11 -7
  3. package/examples/custom-tools/hello/index.ts +2 -2
  4. package/examples/extensions/README.md +19 -8
  5. package/examples/extensions/api-demo.ts +15 -19
  6. package/examples/extensions/hello.ts +5 -6
  7. package/examples/extensions/plan-mode.ts +1 -1
  8. package/examples/extensions/reload-runtime.ts +4 -3
  9. package/examples/extensions/with-deps/index.ts +4 -3
  10. package/examples/sdk/06-extensions.ts +4 -2
  11. package/package.json +8 -18
  12. package/src/autoresearch/tools/init-experiment.ts +38 -41
  13. package/src/autoresearch/tools/log-experiment.ts +32 -41
  14. package/src/autoresearch/tools/run-experiment.ts +3 -3
  15. package/src/autoresearch/tools/update-notes.ts +11 -11
  16. package/src/commands/commit.ts +10 -0
  17. package/src/commit/agentic/tools/analyze-file.ts +4 -4
  18. package/src/commit/agentic/tools/git-file-diff.ts +4 -4
  19. package/src/commit/agentic/tools/git-hunk.ts +5 -5
  20. package/src/commit/agentic/tools/git-overview.ts +4 -4
  21. package/src/commit/agentic/tools/propose-changelog.ts +13 -13
  22. package/src/commit/agentic/tools/propose-commit.ts +6 -6
  23. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  24. package/src/commit/agentic/tools/schemas.ts +28 -28
  25. package/src/commit/agentic/tools/split-commit.ts +22 -21
  26. package/src/commit/analysis/summary.ts +4 -4
  27. package/src/commit/changelog/generate.ts +7 -11
  28. package/src/commit/shared-llm.ts +22 -34
  29. package/src/config/config-file.ts +35 -13
  30. package/src/config/model-registry.ts +40 -191
  31. package/src/config/models-config-schema.ts +166 -0
  32. package/src/config/settings-schema.ts +29 -0
  33. package/src/discovery/claude-plugins.ts +19 -7
  34. package/src/edit/index.ts +2 -2
  35. package/src/edit/modes/apply-patch.ts +7 -6
  36. package/src/edit/modes/patch.ts +18 -25
  37. package/src/edit/modes/replace.ts +18 -20
  38. package/src/eval/js/shared/rewrite-imports.ts +131 -10
  39. package/src/eval/py/executor.ts +233 -623
  40. package/src/eval/py/kernel.ts +27 -2
  41. package/src/eval/py/runner.py +42 -11
  42. package/src/eval/py/runtime.ts +1 -0
  43. package/src/exa/factory.ts +5 -4
  44. package/src/exa/mcp-client.ts +1 -1
  45. package/src/exa/researcher.ts +9 -20
  46. package/src/exa/search.ts +26 -52
  47. package/src/exa/types.ts +1 -1
  48. package/src/exa/websets.ts +54 -53
  49. package/src/exec/bash-executor.ts +2 -1
  50. package/src/extensibility/custom-commands/loader.ts +5 -3
  51. package/src/extensibility/custom-commands/types.ts +4 -2
  52. package/src/extensibility/custom-tools/loader.ts +5 -3
  53. package/src/extensibility/custom-tools/types.ts +7 -6
  54. package/src/extensibility/custom-tools/wrapper.ts +1 -1
  55. package/src/extensibility/extensions/get-commands-handler.ts +77 -0
  56. package/src/extensibility/extensions/loader.ts +7 -3
  57. package/src/extensibility/extensions/types.ts +9 -5
  58. package/src/extensibility/extensions/wrapper.ts +1 -2
  59. package/src/extensibility/hooks/loader.ts +3 -1
  60. package/src/extensibility/hooks/tool-wrapper.ts +1 -1
  61. package/src/extensibility/hooks/types.ts +4 -2
  62. package/src/extensibility/plugins/legacy-pi-compat.ts +78 -31
  63. package/src/extensibility/shared-events.ts +1 -1
  64. package/src/extensibility/typebox.ts +391 -0
  65. package/src/goals/tools/goal-tool.ts +6 -12
  66. package/src/hashline/input.ts +2 -1
  67. package/src/hashline/parser.ts +27 -3
  68. package/src/hashline/types.ts +4 -4
  69. package/src/hindsight/state.ts +2 -2
  70. package/src/index.ts +0 -2
  71. package/src/internal-urls/docs-index.generated.ts +15 -15
  72. package/src/internal-urls/router.ts +8 -0
  73. package/src/internal-urls/types.ts +21 -0
  74. package/src/lsp/config.ts +15 -6
  75. package/src/lsp/defaults.json +6 -2
  76. package/src/lsp/types.ts +30 -38
  77. package/src/mcp/manager.ts +1 -1
  78. package/src/mcp/tool-bridge.ts +1 -1
  79. package/src/modes/acp/acp-agent.ts +248 -50
  80. package/src/modes/components/session-observer-overlay.ts +12 -1
  81. package/src/modes/components/status-line/segments.ts +39 -4
  82. package/src/modes/controllers/command-controller.ts +27 -2
  83. package/src/modes/controllers/event-controller.ts +3 -4
  84. package/src/modes/controllers/extension-ui-controller.ts +3 -2
  85. package/src/modes/interactive-mode.ts +1 -1
  86. package/src/modes/rpc/host-tools.ts +1 -1
  87. package/src/modes/rpc/host-uris.ts +235 -0
  88. package/src/modes/rpc/rpc-client.ts +1 -1
  89. package/src/modes/rpc/rpc-mode.ts +27 -1
  90. package/src/modes/rpc/rpc-types.ts +58 -1
  91. package/src/modes/runtime-init.ts +2 -1
  92. package/src/modes/theme/defaults/dark-poimandres.json +1 -0
  93. package/src/modes/theme/defaults/light-poimandres.json +1 -0
  94. package/src/modes/theme/theme.ts +117 -117
  95. package/src/modes/types.ts +1 -1
  96. package/src/modes/utils/context-usage.ts +2 -2
  97. package/src/prompts/tools/github.md +4 -4
  98. package/src/prompts/tools/hashline.md +22 -26
  99. package/src/prompts/tools/read.md +55 -37
  100. package/src/sdk.ts +31 -8
  101. package/src/session/agent-session.ts +74 -104
  102. package/src/session/messages.ts +16 -51
  103. package/src/session/session-manager.ts +22 -2
  104. package/src/session/streaming-output.ts +16 -6
  105. package/src/task/discovery.ts +5 -2
  106. package/src/task/executor.ts +210 -87
  107. package/src/task/index.ts +15 -11
  108. package/src/task/render.ts +32 -5
  109. package/src/task/types.ts +54 -39
  110. package/src/tools/ask.ts +12 -12
  111. package/src/tools/ast-edit.ts +11 -15
  112. package/src/tools/ast-grep.ts +9 -10
  113. package/src/tools/bash-command-fixup.ts +47 -0
  114. package/src/tools/bash.ts +48 -38
  115. package/src/tools/browser/render.ts +2 -2
  116. package/src/tools/browser.ts +39 -53
  117. package/src/tools/calculator.ts +12 -11
  118. package/src/tools/checkpoint.ts +7 -7
  119. package/src/tools/debug.ts +40 -43
  120. package/src/tools/eval.ts +16 -10
  121. package/src/tools/find.ts +10 -13
  122. package/src/tools/gh.ts +108 -132
  123. package/src/tools/hindsight-recall.ts +4 -6
  124. package/src/tools/hindsight-reflect.ts +5 -5
  125. package/src/tools/hindsight-retain.ts +15 -17
  126. package/src/tools/image-gen.ts +31 -81
  127. package/src/tools/index.ts +4 -1
  128. package/src/tools/inspect-image.ts +8 -9
  129. package/src/tools/irc.ts +15 -27
  130. package/src/tools/job.ts +30 -28
  131. package/src/tools/output-meta.ts +26 -0
  132. package/src/tools/read.ts +39 -12
  133. package/src/tools/recipe/index.ts +7 -9
  134. package/src/tools/render-mermaid.ts +12 -12
  135. package/src/tools/report-tool-issue.ts +4 -4
  136. package/src/tools/resolve.ts +11 -11
  137. package/src/tools/review.ts +14 -26
  138. package/src/tools/search-tool-bm25.ts +7 -9
  139. package/src/tools/search.ts +19 -22
  140. package/src/tools/ssh.ts +10 -9
  141. package/src/tools/todo-write.ts +26 -34
  142. package/src/tools/vim.ts +10 -26
  143. package/src/tools/write.ts +25 -5
  144. package/src/tools/yield.ts +100 -54
  145. package/src/web/search/index.ts +9 -24
  146. package/src/web/search/providers/anthropic.ts +5 -0
  147. package/src/web/search/providers/exa.ts +3 -0
  148. package/src/web/search/providers/gemini.ts +5 -0
  149. package/src/web/search/providers/jina.ts +5 -2
  150. package/src/web/search/providers/zai.ts +5 -2
  151. package/src/prompts/compaction/branch-summary-context.md +0 -5
  152. package/src/prompts/compaction/branch-summary-preamble.md +0 -2
  153. package/src/prompts/compaction/branch-summary.md +0 -30
  154. package/src/prompts/compaction/compaction-short-summary.md +0 -9
  155. package/src/prompts/compaction/compaction-summary-context.md +0 -5
  156. package/src/prompts/compaction/compaction-summary.md +0 -38
  157. package/src/prompts/compaction/compaction-turn-prefix.md +0 -17
  158. package/src/prompts/compaction/compaction-update-summary.md +0 -45
  159. package/src/prompts/system/auto-handoff-threshold-focus.md +0 -1
  160. package/src/prompts/system/file-operations.md +0 -10
  161. package/src/prompts/system/handoff-document.md +0 -49
  162. package/src/prompts/system/summarization-system.md +0 -3
  163. package/src/session/compaction/branch-summarization.ts +0 -324
  164. package/src/session/compaction/compaction.ts +0 -1420
  165. package/src/session/compaction/errors.ts +0 -31
  166. package/src/session/compaction/index.ts +0 -8
  167. package/src/session/compaction/pruning.ts +0 -91
  168. package/src/session/compaction/utils.ts +0 -184
@@ -1,8 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { StringEnum } from "@oh-my-pi/pi-ai";
3
+
4
4
  import { Text } from "@oh-my-pi/pi-tui";
5
- import { Type } from "@sinclair/typebox";
5
+ import * as z from "zod/v4";
6
6
  import type { ToolDefinition } from "../../extensibility/extensions";
7
7
  import type { Theme } from "../../modes/theme/theme";
8
8
  import { replaceTabs, truncateToWidth } from "../../tools/render-utils";
@@ -36,46 +36,37 @@ import type {
36
36
 
37
37
  const EXPERIMENT_TOOL_NAMES = ["init_experiment", "run_experiment", "log_experiment", "update_notes"];
38
38
 
39
- const logExperimentSchema = Type.Object({
40
- metric: Type.Number({
41
- description: "Primary metric value for this run. May differ from the parsed value; deviation is recorded.",
42
- }),
43
- status: StringEnum(["keep", "discard", "crash", "checks_failed"], {
44
- description: "Outcome for this run.",
45
- }),
46
- description: Type.String({ description: "Short description of the experiment." }),
47
- metrics: Type.Optional(
48
- Type.Record(Type.String(), Type.Number(), { description: "Secondary metrics for this run." }),
49
- ),
50
- asi: Type.Optional(
51
- Type.Object(
52
- {},
53
- {
54
- additionalProperties: Type.Unknown(),
55
- description: "Free-form structured metadata captured for this run (hypothesis, learnings, etc.).",
56
- },
57
- ),
58
- ),
59
- commit: Type.Optional(
60
- Type.String({ description: "Override the commit hash recorded for this run. Defaults to the current HEAD." }),
61
- ),
62
- justification: Type.Optional(
63
- Type.String({
64
- description:
65
- "Required when the run modifies paths outside scope or inside off-limits and you still want it kept. Free-form explanation.",
66
- }),
67
- ),
68
- flag_runs: Type.Optional(
69
- Type.Array(
70
- Type.Object({
71
- run_id: Type.Number({ description: "Run id (#) of a previously logged run to flag as suspect." }),
72
- reason: Type.String({
73
- description: "Why this earlier run is suspect (e.g. reward-hacked, broken metric).",
74
- }),
39
+ const logExperimentSchema = z.object({
40
+ metric: z
41
+ .number()
42
+ .describe("Primary metric value for this run. May differ from the parsed value; deviation is recorded."),
43
+ status: z.enum(["keep", "discard", "crash", "checks_failed"] as const).describe("Outcome for this run."),
44
+ description: z.string().describe("Short description of the experiment."),
45
+ metrics: z.record(z.string(), z.number()).describe("Secondary metrics for this run.").optional(),
46
+ asi: z
47
+ .object({})
48
+ .passthrough()
49
+ .describe("Free-form structured metadata captured for this run (hypothesis, learnings, etc.).")
50
+ .optional(),
51
+ commit: z
52
+ .string()
53
+ .describe("Override the commit hash recorded for this run. Defaults to the current HEAD.")
54
+ .optional(),
55
+ justification: z
56
+ .string()
57
+ .describe(
58
+ "Required when the run modifies paths outside scope or inside off-limits and you still want it kept. Free-form explanation.",
59
+ )
60
+ .optional(),
61
+ flag_runs: z
62
+ .array(
63
+ z.object({
64
+ run_id: z.number().describe("Run id (#) of a previously logged run to flag as suspect."),
65
+ reason: z.string().describe("Why this earlier run is suspect (e.g. reward-hacked, broken metric)."),
75
66
  }),
76
- { description: "Mark earlier runs as flagged. Flagged runs are excluded from baseline and best-metric math." },
77
- ),
78
- ),
67
+ )
68
+ .describe("Mark earlier runs as flagged. Flagged runs are excluded from baseline and best-metric math.")
69
+ .optional(),
79
70
  });
80
71
 
81
72
  export function createLogExperimentTool(
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { Text } from "@oh-my-pi/pi-tui";
5
5
  import { formatBytes } from "@oh-my-pi/pi-utils";
6
- import { Type } from "@sinclair/typebox";
6
+ import * as z from "zod/v4";
7
7
  import type { ToolDefinition } from "../../extensibility/extensions";
8
8
  import type { Theme } from "../../modes/theme/theme";
9
9
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, truncateTail } from "../../session/streaming-output";
@@ -26,8 +26,8 @@ import { openAutoresearchStorageIfExists } from "../storage";
26
26
  import type { AutoresearchToolFactoryOptions, RunDetails, RunExperimentProgressDetails } from "../types";
27
27
  import { DEFAULT_HARNESS_COMMAND } from "./init-experiment";
28
28
 
29
- const runExperimentSchema = Type.Object({
30
- timeout_seconds: Type.Optional(Type.Number({ description: "Timeout in seconds. Defaults to 600." })),
29
+ const runExperimentSchema = z.object({
30
+ timeout_seconds: z.number().describe("Timeout in seconds. Defaults to 600.").optional(),
31
31
  });
32
32
 
33
33
  interface ProcessExecutionResult {
@@ -1,5 +1,5 @@
1
1
  import { Text } from "@oh-my-pi/pi-tui";
2
- import { Type } from "@sinclair/typebox";
2
+ import * as z from "zod/v4";
3
3
  import type { ToolDefinition } from "../../extensibility/extensions";
4
4
  import type { Theme } from "../../modes/theme/theme";
5
5
  import { replaceTabs, truncateToWidth } from "../../tools/render-utils";
@@ -8,16 +8,16 @@ import { buildExperimentState } from "../state";
8
8
  import { openAutoresearchStorageIfExists } from "../storage";
9
9
  import type { AutoresearchToolFactoryOptions } from "../types";
10
10
 
11
- const updateNotesSchema = Type.Object({
12
- body: Type.String({
13
- description: "Replacement markdown body for the active autoresearch session's notes (your durable playbook).",
14
- }),
15
- append_idea: Type.Optional(
16
- Type.String({
17
- description:
18
- "When set, append this string as a new bullet under an Ideas section instead of replacing the body. `body` is ignored.",
19
- }),
20
- ),
11
+ const updateNotesSchema = z.object({
12
+ body: z
13
+ .string()
14
+ .describe("Replacement markdown body for the active autoresearch session's notes (your durable playbook)."),
15
+ append_idea: z
16
+ .string()
17
+ .describe(
18
+ "When set, append this string as a new bullet under an Ideas section instead of replacing the body. `body` is ignored.",
19
+ )
20
+ .optional(),
21
21
  });
22
22
 
23
23
  interface UpdateNotesDetails {
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Generate and optionally push a commit with changelog updates.
3
3
  */
4
+ import { postmortem } from "@oh-my-pi/pi-utils";
4
5
  import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
5
6
  import { runCommitCommand } from "../commit";
6
7
  import type { CommitCommandArgs } from "../commit/types";
@@ -31,6 +32,15 @@ export default class Commit extends Command {
31
32
  };
32
33
 
33
34
  await initTheme();
35
+ // The agentic commit flow opens HTTP/2 keep-alive sockets to the model
36
+ // provider (via `installH2Fetch`) and spins up an AgentSession with
37
+ // background async-job + extension machinery. `session.dispose()` releases
38
+ // what it can, but Bun's fetch keeps idle connections warm and a few
39
+ // timers (Settings autosave, OAuth refresh) stay armed long enough to
40
+ // pin the event loop after the commit is already written. Mirror the
41
+ // `runPrintMode` exit pattern from `main.ts` so the CLI returns to the
42
+ // shell instead of stranding the user on Ctrl+C (issue #1041).
34
43
  await runCommitCommand(cmd);
44
+ await postmortem.quit(0);
35
45
  }
36
46
  }
@@ -1,5 +1,5 @@
1
1
  import { prompt } from "@oh-my-pi/pi-utils";
2
- import { Type } from "@sinclair/typebox";
2
+ import * as z from "zod/v4";
3
3
  import analyzeFilePrompt from "../../../commit/agentic/prompts/analyze-file.md" with { type: "text" };
4
4
  import type { CommitAgentState } from "../../../commit/agentic/state";
5
5
  import type { NumstatEntry } from "../../../commit/types";
@@ -12,9 +12,9 @@ import type { TaskParams } from "../../../task/types";
12
12
  import type { ToolSession } from "../../../tools";
13
13
  import { getFilePriority } from "./git-file-diff";
14
14
 
15
- const analyzeFileSchema = Type.Object({
16
- files: Type.Array(Type.String({ description: "File path" }), { minItems: 1 }),
17
- goal: Type.Optional(Type.String({ description: "Optional analysis focus" })),
15
+ const analyzeFileSchema = z.object({
16
+ files: z.array(z.string().describe("File path")).min(1),
17
+ goal: z.string().describe("Optional analysis focus").optional(),
18
18
  });
19
19
 
20
20
  const analyzeFileOutputSchema = {
@@ -1,4 +1,4 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import * as z from "zod/v4";
2
2
  import type { CommitAgentState } from "../../../commit/agentic/state";
3
3
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
4
4
  import * as git from "../../../utils/git";
@@ -131,9 +131,9 @@ function processDiffs(files: string[], diffs: Map<string, string>): { result: st
131
131
  return { result: parts.join("\n\n"), truncatedFiles };
132
132
  }
133
133
 
134
- const gitFileDiffSchema = Type.Object({
135
- files: Type.Array(Type.String({ description: "Files to diff" }), { minItems: 1, maxItems: 10 }),
136
- staged: Type.Optional(Type.Boolean({ description: "Use staged changes (default: true)" })),
134
+ const gitFileDiffSchema = z.object({
135
+ files: z.array(z.string().describe("Files to diff")).min(1).max(10),
136
+ staged: z.boolean().describe("Use staged changes (default: true)").optional(),
137
137
  });
138
138
 
139
139
  export function createGitFileDiffTool(cwd: string, state: CommitAgentState): CustomTool<typeof gitFileDiffSchema> {
@@ -1,12 +1,12 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import * as z from "zod/v4";
2
2
  import type { DiffHunk, FileHunks } from "../../../commit/types";
3
3
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
4
4
  import * as git from "../../../utils/git";
5
5
 
6
- const gitHunkSchema = Type.Object({
7
- file: Type.String({ description: "File path" }),
8
- hunks: Type.Optional(Type.Array(Type.Number({ description: "1-based hunk indices" }), { minItems: 1 })),
9
- staged: Type.Optional(Type.Boolean({ description: "Use staged changes (default: true)" })),
6
+ const gitHunkSchema = z.object({
7
+ file: z.string().describe("File path"),
8
+ hunks: z.array(z.number().describe("1-based hunk indices")).min(1).optional(),
9
+ staged: z.boolean().describe("Use staged changes (default: true)").optional(),
10
10
  });
11
11
 
12
12
  function selectHunks(fileHunks: FileHunks, requested?: number[]): DiffHunk[] {
@@ -1,4 +1,4 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import * as z from "zod/v4";
2
2
  import type { CommitAgentState, GitOverviewSnapshot } from "../../../commit/agentic/state";
3
3
  import { extractScopeCandidates } from "../../../commit/analysis/scope";
4
4
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
@@ -42,9 +42,9 @@ function filterExcludedFiles(files: string[]): { filtered: string[]; excluded: s
42
42
  return { filtered, excluded };
43
43
  }
44
44
 
45
- const gitOverviewSchema = Type.Object({
46
- staged: Type.Optional(Type.Boolean({ description: "Use staged changes (default: true)" })),
47
- include_untracked: Type.Optional(Type.Boolean({ description: "Include untracked files when staged=false" })),
45
+ const gitOverviewSchema = z.object({
46
+ staged: z.boolean().describe("Use staged changes (default: true)").optional(),
47
+ include_untracked: z.boolean().describe("Include untracked files when staged=false").optional(),
48
48
  });
49
49
 
50
50
  export function createGitOverviewTool(cwd: string, state: CommitAgentState): CustomTool<typeof gitOverviewSchema> {
@@ -1,29 +1,29 @@
1
- import { type TSchema, Type } from "@sinclair/typebox";
1
+ import * as z from "zod/v4";
2
2
  import type { CommitAgentState } from "../../../commit/agentic/state";
3
3
  import { CHANGELOG_CATEGORIES, type ChangelogCategory } from "../../../commit/types";
4
4
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
5
5
 
6
- const changelogEntryProperties = CHANGELOG_CATEGORIES.reduce<Record<ChangelogCategory, TSchema>>(
6
+ const changelogEntryProperties = CHANGELOG_CATEGORIES.reduce<Record<ChangelogCategory, z.ZodType>>(
7
7
  (acc, category) => {
8
- acc[category] = Type.Optional(Type.Array(Type.String()));
8
+ acc[category] = z.array(z.string()).optional();
9
9
  return acc;
10
10
  },
11
- {} as Record<ChangelogCategory, TSchema>,
11
+ {} as Record<ChangelogCategory, z.ZodType>,
12
12
  );
13
13
 
14
- const changelogEntriesSchema = Type.Object(changelogEntryProperties);
15
- const changelogDeletionsSchema = Type.Object(changelogEntryProperties, {
16
- description: "Entries to remove from existing changelog sections (case-insensitive match)",
17
- });
14
+ const changelogEntriesSchema = z.object(changelogEntryProperties);
15
+ const changelogDeletionsSchema = z
16
+ .object(changelogEntryProperties)
17
+ .describe("Entries to remove from existing changelog sections (case-insensitive match)");
18
18
 
19
- const changelogEntrySchema = Type.Object({
20
- path: Type.String(),
19
+ const changelogEntrySchema = z.object({
20
+ path: z.string(),
21
21
  entries: changelogEntriesSchema,
22
- deletions: Type.Optional(changelogDeletionsSchema),
22
+ deletions: changelogDeletionsSchema.optional(),
23
23
  });
24
24
 
25
- const proposeChangelogSchema = Type.Object({
26
- entries: Type.Array(changelogEntrySchema),
25
+ const proposeChangelogSchema = z.object({
26
+ entries: z.array(changelogEntrySchema),
27
27
  });
28
28
 
29
29
  interface ChangelogResponse {
@@ -1,4 +1,4 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import * as z from "zod/v4";
2
2
  import type { CommitAgentState } from "../../../commit/agentic/state";
3
3
  import {
4
4
  capDetails,
@@ -15,12 +15,12 @@ import type { CustomTool } from "../../../extensibility/custom-tools/types";
15
15
  import * as git from "../../../utils/git";
16
16
  import { commitTypeSchema, detailSchema } from "./schemas.js";
17
17
 
18
- const proposeCommitSchema = Type.Object({
18
+ const proposeCommitSchema = z.object({
19
19
  type: commitTypeSchema,
20
- scope: Type.Union([Type.String(), Type.Null()]),
21
- summary: Type.String(),
22
- details: Type.Array(detailSchema),
23
- issue_refs: Type.Array(Type.String()),
20
+ scope: z.union([z.string(), z.null()]),
21
+ summary: z.string(),
22
+ details: z.array(detailSchema),
23
+ issue_refs: z.array(z.string()),
24
24
  });
25
25
 
26
26
  interface ProposalResponse {
@@ -1,9 +1,9 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import * as z from "zod/v4";
2
2
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
3
3
  import * as git from "../../../utils/git";
4
4
 
5
- const recentCommitsSchema = Type.Object({
6
- count: Type.Optional(Type.Number({ description: "Number of commits to fetch", minimum: 1, maximum: 50 })),
5
+ const recentCommitsSchema = z.object({
6
+ count: z.number().min(1).max(50).describe("Number of commits to fetch").optional(),
7
7
  });
8
8
 
9
9
  interface RecentCommitStats {
@@ -1,31 +1,31 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import * as z from "zod/v4";
2
2
 
3
- export const commitTypeSchema = Type.Union([
4
- Type.Literal("feat"),
5
- Type.Literal("fix"),
6
- Type.Literal("refactor"),
7
- Type.Literal("perf"),
8
- Type.Literal("docs"),
9
- Type.Literal("test"),
10
- Type.Literal("build"),
11
- Type.Literal("ci"),
12
- Type.Literal("chore"),
13
- Type.Literal("style"),
14
- Type.Literal("revert"),
15
- ]);
3
+ export const commitTypeSchema = z.enum([
4
+ "feat",
5
+ "fix",
6
+ "refactor",
7
+ "perf",
8
+ "docs",
9
+ "test",
10
+ "build",
11
+ "ci",
12
+ "chore",
13
+ "style",
14
+ "revert",
15
+ ] as const);
16
16
 
17
- export const detailSchema = Type.Object({
18
- text: Type.String(),
19
- changelog_category: Type.Optional(
20
- Type.Union([
21
- Type.Literal("Added"),
22
- Type.Literal("Changed"),
23
- Type.Literal("Fixed"),
24
- Type.Literal("Deprecated"),
25
- Type.Literal("Removed"),
26
- Type.Literal("Security"),
27
- Type.Literal("Breaking Changes"),
28
- ]),
29
- ),
30
- user_visible: Type.Optional(Type.Boolean()),
17
+ export const detailSchema = z.object({
18
+ text: z.string(),
19
+ changelog_category: z
20
+ .union([
21
+ z.literal("Added"),
22
+ z.literal("Changed"),
23
+ z.literal("Fixed"),
24
+ z.literal("Deprecated"),
25
+ z.literal("Removed"),
26
+ z.literal("Security"),
27
+ z.literal("Breaking Changes"),
28
+ ])
29
+ .optional(),
30
+ user_visible: z.boolean().optional(),
31
31
  });
@@ -1,4 +1,4 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import * as z from "zod/v4";
2
2
  import type { CommitAgentState, SplitCommitGroup, SplitCommitPlan } from "../../../commit/agentic/state";
3
3
  import { computeDependencyOrder } from "../../../commit/agentic/topo-sort";
4
4
  import {
@@ -15,31 +15,32 @@ import type { CustomTool } from "../../../extensibility/custom-tools/types";
15
15
  import * as git from "../../../utils/git";
16
16
  import { commitTypeSchema, detailSchema } from "./schemas.js";
17
17
 
18
- const hunkSelectorSchema = Type.Union([
19
- Type.Object({ type: Type.Literal("all") }),
20
- Type.Object({ type: Type.Literal("indices"), indices: Type.Array(Type.Number(), { minItems: 1 }) }),
21
- Type.Object({ type: Type.Literal("lines"), start: Type.Number(), end: Type.Number() }),
18
+ const hunkSelectorSchema = z.discriminatedUnion("type", [
19
+ z.object({ type: z.literal("all") }),
20
+ z.object({ type: z.literal("indices"), indices: z.array(z.number()).min(1) }),
21
+ z.object({ type: z.literal("lines"), start: z.number(), end: z.number() }),
22
22
  ]);
23
23
 
24
- const fileChangeSchema = Type.Object({
25
- path: Type.String(),
24
+ const fileChangeSchema = z.object({
25
+ path: z.string(),
26
26
  hunks: hunkSelectorSchema,
27
27
  });
28
28
 
29
- const splitCommitSchema = Type.Object({
30
- commits: Type.Array(
31
- Type.Object({
32
- changes: Type.Array(fileChangeSchema, { minItems: 1 }),
33
- type: commitTypeSchema,
34
- scope: Type.Union([Type.String(), Type.Null()]),
35
- summary: Type.String(),
36
- details: Type.Optional(Type.Array(detailSchema)),
37
- issue_refs: Type.Optional(Type.Array(Type.String())),
38
- rationale: Type.Optional(Type.String()),
39
- dependencies: Type.Optional(Type.Array(Type.Number())),
40
- }),
41
- { minItems: 2 },
42
- ),
29
+ const splitCommitSchema = z.object({
30
+ commits: z
31
+ .array(
32
+ z.object({
33
+ changes: z.array(fileChangeSchema).min(1),
34
+ type: commitTypeSchema,
35
+ scope: z.union([z.string(), z.null()]),
36
+ summary: z.string(),
37
+ details: z.array(detailSchema).optional(),
38
+ issue_refs: z.array(z.string()).optional(),
39
+ rationale: z.string().optional(),
40
+ dependencies: z.array(z.number()).optional(),
41
+ }),
42
+ )
43
+ .min(2),
43
44
  });
44
45
 
45
46
  interface SplitCommitResponse {
@@ -2,7 +2,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
3
3
  import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
4
4
  import { prompt } from "@oh-my-pi/pi-utils";
5
- import { Type } from "@sinclair/typebox";
5
+ import * as z from "zod/v4";
6
6
  import summarySystemPrompt from "../../commit/prompts/summary-system.md" with { type: "text" };
7
7
  import summaryUserPrompt from "../../commit/prompts/summary-user.md" with { type: "text" };
8
8
  import type { CommitSummary } from "../../commit/types";
@@ -12,8 +12,8 @@ import { extractTextContent, extractToolCall } from "../utils";
12
12
  const SummaryTool = {
13
13
  name: "create_commit_summary",
14
14
  description: "Generate the summary line for a conventional commit message.",
15
- parameters: Type.Object({
16
- summary: Type.String(),
15
+ parameters: z.object({
16
+ summary: z.string(),
17
17
  }),
18
18
  };
19
19
 
@@ -83,7 +83,7 @@ function renderSummaryPrompt({
83
83
  function parseSummaryFromResponse(message: AssistantMessage, commitType: string, scope: string | null): CommitSummary {
84
84
  const toolCall = extractToolCall(message, "create_commit_summary");
85
85
  if (toolCall) {
86
- const parsed = validateToolCall([SummaryTool], toolCall) as { summary: string };
86
+ const parsed = validateToolCall([SummaryTool], toolCall) as z.infer<(typeof SummaryTool)["parameters"]>;
87
87
  return { summary: stripTypePrefix(parsed.summary, commitType, scope) };
88
88
  }
89
89
  const text = extractTextContent(message);
@@ -2,27 +2,23 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
3
3
  import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
4
4
  import { prompt } from "@oh-my-pi/pi-utils";
5
- import { type TSchema, Type } from "@sinclair/typebox";
5
+ import * as z from "zod/v4";
6
6
  import changelogSystemPrompt from "../../commit/prompts/changelog-system.md" with { type: "text" };
7
7
  import changelogUserPrompt from "../../commit/prompts/changelog-user.md" with { type: "text" };
8
8
  import { CHANGELOG_CATEGORIES, type ChangelogCategory, type ChangelogGenerationResult } from "../../commit/types";
9
9
  import { toReasoningEffort } from "../../thinking";
10
10
  import { extractTextContent, extractToolCall, parseJsonPayload } from "../utils";
11
11
 
12
- const changelogEntryProperties = CHANGELOG_CATEGORIES.reduce<Record<ChangelogCategory, TSchema>>(
13
- (acc, category) => {
14
- acc[category] = Type.Optional(Type.Array(Type.String()));
15
- return acc;
16
- },
17
- {} as Record<ChangelogCategory, TSchema>,
18
- );
12
+ const changelogEntryShape = Object.fromEntries(
13
+ CHANGELOG_CATEGORIES.map(c => [c, z.array(z.string()).optional()] as const),
14
+ ) as Record<ChangelogCategory, z.ZodOptional<z.ZodArray<z.ZodString>>>;
19
15
 
20
- const changelogEntriesSchema = Type.Object(changelogEntryProperties);
16
+ const changelogEntriesSchema = z.object(changelogEntryShape);
21
17
 
22
18
  export const changelogTool = {
23
19
  name: "create_changelog_entries",
24
20
  description: "Generate changelog entries grouped by Keep a Changelog categories.",
25
- parameters: Type.Object({
21
+ parameters: z.object({
26
22
  entries: changelogEntriesSchema,
27
23
  }),
28
24
  };
@@ -72,7 +68,7 @@ export async function generateChangelogEntries({
72
68
  function parseChangelogResponse(message: AssistantMessage): ChangelogGenerationResult {
73
69
  const toolCall = extractToolCall(message, "create_changelog_entries");
74
70
  if (toolCall) {
75
- const parsed = validateToolCall([changelogTool], toolCall) as ChangelogGenerationResult;
71
+ const parsed = validateToolCall([changelogTool], toolCall) as z.infer<(typeof changelogTool)["parameters"]>;
76
72
  return { entries: parsed.entries ?? {} };
77
73
  }
78
74
 
@@ -1,48 +1,36 @@
1
1
  import type { AssistantMessage } from "@oh-my-pi/pi-ai";
2
2
  import { validateToolCall } from "@oh-my-pi/pi-ai";
3
- import { Type } from "@sinclair/typebox";
3
+ import * as z from "zod/v4";
4
4
  import type { ChangelogCategory, ConventionalAnalysis } from "./types";
5
5
  import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "./utils";
6
6
 
7
+ const changelogCategoryLiteral = z.enum([
8
+ "Added",
9
+ "Changed",
10
+ "Fixed",
11
+ "Deprecated",
12
+ "Removed",
13
+ "Security",
14
+ "Breaking Changes",
15
+ ]);
16
+
7
17
  /**
8
- * Shared TypeBox schema for the `create_conventional_analysis` tool used by
18
+ * Shared Zod schema for the `create_conventional_analysis` tool used by
9
19
  * both the single-pass analysis call and the map-reduce reduce phase. Schemas
10
20
  * are identical across phases — only the surrounding tool `description`
11
21
  * differs to reflect the input the phase is summarizing.
12
22
  */
13
- export const conventionalAnalysisParameters = Type.Object({
14
- type: Type.Union([
15
- Type.Literal("feat"),
16
- Type.Literal("fix"),
17
- Type.Literal("refactor"),
18
- Type.Literal("docs"),
19
- Type.Literal("test"),
20
- Type.Literal("chore"),
21
- Type.Literal("style"),
22
- Type.Literal("perf"),
23
- Type.Literal("build"),
24
- Type.Literal("ci"),
25
- Type.Literal("revert"),
26
- ]),
27
- scope: Type.Union([Type.String(), Type.Null()]),
28
- details: Type.Array(
29
- Type.Object({
30
- text: Type.String(),
31
- changelog_category: Type.Optional(
32
- Type.Union([
33
- Type.Literal("Added"),
34
- Type.Literal("Changed"),
35
- Type.Literal("Fixed"),
36
- Type.Literal("Deprecated"),
37
- Type.Literal("Removed"),
38
- Type.Literal("Security"),
39
- Type.Literal("Breaking Changes"),
40
- ]),
41
- ),
42
- user_visible: Type.Optional(Type.Boolean()),
23
+ export const conventionalAnalysisParameters = z.object({
24
+ type: z.enum(["feat", "fix", "refactor", "docs", "test", "chore", "style", "perf", "build", "ci", "revert"]),
25
+ scope: z.union([z.string(), z.null()]),
26
+ details: z.array(
27
+ z.object({
28
+ text: z.string(),
29
+ changelog_category: changelogCategoryLiteral.optional(),
30
+ user_visible: z.boolean().optional(),
43
31
  }),
44
32
  ),
45
- issue_refs: Type.Array(Type.String()),
33
+ issue_refs: z.array(z.string()),
46
34
  });
47
35
 
48
36
  export interface ConventionalAnalysisTool {
@@ -80,7 +68,7 @@ export function parseConventionalAnalysisResponse(
80
68
  ): ConventionalAnalysis {
81
69
  const toolCall = extractToolCall(message, tool.name);
82
70
  if (toolCall) {
83
- const parsed = validateToolCall([tool], toolCall) as ParsedConventionalAnalysis;
71
+ const parsed = validateToolCall([tool], toolCall) as z.infer<typeof conventionalAnalysisParameters>;
84
72
  return normalizeAnalysis(parsed);
85
73
  }
86
74
  const text = extractTextContent(message);