refactor(coding-agent)!: removed StringEnum helper and shortened tool schema descriptions

- Replaced all StringEnum(...) usages with z.enum([...]) across tools, examples, and tests.
- Removed StringEnum re-export from @oh-my-pi/pi-coding-agent public API.
- Condensed verbose tool parameter descriptions to minimal lowercase phrases.
- Renamed AuthCredentialStore to SqliteAuthCredentialStore at usage sites.
This commit is contained in:
can1357
2026-05-16 19:22:42 +02:00
parent e7200c2e4a
commit 64fcdc308f
47 changed files with 209 additions and 402 deletions
+1 -1
View File
@@ -119,7 +119,7 @@ Supported formats:
Behavior:
- Validates parsed data with AJV against a provided TypeBox schema.
- Validates parsed data against a provided Zod schema.
- Caches load result until `invalidate()`.
- Returns tri-state result via `tryLoad()`:
- `ok`
+4 -5
View File
@@ -6,7 +6,7 @@ A custom tool is a TypeScript/JavaScript module that exports a factory. The fact
## What this is (and is not)
- **Custom tool**: callable by the model during a turn (`execute` + Zod parameter schema; legacy TypeBox is still accepted and lifted to Zod at registration).
- **Custom tool**: callable by the model during a turn (`execute` + Zod parameter schema).
- **Extension**: lifecycle/event framework that can register tools and intercept/modify events.
- **Hook**: external pre/post command scripts.
- **Skill**: static guidance/context package, not executable tool code.
@@ -105,7 +105,7 @@ const factory: CustomToolFactory = (pi) => ({
export default factory;
```
Legacy TypeBox-authored factories can still call `pi.typebox` — it's now a small Zod-backed shim (`Type.Object`, `Type.String`, etc.) baked into the host, not the real `@sinclair/typebox` package. Schemas flow through the same Zod pipeline as `pi.zod` and need no separate normalization.
Schemas are authored with Zod (`pi.zod`) and flow through the shared validation/wire pipeline.
Factory return type:
@@ -122,8 +122,7 @@ From `types.ts` and `loader.ts`:
- `ui`: UI context (can be no-op in headless modes)
- `hasUI`: `false` in non-interactive flows
- `logger`: shared file logger
- `zod`: injected `zod` module (**preferred** for new tool schemas; use `pi.zod.object`, `pi.zod.string`, …)
- `typebox`: injected zod-backed `Type.*` shim (legacy extension compatibility)
- `zod`: injected `zod` module (use `pi.zod.object`, `pi.zod.string`, …)
- `pi`: injected `@oh-my-pi/pi-coding-agent` exports
- `pushPendingAction(action)`: register a preview action for hidden `resolve` tool (`docs/resolve-tool-runtime.md`)
@@ -137,7 +136,7 @@ Loader starts with a no-op UI context and requires host code to call `setUIConte
execute(toolCallId, params, onUpdate, ctx, signal);
```
- `params` is statically typed from your Zod schema via `z.infer<typeof schema>` (`Static<TParams>` in API types). Legacy TypeBox schemas are lifted to Zod internally.
- `params` is statically typed from your Zod schema via `z.infer<typeof schema>` (`Static<TParams>` in API types).
- Runtime argument validation happens before execution in the agent loop.
- `onUpdate` emits partial results for UI streaming.
- `ctx` includes session/model state and an `abort()` helper.
+1 -2
View File
@@ -125,8 +125,7 @@ In interactive mode, `input` handlers run before the built-in first-message auto
Also exposed:
- `pi.logger`
- `pi.zod` (injected `zod` module — **preferred** for new tool schemas)
- `pi.typebox` (zod-backed `Type.*` shim — retained for legacy extension compat)
- `pi.zod` (injected `zod` module — use for tool parameter schemas)
- `pi.pi` (package exports)
### Message delivery semantics
+1 -1
View File
@@ -341,6 +341,6 @@ Use this workflow:
- All `colors` tokens are required for custom themes.
- `export` and `symbols` are optional.
- `$schema` in theme JSON is informational; runtime validation is enforced by compiled TypeBox schema in code.
- `$schema` in theme JSON is informational; runtime validation is enforced by a Zod schema in code.
- `setTheme` failure falls back to `dark`; `previewTheme` failure does not replace current theme.
- File watcher reload errors or temporary missing files keep the current loaded theme until a successful reload or explicit theme switch.
+1 -1
View File
@@ -15,7 +15,7 @@
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `goal` | `string` | Yes | Investigation goal. Required by the TypeBox schema and echoed in the tool result. |
| `goal` | `string` | Yes | Investigation goal. Required by the schema and echoed in the tool result. |
## Outputs
The tool returns a single text result plus structured details:
+1 -1
View File
@@ -279,7 +279,7 @@ const agent = new Agent({
## Tools
Define tools using `AgentTool` with a Zod parameter schema (via `z` from `@oh-my-pi/pi-ai`). Legacy TypeBox-authored schemas are still accepted at runtime and are lifted to Zod internally.
Define tools using `AgentTool` with a Zod parameter schema (via `z` from `@oh-my-pi/pi-ai`).
```typescript
import { z } from "@oh-my-pi/pi-ai";
+1
View File
@@ -5,6 +5,7 @@
- Renamed public schema utilities in `@oh-my-pi/pi-ai/utils/schema` by replacing `sanitizeSchemaForGoogle`, `sanitizeSchemaForCCA`, `prepareSchemaForCCA`, and `sanitizeSchemaForMCP` with `normalizeSchemaForGoogle`, `normalizeSchemaForCCA`, and `normalizeSchemaForMCP`
- Added MCP schema normalization via `normalizeSchemaForMCP` for compatibility checks
- Removed the `StringEnum` helper from `@oh-my-pi/pi-ai/utils/schema`. Use `z.enum([...])` directly; Zod's emitted JSON Schema is already wire-compatible with Google and other providers.
### Changed
+3 -6
View File
@@ -89,7 +89,7 @@ npm install @oh-my-pi/pi-ai
## Quick Start
```typescript
import { z, getModel, stream, complete, Context, Tool, StringEnum } from "@oh-my-pi/pi-ai";
import { z, getModel, stream, complete, Context, Tool } from "@oh-my-pi/pi-ai";
// Fully typed with auto-complete support for both providers and models
const model = getModel("openai", "gpt-4o-mini");
@@ -221,7 +221,7 @@ Tools enable LLMs to interact with external systems. This library uses **Zod** s
### Defining Tools
```typescript
import { z, Tool, StringEnum } from "@oh-my-pi/pi-ai";
import { z, Tool } from "@oh-my-pi/pi-ai";
// Define tool parameters with Zod
const weatherTool: Tool = {
@@ -229,13 +229,10 @@ const weatherTool: Tool = {
description: "Get current weather for a location",
parameters: z.object({
location: z.string().describe("City name or coordinates"),
units: StringEnum(["celsius", "fahrenheit"], { default: "celsius" }),
units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
}),
};
// Note: For Google API compatibility, use the StringEnum helper instead of z.enum alone
// when you need wire-compatible { type: "string", enum: [...] } shapes.
const bookMeetingTool: Tool = {
name: "book_meeting",
description: "Schedule a meeting",
+3 -6
View File
@@ -6,7 +6,6 @@ import { Effort } from "@oh-my-pi/pi-ai";
import { getBundledModel } from "@oh-my-pi/pi-ai/models";
import { complete, getEnvApiKey, stream } from "@oh-my-pi/pi-ai/stream";
import type { Api, Context, ImageContent, Model, OptionsForApi, Tool, ToolResultMessage } from "@oh-my-pi/pi-ai/types";
import { StringEnum } from "@oh-my-pi/pi-ai/utils/schema";
import { $which } from "@oh-my-pi/pi-utils";
import * as z from "zod/v4";
import { e2eApiKey, resolveApiKey } from "./oauth";
@@ -34,14 +33,12 @@ function hasBedrockCredentials(): boolean {
}
// Calculator tool definition (same as examples)
// Note: Using StringEnum helper because Google's API doesn't support anyOf/const patterns
// that some schema authors emit for string unions. Google requires { type: "string", enum: [...] } format.
const calculatorSchema = z.object({
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
operation: StringEnum(["add", "subtract", "multiply", "divide"], {
description: "The operation to perform. One of 'add', 'subtract', 'multiply', 'divide'.",
}),
operation: z
.enum(["add", "subtract", "multiply", "divide"])
.describe("The operation to perform. One of 'add', 'subtract', 'multiply', 'divide'."),
});
const calculatorTool: Tool<typeof calculatorSchema> = {
+1
View File
@@ -5,6 +5,7 @@
### Breaking Changes
- Renamed the embedded-documentation internal URL scheme from `pi://` to `omp://`. `OmpProtocolHandler` replaces `PiProtocolHandler`; update any external references accordingly.
- Removed the `StringEnum` re-export from `@oh-my-pi/pi-coding-agent`. Custom tools and extensions should use `z.enum([...])` directly via the injected `pi.zod`.
## [15.1.2] - 2026-05-15
### Fixed
@@ -47,7 +47,6 @@ See [docs/custom-tools.md](../../docs/custom-tools.md) for full documentation.
**Factory pattern:**
```typescript
import { StringEnum } from "@oh-my-pi/pi-ai";
import { Text } from "@oh-my-pi/pi-tui";
import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
@@ -56,7 +55,7 @@ const factory: CustomToolFactory = (pi) => ({
label: "My Tool",
description: "Tool description for LLM",
parameters: pi.zod.object({
action: StringEnum(["list", "add"] as const),
action: pi.zod.enum(["list", "add"]),
}),
// Called on session start/switch/branch/clear
@@ -76,9 +75,6 @@ const factory: CustomToolFactory = (pi) => ({
export default factory;
```
**Legacy:** `parameters: pi.typebox.Type.Object({ ... })` still works; the injected `typebox` is a small Zod-backed shim, and schemas flow through the same Zod pipeline as `pi.zod` schemas.
**Custom rendering:**
```typescript
@@ -97,17 +93,12 @@ renderResult(result, { expanded, isPartial }, theme) {
},
```
**Use `StringEnum` for discriminated string tool args** (required for Google API compatibility):
**Use `z.enum` for discriminated string tool args:**
```typescript
import { StringEnum } from "@oh-my-pi/pi-ai";
const { z } = pi.zod;
// Good — Google-safe enum wiring
parameters: z.object({
action: StringEnum(["list", "add"] as const),
action: z.enum(["list", "add"]),
});
// Avoid raw union-of-literals patterns that don't degrade well for strict JSON Schema providers
```
@@ -108,29 +108,16 @@ export default function (pi: ExtensionAPI) {
});
}
```
**Legacy TypeBox-style schemas** (`pi.typebox`) remain available for older extensions and are backed by a tiny Zod-shim — prefer `pi.zod` directly for new code.
```typescript
const { Type } = pi.typebox;
parameters: Type.Object({ name: Type.String() });
```
## Key Patterns
**Use `StringEnum` for discriminated string tool args** (required for Google API compatibility):
**Use `z.enum` for discriminated string tool args:**
```typescript
import { StringEnum } from "@oh-my-pi/pi-ai";
const { z } = pi.zod;
// Good — Google-safe enum wiring
parameters: z.object({
action: StringEnum(["list", "add"] as const),
action: z.enum(["list", "add"]),
});
// Avoid raw union-of-literals patterns that don't degrade well for strict JSON Schema providers
```
**State persistence via details:**
@@ -10,9 +10,6 @@ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
export default function (pi: ExtensionAPI) {
const { z } = pi.zod;
// Access shared schema helpers from package exports (e.g. StringEnum for Google-safe enums)
const { StringEnum } = pi.pi;
// Access the logger for debugging
pi.logger.debug("API demo extension loaded");
@@ -22,10 +19,7 @@ export default function (pi: ExtensionAPI) {
description: "Demonstrates ExtensionAPI capabilities: logger, zod, and pi module access",
parameters: z.object({
message: z.string().describe("Test message"),
logLevel: StringEnum(["error", "warn", "debug"], {
description: "Log level to use",
default: "debug",
}),
logLevel: z.enum(["error", "warn", "debug"]).default("debug").describe("Log level to use"),
}),
async execute(_toolCallId, params, _onUpdate, ctx, _signal) {
@@ -17,42 +17,20 @@ export const DEFAULT_HARNESS_COMMAND = `bash ${HARNESS_FILENAME}`;
const HARNESS_COMMIT_TITLE = "autoresearch: harness setup";
const initExperimentSchema = z.object({
name: z.string().describe("Human-readable experiment name."),
goal: z.string().describe("Free-form description of what this session optimizes.").optional(),
primary_metric: z
.string()
.describe(
"Primary metric name shown in the dashboard. Match the `METRIC <name>=<value>` lines printed by the benchmark.",
),
metric_unit: z.string().describe("Unit for the primary metric (e.g. ms, µs, mb). Empty when unitless.").optional(),
name: z.string().describe("experiment name"),
goal: z.string().describe("session goal").optional(),
primary_metric: z.string().describe("primary metric name"),
metric_unit: z.string().describe("metric unit (e.g. ms, µs, mb)").optional(),
direction: z
.enum(["lower", "higher"] as const)
.describe("Whether lower or higher values are better. Defaults to lower.")
.optional(),
secondary_metrics: z
.array(z.string())
.describe("Names of secondary metrics tracked alongside the primary metric.")
.optional(),
scope_paths: z
.array(z.string())
.describe(
"Files or directories the agent expects to modify. Used post-hoc to flag scope deviations on log_experiment; never used to block edits.",
)
.optional(),
off_limits: z
.array(z.string())
.describe(
"Paths the agent SHOULD NOT modify. Used post-hoc to flag scope deviations on log_experiment; never used to block edits.",
)
.optional(),
constraints: z.array(z.string()).describe("Free-form constraints (e.g. 'no api break').").optional(),
max_iterations: z.number().describe("Soft cap on iterations per segment. Optional.").optional(),
new_segment: z
.boolean()
.describe(
"When true, bump to a new segment even when an active session exists. New baselines and best-metric reset.",
)
.describe("better direction (default lower)")
.optional(),
secondary_metrics: z.array(z.string()).describe("secondary metric names").optional(),
scope_paths: z.array(z.string()).describe("expected-to-modify paths").optional(),
off_limits: z.array(z.string()).describe("off-limits paths").optional(),
constraints: z.array(z.string()).describe("free-form constraints").optional(),
max_iterations: z.number().describe("soft iteration cap per segment").optional(),
new_segment: z.boolean().describe("bump to a new segment in existing session").optional(),
});
interface InitExperimentDetails {
@@ -37,35 +37,21 @@ import type {
const EXPERIMENT_TOOL_NAMES = ["init_experiment", "run_experiment", "log_experiment", "update_notes"];
const logExperimentSchema = z.object({
metric: z
.number()
.describe("Primary metric value for this run. May differ from the parsed value; deviation is recorded."),
status: z.enum(["keep", "discard", "crash", "checks_failed"] as const).describe("Outcome for this run."),
description: z.string().describe("Short description of the experiment."),
metrics: z.record(z.string(), z.number()).describe("Secondary metrics for this run.").optional(),
asi: z
.object({})
.passthrough()
.describe("Free-form structured metadata captured for this run (hypothesis, learnings, etc.).")
.optional(),
commit: z
.string()
.describe("Override the commit hash recorded for this run. Defaults to the current HEAD.")
.optional(),
justification: z
.string()
.describe(
"Required when the run modifies paths outside scope or inside off-limits and you still want it kept. Free-form explanation.",
)
.optional(),
metric: z.number().describe("primary metric value"),
status: z.enum(["keep", "discard", "crash", "checks_failed"] as const).describe("run outcome"),
description: z.string().describe("short run description"),
metrics: z.record(z.string(), z.number()).describe("secondary metrics").optional(),
asi: z.object({}).passthrough().describe("free-form structured metadata").optional(),
commit: z.string().describe("override recorded commit hash").optional(),
justification: z.string().describe("required when keeping a scope-deviating run").optional(),
flag_runs: z
.array(
z.object({
run_id: z.number().describe("Run id (#) of a previously logged run to flag as suspect."),
reason: z.string().describe("Why this earlier run is suspect (e.g. reward-hacked, broken metric)."),
run_id: z.number().describe("run id to flag"),
reason: z.string().describe("why this run is suspect"),
}),
)
.describe("Mark earlier runs as flagged. Flagged runs are excluded from baseline and best-metric math.")
.describe("flag earlier runs as suspect")
.optional(),
});
@@ -27,7 +27,7 @@ import type { AutoresearchToolFactoryOptions, RunDetails, RunExperimentProgressD
import { DEFAULT_HARNESS_COMMAND } from "./init-experiment";
const runExperimentSchema = z.object({
timeout_seconds: z.number().describe("Timeout in seconds. Defaults to 600.").optional(),
timeout_seconds: z.number().describe("timeout in seconds (default 600)").optional(),
});
interface ProcessExecutionResult {
@@ -9,15 +9,8 @@ import { openAutoresearchStorageIfExists } from "../storage";
import type { AutoresearchToolFactoryOptions } from "../types";
const updateNotesSchema = z.object({
body: z
.string()
.describe("Replacement markdown body for the active autoresearch session's notes (your durable playbook)."),
append_idea: z
.string()
.describe(
"When set, append this string as a new bullet under an Ideas section instead of replacing the body. `body` is ignored.",
)
.optional(),
body: z.string().describe("replacement notes body"),
append_idea: z.string().describe("append as bullet under Ideas instead of replacing body").optional(),
});
interface UpdateNotesDetails {
@@ -13,8 +13,8 @@ import type { ToolSession } from "../../../tools";
import { getFilePriority } from "./git-file-diff";
const analyzeFileSchema = z.object({
files: z.array(z.string().describe("File path")).min(1),
goal: z.string().describe("Optional analysis focus").optional(),
files: z.array(z.string().describe("file path")).min(1),
goal: z.string().describe("analysis focus").optional(),
});
const analyzeFileOutputSchema = {
@@ -132,8 +132,8 @@ function processDiffs(files: string[], diffs: Map<string, string>): { result: st
}
const gitFileDiffSchema = z.object({
files: z.array(z.string().describe("Files to diff")).min(1).max(10),
staged: z.boolean().describe("Use staged changes (default: true)").optional(),
files: z.array(z.string().describe("file to diff")).min(1).max(10),
staged: z.boolean().describe("use staged changes (default true)").optional(),
});
export function createGitFileDiffTool(cwd: string, state: CommitAgentState): CustomTool<typeof gitFileDiffSchema> {
@@ -4,9 +4,9 @@ import type { CustomTool } from "../../../extensibility/custom-tools/types";
import * as git from "../../../utils/git";
const gitHunkSchema = z.object({
file: z.string().describe("File path"),
hunks: z.array(z.number().describe("1-based hunk indices")).min(1).optional(),
staged: z.boolean().describe("Use staged changes (default: true)").optional(),
file: z.string().describe("file path"),
hunks: z.array(z.number().describe("1-based hunk index")).min(1).optional(),
staged: z.boolean().describe("use staged changes (default true)").optional(),
});
function selectHunks(fileHunks: FileHunks, requested?: number[]): DiffHunk[] {
@@ -43,8 +43,8 @@ function filterExcludedFiles(files: string[]): { filtered: string[]; excluded: s
}
const gitOverviewSchema = z.object({
staged: z.boolean().describe("Use staged changes (default: true)").optional(),
include_untracked: z.boolean().describe("Include untracked files when staged=false").optional(),
staged: z.boolean().describe("use staged changes (default true)").optional(),
include_untracked: z.boolean().describe("include untracked when unstaged").optional(),
});
export function createGitOverviewTool(cwd: string, state: CommitAgentState): CustomTool<typeof gitOverviewSchema> {
@@ -12,9 +12,7 @@ const changelogEntryProperties = CHANGELOG_CATEGORIES.reduce<Record<ChangelogCat
);
const changelogEntriesSchema = z.object(changelogEntryProperties);
const changelogDeletionsSchema = z
.object(changelogEntryProperties)
.describe("Entries to remove from existing changelog sections (case-insensitive match)");
const changelogDeletionsSchema = z.object(changelogEntryProperties).describe("entries to remove");
const changelogEntrySchema = z.object({
path: z.string(),
@@ -3,7 +3,7 @@ import type { CustomTool } from "../../../extensibility/custom-tools/types";
import * as git from "../../../utils/git";
const recentCommitsSchema = z.object({
count: z.number().min(1).max(50).describe("Number of commits to fetch").optional(),
count: z.number().min(1).max(50).describe("commit count").optional(),
});
interface RecentCommitStats {
@@ -17,15 +17,7 @@ export const commitTypeSchema = z.enum([
export const detailSchema = z.object({
text: z.string(),
changelog_category: z
.union([
z.literal("Added"),
z.literal("Changed"),
z.literal("Fixed"),
z.literal("Deprecated"),
z.literal("Removed"),
z.literal("Security"),
z.literal("Breaking Changes"),
])
.enum(["Added", "Changed", "Fixed", "Deprecated", "Removed", "Security", "Breaking Changes"])
.optional(),
user_visible: z.boolean().optional(),
});
@@ -14,11 +14,7 @@ import { ApplyPatchError } from "../diff";
import type { PatchEditEntry } from "./patch";
export const applyPatchSchema = z.object({
input: z
.string()
.describe(
"Full Codex apply_patch envelope, including '*** Begin Patch' and '*** End Patch'. Contains any mix of Add/Delete/Update (with optional Move to) file operations.",
),
input: z.string().describe("apply_patch envelope"),
});
export type ApplyPatchParams = z.infer<typeof applyPatchSchema>;
@@ -1578,16 +1578,16 @@ export async function computePatchDiff(
export const patchEditEntrySchema = z
.object({
op: z.enum(["create", "delete", "update"]).optional().describe("Operation (default: update)"),
rename: z.string().describe("New path for move").optional(),
diff: z.string().describe("Diff hunks (update) or full content (create)").optional(),
op: z.enum(["create", "delete", "update"]).optional().describe("operation (default update)"),
rename: z.string().describe("new path for move").optional(),
diff: z.string().describe("diff hunks or full content for create").optional(),
})
.strict();
export const patchEditSchema = z
.object({
path: z.string().describe("file path for edits"),
edits: z.array(patchEditEntrySchema).min(1).describe("Patch operations"),
path: z.string().describe("file path"),
edits: z.array(patchEditEntrySchema).min(1).describe("patch operations"),
})
.strict();
@@ -978,16 +978,16 @@ export function findContextLine(
export const replaceEditEntrySchema = z
.object({
old_text: z.string().describe("Text to find (fuzzy whitespace matching enabled)"),
new_text: z.string().describe("Replacement text"),
all: z.boolean().describe("Replace all occurrences (default: unique match required)").optional(),
old_text: z.string().describe("text to find"),
new_text: z.string().describe("replacement text"),
all: z.boolean().describe("replace all occurrences").optional(),
})
.strict();
export const replaceEditSchema = z
.object({
path: z.string().describe("file path for edits"),
edits: z.array(replaceEditEntrySchema).min(1).describe("Replacements"),
path: z.string().describe("file path"),
edits: z.array(replaceEditEntrySchema).min(1).describe("replacements"),
})
.strict();
+4 -4
View File
@@ -14,9 +14,9 @@ const researcherStartTool = createExaTool(
"Start Deep Research",
"Start an asynchronous deep research task using Exa's researcher. Returns a task_id for polling completion.",
z.object({
query: z.string().describe("Research query to investigate"),
depth: z.number().int().min(1).max(5).describe("Research depth (1-5, default: 3)").optional(),
breadth: z.number().int().min(1).max(5).describe("Research breadth (1-5, default: 3)").optional(),
query: z.string().describe("research query"),
depth: z.number().int().min(1).max(5).describe("research depth (1-5)").optional(),
breadth: z.number().int().min(1).max(5).describe("research breadth (1-5)").optional(),
}),
"deep_researcher_start",
{ formatResponse: false },
@@ -27,7 +27,7 @@ const researcherPollTool = createExaTool(
"Poll Research Status",
"Poll the status of an asynchronous research task. Returns status (pending|running|completed|failed) and result if completed.",
z.object({
task_id: z.string().describe("Task ID returned from exa_researcher_start"),
task_id: z.string().describe("task id"),
}),
"deep_researcher_check",
{ formatResponse: false },
+10 -22
View File
@@ -30,28 +30,16 @@ Parameters:
- num_results: Maximum number of results to return (default: 10, max: 100)`,
z.object({
query: z.string().describe("Search query"),
type: z
.enum(["keyword", "neural", "auto"])
.describe("Search type - neural (semantic), keyword (exact), or auto")
.optional(),
include_domains: z.array(z.string()).describe("Only include results from these domains").optional(),
exclude_domains: z.array(z.string()).describe("Exclude results from these domains").optional(),
start_published_date: z
.string()
.describe("Filter results published after this date (ISO 8601 format)")
.optional(),
end_published_date: z.string().describe("Filter results published before this date (ISO 8601 format)").optional(),
use_autoprompt: z.boolean().describe("Let Exa optimize your query automatically (default: true)").optional(),
text: z.boolean().describe("Include page text content in results (costs more, default: false)").optional(),
highlights: z.boolean().describe("Include highlighted relevant snippets (default: false)").optional(),
num_results: z
.number()
.int()
.min(1)
.max(100)
.describe("Maximum number of results to return (default: 10, max: 100)")
.optional(),
query: z.string().describe("search query"),
type: z.enum(["keyword", "neural", "auto"]).describe("search type").optional(),
include_domains: z.array(z.string()).describe("include domains").optional(),
exclude_domains: z.array(z.string()).describe("exclude domains").optional(),
start_published_date: z.string().describe("published after (iso 8601)").optional(),
end_published_date: z.string().describe("published before (iso 8601)").optional(),
use_autoprompt: z.boolean().describe("autoprompt").optional(),
text: z.boolean().describe("include page text").optional(),
highlights: z.boolean().describe("include highlights").optional(),
num_results: z.number().int().min(1).max(100).describe("max results (1-100)").optional(),
}),
"web_search_exa",
);
+33 -33
View File
@@ -53,8 +53,8 @@ const websetCreateTool = createWebsetTool(
"Create Webset",
"Create a new webset collection for organizing web content.",
z.object({
name: z.string().describe("Name of the webset"),
description: z.string().describe("Optional description").optional(),
name: z.string().describe("webset name"),
description: z.string().describe("description").optional(),
}),
"create_webset",
);
@@ -72,7 +72,7 @@ const websetGetTool = createWebsetTool(
"Get Webset",
"Get details of a specific webset by ID.",
z.object({
id: z.string().describe("Webset ID"),
id: z.string().describe("webset id"),
}),
"get_webset",
);
@@ -82,9 +82,9 @@ const websetUpdateTool = createWebsetTool(
"Update Webset",
"Update a webset's name or description.",
z.object({
id: z.string().describe("Webset ID"),
name: z.string().describe("New name").optional(),
description: z.string().describe("New description").optional(),
id: z.string().describe("webset id"),
name: z.string().describe("new name").optional(),
description: z.string().describe("new description").optional(),
}),
"update_webset",
);
@@ -94,7 +94,7 @@ const websetDeleteTool = createWebsetTool(
"Delete Webset",
"Delete a webset and all its contents.",
z.object({
id: z.string().describe("Webset ID"),
id: z.string().describe("webset id"),
}),
"delete_webset",
);
@@ -105,9 +105,9 @@ const websetItemsListTool = createWebsetTool(
"List Webset Items",
"List items in a webset with optional pagination.",
z.object({
webset_id: z.string().describe("Webset ID"),
limit: z.number().describe("Number of items to return").optional(),
offset: z.number().describe("Pagination offset").optional(),
webset_id: z.string().describe("webset id"),
limit: z.number().describe("max items").optional(),
offset: z.number().describe("offset").optional(),
}),
"list_webset_items",
);
@@ -117,8 +117,8 @@ const websetItemGetTool = createWebsetTool(
"Get Webset Item",
"Get a specific item from a webset.",
z.object({
webset_id: z.string().describe("Webset ID"),
item_id: z.string().describe("Item ID"),
webset_id: z.string().describe("webset id"),
item_id: z.string().describe("item id"),
}),
"get_item",
);
@@ -129,8 +129,8 @@ const websetSearchCreateTool = createWebsetTool(
"Create Webset Search",
"Create a new search within a webset.",
z.object({
webset_id: z.string().describe("Webset ID"),
query: z.string().describe("Search query"),
webset_id: z.string().describe("webset id"),
query: z.string().describe("search query"),
}),
"create_search",
);
@@ -140,8 +140,8 @@ const websetSearchGetTool = createWebsetTool(
"Get Webset Search",
"Get the status and results of a webset search.",
z.object({
webset_id: z.string().describe("Webset ID"),
search_id: z.string().describe("Search ID"),
webset_id: z.string().describe("webset id"),
search_id: z.string().describe("search id"),
}),
"get_search",
);
@@ -151,8 +151,8 @@ const websetSearchCancelTool = createWebsetTool(
"Cancel Webset Search",
"Cancel a running webset search.",
z.object({
webset_id: z.string().describe("Webset ID"),
search_id: z.string().describe("Search ID"),
webset_id: z.string().describe("webset id"),
search_id: z.string().describe("search id"),
}),
"cancel_search",
);
@@ -163,9 +163,9 @@ const websetEnrichmentCreateTool = createWebsetTool(
"Create Enrichment",
"Create a new enrichment task for a webset.",
z.object({
webset_id: z.string().describe("Webset ID"),
name: z.string().describe("Enrichment name"),
prompt: z.string().describe("Enrichment prompt"),
webset_id: z.string().describe("webset id"),
name: z.string().describe("enrichment name"),
prompt: z.string().describe("enrichment prompt"),
}),
"create_enrichment",
);
@@ -175,8 +175,8 @@ const websetEnrichmentGetTool = createWebsetTool(
"Get Enrichment",
"Get the status and results of an enrichment task.",
z.object({
webset_id: z.string().describe("Webset ID"),
enrichment_id: z.string().describe("Enrichment ID"),
webset_id: z.string().describe("webset id"),
enrichment_id: z.string().describe("enrichment id"),
}),
"get_enrichment",
);
@@ -186,10 +186,10 @@ const websetEnrichmentUpdateTool = createWebsetTool(
"Update Enrichment",
"Update an enrichment's name or prompt.",
z.object({
webset_id: z.string().describe("Webset ID"),
enrichment_id: z.string().describe("Enrichment ID"),
name: z.string().describe("New name").optional(),
prompt: z.string().describe("New prompt").optional(),
webset_id: z.string().describe("webset id"),
enrichment_id: z.string().describe("enrichment id"),
name: z.string().describe("new name").optional(),
prompt: z.string().describe("new prompt").optional(),
}),
"update_enrichment",
);
@@ -199,8 +199,8 @@ const websetEnrichmentDeleteTool = createWebsetTool(
"Delete Enrichment",
"Delete an enrichment task.",
z.object({
webset_id: z.string().describe("Webset ID"),
enrichment_id: z.string().describe("Enrichment ID"),
webset_id: z.string().describe("webset id"),
enrichment_id: z.string().describe("enrichment id"),
}),
"delete_enrichment",
);
@@ -210,8 +210,8 @@ const websetEnrichmentCancelTool = createWebsetTool(
"Cancel Enrichment",
"Cancel a running enrichment task.",
z.object({
webset_id: z.string().describe("Webset ID"),
enrichment_id: z.string().describe("Enrichment ID"),
webset_id: z.string().describe("webset id"),
enrichment_id: z.string().describe("enrichment id"),
}),
"cancel_enrichment",
);
@@ -222,8 +222,8 @@ const websetMonitorCreateTool = createWebsetTool(
"Create Monitor",
"Create a monitoring task for a webset with optional webhook notifications.",
z.object({
webset_id: z.string().describe("Webset ID"),
webhook_url: z.string().describe("Webhook URL for notifications").optional(),
webset_id: z.string().describe("webset id"),
webhook_url: z.string().describe("webhook url").optional(),
}),
"create_monitor",
);
@@ -15,9 +15,9 @@ import { completionBudgetReport, remainingTokens } from "../runtime";
import type { Goal, GoalStatus, GoalToolDetails } from "../state";
const goalSchema = z.object({
op: z.union([z.literal("create"), z.literal("get"), z.literal("complete")]).describe("Goal operation."),
objective: z.string().describe("Goal objective. Required when op=create.").optional(),
token_budget: z.number().int().describe("Optional positive token budget. Only honored when op=create.").optional(),
op: z.enum(["create", "get", "complete"]).describe("goal operation"),
objective: z.string().describe("goal objective").optional(),
token_budget: z.number().int().describe("token budget").optional(),
});
export type GoalToolInput = z.infer<typeof goalSchema>;
-3
View File
@@ -2,9 +2,6 @@ import { HookEditorComponent, HookInputComponent, HookSelectorComponent } from "
// Core session management
// TypeBox helper for string enums (convenience for custom tools)
// Re-export from pi-ai which uses the correct enum-based schema format
export { StringEnum } from "@oh-my-pi/pi-ai";
// Re-export TUI components for custom tool rendering
export { Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
// Logging
+8 -11
View File
@@ -22,17 +22,14 @@ export const lspSchema = z.object({
"capabilities",
"request",
]),
file: z.string().describe("File path or source path for rename_file").optional(),
line: z.number().describe("Line number (1-indexed)").optional(),
symbol: z.string().describe("Symbol/substring to locate on the line").optional(),
query: z.string().describe("Search query, code-action selector, or LSP method name for action=request").optional(),
new_name: z.string().describe("New name for rename, or destination path for rename_file").optional(),
apply: z.boolean().describe("Apply edits (default: true for rename/rename_file)").optional(),
timeout: z.number().describe("Request timeout in seconds").optional(),
payload: z
.string()
.describe("JSON-encoded params for action=request. When omitted, params are auto-built from file/line/symbol.")
.optional(),
file: z.string().describe("file path or source path for rename_file").optional(),
line: z.number().describe("line number (1-indexed)").optional(),
symbol: z.string().describe("symbol substring on the line").optional(),
query: z.string().describe("search query or code-action selector").optional(),
new_name: z.string().describe("new symbol name or destination path").optional(),
apply: z.boolean().describe("apply edits").optional(),
timeout: z.number().describe("request timeout in seconds").optional(),
payload: z.string().describe("json-encoded request params").optional(),
});
export type LspParams = z.infer<typeof lspSchema>;
@@ -2,7 +2,7 @@ Resolves a pending action by either applying or discarding it.
- `action` is required:
- `"apply"` persists / submits the pending action.
- `"discard"` rejects the pending action.
- `reason` is required and must briefly explain why you chose to apply or discard.
- `reason` is required: one short complete sentence explaining why, starting with a capital letter and ending with a period.
- `extra` (optional) is free-form metadata passed to the resolving tool. Schema depends on context:
Valid whenever a pending action exists — either a preview-style staging (e.g. `ast_edit`) or a long-lived approval gate.
@@ -1,7 +1,12 @@
import { Database, type Statement } from "bun:sqlite";
import * as fs from "node:fs";
import * as path from "node:path";
import { type AuthCredential, AuthCredentialStore, type StoredAuthCredential } from "@oh-my-pi/pi-ai";
import {
type AuthCredential,
type AuthCredentialStore,
SqliteAuthCredentialStore,
type StoredAuthCredential,
} from "@oh-my-pi/pi-ai";
import { getAgentDbPath, isRecord, logger } from "@oh-my-pi/pi-utils";
import type { RawSettings as Settings } from "../config/settings";
@@ -57,7 +62,7 @@ export class AgentStorage {
this.#hardenPermissions(dbPath);
// Create AuthCredentialStore with our open database
this.#authStore = new AuthCredentialStore(this.#db);
this.#authStore = new SqliteAuthCredentialStore(this.#db);
this.#listSettingsStmt = this.#db.prepare("SELECT key, value FROM settings");
this.#upsertModelUsageStmt = this.#db.prepare(
@@ -14,4 +14,4 @@ export type {
SerializedAuthStorage,
StoredAuthCredential,
} from "@oh-my-pi/pi-ai";
export { AuthStorage } from "@oh-my-pi/pi-ai";
export { AuthStorage, SqliteAuthCredentialStore } from "@oh-my-pi/pi-ai";
+10 -35
View File
@@ -57,18 +57,13 @@ export interface SubagentLifecyclePayload {
index: number;
}
const assignmentDescriptionForContextEnabled =
"Complete per-task instructions the subagent executes. Must follow the Target/Change/Edge Cases/Acceptance structure. Only include per-task deltas — shared background belongs in `context`.";
const assignmentDescriptionForContextDisabled =
"Complete per-task instructions the subagent executes. Must follow the Target/Change/Edge Cases/Acceptance structure, and include any background that would otherwise live in `context` since shared context is disabled in this mode.";
const assignmentDescription = "per-task instructions; self-contained";
const createTaskItemSchema = (contextEnabled: boolean) =>
const createTaskItemSchema = (_contextEnabled: boolean) =>
z.object({
id: z.string().max(48).describe("CamelCase identifier, max 48 chars"),
description: z.string().describe("Short one-liner for UI display only — not seen by the subagent"),
assignment: z
.string()
.describe(contextEnabled ? assignmentDescriptionForContextEnabled : assignmentDescriptionForContextDisabled),
id: z.string().max(48).describe("camelcase identifier"),
description: z.string().describe("ui label, not seen by subagent"),
assignment: z.string().describe(assignmentDescription),
});
/** Single task item for parallel execution (default shape with context enabled). */
@@ -80,44 +75,24 @@ const createTaskSchema = (options: { isolationEnabled: boolean; simpleMode: Task
const itemSchema = createTaskItemSchema(contextEnabled);
let schema = z.object({
agent: z.string().describe("Agent type for all tasks in this batch"),
tasks: z
.array(itemSchema)
.describe(
contextEnabled
? "Tasks to execute in parallel. Each must be small-scoped (3-5 files max) and self-contained given context + assignment."
: "Tasks to execute in parallel. Each must be small-scoped (3-5 files max) and fully self-contained inside assignment because shared context is disabled.",
),
agent: z.string().describe("agent type"),
tasks: z.array(itemSchema).describe("tasks to execute in parallel"),
});
if (contextEnabled) {
schema = schema.extend({
context: z
.string()
.optional()
.describe(
"Shared background prepended to every task's assignment. Put goal, non-goals, constraints, conventions, reference paths, API contracts, and global acceptance commands here once — instead of duplicating across assignments.",
),
context: z.string().optional().describe("shared background prepended to each assignment"),
});
}
if (customSchemaEnabled) {
schema = schema.extend({
schema: z
.string()
.optional()
.describe(
"JSON-encoded JTD schema defining expected response structure. Output format belongs here — never in context or assignment.",
),
schema: z.string().optional().describe("jtd schema for expected response shape"),
});
}
if (options.isolationEnabled) {
schema = schema.extend({
isolated: z
.boolean()
.optional()
.describe("Run in isolated environment; returns patches. Use when tasks edit overlapping files."),
isolated: z.boolean().optional().describe("run in isolated env; returns patches"),
});
}
+12 -20
View File
@@ -18,19 +18,16 @@ export type { Observation, ObservationEntry } from "./browser/tab-protocol";
const DEFAULT_TAB_NAME = "main";
const appSchema = z.object({
path: z.string().describe("absolute path to a binary to spawn (single-instance reuse)").optional(),
cdp_url: z.string().describe("existing CDP endpoint to connect to (e.g. http://127.0.0.1:9222)").optional(),
args: z.array(z.string()).describe("extra CLI args when spawning").optional(),
target: z.string().describe("substring matched against url+title to pick a BrowserWindow").optional(),
path: z.string().describe("binary path to spawn").optional(),
cdp_url: z.string().describe("existing cdp endpoint").optional(),
args: z.array(z.string()).describe("extra cli args").optional(),
target: z.string().describe("substring to pick a window").optional(),
});
const browserSchema = z.object({
action: z.enum(["open", "close", "run"] as const).describe("tab/browser operation"),
name: z
.string()
.describe("tab id; default 'main'. Multiple tabs can coexist; reusable across run() calls and subagents.")
.optional(),
url: z.string().describe("open: navigate after acquiring tab").optional(),
action: z.enum(["open", "close", "run"] as const).describe("operation"),
name: z.string().describe("tab id (default 'main')").optional(),
url: z.string().describe("url to open").optional(),
app: appSchema.optional(),
viewport: z
.object({
@@ -41,21 +38,16 @@ const browserSchema = z.object({
.optional(),
wait_until: z
.enum(["load", "domcontentloaded", "networkidle0", "networkidle2"] as const)
.describe("navigation wait condition for url")
.describe("navigation wait condition")
.optional(),
dialogs: z
.enum(["accept", "dismiss"] as const)
.describe("open: auto-handle alert/confirm/beforeunload dialogs (default: leave for caller to handle)")
.optional(),
code: z
.string()
.describe(
"run: JS body executed with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. Treated as the body of an async function. Use `display(value)` to attach text/JSON/images; the function's return value is JSON-serialized as a final block.",
)
.describe("auto-handle dialogs")
.optional(),
code: z.string().describe("js body to run in tab").optional(),
timeout: z.number().default(30).describe("timeout in seconds").optional(),
all: z.boolean().describe("close: close every tab").optional(),
kill: z.boolean().describe("close: also kill spawned-app browsers (default: leave running)").optional(),
all: z.boolean().describe("close every tab").optional(),
kill: z.boolean().describe("also kill spawned-app browsers").optional(),
});
/** Input schema for the browser tool. */
+21 -45
View File
@@ -213,58 +213,34 @@ const githubSchema = z
"run_watch",
] as const)
.describe("github operation"),
repo: z.string().describe("owner/repo (any op)").optional(),
branch: z.string().describe("branch (repo_view, pr_push local branch, run_watch)").optional(),
repo: z.string().describe("owner/repo").optional(),
branch: z.string().describe("branch").optional(),
pr: z
.union([z.string(), z.array(z.string())])
.describe(
"pr number, url, or branch (pr_checkout); pass an array to batch-process multiple pull requests in one call",
)
.optional(),
force: z.boolean().describe("reset existing local branch (pr_checkout)").optional(),
forceWithLease: z.boolean().describe("force-with-lease push (pr_push)").optional(),
title: z.string().describe("PR title (pr_create)").optional(),
body: z.string().describe("PR body markdown (pr_create); mutually exclusive with fill").optional(),
base: z.string().describe("PR base branch (pr_create); defaults to repo default branch").optional(),
head: z.string().describe("PR head branch (pr_create); defaults to current branch").optional(),
draft: z.boolean().describe("open PR as draft (pr_create)").optional(),
fill: z
.boolean()
.describe("auto-fill PR title/body from commits (pr_create); mutually exclusive with title/body")
.optional(),
reviewer: z.array(z.string()).describe("reviewers to request (pr_create); accepts users or org/team").optional(),
assignee: z.array(z.string()).describe("assignees (pr_create); use @me for the authenticated user").optional(),
label: z.array(z.string()).describe("labels to apply (pr_create)").optional(),
query: z
.string()
.describe("search query (search_issues, search_prs, search_code, search_commits, search_repos)")
.optional(),
since: z
.string()
.describe(
"lower-bound date for search_issues/search_prs/search_commits/search_repos. Accepts a relative duration (`<n><unit>` with unit `m`/`h`/`d`/`w`/`mo`/`y`, e.g. `3d`, `12h`, `2w`) or an ISO date (`YYYY-MM-DD`) / datetime. Translated to a `created:>=…` (or `committer-date:`/`pushed:`) qualifier; not supported by search_code.",
)
.optional(),
until: z
.string()
.describe(
"upper-bound date in the same format as `since`. With both, builds a `field:since..until` range qualifier.",
)
.describe("pr number, url, or branch")
.optional(),
force: z.boolean().describe("reset existing local branch").optional(),
forceWithLease: z.boolean().describe("force-with-lease push").optional(),
title: z.string().describe("pr title").optional(),
body: z.string().describe("pr body markdown").optional(),
base: z.string().describe("pr base branch").optional(),
head: z.string().describe("pr head branch").optional(),
draft: z.boolean().describe("open pr as draft").optional(),
fill: z.boolean().describe("auto-fill pr title/body from commits").optional(),
reviewer: z.array(z.string()).describe("reviewers").optional(),
assignee: z.array(z.string()).describe("assignees").optional(),
label: z.array(z.string()).describe("labels").optional(),
query: z.string().describe("search query").optional(),
since: z.string().describe("lower-bound date filter").optional(),
until: z.string().describe("upper-bound date filter").optional(),
dateField: z
.enum(["created", "updated"] as const)
.describe(
"date field used by `since`/`until`. issues/prs: `created` (default) or `updated`. repos: `created` (default) or `updated` (mapped to GitHub's `pushed:`). commits: ignored — always uses `committer-date`.",
)
.describe("date field")
.default("created")
.optional(),
limit: z
.number()
.default(10)
.describe("max results (search_issues, search_prs, search_code, search_commits, search_repos)")
.optional(),
run: z.string().describe("actions run id or url (run_watch)").optional(),
tail: z.number().default(15).describe("log lines per failed job (run_watch)").optional(),
limit: z.number().default(10).describe("max results").optional(),
run: z.string().describe("actions run id or url").optional(),
tail: z.number().default(15).describe("log lines per failed job").optional(),
})
.strict();
@@ -6,7 +6,7 @@ import recallDescription from "../prompts/tools/recall.md" with { type: "text" }
import type { ToolSession } from ".";
const hindsightRecallSchema = z.object({
query: z.string().describe("Natural language search query. Be specific about what you need to know."),
query: z.string().describe("natural language search query"),
});
export type HindsightRecallParams = z.infer<typeof hindsightRecallSchema>;
@@ -6,8 +6,8 @@ import reflectDescription from "../prompts/tools/reflect.md" with { type: "text"
import type { ToolSession } from ".";
const hindsightReflectSchema = z.object({
query: z.string().describe("The question to answer using long-term memory."),
context: z.string().describe("Optional additional context to guide the reflection.").optional(),
query: z.string().describe("question to answer"),
context: z.string().describe("optional context").optional(),
});
export type HindsightReflectParams = z.infer<typeof hindsightReflectSchema>;
@@ -7,16 +7,12 @@ const hindsightRetainSchema = z.object({
items: z
.array(
z.object({
content: z
.string()
.describe("The information to remember. Be specific and self-contained — include who, what, when, why."),
context: z.string().describe("Optional context describing where this information came from.").optional(),
content: z.string().describe("information to remember"),
context: z.string().describe("source context").optional(),
}),
)
.min(1)
.describe(
"One or more memories to retain. Batch related facts in a single call rather than calling retain repeatedly — they are deduplicated and consolidated together.",
),
.describe("memories to retain"),
});
export type HindsightRetainParams = z.infer<typeof hindsightRetainSchema>;
+4 -12
View File
@@ -26,18 +26,10 @@ import type { AgentRef, AgentRegistry } from "../registry/agent-registry";
import type { ToolSession } from ".";
const ircSchema = z.object({
op: z
.union([
z.literal("send").describe("Send a message to one peer or to all peers"),
z.literal("list").describe("List currently visible peers"),
])
.describe("IRC operation"),
to: z.string().optional().describe('Recipient agent id (e.g. "0-Main", "0-AuthLoader") or "all" to broadcast'),
message: z.string().optional().describe("Message body to deliver"),
awaitReply: z
.boolean()
.optional()
.describe("Wait for the recipient's prose reply (default: true for DM, false for broadcast)"),
op: z.enum(["send", "list"]).describe("irc operation"),
to: z.string().optional().describe('recipient agent id or "all"'),
message: z.string().optional().describe("message body"),
awaitReply: z.boolean().optional().describe("wait for prose reply"),
});
type IrcParams = z.infer<typeof ircSchema>;
+3 -11
View File
@@ -23,17 +23,9 @@ import {
import { ToolError } from "./tool-errors";
const jobSchema = z.object({
poll: z
.array(z.string())
.optional()
.describe("background job ids to wait for; omit (with no `cancel`) to wait on all running jobs"),
cancel: z.array(z.string()).optional().describe("background job ids to cancel"),
list: z
.boolean()
.optional()
.describe(
"Return an immediate snapshot of every job spawned by this agent (running + completed within retention). Read-only \u2014 cannot be combined with `poll` or `cancel`.",
),
poll: z.array(z.string()).optional().describe("job ids to wait for"),
cancel: z.array(z.string()).optional().describe("job ids to cancel"),
list: z.boolean().optional().describe("snapshot all jobs"),
});
type JobParams = z.infer<typeof jobSchema>;
+2 -7
View File
@@ -12,14 +12,9 @@ import { replaceTabs } from "./render-utils";
import { ToolError } from "./tool-errors";
const resolveSchema = z.object({
action: z.union([z.literal("apply"), z.literal("discard")]),
action: z.enum(["apply", "discard"]),
reason: z.string().describe("reason for action"),
extra: z
.record(z.string(), z.unknown())
.optional()
.describe(
'Free-form metadata interpreted by the resolving tool (e.g. plan-mode approval requires `{ title: "<PLAN_TITLE>" }`).',
),
extra: z.record(z.string(), z.unknown()).optional().describe("free-form metadata"),
});
type ResolveParams = z.infer<typeof resolveSchema>;
+8 -15
View File
@@ -49,31 +49,24 @@ const TodoOp = z
.describe("operation to apply");
const InitListEntry = z.object({
phase: z.string().describe("phase name (short noun phrase)"),
items: z
.array(z.string().describe("task content (5-10 words)"))
.min(1)
.describe("tasks for this phase, in execution order; all start as pending"),
phase: z.string().describe("phase name"),
items: z.array(z.string().describe("task content")).min(1).describe("tasks for this phase"),
});
const TodoOpEntry = z.object({
op: TodoOp,
list: z.array(InitListEntry).optional().describe("phased task list for op=init"),
task: z.string().optional().describe("task content for start/done/rm/drop/note"),
phase: z.string().optional().describe("phase name for done/rm/drop/append"),
items: z
.array(z.string().describe("task content (5-10 words)"))
.min(1)
.optional()
.describe("tasks to append to `phase` for op=append"),
text: z.string().optional().describe("note text for op=note (appended with newline)"),
list: z.array(InitListEntry).optional().describe("phased task list (init)"),
task: z.string().optional().describe("task content"),
phase: z.string().optional().describe("phase name"),
items: z.array(z.string().describe("task content")).min(1).optional().describe("tasks to append"),
text: z.string().optional().describe("note text"),
});
const todoWriteSchema = z
.object({
ops: z.array(TodoOpEntry).min(1).describe("ordered todo operations"),
})
.describe("Apply ordered todo operations");
.describe("apply ordered todo operations");
type TodoWriteParams = z.infer<typeof todoWriteSchema>;
type TodoOpEntryValue = TodoWriteParams["ops"][number];
@@ -21,12 +21,12 @@ import { SearchProviderError } from "./types";
/** Web search tool parameters schema */
export const webSearchSchema = z.object({
query: z.string().describe("Search query"),
recency: z.enum(["day", "week", "month", "year"]).describe("Recency filter (Brave, Perplexity)").optional(),
limit: z.number().describe("Max results to return").optional(),
max_tokens: z.number().describe("Maximum output tokens").optional(),
temperature: z.number().describe("Sampling temperature").optional(),
num_search_results: z.number().describe("Number of search results to retrieve").optional(),
query: z.string().describe("search query"),
recency: z.enum(["day", "week", "month", "year"]).describe("recency filter").optional(),
limit: z.number().describe("max results").optional(),
max_tokens: z.number().describe("max output tokens").optional(),
temperature: z.number().describe("sampling temperature").optional(),
num_search_results: z.number().describe("number of search results").optional(),
});
export type SearchToolParams = z.infer<typeof webSearchSchema>;