@oh-my-pi/pi-coding-agent 4.4.6 → 4.4.9
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.
- package/CHANGELOG.md +8 -0
- package/examples/extensions/api-demo.ts +4 -1
- package/package.json +5 -5
- package/src/core/tools/ask.ts +1 -5
- package/src/core/tools/calculator.ts +1 -1
- package/src/core/tools/complete.ts +3 -3
- package/src/core/tools/exa/search.ts +3 -2
- package/src/core/tools/find.ts +2 -1
- package/src/core/tools/gemini-image.ts +6 -6
- package/src/core/tools/git.ts +30 -53
- package/src/core/tools/grep.ts +2 -1
- package/src/core/tools/lsp/types.ts +20 -19
- package/src/core/tools/notebook.ts +3 -2
- package/src/core/tools/output.ts +2 -3
- package/src/core/tools/review.ts +29 -19
- package/src/core/tools/schema-validation.test.ts +4 -6
- package/src/core/tools/task/render.ts +19 -28
- package/src/core/tools/task/types.ts +2 -3
- package/src/core/tools/todo-write.ts +4 -3
- package/src/core/tools/web-search/index.ts +6 -5
- package/src/index.ts +3 -12
- package/src/modes/interactive/theme/theme.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.4.9] - 2026-01-12
|
|
6
|
+
|
|
7
|
+
## [4.4.8] - 2026-01-12
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- Changed review finding priority format from numeric (0-3) to string labels (P0-P3) for clearer severity indication
|
|
11
|
+
- Replaced Type.Union with Type.Literal patterns with StringEnum helper across tool schemas for cleaner enum definitions
|
|
12
|
+
|
|
5
13
|
## [4.4.6] - 2026-01-11
|
|
6
14
|
|
|
7
15
|
## [4.4.5] - 2026-01-11
|
|
@@ -16,6 +16,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
16
16
|
pi.logger.debug("API demo extension loaded");
|
|
17
17
|
|
|
18
18
|
// 3. Register a tool that uses all three API features
|
|
19
|
+
// Import StringEnum from typebox helpers
|
|
20
|
+
const { StringEnum } = pi.pi;
|
|
21
|
+
|
|
19
22
|
pi.registerTool({
|
|
20
23
|
name: "api_demo",
|
|
21
24
|
label: "API Demo",
|
|
@@ -23,7 +26,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
23
26
|
parameters: Type.Object({
|
|
24
27
|
message: Type.String({ description: "Test message" }),
|
|
25
28
|
logLevel: Type.Optional(
|
|
26
|
-
|
|
29
|
+
StringEnum(["error", "warn", "debug"], {
|
|
27
30
|
description: "Log level to use",
|
|
28
31
|
default: "debug",
|
|
29
32
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.9",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"prepublishOnly": "bun run generate-template && bun run clean && bun run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "4.4.
|
|
43
|
-
"@oh-my-pi/pi-agent-core": "4.4.
|
|
44
|
-
"@oh-my-pi/pi-git-tool": "4.4.
|
|
45
|
-
"@oh-my-pi/pi-tui": "4.4.
|
|
42
|
+
"@oh-my-pi/pi-ai": "4.4.9",
|
|
43
|
+
"@oh-my-pi/pi-agent-core": "4.4.9",
|
|
44
|
+
"@oh-my-pi/pi-git-tool": "4.4.9",
|
|
45
|
+
"@oh-my-pi/pi-tui": "4.4.9",
|
|
46
46
|
"@openai/agents": "^0.3.7",
|
|
47
47
|
"@sinclair/typebox": "^0.34.46",
|
|
48
48
|
"ajv": "^8.17.1",
|
package/src/core/tools/ask.ts
CHANGED
|
@@ -36,14 +36,10 @@ const OptionItem = Type.Object({
|
|
|
36
36
|
|
|
37
37
|
const askSchema = Type.Object({
|
|
38
38
|
question: Type.String({ description: "The question to ask the user" }),
|
|
39
|
-
options: Type.Array(OptionItem, {
|
|
40
|
-
description: "Available options for the user to choose from.",
|
|
41
|
-
minItems: 1,
|
|
42
|
-
}),
|
|
39
|
+
options: Type.Array(OptionItem, { description: "Available options for the user to choose from." }),
|
|
43
40
|
multi: Type.Optional(
|
|
44
41
|
Type.Boolean({
|
|
45
42
|
description: "Allow multiple options to be selected (default: false)",
|
|
46
|
-
default: false,
|
|
47
43
|
}),
|
|
48
44
|
),
|
|
49
45
|
});
|
|
@@ -44,7 +44,7 @@ const calculatorSchema = Type.Object({
|
|
|
44
44
|
prefix: Type.String({ description: "Text to prepend to the result" }),
|
|
45
45
|
suffix: Type.String({ description: "Text to append to the result" }),
|
|
46
46
|
}),
|
|
47
|
-
{ description: "List of calculations to evaluate"
|
|
47
|
+
{ description: "List of calculations to evaluate" },
|
|
48
48
|
),
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
8
9
|
import { Type } from "@sinclair/typebox";
|
|
9
10
|
import Ajv, { type ErrorObject, type ValidateFunction } from "ajv";
|
|
10
11
|
import type { ToolSession } from "./index";
|
|
@@ -81,9 +82,8 @@ export function createCompleteTool(session: ToolSession) {
|
|
|
81
82
|
const completeParams = Type.Object({
|
|
82
83
|
data: Type.Optional(dataSchema),
|
|
83
84
|
status: Type.Optional(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
description: "Use 'aborted' if the task cannot be completed",
|
|
85
|
+
StringEnum(["success", "aborted"], {
|
|
86
|
+
description: "Use 'aborted' if the task cannot be completed, defaults to 'success'",
|
|
87
87
|
}),
|
|
88
88
|
),
|
|
89
89
|
error: Type.Optional(Type.String({ description: "Error message when status is 'aborted'" })),
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Basic neural/keyword search, deep research, code search, and URL crawling.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
7
8
|
import { Type } from "@sinclair/typebox";
|
|
8
9
|
import type { CustomTool } from "../../custom-tools/types";
|
|
9
10
|
import type { ExaRenderDetails } from "./types";
|
|
@@ -31,7 +32,7 @@ Parameters:
|
|
|
31
32
|
parameters: Type.Object({
|
|
32
33
|
query: Type.String({ description: "Search query" }),
|
|
33
34
|
type: Type.Optional(
|
|
34
|
-
|
|
35
|
+
StringEnum(["keyword", "neural", "auto"], {
|
|
35
36
|
description: "Search type - neural (semantic), keyword (exact), or auto",
|
|
36
37
|
}),
|
|
37
38
|
),
|
|
@@ -128,7 +129,7 @@ Similar parameters to exa_search, optimized for research depth.`,
|
|
|
128
129
|
parameters: Type.Object({
|
|
129
130
|
query: Type.String({ description: "Research query" }),
|
|
130
131
|
type: Type.Optional(
|
|
131
|
-
|
|
132
|
+
StringEnum(["keyword", "neural", "auto"], {
|
|
132
133
|
description: "Search type - neural (semantic), keyword (exact), or auto",
|
|
133
134
|
}),
|
|
134
135
|
),
|
package/src/core/tools/find.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
3
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
5
6
|
import { Type } from "@sinclair/typebox";
|
|
@@ -25,7 +26,7 @@ const findSchema = Type.Object({
|
|
|
25
26
|
Type.Boolean({ description: "Sort results by modification time, most recent first (default: false)" }),
|
|
26
27
|
),
|
|
27
28
|
type: Type.Optional(
|
|
28
|
-
|
|
29
|
+
StringEnum(["file", "dir", "all"], {
|
|
29
30
|
description:
|
|
30
31
|
"Filter by type: 'file' for files only, 'dir' for directories only, 'all' for both (default: 'all')",
|
|
31
32
|
}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { tmpdir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { type Static, Type } from "@sinclair/typebox";
|
|
4
5
|
import { nanoid } from "nanoid";
|
|
5
6
|
import geminiImageDescription from "../../prompts/tools/gemini-image.md" with { type: "text" };
|
|
@@ -21,12 +22,11 @@ interface ImageApiKey {
|
|
|
21
22
|
apiKey: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const responseModalitySchema =
|
|
25
|
-
const aspectRatioSchema =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const imageSizeSchema = Type.Union([Type.Literal("1024x1024"), Type.Literal("1536x1024"), Type.Literal("1024x1536")], {
|
|
25
|
+
const responseModalitySchema = StringEnum(["Image", "Text"]);
|
|
26
|
+
const aspectRatioSchema = StringEnum(["1:1", "3:4", "4:3", "9:16", "16:9"], {
|
|
27
|
+
description: "Aspect ratio (1:1, 3:4, 4:3, 9:16, 16:9).",
|
|
28
|
+
});
|
|
29
|
+
const imageSizeSchema = StringEnum(["1024x1024", "1536x1024", "1024x1536"], {
|
|
30
30
|
description: "Image size, mainly for gemini-3-pro-image-preview.",
|
|
31
31
|
});
|
|
32
32
|
|
package/src/core/tools/git.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { type GitParams, gitTool as gitToolCore, type ToolResponse } from "@oh-my-pi/pi-git-tool";
|
|
3
4
|
import { type Static, Type } from "@sinclair/typebox";
|
|
4
5
|
import gitDescription from "../../prompts/tools/git.md" with { type: "text" };
|
|
@@ -6,50 +7,38 @@ import { renderPromptTemplate } from "../prompt-templates";
|
|
|
6
7
|
import type { ToolSession } from "./index";
|
|
7
8
|
|
|
8
9
|
const gitSchema = Type.Object({
|
|
9
|
-
operation:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
10
|
+
operation: StringEnum([
|
|
11
|
+
"status",
|
|
12
|
+
"diff",
|
|
13
|
+
"log",
|
|
14
|
+
"show",
|
|
15
|
+
"blame",
|
|
16
|
+
"branch",
|
|
17
|
+
"add",
|
|
18
|
+
"restore",
|
|
19
|
+
"commit",
|
|
20
|
+
"checkout",
|
|
21
|
+
"merge",
|
|
22
|
+
"rebase",
|
|
23
|
+
"stash",
|
|
24
|
+
"cherry-pick",
|
|
25
|
+
"fetch",
|
|
26
|
+
"pull",
|
|
27
|
+
"push",
|
|
28
|
+
"tag",
|
|
29
|
+
"pr",
|
|
30
|
+
"issue",
|
|
31
|
+
"ci",
|
|
32
|
+
"release",
|
|
32
33
|
]),
|
|
33
34
|
|
|
34
35
|
// Status
|
|
35
|
-
only: Type.Optional(
|
|
36
|
-
Type.Union([
|
|
37
|
-
Type.Literal("branch"),
|
|
38
|
-
Type.Literal("modified"),
|
|
39
|
-
Type.Literal("staged"),
|
|
40
|
-
Type.Literal("untracked"),
|
|
41
|
-
Type.Literal("conflicts"),
|
|
42
|
-
Type.Literal("sync"),
|
|
43
|
-
]),
|
|
44
|
-
),
|
|
36
|
+
only: Type.Optional(StringEnum(["branch", "modified", "staged", "untracked", "conflicts", "sync"])),
|
|
45
37
|
ignored: Type.Optional(Type.Boolean()),
|
|
46
38
|
|
|
47
39
|
// Diff
|
|
48
40
|
target: Type.Optional(
|
|
49
41
|
Type.Union([
|
|
50
|
-
Type.Literal("unstaged"),
|
|
51
|
-
Type.Literal("staged"),
|
|
52
|
-
Type.Literal("head"),
|
|
53
42
|
Type.Object({
|
|
54
43
|
from: Type.String(),
|
|
55
44
|
to: Type.Optional(Type.String()),
|
|
@@ -71,7 +60,7 @@ const gitSchema = Type.Object({
|
|
|
71
60
|
since: Type.Optional(Type.String()),
|
|
72
61
|
until: Type.Optional(Type.String()),
|
|
73
62
|
grep: Type.Optional(Type.String()),
|
|
74
|
-
format: Type.Optional(
|
|
63
|
+
format: Type.Optional(StringEnum(["oneline", "short", "full"])),
|
|
75
64
|
stat: Type.Optional(Type.Boolean()),
|
|
76
65
|
merges: Type.Optional(Type.Boolean()),
|
|
77
66
|
first_parent: Type.Optional(Type.Boolean()),
|
|
@@ -90,15 +79,7 @@ const gitSchema = Type.Object({
|
|
|
90
79
|
root: Type.Optional(Type.Boolean()),
|
|
91
80
|
|
|
92
81
|
// Branch
|
|
93
|
-
action: Type.Optional(
|
|
94
|
-
Type.Union([
|
|
95
|
-
Type.Literal("list"),
|
|
96
|
-
Type.Literal("create"),
|
|
97
|
-
Type.Literal("delete"),
|
|
98
|
-
Type.Literal("rename"),
|
|
99
|
-
Type.Literal("current"),
|
|
100
|
-
]),
|
|
101
|
-
),
|
|
82
|
+
action: Type.Optional(StringEnum(["list", "create", "delete", "rename", "current"])),
|
|
102
83
|
name: Type.Optional(Type.String()),
|
|
103
84
|
newName: Type.Optional(Type.String()),
|
|
104
85
|
startPoint: Type.Optional(Type.String()),
|
|
@@ -165,13 +146,9 @@ const gitSchema = Type.Object({
|
|
|
165
146
|
base: Type.Optional(Type.String()),
|
|
166
147
|
head: Type.Optional(Type.String()),
|
|
167
148
|
draft: Type.Optional(Type.Boolean()),
|
|
168
|
-
state: Type.Optional(
|
|
169
|
-
|
|
170
|
-
),
|
|
171
|
-
merge_method: Type.Optional(Type.Union([Type.Literal("merge"), Type.Literal("squash"), Type.Literal("rebase")])),
|
|
172
|
-
review_action: Type.Optional(
|
|
173
|
-
Type.Union([Type.Literal("approve"), Type.Literal("request-changes"), Type.Literal("comment")]),
|
|
174
|
-
),
|
|
149
|
+
state: Type.Optional(StringEnum(["open", "closed", "merged", "all"])),
|
|
150
|
+
merge_method: Type.Optional(StringEnum(["merge", "squash", "rebase"])),
|
|
151
|
+
review_action: Type.Optional(StringEnum(["approve", "request-changes", "comment"])),
|
|
175
152
|
review_body: Type.Optional(Type.String()),
|
|
176
153
|
|
|
177
154
|
// Issue
|
package/src/core/tools/grep.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import nodePath from "node:path";
|
|
2
2
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
3
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
5
6
|
import { Type } from "@sinclair/typebox";
|
|
@@ -44,7 +45,7 @@ const grepSchema = Type.Object({
|
|
|
44
45
|
),
|
|
45
46
|
limit: Type.Optional(Type.Number({ description: "Maximum number of matches to return (default: 100)" })),
|
|
46
47
|
outputMode: Type.Optional(
|
|
47
|
-
|
|
48
|
+
StringEnum(["content", "files_with_matches", "count"], {
|
|
48
49
|
description:
|
|
49
50
|
"Output mode: 'content' shows matching lines, 'files_with_matches' shows only file paths, 'count' shows match counts per file (default: 'content')",
|
|
50
51
|
}),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
1
2
|
import { type Static, Type } from "@sinclair/typebox";
|
|
2
3
|
import type { Subprocess } from "bun";
|
|
3
4
|
|
|
@@ -6,28 +7,28 @@ import type { Subprocess } from "bun";
|
|
|
6
7
|
// =============================================================================
|
|
7
8
|
|
|
8
9
|
export const lspSchema = Type.Object({
|
|
9
|
-
action:
|
|
10
|
+
action: StringEnum(
|
|
10
11
|
[
|
|
11
12
|
// Standard LSP operations
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
"diagnostics",
|
|
14
|
+
"workspace_diagnostics",
|
|
15
|
+
"references",
|
|
16
|
+
"definition",
|
|
17
|
+
"hover",
|
|
18
|
+
"symbols",
|
|
19
|
+
"workspace_symbols",
|
|
20
|
+
"rename",
|
|
21
|
+
"actions",
|
|
22
|
+
"incoming_calls",
|
|
23
|
+
"outgoing_calls",
|
|
24
|
+
"status",
|
|
24
25
|
// Rust-analyzer specific operations
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
"flycheck",
|
|
27
|
+
"expand_macro",
|
|
28
|
+
"ssr",
|
|
29
|
+
"runnables",
|
|
30
|
+
"related_tests",
|
|
31
|
+
"reload_workspace",
|
|
31
32
|
],
|
|
32
33
|
{ description: "LSP action to perform" },
|
|
33
34
|
),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
4
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
5
|
import { Type } from "@sinclair/typebox";
|
|
@@ -17,14 +18,14 @@ import {
|
|
|
17
18
|
} from "./render-utils";
|
|
18
19
|
|
|
19
20
|
const notebookSchema = Type.Object({
|
|
20
|
-
action:
|
|
21
|
+
action: StringEnum(["edit", "insert", "delete"], {
|
|
21
22
|
description: "Action to perform on the notebook cell",
|
|
22
23
|
}),
|
|
23
24
|
notebook_path: Type.String({ description: "Path to the .ipynb file (relative or absolute)" }),
|
|
24
25
|
cell_index: Type.Number({ description: "0-based index of the cell to operate on" }),
|
|
25
26
|
content: Type.Optional(Type.String({ description: "New cell content (required for edit/insert)" })),
|
|
26
27
|
cell_type: Type.Optional(
|
|
27
|
-
|
|
28
|
+
StringEnum(["code", "markdown"], {
|
|
28
29
|
description: "Cell type for insert (default: code)",
|
|
29
30
|
}),
|
|
30
31
|
),
|
package/src/core/tools/output.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
10
|
-
import type
|
|
10
|
+
import { StringEnum, type TextContent } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
12
12
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
13
13
|
import { Type } from "@sinclair/typebox";
|
|
@@ -30,10 +30,9 @@ import { getArtifactsDir } from "./task/artifacts";
|
|
|
30
30
|
const outputSchema = Type.Object({
|
|
31
31
|
ids: Type.Array(Type.String(), {
|
|
32
32
|
description: "Agent output IDs to read (e.g., ['reviewer_0', 'explore_1'])",
|
|
33
|
-
minItems: 1,
|
|
34
33
|
}),
|
|
35
34
|
format: Type.Optional(
|
|
36
|
-
|
|
35
|
+
StringEnum(["raw", "json", "stripped"], {
|
|
37
36
|
description: "Output format: raw (default), json (structured), stripped (no ANSI)",
|
|
38
37
|
}),
|
|
39
38
|
),
|
package/src/core/tools/review.ts
CHANGED
|
@@ -12,24 +12,33 @@ import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
|
12
12
|
import { Type } from "@sinclair/typebox";
|
|
13
13
|
import type { Theme, ThemeColor } from "../../modes/interactive/theme/theme";
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
export type FindingPriority = "P0" | "P1" | "P2" | "P3";
|
|
16
|
+
|
|
17
|
+
export interface FindingPriorityInfo {
|
|
18
|
+
ord: 0 | 1 | 2 | 3;
|
|
19
|
+
symbol: "status.error" | "status.warning" | "status.info";
|
|
20
|
+
color: ThemeColor;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PRIORITY_INFO: Record<FindingPriority, FindingPriorityInfo> = {
|
|
24
|
+
P0: { ord: 0, symbol: "status.error", color: "error" },
|
|
25
|
+
P1: { ord: 1, symbol: "status.warning", color: "warning" },
|
|
26
|
+
P2: { ord: 2, symbol: "status.warning", color: "muted" },
|
|
27
|
+
P3: { ord: 3, symbol: "status.info", color: "accent" },
|
|
20
28
|
};
|
|
21
29
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
3: { symbol: "status.info", color: "accent" },
|
|
28
|
-
};
|
|
30
|
+
export const PRIORITY_LABELS: FindingPriority[] = ["P0", "P1", "P2", "P3"];
|
|
31
|
+
|
|
32
|
+
export function getPriorityInfo(priority: FindingPriority): FindingPriorityInfo {
|
|
33
|
+
return PRIORITY_INFO[priority] ?? { ord: 3, symbol: "status.info", color: "muted" };
|
|
34
|
+
}
|
|
29
35
|
|
|
30
|
-
function getPriorityDisplay(
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
function getPriorityDisplay(
|
|
37
|
+
priority: FindingPriority,
|
|
38
|
+
theme: Theme,
|
|
39
|
+
): { label: string; icon: string; color: ThemeColor } {
|
|
40
|
+
const label = priority;
|
|
41
|
+
const meta = PRIORITY_INFO[priority] ?? { symbol: "status.info", color: "muted" as const };
|
|
33
42
|
return {
|
|
34
43
|
label,
|
|
35
44
|
icon: theme.styledSymbol(meta.symbol, meta.color),
|
|
@@ -45,7 +54,7 @@ const ReportFindingParams = Type.Object({
|
|
|
45
54
|
body: Type.String({
|
|
46
55
|
description: "Markdown explaining why this is a problem. One paragraph max.",
|
|
47
56
|
}),
|
|
48
|
-
priority:
|
|
57
|
+
priority: StringEnum(["P0", "P1", "P2", "P3"], {
|
|
49
58
|
description: "0=P0 (critical), 1=P1 (urgent), 2=P2 (normal), 3=P3 (low)",
|
|
50
59
|
}),
|
|
51
60
|
confidence: Type.Number({
|
|
@@ -61,7 +70,7 @@ const ReportFindingParams = Type.Object({
|
|
|
61
70
|
interface ReportFindingDetails {
|
|
62
71
|
title: string;
|
|
63
72
|
body: string;
|
|
64
|
-
priority:
|
|
73
|
+
priority: FindingPriority;
|
|
65
74
|
confidence: number;
|
|
66
75
|
file_path: string;
|
|
67
76
|
line_start: number;
|
|
@@ -81,7 +90,7 @@ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFind
|
|
|
81
90
|
content: [
|
|
82
91
|
{
|
|
83
92
|
type: "text",
|
|
84
|
-
text: `Finding recorded: ${
|
|
93
|
+
text: `Finding recorded: ${priority} ${title}\nLocation: ${location}\nConfidence: ${(
|
|
85
94
|
confidence * 100
|
|
86
95
|
).toFixed(0)}%`,
|
|
87
96
|
},
|
|
@@ -91,7 +100,7 @@ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFind
|
|
|
91
100
|
},
|
|
92
101
|
|
|
93
102
|
renderCall(args, theme): Component {
|
|
94
|
-
const { label, icon, color } = getPriorityDisplay(args.priority
|
|
103
|
+
const { label, icon, color } = getPriorityDisplay(args.priority, theme);
|
|
95
104
|
const titleText = String(args.title).replace(/^\[P\d\]\s*/, "");
|
|
96
105
|
return new Text(
|
|
97
106
|
`${theme.fg("toolTitle", theme.bold("report_finding "))}${icon} ${theme.fg(color, `[${label}]`)} ${theme.fg(
|
|
@@ -141,6 +150,7 @@ export type { ReportFindingDetails };
|
|
|
141
150
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
142
151
|
|
|
143
152
|
import path from "node:path";
|
|
153
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
144
154
|
import { subprocessToolRegistry } from "./task/subprocess-tool-registry";
|
|
145
155
|
|
|
146
156
|
// Register report_finding handler
|
|
@@ -130,7 +130,7 @@ describe("sanitizeSchemaForGoogle", () => {
|
|
|
130
130
|
expect(sanitized).toEqual({ type: "string", enum: ["active", "inactive"] });
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
-
it("
|
|
133
|
+
it("collapses anyOf with const values into enum", () => {
|
|
134
134
|
const schema = {
|
|
135
135
|
anyOf: [
|
|
136
136
|
{ type: "string", const: "file" },
|
|
@@ -138,11 +138,10 @@ describe("sanitizeSchemaForGoogle", () => {
|
|
|
138
138
|
],
|
|
139
139
|
};
|
|
140
140
|
const sanitized = sanitizeSchemaForGoogle(schema);
|
|
141
|
+
// anyOf with all const values should collapse into a single enum
|
|
141
142
|
expect(sanitized).toEqual({
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
{ type: "string", enum: ["dir"] },
|
|
145
|
-
],
|
|
143
|
+
type: "string",
|
|
144
|
+
enum: ["file", "dir"],
|
|
146
145
|
});
|
|
147
146
|
});
|
|
148
147
|
|
|
@@ -179,7 +178,6 @@ describe("sanitizeSchemaForGoogle", () => {
|
|
|
179
178
|
type: "string",
|
|
180
179
|
enum: ["value"],
|
|
181
180
|
description: "A description",
|
|
182
|
-
minLength: 1,
|
|
183
181
|
});
|
|
184
182
|
});
|
|
185
183
|
|
|
@@ -18,18 +18,16 @@ import {
|
|
|
18
18
|
formatTokens,
|
|
19
19
|
truncate,
|
|
20
20
|
} from "../render-utils";
|
|
21
|
-
import
|
|
21
|
+
import {
|
|
22
|
+
type FindingPriority,
|
|
23
|
+
getPriorityInfo,
|
|
24
|
+
PRIORITY_LABELS,
|
|
25
|
+
type ReportFindingDetails,
|
|
26
|
+
type SubmitReviewDetails,
|
|
27
|
+
} from "../review";
|
|
22
28
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
23
29
|
import type { AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types";
|
|
24
30
|
|
|
25
|
-
/** Priority labels for review findings */
|
|
26
|
-
const PRIORITY_LABELS: Record<number, string> = {
|
|
27
|
-
0: "P0",
|
|
28
|
-
1: "P1",
|
|
29
|
-
2: "P2",
|
|
30
|
-
3: "P3",
|
|
31
|
-
};
|
|
32
|
-
|
|
33
31
|
/**
|
|
34
32
|
* Get status icon for agent state.
|
|
35
33
|
* For running status, uses animated spinner if spinnerFrame is provided.
|
|
@@ -53,25 +51,17 @@ function getStatusIcon(status: AgentProgress["status"], theme: Theme, spinnerFra
|
|
|
53
51
|
function formatFindingSummary(findings: ReportFindingDetails[], theme: Theme): string {
|
|
54
52
|
if (findings.length === 0) return theme.fg("dim", "Findings: none");
|
|
55
53
|
|
|
56
|
-
const counts
|
|
54
|
+
const counts: { [P in FindingPriority]?: number } = {};
|
|
57
55
|
for (const finding of findings) {
|
|
58
|
-
counts
|
|
56
|
+
counts[finding.priority] = (counts[finding.priority] ?? 0) + 1;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
const priorityMeta: Record<number, { icon: string; color: "error" | "warning" | "muted" | "accent" }> = {
|
|
62
|
-
0: { icon: theme.styledSymbol("status.error", "error"), color: "error" },
|
|
63
|
-
1: { icon: theme.styledSymbol("status.warning", "warning"), color: "warning" },
|
|
64
|
-
2: { icon: theme.styledSymbol("status.warning", "muted"), color: "muted" },
|
|
65
|
-
3: { icon: theme.styledSymbol("status.info", "accent"), color: "accent" },
|
|
66
|
-
};
|
|
67
|
-
|
|
68
59
|
const parts: string[] = [];
|
|
69
|
-
for (const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
parts.push(meta.icon ? `${meta.icon} ${text}` : text);
|
|
60
|
+
for (const label of PRIORITY_LABELS) {
|
|
61
|
+
const { symbol, color } = getPriorityInfo(label);
|
|
62
|
+
const count = counts[label] ?? 0;
|
|
63
|
+
const text = theme.fg(color, `${label}:${count}`);
|
|
64
|
+
parts.push(theme.styledSymbol(symbol, color) ? `${theme.styledSymbol(symbol, color)} ${text}` : text);
|
|
75
65
|
}
|
|
76
66
|
|
|
77
67
|
return `${theme.fg("dim", "Findings:")} ${parts.join(theme.sep.dot)}`;
|
|
@@ -467,7 +457,9 @@ function renderFindings(
|
|
|
467
457
|
const lines: string[] = [];
|
|
468
458
|
|
|
469
459
|
// Sort by priority (lower = more severe) when collapsed to show most important first
|
|
470
|
-
const sortedFindings = expanded
|
|
460
|
+
const sortedFindings = expanded
|
|
461
|
+
? findings
|
|
462
|
+
: [...findings].sort((a, b) => getPriorityInfo(a.priority).ord - getPriorityInfo(b.priority).ord);
|
|
471
463
|
const displayCount = expanded ? sortedFindings.length : Math.min(3, sortedFindings.length);
|
|
472
464
|
|
|
473
465
|
for (let i = 0; i < displayCount; i++) {
|
|
@@ -476,13 +468,12 @@ function renderFindings(
|
|
|
476
468
|
const findingPrefix = isLastFinding ? theme.tree.last : theme.tree.branch;
|
|
477
469
|
const findingContinue = isLastFinding ? " " : `${theme.tree.vertical} `;
|
|
478
470
|
|
|
479
|
-
const
|
|
480
|
-
const color = finding.priority === 0 ? "error" : finding.priority === 1 ? "warning" : "muted";
|
|
471
|
+
const { color } = getPriorityInfo(finding.priority);
|
|
481
472
|
const titleText = finding.title.replace(/^\[P\d\]\s*/, "");
|
|
482
473
|
const loc = `${path.basename(finding.file_path)}:${finding.line_start}`;
|
|
483
474
|
|
|
484
475
|
lines.push(
|
|
485
|
-
`${continuePrefix}${findingPrefix} ${theme.fg(color, `[${priority}]`)} ${titleText} ${theme.fg("dim", loc)}`,
|
|
476
|
+
`${continuePrefix}${findingPrefix} ${theme.fg(color, `[${finding.priority}]`)} ${titleText} ${theme.fg("dim", loc)}`,
|
|
486
477
|
);
|
|
487
478
|
|
|
488
479
|
// Show body when expanded
|
|
@@ -45,7 +45,6 @@ export const taskItemSchema = Type.Object({
|
|
|
45
45
|
id: Type.String({
|
|
46
46
|
description: "Short task identifier for display (max 32 chars, CamelCase, e.g. 'SessionStore', 'WebFetchFix')",
|
|
47
47
|
maxLength: 32,
|
|
48
|
-
pattern: "^[A-Za-z][A-Za-z0-9]*$",
|
|
49
48
|
}),
|
|
50
49
|
task: Type.String({ description: "Task description for the agent" }),
|
|
51
50
|
description: Type.String({ description: "Short description for UI display" }),
|
|
@@ -63,8 +62,8 @@ export const taskSchema = Type.Object({
|
|
|
63
62
|
}),
|
|
64
63
|
),
|
|
65
64
|
output: Type.Optional(
|
|
66
|
-
Type.
|
|
67
|
-
description: "JTD schema for structured subagent output
|
|
65
|
+
Type.Record(Type.String(), Type.Unknown(), {
|
|
66
|
+
description: "JTD schema for structured subagent output",
|
|
68
67
|
}),
|
|
69
68
|
),
|
|
70
69
|
tasks: Type.Array(taskItemSchema, {
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdirSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
5
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
5
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
7
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
7
8
|
import { Type } from "@sinclair/typebox";
|
|
@@ -18,9 +19,9 @@ const todoWriteSchema = Type.Object({
|
|
|
18
19
|
todos: Type.Array(
|
|
19
20
|
Type.Object({
|
|
20
21
|
id: Type.Optional(Type.String({ description: "Stable todo id" })),
|
|
21
|
-
content: Type.String({
|
|
22
|
-
activeForm: Type.String({
|
|
23
|
-
status:
|
|
22
|
+
content: Type.String({ description: "Imperative task description (e.g., 'Run tests')" }),
|
|
23
|
+
activeForm: Type.String({ description: "Present continuous form (e.g., 'Running tests')" }),
|
|
24
|
+
status: StringEnum(["pending", "in_progress", "completed"]),
|
|
24
25
|
}),
|
|
25
26
|
{ description: "The updated todo list" },
|
|
26
27
|
),
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
16
|
+
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
16
17
|
import { Type } from "@sinclair/typebox";
|
|
17
18
|
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
18
19
|
import webSearchDescription from "../../../prompts/tools/web-search.md" with { type: "text" };
|
|
@@ -36,7 +37,7 @@ export const webSearchSchema = Type.Object({
|
|
|
36
37
|
// Common
|
|
37
38
|
query: Type.String({ description: "Search query" }),
|
|
38
39
|
provider: Type.Optional(
|
|
39
|
-
|
|
40
|
+
StringEnum(["auto", "exa", "anthropic", "perplexity"], {
|
|
40
41
|
description: "Search provider (auto-detected if omitted or set to auto)",
|
|
41
42
|
}),
|
|
42
43
|
),
|
|
@@ -58,12 +59,12 @@ export const webSearchSchema = Type.Object({
|
|
|
58
59
|
|
|
59
60
|
// Perplexity-specific
|
|
60
61
|
model: Type.Optional(
|
|
61
|
-
|
|
62
|
+
StringEnum(["sonar", "sonar-pro"], {
|
|
62
63
|
description: "Perplexity model - sonar (fast) or sonar-pro (comprehensive research)",
|
|
63
64
|
}),
|
|
64
65
|
),
|
|
65
66
|
search_recency_filter: Type.Optional(
|
|
66
|
-
|
|
67
|
+
StringEnum(["day", "week", "month", "year"], {
|
|
67
68
|
description: "Filter results by recency (Perplexity only)",
|
|
68
69
|
}),
|
|
69
70
|
),
|
|
@@ -73,7 +74,7 @@ export const webSearchSchema = Type.Object({
|
|
|
73
74
|
}),
|
|
74
75
|
),
|
|
75
76
|
search_context_size: Type.Optional(
|
|
76
|
-
|
|
77
|
+
StringEnum(["low", "medium", "high"], {
|
|
77
78
|
description: "Context size for cost control (Perplexity only)",
|
|
78
79
|
}),
|
|
79
80
|
),
|
|
@@ -379,7 +380,7 @@ export function createWebSearchTool(_session: ToolSession): AgentTool<typeof web
|
|
|
379
380
|
const webSearchDeepSchema = Type.Object({
|
|
380
381
|
query: Type.String({ description: "Research query" }),
|
|
381
382
|
type: Type.Optional(
|
|
382
|
-
|
|
383
|
+
StringEnum(["keyword", "neural", "auto"], {
|
|
383
384
|
description: "Search type - neural (semantic), keyword (exact), or auto",
|
|
384
385
|
}),
|
|
385
386
|
),
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Core session management
|
|
2
2
|
|
|
3
|
+
// TypeBox helper for string enums (convenience for custom tools)
|
|
4
|
+
// Re-export from pi-ai which uses the correct enum-based schema format
|
|
5
|
+
export { StringEnum } from "@oh-my-pi/pi-ai";
|
|
3
6
|
// Re-export TUI components for custom tool rendering
|
|
4
7
|
export { Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
5
8
|
export {
|
|
@@ -264,15 +267,3 @@ export {
|
|
|
264
267
|
Theme,
|
|
265
268
|
type ThemeColor,
|
|
266
269
|
} from "./modes/interactive/theme/theme";
|
|
267
|
-
|
|
268
|
-
// TypeBox helper for string enums (convenience for custom tools)
|
|
269
|
-
import { type TSchema, Type } from "@sinclair/typebox";
|
|
270
|
-
export function StringEnum<T extends readonly string[]>(
|
|
271
|
-
values: T,
|
|
272
|
-
options?: { description?: string; default?: T[number] },
|
|
273
|
-
): TSchema {
|
|
274
|
-
return Type.Union(
|
|
275
|
-
values.map((v) => Type.Literal(v)),
|
|
276
|
-
options,
|
|
277
|
-
);
|
|
278
|
-
}
|
|
@@ -824,6 +824,7 @@ const ColorValueSchema = Type.Union([
|
|
|
824
824
|
|
|
825
825
|
type ColorValue = Static<typeof ColorValueSchema>;
|
|
826
826
|
|
|
827
|
+
// Use Type.Union here (not StringEnum) because TypeCompiler doesn't support Type.Unsafe
|
|
827
828
|
const SymbolPresetSchema = Type.Union([Type.Literal("unicode"), Type.Literal("nerd"), Type.Literal("ascii")]);
|
|
828
829
|
|
|
829
830
|
const SymbolsSchema = Type.Optional(
|