@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.
- package/CHANGELOG.md +94 -1
- package/examples/custom-tools/README.md +11 -7
- package/examples/custom-tools/hello/index.ts +2 -2
- package/examples/extensions/README.md +19 -8
- package/examples/extensions/api-demo.ts +15 -19
- package/examples/extensions/hello.ts +5 -6
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/extensions/reload-runtime.ts +4 -3
- package/examples/extensions/with-deps/index.ts +4 -3
- package/examples/sdk/06-extensions.ts +4 -2
- package/package.json +8 -18
- package/src/autoresearch/tools/init-experiment.ts +38 -41
- package/src/autoresearch/tools/log-experiment.ts +32 -41
- package/src/autoresearch/tools/run-experiment.ts +3 -3
- package/src/autoresearch/tools/update-notes.ts +11 -11
- package/src/commands/commit.ts +10 -0
- package/src/commit/agentic/tools/analyze-file.ts +4 -4
- package/src/commit/agentic/tools/git-file-diff.ts +4 -4
- package/src/commit/agentic/tools/git-hunk.ts +5 -5
- package/src/commit/agentic/tools/git-overview.ts +4 -4
- package/src/commit/agentic/tools/propose-changelog.ts +13 -13
- package/src/commit/agentic/tools/propose-commit.ts +6 -6
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/schemas.ts +28 -28
- package/src/commit/agentic/tools/split-commit.ts +22 -21
- package/src/commit/analysis/summary.ts +4 -4
- package/src/commit/changelog/generate.ts +7 -11
- package/src/commit/shared-llm.ts +22 -34
- package/src/config/config-file.ts +35 -13
- package/src/config/model-registry.ts +40 -191
- package/src/config/models-config-schema.ts +166 -0
- package/src/config/settings-schema.ts +29 -0
- package/src/discovery/claude-plugins.ts +19 -7
- package/src/edit/index.ts +2 -2
- package/src/edit/modes/apply-patch.ts +7 -6
- package/src/edit/modes/patch.ts +18 -25
- package/src/edit/modes/replace.ts +18 -20
- package/src/eval/js/shared/rewrite-imports.ts +131 -10
- package/src/eval/py/executor.ts +233 -623
- package/src/eval/py/kernel.ts +27 -2
- package/src/eval/py/runner.py +42 -11
- package/src/eval/py/runtime.ts +1 -0
- package/src/exa/factory.ts +5 -4
- package/src/exa/mcp-client.ts +1 -1
- package/src/exa/researcher.ts +9 -20
- package/src/exa/search.ts +26 -52
- package/src/exa/types.ts +1 -1
- package/src/exa/websets.ts +54 -53
- package/src/exec/bash-executor.ts +2 -1
- package/src/extensibility/custom-commands/loader.ts +5 -3
- package/src/extensibility/custom-commands/types.ts +4 -2
- package/src/extensibility/custom-tools/loader.ts +5 -3
- package/src/extensibility/custom-tools/types.ts +7 -6
- package/src/extensibility/custom-tools/wrapper.ts +1 -1
- package/src/extensibility/extensions/get-commands-handler.ts +77 -0
- package/src/extensibility/extensions/loader.ts +7 -3
- package/src/extensibility/extensions/types.ts +9 -5
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/hooks/loader.ts +3 -1
- package/src/extensibility/hooks/tool-wrapper.ts +1 -1
- package/src/extensibility/hooks/types.ts +4 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +78 -31
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/typebox.ts +391 -0
- package/src/goals/tools/goal-tool.ts +6 -12
- package/src/hashline/input.ts +2 -1
- package/src/hashline/parser.ts +27 -3
- package/src/hashline/types.ts +4 -4
- package/src/hindsight/state.ts +2 -2
- package/src/index.ts +0 -2
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/internal-urls/router.ts +8 -0
- package/src/internal-urls/types.ts +21 -0
- package/src/lsp/config.ts +15 -6
- package/src/lsp/defaults.json +6 -2
- package/src/lsp/types.ts +30 -38
- package/src/mcp/manager.ts +1 -1
- package/src/mcp/tool-bridge.ts +1 -1
- package/src/modes/acp/acp-agent.ts +248 -50
- package/src/modes/components/session-observer-overlay.ts +12 -1
- package/src/modes/components/status-line/segments.ts +39 -4
- package/src/modes/controllers/command-controller.ts +27 -2
- package/src/modes/controllers/event-controller.ts +3 -4
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/interactive-mode.ts +1 -1
- package/src/modes/rpc/host-tools.ts +1 -1
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-mode.ts +27 -1
- package/src/modes/rpc/rpc-types.ts +58 -1
- package/src/modes/runtime-init.ts +2 -1
- package/src/modes/theme/defaults/dark-poimandres.json +1 -0
- package/src/modes/theme/defaults/light-poimandres.json +1 -0
- package/src/modes/theme/theme.ts +117 -117
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/prompts/tools/github.md +4 -4
- package/src/prompts/tools/hashline.md +22 -26
- package/src/prompts/tools/read.md +55 -37
- package/src/sdk.ts +31 -8
- package/src/session/agent-session.ts +74 -104
- package/src/session/messages.ts +16 -51
- package/src/session/session-manager.ts +22 -2
- package/src/session/streaming-output.ts +16 -6
- package/src/task/discovery.ts +5 -2
- package/src/task/executor.ts +210 -87
- package/src/task/index.ts +15 -11
- package/src/task/render.ts +32 -5
- package/src/task/types.ts +54 -39
- package/src/tools/ask.ts +12 -12
- package/src/tools/ast-edit.ts +11 -15
- package/src/tools/ast-grep.ts +9 -10
- package/src/tools/bash-command-fixup.ts +47 -0
- package/src/tools/bash.ts +48 -38
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser.ts +39 -53
- package/src/tools/calculator.ts +12 -11
- package/src/tools/checkpoint.ts +7 -7
- package/src/tools/debug.ts +40 -43
- package/src/tools/eval.ts +16 -10
- package/src/tools/find.ts +10 -13
- package/src/tools/gh.ts +108 -132
- package/src/tools/hindsight-recall.ts +4 -6
- package/src/tools/hindsight-reflect.ts +5 -5
- package/src/tools/hindsight-retain.ts +15 -17
- package/src/tools/image-gen.ts +31 -81
- package/src/tools/index.ts +4 -1
- package/src/tools/inspect-image.ts +8 -9
- package/src/tools/irc.ts +15 -27
- package/src/tools/job.ts +30 -28
- package/src/tools/output-meta.ts +26 -0
- package/src/tools/read.ts +39 -12
- package/src/tools/recipe/index.ts +7 -9
- package/src/tools/render-mermaid.ts +12 -12
- package/src/tools/report-tool-issue.ts +4 -4
- package/src/tools/resolve.ts +11 -11
- package/src/tools/review.ts +14 -26
- package/src/tools/search-tool-bm25.ts +7 -9
- package/src/tools/search.ts +19 -22
- package/src/tools/ssh.ts +10 -9
- package/src/tools/todo-write.ts +26 -34
- package/src/tools/vim.ts +10 -26
- package/src/tools/write.ts +25 -5
- package/src/tools/yield.ts +100 -54
- package/src/web/search/index.ts +9 -24
- package/src/web/search/providers/anthropic.ts +5 -0
- package/src/web/search/providers/exa.ts +3 -0
- package/src/web/search/providers/gemini.ts +5 -0
- package/src/web/search/providers/jina.ts +5 -2
- package/src/web/search/providers/zai.ts +5 -2
- package/src/prompts/compaction/branch-summary-context.md +0 -5
- package/src/prompts/compaction/branch-summary-preamble.md +0 -2
- package/src/prompts/compaction/branch-summary.md +0 -30
- package/src/prompts/compaction/compaction-short-summary.md +0 -9
- package/src/prompts/compaction/compaction-summary-context.md +0 -5
- package/src/prompts/compaction/compaction-summary.md +0 -38
- package/src/prompts/compaction/compaction-turn-prefix.md +0 -17
- package/src/prompts/compaction/compaction-update-summary.md +0 -45
- package/src/prompts/system/auto-handoff-threshold-focus.md +0 -1
- package/src/prompts/system/file-operations.md +0 -10
- package/src/prompts/system/handoff-document.md +0 -49
- package/src/prompts/system/summarization-system.md +0 -3
- package/src/session/compaction/branch-summarization.ts +0 -324
- package/src/session/compaction/compaction.ts +0 -1420
- package/src/session/compaction/errors.ts +0 -31
- package/src/session/compaction/index.ts +0 -8
- package/src/session/compaction/pruning.ts +0 -91
- package/src/session/compaction/utils.ts +0 -184
package/src/tools/todo-write.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
3
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
5
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
6
|
-
import { type Static, Type } from "@sinclair/typebox";
|
|
7
5
|
import chalk from "chalk";
|
|
6
|
+
import * as z from "zod/v4";
|
|
8
7
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
8
|
import type { Theme } from "../modes/theme/theme";
|
|
10
9
|
import todoWriteDescription from "../prompts/tools/todo-write.md" with { type: "text" };
|
|
@@ -45,45 +44,38 @@ export interface TodoWriteToolDetails {
|
|
|
45
44
|
// Schema
|
|
46
45
|
// =============================================================================
|
|
47
46
|
|
|
48
|
-
const TodoOp =
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
const TodoOp = z
|
|
48
|
+
.enum(["init", "start", "done", "rm", "drop", "append", "note"] as const)
|
|
49
|
+
.describe("operation to apply");
|
|
51
50
|
|
|
52
|
-
const InitListEntry =
|
|
53
|
-
phase:
|
|
54
|
-
items:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
const InitListEntry = z.object({
|
|
52
|
+
phase: z.string().describe("phase name (short noun phrase)"),
|
|
53
|
+
items: z
|
|
54
|
+
.array(z.string().describe("task content (5-10 words)"))
|
|
55
|
+
.min(1)
|
|
56
|
+
.describe("tasks for this phase, in execution order; all start as pending"),
|
|
58
57
|
});
|
|
59
58
|
|
|
60
|
-
const TodoOpEntry =
|
|
59
|
+
const TodoOpEntry = z.object({
|
|
61
60
|
op: TodoOp,
|
|
62
|
-
list:
|
|
63
|
-
task:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}),
|
|
72
|
-
),
|
|
73
|
-
text: Type.Optional(Type.String({ description: "note text for op=note (appended with newline)" })),
|
|
61
|
+
list: z.array(InitListEntry).optional().describe("phased task list for op=init"),
|
|
62
|
+
task: z.string().optional().describe("task content for start/done/rm/drop/note"),
|
|
63
|
+
phase: z.string().optional().describe("phase name for done/rm/drop/append"),
|
|
64
|
+
items: z
|
|
65
|
+
.array(z.string().describe("task content (5-10 words)"))
|
|
66
|
+
.min(1)
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("tasks to append to `phase` for op=append"),
|
|
69
|
+
text: z.string().optional().describe("note text for op=note (appended with newline)"),
|
|
74
70
|
});
|
|
75
71
|
|
|
76
|
-
const todoWriteSchema =
|
|
77
|
-
{
|
|
78
|
-
ops:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}),
|
|
82
|
-
},
|
|
83
|
-
{ description: "Apply ordered todo operations" },
|
|
84
|
-
);
|
|
72
|
+
const todoWriteSchema = z
|
|
73
|
+
.object({
|
|
74
|
+
ops: z.array(TodoOpEntry).min(1).describe("ordered todo operations"),
|
|
75
|
+
})
|
|
76
|
+
.describe("Apply ordered todo operations");
|
|
85
77
|
|
|
86
|
-
type TodoWriteParams =
|
|
78
|
+
type TodoWriteParams = z.infer<typeof todoWriteSchema>;
|
|
87
79
|
type TodoOpEntryValue = TodoWriteParams["ops"][number];
|
|
88
80
|
|
|
89
81
|
// =============================================================================
|
package/src/tools/vim.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { extractSegments, sliceWithWidth, Text } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { isEnoent, logger, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import { type Static, Type } from "@sinclair/typebox";
|
|
6
5
|
import * as Diff from "diff";
|
|
6
|
+
import * as z from "zod/v4";
|
|
7
7
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
8
|
import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
|
|
9
9
|
import { getLanguageFromPath, highlightCode, type Theme } from "../modes/theme/theme";
|
|
@@ -36,35 +36,19 @@ import { toolResult } from "./tool-result";
|
|
|
36
36
|
const INTERNAL_URL_PREFIX = /^(agent|artifact|skill|rule|local|mcp):\/\//;
|
|
37
37
|
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
38
38
|
|
|
39
|
-
const vimStepSchema =
|
|
40
|
-
kbd:
|
|
41
|
-
|
|
42
|
-
examples: [["ggdGi"], ["3Go"], ["dd"]],
|
|
43
|
-
}),
|
|
44
|
-
insert: Type.Optional(
|
|
45
|
-
Type.String({
|
|
46
|
-
description: "raw text to insert",
|
|
47
|
-
examples: ["hello world"],
|
|
48
|
-
}),
|
|
49
|
-
),
|
|
39
|
+
const vimStepSchema = z.object({
|
|
40
|
+
kbd: z.array(z.string()).describe("vim key sequences"),
|
|
41
|
+
insert: z.string().optional().describe("raw text to insert"),
|
|
50
42
|
});
|
|
51
43
|
|
|
52
|
-
const vimSchema =
|
|
53
|
-
file:
|
|
54
|
-
steps:
|
|
55
|
-
|
|
56
|
-
description: "editing steps",
|
|
57
|
-
}),
|
|
58
|
-
),
|
|
59
|
-
pause: Type.Optional(
|
|
60
|
-
Type.Boolean({
|
|
61
|
-
description: "skip auto-save",
|
|
62
|
-
}),
|
|
63
|
-
),
|
|
44
|
+
const vimSchema = z.object({
|
|
45
|
+
file: z.string().describe("file path"),
|
|
46
|
+
steps: z.array(vimStepSchema).optional().describe("editing steps"),
|
|
47
|
+
pause: z.boolean().optional().describe("skip auto-save"),
|
|
64
48
|
});
|
|
65
49
|
|
|
66
|
-
type VimParams =
|
|
67
|
-
type VimStep =
|
|
50
|
+
type VimParams = z.infer<typeof vimSchema>;
|
|
51
|
+
type VimStep = z.infer<typeof vimStepSchema>;
|
|
68
52
|
|
|
69
53
|
interface VimRenderStep {
|
|
70
54
|
kbd?: string[];
|
package/src/tools/write.ts
CHANGED
|
@@ -5,9 +5,11 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
5
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
7
7
|
import { isEnoent, isRecord, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
8
|
-
import
|
|
8
|
+
import * as z from "zod/v4";
|
|
9
9
|
import { stripHashlinePrefixes } from "../edit";
|
|
10
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
|
+
import { InternalUrlRouter } from "../internal-urls";
|
|
12
|
+
import { parseInternalUrl } from "../internal-urls/parse";
|
|
11
13
|
import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
|
|
12
14
|
import { getLanguageFromPath, highlightCode, type Theme } from "../modes/theme/theme";
|
|
13
15
|
import writeDescription from "../prompts/tools/write.md" with { type: "text" };
|
|
@@ -56,12 +58,12 @@ async function loadFflate(): Promise<typeof import("fflate")> {
|
|
|
56
58
|
return fflateModulePromise;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
const writeSchema =
|
|
60
|
-
path:
|
|
61
|
-
content:
|
|
61
|
+
const writeSchema = z.object({
|
|
62
|
+
path: z.string().describe("file path"),
|
|
63
|
+
content: z.string().describe("file content"),
|
|
62
64
|
});
|
|
63
65
|
|
|
64
|
-
export type WriteToolInput =
|
|
66
|
+
export type WriteToolInput = z.infer<typeof writeSchema>;
|
|
65
67
|
|
|
66
68
|
/** Details returned by the write tool for TUI rendering */
|
|
67
69
|
export interface WriteToolDetails {
|
|
@@ -658,6 +660,24 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
658
660
|
return untilAborted(signal, async () => {
|
|
659
661
|
// Strip hashline display prefixes (LINE+ID|) if the model copied them from read output
|
|
660
662
|
const { text: cleanContent, stripped } = stripWriteContent(this.session, content);
|
|
663
|
+
const internalRouter = InternalUrlRouter.instance();
|
|
664
|
+
if (internalRouter.canHandle(path)) {
|
|
665
|
+
const parsed = parseInternalUrl(path);
|
|
666
|
+
const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
667
|
+
const handler = internalRouter.getHandler(scheme);
|
|
668
|
+
if (handler?.write) {
|
|
669
|
+
await handler.write(parsed, cleanContent, { cwd: this.session.cwd, signal });
|
|
670
|
+
let resultText = `Successfully wrote ${cleanContent.length} bytes to ${path}`;
|
|
671
|
+
if (stripped) {
|
|
672
|
+
resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
|
|
673
|
+
}
|
|
674
|
+
return { content: [{ type: "text", text: resultText }], details: {} };
|
|
675
|
+
}
|
|
676
|
+
// Schemes without a `write` hook fall through to existing logic
|
|
677
|
+
// (local:// resolves to a backing file via plan-mode-guard) or are
|
|
678
|
+
// rejected downstream when no backing file exists.
|
|
679
|
+
}
|
|
680
|
+
|
|
661
681
|
const conflictUri = parseConflictUri(path);
|
|
662
682
|
if (conflictUri) {
|
|
663
683
|
if (conflictUri.scope) {
|
package/src/tools/yield.ts
CHANGED
|
@@ -4,10 +4,15 @@
|
|
|
4
4
|
* Subagents must call this tool to finish and return structured JSON output.
|
|
5
5
|
*/
|
|
6
6
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import type { TSchema } from "@oh-my-pi/pi-ai/types";
|
|
8
|
+
import {
|
|
9
|
+
dereferenceJsonSchema,
|
|
10
|
+
isValidJsonSchema,
|
|
11
|
+
type JsonSchemaValidationIssue,
|
|
12
|
+
type JsonSchemaValidationResult,
|
|
13
|
+
sanitizeSchemaForStrictMode,
|
|
14
|
+
validateJsonSchemaValue,
|
|
15
|
+
} from "@oh-my-pi/pi-ai/utils/schema";
|
|
11
16
|
import { subprocessToolRegistry } from "../task/subprocess-tool-registry";
|
|
12
17
|
import type { ToolSession } from ".";
|
|
13
18
|
import { jtdToJsonSchema, normalizeSchema } from "./jtd-to-json-schema";
|
|
@@ -18,8 +23,6 @@ export interface YieldDetails {
|
|
|
18
23
|
error?: string;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
const ajv = new Ajv({ allErrors: true, strict: false, logger: false });
|
|
22
|
-
|
|
23
26
|
function formatSchema(schema: unknown): string {
|
|
24
27
|
if (schema === undefined) return "No schema provided.";
|
|
25
28
|
if (typeof schema === "string") return schema;
|
|
@@ -30,16 +33,72 @@ function formatSchema(schema: unknown): string {
|
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
function
|
|
34
|
-
if (!
|
|
35
|
-
return
|
|
36
|
-
.map(
|
|
37
|
-
const path =
|
|
38
|
-
return `${path}${
|
|
36
|
+
function formatJsonSchemaIssues(issues: ReadonlyArray<JsonSchemaValidationIssue> | undefined): string {
|
|
37
|
+
if (!issues || issues.length === 0) return "Unknown schema validation error.";
|
|
38
|
+
return issues
|
|
39
|
+
.map(issue => {
|
|
40
|
+
const path = issue.path.length === 0 ? "" : `${issue.path.map(seg => String(seg)).join("/")}: `;
|
|
41
|
+
return `${path}${issue.message}`;
|
|
39
42
|
})
|
|
40
43
|
.join("; ");
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
function looseRecordSchema(description: string): Record<string, unknown> {
|
|
47
|
+
return {
|
|
48
|
+
type: "object",
|
|
49
|
+
additionalProperties: true,
|
|
50
|
+
description,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hasUnresolvedRefs(schema: unknown): boolean {
|
|
55
|
+
if (schema == null) return false;
|
|
56
|
+
if (Array.isArray(schema)) {
|
|
57
|
+
for (const item of schema) {
|
|
58
|
+
if (hasUnresolvedRefs(item)) return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (typeof schema !== "object") return false;
|
|
63
|
+
const record = schema as Record<string, unknown>;
|
|
64
|
+
if (typeof record.$ref === "string") return true;
|
|
65
|
+
for (const key in record) {
|
|
66
|
+
if (key === "const" || key === "default" || key === "enum" || key === "examples") continue;
|
|
67
|
+
if (hasUnresolvedRefs(record[key])) return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function wrapYieldParameters(dataSchema: Record<string, unknown>): Record<string, unknown> {
|
|
73
|
+
return {
|
|
74
|
+
type: "object",
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
description: "submit data or error",
|
|
77
|
+
properties: {
|
|
78
|
+
result: {
|
|
79
|
+
anyOf: [
|
|
80
|
+
{
|
|
81
|
+
type: "object",
|
|
82
|
+
additionalProperties: false,
|
|
83
|
+
description: "task succeeded",
|
|
84
|
+
properties: { data: dataSchema },
|
|
85
|
+
required: ["data"],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "object",
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
properties: {
|
|
91
|
+
error: { type: "string", description: "error message" },
|
|
92
|
+
},
|
|
93
|
+
required: ["error"],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: ["result"],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
43
102
|
export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
44
103
|
readonly name = "yield";
|
|
45
104
|
readonly label = "Submit Result";
|
|
@@ -52,33 +111,15 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
52
111
|
readonly intent = "omit" as const;
|
|
53
112
|
lenientArgValidation = true;
|
|
54
113
|
|
|
55
|
-
readonly #validate?:
|
|
114
|
+
readonly #validate?: (value: unknown) => JsonSchemaValidationResult;
|
|
56
115
|
#schemaValidationFailures = 0;
|
|
57
116
|
|
|
58
117
|
constructor(session: ToolSession) {
|
|
59
|
-
|
|
60
|
-
Type.Object(
|
|
61
|
-
{
|
|
62
|
-
result: Type.Union([
|
|
63
|
-
Type.Object({ data: dataSchema }, { description: "task succeeded" }),
|
|
64
|
-
Type.Object({
|
|
65
|
-
error: Type.String({ description: "error message" }),
|
|
66
|
-
}),
|
|
67
|
-
]),
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
additionalProperties: false,
|
|
71
|
-
description: "submit data or error",
|
|
72
|
-
},
|
|
73
|
-
) as TSchema;
|
|
74
|
-
|
|
75
|
-
let validate: ValidateFunction | undefined;
|
|
76
|
-
let dataSchema: TSchema;
|
|
118
|
+
let validate: ((value: unknown) => JsonSchemaValidationResult) | undefined;
|
|
77
119
|
let parameters: TSchema;
|
|
78
120
|
|
|
79
121
|
try {
|
|
80
122
|
const schemaResult = normalizeSchema(session.outputSchema);
|
|
81
|
-
// Convert JTD to JSON Schema if needed (auto-detected)
|
|
82
123
|
const normalizedSchema =
|
|
83
124
|
schemaResult.normalized !== undefined ? jtdToJsonSchema(schemaResult.normalized) : undefined;
|
|
84
125
|
let schemaError = schemaResult.error;
|
|
@@ -88,10 +129,10 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
88
129
|
}
|
|
89
130
|
|
|
90
131
|
if (normalizedSchema !== undefined && normalizedSchema !== false && !schemaError) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
132
|
+
if (!isValidJsonSchema(normalizedSchema)) {
|
|
133
|
+
schemaError = "invalid JSON schema";
|
|
134
|
+
} else {
|
|
135
|
+
validate = value => validateJsonSchemaValue(normalizedSchema, value);
|
|
95
136
|
}
|
|
96
137
|
}
|
|
97
138
|
|
|
@@ -109,27 +150,29 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
109
150
|
? {}
|
|
110
151
|
: undefined;
|
|
111
152
|
|
|
153
|
+
let dataSchema: Record<string, unknown>;
|
|
112
154
|
if (sanitizedSchema !== undefined) {
|
|
113
155
|
const resolved = dereferenceJsonSchema({
|
|
114
156
|
...sanitizedSchema,
|
|
115
157
|
description: schemaDescription,
|
|
116
|
-
})
|
|
117
|
-
|
|
158
|
+
}) as Record<string, unknown>;
|
|
159
|
+
if (hasUnresolvedRefs(resolved)) {
|
|
160
|
+
throw new Error("schema contains unresolved $ref after dereferencing");
|
|
161
|
+
}
|
|
162
|
+
dataSchema = resolved;
|
|
118
163
|
} else {
|
|
119
|
-
dataSchema =
|
|
120
|
-
|
|
121
|
-
|
|
164
|
+
dataSchema = looseRecordSchema(
|
|
165
|
+
schemaError ? schemaDescription : "Structured JSON output (no schema specified)",
|
|
166
|
+
);
|
|
122
167
|
}
|
|
123
|
-
parameters =
|
|
168
|
+
parameters = wrapYieldParameters(dataSchema);
|
|
124
169
|
JSON.stringify(parameters);
|
|
125
|
-
|
|
126
|
-
ajv.compile(parameters as Record<string, unknown>);
|
|
170
|
+
if (!isValidJsonSchema(parameters)) throw new Error("yield parameters schema is invalid");
|
|
127
171
|
} catch (err) {
|
|
128
172
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
parameters = createParameters(dataSchema);
|
|
173
|
+
parameters = wrapYieldParameters(
|
|
174
|
+
looseRecordSchema(`Structured JSON output (schema processing failed: ${errorMsg})`),
|
|
175
|
+
);
|
|
133
176
|
validate = undefined;
|
|
134
177
|
this.strict = false;
|
|
135
178
|
}
|
|
@@ -140,7 +183,7 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
140
183
|
|
|
141
184
|
async execute(
|
|
142
185
|
_toolCallId: string,
|
|
143
|
-
params:
|
|
186
|
+
params: unknown,
|
|
144
187
|
_signal?: AbortSignal,
|
|
145
188
|
_onUpdate?: AgentToolUpdateCallback<YieldDetails>,
|
|
146
189
|
_context?: AgentToolContext,
|
|
@@ -170,12 +213,15 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
170
213
|
if (data === undefined || data === null) {
|
|
171
214
|
throw new Error("data is required when yield indicates success");
|
|
172
215
|
}
|
|
173
|
-
if (this.#validate
|
|
174
|
-
this.#
|
|
175
|
-
if (
|
|
176
|
-
|
|
216
|
+
if (this.#validate) {
|
|
217
|
+
const parsed = this.#validate(data);
|
|
218
|
+
if (!parsed.success) {
|
|
219
|
+
this.#schemaValidationFailures++;
|
|
220
|
+
if (this.#schemaValidationFailures <= 1) {
|
|
221
|
+
throw new Error(`Output does not match schema: ${formatJsonSchemaIssues(parsed.issues)}`);
|
|
222
|
+
}
|
|
223
|
+
schemaValidationOverridden = true;
|
|
177
224
|
}
|
|
178
|
-
schemaValidationOverridden = true;
|
|
179
225
|
}
|
|
180
226
|
}
|
|
181
227
|
|
package/src/web/search/index.ts
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
9
|
-
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
10
9
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
11
|
-
import
|
|
10
|
+
import * as z from "zod/v4";
|
|
12
11
|
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
13
12
|
import type { Theme } from "../../modes/theme/theme";
|
|
14
13
|
import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { type: "text" };
|
|
@@ -21,30 +20,16 @@ import type { SearchProviderId, SearchResponse } from "./types";
|
|
|
21
20
|
import { SearchProviderError } from "./types";
|
|
22
21
|
|
|
23
22
|
/** Web search tool parameters schema */
|
|
24
|
-
export const webSearchSchema =
|
|
25
|
-
query:
|
|
26
|
-
recency:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
),
|
|
31
|
-
limit: Type.Optional(Type.Number({ description: "Max results to return" })),
|
|
32
|
-
max_tokens: Type.Optional(Type.Number({ description: "Maximum output tokens" })),
|
|
33
|
-
temperature: Type.Optional(Type.Number({ description: "Sampling temperature" })),
|
|
34
|
-
num_search_results: Type.Optional(Type.Number({ description: "Number of search results to retrieve" })),
|
|
23
|
+
export const webSearchSchema = z.object({
|
|
24
|
+
query: z.string().describe("Search query"),
|
|
25
|
+
recency: z.enum(["day", "week", "month", "year"]).describe("Recency filter (Brave, Perplexity)").optional(),
|
|
26
|
+
limit: z.number().describe("Max results to return").optional(),
|
|
27
|
+
max_tokens: z.number().describe("Maximum output tokens").optional(),
|
|
28
|
+
temperature: z.number().describe("Sampling temperature").optional(),
|
|
29
|
+
num_search_results: z.number().describe("Number of search results to retrieve").optional(),
|
|
35
30
|
});
|
|
36
31
|
|
|
37
|
-
export type SearchToolParams =
|
|
38
|
-
query: string;
|
|
39
|
-
recency?: "day" | "week" | "month" | "year";
|
|
40
|
-
limit?: number;
|
|
41
|
-
/** Maximum output tokens. Defaults to 4096. */
|
|
42
|
-
max_tokens?: number;
|
|
43
|
-
/** Sampling temperature (0–1). Lower = more focused/factual. Defaults to 0.2. */
|
|
44
|
-
temperature?: number;
|
|
45
|
-
/** Number of search results to retrieve. Defaults to 10. */
|
|
46
|
-
num_search_results?: number;
|
|
47
|
-
};
|
|
32
|
+
export type SearchToolParams = z.infer<typeof webSearchSchema>;
|
|
48
33
|
|
|
49
34
|
export interface SearchQueryParams extends SearchToolParams {
|
|
50
35
|
provider?: SearchProviderId | "auto";
|
|
@@ -38,6 +38,7 @@ export interface AnthropicSearchParams {
|
|
|
38
38
|
max_tokens?: number;
|
|
39
39
|
/** Sampling temperature (0–1). Lower = more focused/factual. */
|
|
40
40
|
temperature?: number;
|
|
41
|
+
signal?: AbortSignal;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -86,6 +87,7 @@ async function callSearch(
|
|
|
86
87
|
systemPrompt?: string,
|
|
87
88
|
maxTokens?: number,
|
|
88
89
|
temperature?: number,
|
|
90
|
+
signal?: AbortSignal,
|
|
89
91
|
): Promise<AnthropicApiResponse> {
|
|
90
92
|
const url = buildAnthropicUrl(auth);
|
|
91
93
|
const headers = buildAnthropicSearchHeaders(auth);
|
|
@@ -116,6 +118,7 @@ async function callSearch(
|
|
|
116
118
|
method: "POST",
|
|
117
119
|
headers,
|
|
118
120
|
body: JSON.stringify(body),
|
|
121
|
+
signal,
|
|
119
122
|
});
|
|
120
123
|
|
|
121
124
|
if (!response.ok) {
|
|
@@ -253,6 +256,7 @@ export async function searchAnthropic(params: AnthropicSearchParams): Promise<Se
|
|
|
253
256
|
params.system_prompt,
|
|
254
257
|
params.max_tokens,
|
|
255
258
|
params.temperature,
|
|
259
|
+
params.signal,
|
|
256
260
|
);
|
|
257
261
|
|
|
258
262
|
const result = parseResponse(response);
|
|
@@ -281,6 +285,7 @@ export class AnthropicProvider extends SearchProvider {
|
|
|
281
285
|
num_results: params.numSearchResults ?? params.limit,
|
|
282
286
|
max_tokens: params.maxOutputTokens,
|
|
283
287
|
temperature: params.temperature,
|
|
288
|
+
signal: params.signal,
|
|
284
289
|
});
|
|
285
290
|
}
|
|
286
291
|
}
|
|
@@ -29,6 +29,7 @@ export interface ExaSearchParams {
|
|
|
29
29
|
exclude_domains?: string[];
|
|
30
30
|
start_published_date?: string;
|
|
31
31
|
end_published_date?: string;
|
|
32
|
+
signal?: AbortSignal;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
interface ExaSearchResult {
|
|
@@ -179,6 +180,7 @@ async function callExaSearch(apiKey: string, params: ExaSearchParams): Promise<E
|
|
|
179
180
|
"x-api-key": apiKey,
|
|
180
181
|
},
|
|
181
182
|
body: JSON.stringify(body),
|
|
183
|
+
signal: params.signal,
|
|
182
184
|
});
|
|
183
185
|
|
|
184
186
|
if (!response.ok) {
|
|
@@ -259,6 +261,7 @@ export class ExaProvider extends SearchProvider {
|
|
|
259
261
|
return searchExa({
|
|
260
262
|
query: params.query,
|
|
261
263
|
num_results: params.numSearchResults ?? params.limit,
|
|
264
|
+
signal: params.signal,
|
|
262
265
|
});
|
|
263
266
|
}
|
|
264
267
|
}
|
|
@@ -39,6 +39,7 @@ export interface GeminiSearchParams extends GeminiToolParams {
|
|
|
39
39
|
max_output_tokens?: number;
|
|
40
40
|
/** Sampling temperature (0–1). Lower = more focused/factual. */
|
|
41
41
|
temperature?: number;
|
|
42
|
+
signal?: AbortSignal;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export function buildGeminiRequestTools(params: GeminiToolParams): Array<Record<string, Record<string, unknown>>> {
|
|
@@ -235,6 +236,7 @@ async function callGeminiSearch(
|
|
|
235
236
|
maxOutputTokens?: number,
|
|
236
237
|
temperature?: number,
|
|
237
238
|
toolParams: GeminiToolParams = {},
|
|
239
|
+
signal?: AbortSignal,
|
|
238
240
|
): Promise<{
|
|
239
241
|
answer: string;
|
|
240
242
|
sources: SearchSource[];
|
|
@@ -308,6 +310,7 @@ async function callGeminiSearch(
|
|
|
308
310
|
...headers,
|
|
309
311
|
},
|
|
310
312
|
body: JSON.stringify(requestBody),
|
|
313
|
+
signal,
|
|
311
314
|
});
|
|
312
315
|
const urlFor = (attempt: number) =>
|
|
313
316
|
`${endpoints[Math.min(attempt, endpoints.length - 1)]}/v1internal:streamGenerateContent?alt=sse`;
|
|
@@ -500,6 +503,7 @@ export async function searchGemini(params: GeminiSearchParams): Promise<SearchRe
|
|
|
500
503
|
code_execution: params.code_execution,
|
|
501
504
|
url_context: params.url_context,
|
|
502
505
|
},
|
|
506
|
+
params.signal,
|
|
503
507
|
);
|
|
504
508
|
|
|
505
509
|
let sources = result.sources;
|
|
@@ -539,6 +543,7 @@ export class GeminiProvider extends SearchProvider {
|
|
|
539
543
|
google_search: params.googleSearch,
|
|
540
544
|
code_execution: params.codeExecution,
|
|
541
545
|
url_context: params.urlContext,
|
|
546
|
+
signal: params.signal,
|
|
542
547
|
});
|
|
543
548
|
}
|
|
544
549
|
}
|
|
@@ -17,6 +17,7 @@ const JINA_SEARCH_URL = "https://s.jina.ai";
|
|
|
17
17
|
export interface JinaSearchParams {
|
|
18
18
|
query: string;
|
|
19
19
|
num_results?: number;
|
|
20
|
+
signal?: AbortSignal;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
interface JinaSearchResult {
|
|
@@ -33,13 +34,14 @@ export function findApiKey(): string | null {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/** Call Jina Reader search API. */
|
|
36
|
-
async function callJinaSearch(apiKey: string, query: string): Promise<JinaSearchResponse> {
|
|
37
|
+
async function callJinaSearch(apiKey: string, query: string, signal?: AbortSignal): Promise<JinaSearchResponse> {
|
|
37
38
|
const requestUrl = `${JINA_SEARCH_URL}/${encodeURIComponent(query)}`;
|
|
38
39
|
const response = await fetch(requestUrl, {
|
|
39
40
|
headers: {
|
|
40
41
|
Accept: "application/json",
|
|
41
42
|
Authorization: `Bearer ${apiKey}`,
|
|
42
43
|
},
|
|
44
|
+
signal,
|
|
43
45
|
});
|
|
44
46
|
|
|
45
47
|
if (!response.ok) {
|
|
@@ -58,7 +60,7 @@ export async function searchJina(params: JinaSearchParams): Promise<SearchRespon
|
|
|
58
60
|
throw new Error("JINA_API_KEY not found. Set it in environment or .env file.");
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
const response = await callJinaSearch(apiKey, params.query);
|
|
63
|
+
const response = await callJinaSearch(apiKey, params.query, params.signal);
|
|
62
64
|
const sources: SearchSource[] = [];
|
|
63
65
|
|
|
64
66
|
for (const result of response) {
|
|
@@ -91,6 +93,7 @@ export class JinaProvider extends SearchProvider {
|
|
|
91
93
|
return searchJina({
|
|
92
94
|
query: params.query,
|
|
93
95
|
num_results: params.numSearchResults ?? params.limit,
|
|
96
|
+
signal: params.signal,
|
|
94
97
|
});
|
|
95
98
|
}
|
|
96
99
|
}
|
|
@@ -20,6 +20,7 @@ const DEFAULT_NUM_RESULTS = 10;
|
|
|
20
20
|
export interface ZaiSearchParams {
|
|
21
21
|
query: string;
|
|
22
22
|
num_results?: number;
|
|
23
|
+
signal?: AbortSignal;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
interface ZaiSearchResult {
|
|
@@ -55,7 +56,7 @@ export async function findApiKey(): Promise<string | null> {
|
|
|
55
56
|
return findCredential(getEnvApiKey("zai"), "zai");
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
async function callZaiTool(apiKey: string, args: Record<string, unknown
|
|
59
|
+
async function callZaiTool(apiKey: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {
|
|
59
60
|
const response = await fetch(ZAI_MCP_URL, {
|
|
60
61
|
method: "POST",
|
|
61
62
|
headers: {
|
|
@@ -72,6 +73,7 @@ async function callZaiTool(apiKey: string, args: Record<string, unknown>): Promi
|
|
|
72
73
|
arguments: args,
|
|
73
74
|
},
|
|
74
75
|
}),
|
|
76
|
+
signal,
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
if (!response.ok) {
|
|
@@ -157,7 +159,7 @@ async function callZaiSearch(apiKey: string, params: ZaiSearchParams): Promise<u
|
|
|
157
159
|
let lastError: unknown;
|
|
158
160
|
for (let i = 0; i < attempts.length; i++) {
|
|
159
161
|
try {
|
|
160
|
-
return await callZaiTool(apiKey, attempts[i]);
|
|
162
|
+
return await callZaiTool(apiKey, attempts[i], params.signal);
|
|
161
163
|
} catch (error) {
|
|
162
164
|
lastError = error;
|
|
163
165
|
const isLastAttempt = i === attempts.length - 1;
|
|
@@ -302,6 +304,7 @@ export class ZaiProvider extends SearchProvider {
|
|
|
302
304
|
return searchZai({
|
|
303
305
|
query: params.query,
|
|
304
306
|
num_results: params.numSearchResults ?? params.limit,
|
|
307
|
+
signal: params.signal,
|
|
305
308
|
});
|
|
306
309
|
}
|
|
307
310
|
}
|