@oh-my-pi/pi-coding-agent 3.21.0 → 3.25.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 +55 -1
- package/docs/sdk.md +47 -50
- package/examples/custom-tools/README.md +0 -15
- package/examples/hooks/custom-compaction.ts +1 -3
- package/examples/sdk/README.md +6 -10
- package/package.json +5 -5
- package/src/cli/args.ts +9 -6
- package/src/core/agent-session.ts +3 -3
- package/src/core/custom-commands/bundled/wt/index.ts +3 -0
- package/src/core/custom-tools/wrapper.ts +0 -1
- package/src/core/extensions/index.ts +1 -6
- package/src/core/extensions/wrapper.ts +0 -7
- package/src/core/file-mentions.ts +5 -8
- package/src/core/sdk.ts +48 -111
- package/src/core/session-manager.ts +7 -0
- package/src/core/system-prompt.ts +22 -33
- package/src/core/tools/ask.ts +14 -7
- package/src/core/tools/bash-interceptor.ts +4 -4
- package/src/core/tools/bash.ts +19 -9
- package/src/core/tools/complete.ts +131 -0
- package/src/core/tools/context.ts +7 -0
- package/src/core/tools/edit.ts +8 -15
- package/src/core/tools/exa/render.ts +4 -16
- package/src/core/tools/find.ts +7 -18
- package/src/core/tools/git.ts +13 -3
- package/src/core/tools/grep.ts +7 -18
- package/src/core/tools/index.test.ts +188 -0
- package/src/core/tools/index.ts +106 -236
- package/src/core/tools/jtd-to-json-schema.ts +274 -0
- package/src/core/tools/ls.ts +4 -9
- package/src/core/tools/lsp/index.ts +32 -29
- package/src/core/tools/lsp/render.ts +7 -28
- package/src/core/tools/notebook.ts +3 -5
- package/src/core/tools/output.ts +130 -31
- package/src/core/tools/read.ts +8 -19
- package/src/core/tools/review.ts +0 -18
- package/src/core/tools/rulebook.ts +8 -2
- package/src/core/tools/task/agents.ts +28 -7
- package/src/core/tools/task/artifacts.ts +6 -9
- package/src/core/tools/task/discovery.ts +0 -6
- package/src/core/tools/task/executor.ts +306 -257
- package/src/core/tools/task/index.ts +65 -235
- package/src/core/tools/task/name-generator.ts +247 -0
- package/src/core/tools/task/render.ts +158 -19
- package/src/core/tools/task/types.ts +13 -11
- package/src/core/tools/task/worker-protocol.ts +18 -0
- package/src/core/tools/task/worker.ts +270 -0
- package/src/core/tools/web-fetch.ts +4 -36
- package/src/core/tools/web-search/index.ts +2 -1
- package/src/core/tools/web-search/render.ts +1 -4
- package/src/core/tools/write.ts +7 -15
- package/src/discovery/helpers.test.ts +1 -1
- package/src/index.ts +5 -16
- package/src/main.ts +4 -4
- package/src/modes/interactive/theme/theme.ts +4 -4
- package/src/prompts/task.md +14 -57
- package/src/prompts/tools/output.md +4 -3
- package/src/prompts/tools/task.md +70 -0
- package/examples/custom-tools/question/index.ts +0 -84
- package/examples/custom-tools/subagent/README.md +0 -172
- package/examples/custom-tools/subagent/agents/planner.md +0 -37
- package/examples/custom-tools/subagent/agents/scout.md +0 -50
- package/examples/custom-tools/subagent/agents/worker.md +0 -24
- package/examples/custom-tools/subagent/agents.ts +0 -156
- package/examples/custom-tools/subagent/commands/implement-and-review.md +0 -10
- package/examples/custom-tools/subagent/commands/implement.md +0 -10
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +0 -9
- package/examples/custom-tools/subagent/index.ts +0 -1002
- package/examples/sdk/05-tools.ts +0 -94
- package/examples/sdk/12-full-control.ts +0 -95
- package/src/prompts/browser.md +0 -71
package/src/core/tools/index.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export { type AskToolDetails, askTool, createAskTool } from "./ask";
|
|
2
|
-
export { type BashToolDetails,
|
|
3
|
-
export {
|
|
2
|
+
export { type BashToolDetails, createBashTool } from "./bash";
|
|
3
|
+
export { createCompleteTool } from "./complete";
|
|
4
|
+
export { createEditTool } from "./edit";
|
|
4
5
|
// Exa MCP tools (22 tools)
|
|
5
6
|
export { exaTools } from "./exa/index";
|
|
6
7
|
export type { ExaRenderDetails, ExaSearchResponse, ExaSearchResult } from "./exa/types";
|
|
7
|
-
export { createFindTool, type FindToolDetails
|
|
8
|
+
export { createFindTool, type FindToolDetails } from "./find";
|
|
8
9
|
export { setPreferredImageProvider } from "./gemini-image";
|
|
9
10
|
export { createGitTool, type GitToolDetails, gitTool } from "./git";
|
|
10
|
-
export { createGrepTool, type GrepToolDetails
|
|
11
|
-
export { createLsTool, type LsToolDetails
|
|
11
|
+
export { createGrepTool, type GrepToolDetails } from "./grep";
|
|
12
|
+
export { createLsTool, type LsToolDetails } from "./ls";
|
|
12
13
|
export {
|
|
13
14
|
createLspTool,
|
|
14
15
|
type FileDiagnosticsResult,
|
|
@@ -20,14 +21,14 @@ export {
|
|
|
20
21
|
lspTool,
|
|
21
22
|
warmupLspServers,
|
|
22
23
|
} from "./lsp/index";
|
|
23
|
-
export { createNotebookTool, type NotebookToolDetails
|
|
24
|
-
export { createOutputTool, type OutputToolDetails
|
|
25
|
-
export { createReadTool, type ReadToolDetails
|
|
26
|
-
export {
|
|
27
|
-
export {
|
|
24
|
+
export { createNotebookTool, type NotebookToolDetails } from "./notebook";
|
|
25
|
+
export { createOutputTool, type OutputToolDetails } from "./output";
|
|
26
|
+
export { createReadTool, type ReadToolDetails } from "./read";
|
|
27
|
+
export { reportFindingTool, submitReviewTool } from "./review";
|
|
28
|
+
export { filterRulebookRules, formatRulesForPrompt, type RulebookToolDetails } from "./rulebook";
|
|
28
29
|
export { BUNDLED_AGENTS, createTaskTool, taskTool } from "./task/index";
|
|
29
30
|
export type { TruncationResult } from "./truncate";
|
|
30
|
-
export { createWebFetchTool, type WebFetchToolDetails
|
|
31
|
+
export { createWebFetchTool, type WebFetchToolDetails } from "./web-fetch";
|
|
31
32
|
export {
|
|
32
33
|
companyWebSearchTools,
|
|
33
34
|
createWebSearchTool,
|
|
@@ -47,247 +48,116 @@ export {
|
|
|
47
48
|
webSearchLinkedinTool,
|
|
48
49
|
webSearchTool,
|
|
49
50
|
} from "./web-search/index";
|
|
50
|
-
export { createWriteTool, type WriteToolDetails
|
|
51
|
+
export { createWriteTool, type WriteToolDetails } from "./write";
|
|
51
52
|
|
|
52
53
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
54
|
+
import type { Rule } from "../../capability/rule";
|
|
55
|
+
import type { EventBus } from "../event-bus";
|
|
56
|
+
import { createAskTool } from "./ask";
|
|
57
|
+
import { createBashTool } from "./bash";
|
|
58
|
+
import { createCompleteTool } from "./complete";
|
|
59
|
+
import { createEditTool } from "./edit";
|
|
60
|
+
import { createFindTool } from "./find";
|
|
61
|
+
import { createGitTool } from "./git";
|
|
62
|
+
import { createGrepTool } from "./grep";
|
|
63
|
+
import { createLsTool } from "./ls";
|
|
64
|
+
import { createLspTool } from "./lsp/index";
|
|
65
|
+
import { createNotebookTool } from "./notebook";
|
|
66
|
+
import { createOutputTool } from "./output";
|
|
67
|
+
import { createReadTool } from "./read";
|
|
68
|
+
import { reportFindingTool, submitReviewTool } from "./review";
|
|
69
|
+
import { createRulebookTool } from "./rulebook";
|
|
70
|
+
import { createTaskTool } from "./task/index";
|
|
71
|
+
import { createWebFetchTool } from "./web-fetch";
|
|
72
|
+
import { createWebSearchTool } from "./web-search/index";
|
|
73
|
+
import { createWriteTool } from "./write";
|
|
70
74
|
|
|
71
75
|
/** Tool type (AgentTool from pi-ai) */
|
|
72
76
|
export type Tool = AgentTool<any, any, any>;
|
|
73
77
|
|
|
74
|
-
/**
|
|
75
|
-
export interface
|
|
78
|
+
/** Session context for tool factories */
|
|
79
|
+
export interface ToolSession {
|
|
80
|
+
/** Current working directory */
|
|
81
|
+
cwd: string;
|
|
82
|
+
/** Whether UI is available */
|
|
83
|
+
hasUI: boolean;
|
|
84
|
+
/** Rulebook rules */
|
|
85
|
+
rulebookRules: Rule[];
|
|
86
|
+
/** Event bus for tool/extension communication */
|
|
87
|
+
eventBus?: EventBus;
|
|
88
|
+
/** Output schema for structured completion (subagents) */
|
|
89
|
+
outputSchema?: unknown;
|
|
90
|
+
/** Whether to include the complete tool by default */
|
|
91
|
+
requireCompleteTool?: boolean;
|
|
92
|
+
/** Get session file */
|
|
76
93
|
getSessionFile: () => string | null;
|
|
94
|
+
/** Get session spawns */
|
|
95
|
+
getSessionSpawns: () => string | null;
|
|
96
|
+
/** Settings manager (optional) */
|
|
97
|
+
settings?: {
|
|
98
|
+
getImageAutoResize(): boolean;
|
|
99
|
+
getLspFormatOnWrite(): boolean;
|
|
100
|
+
getLspDiagnosticsOnWrite(): boolean;
|
|
101
|
+
getLspDiagnosticsOnEdit(): boolean;
|
|
102
|
+
getEditFuzzyMatch(): boolean;
|
|
103
|
+
getGitToolEnabled(): boolean;
|
|
104
|
+
getBashInterceptorEnabled(): boolean;
|
|
105
|
+
};
|
|
77
106
|
}
|
|
78
107
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// Tool definitions: static tools and their factory functions
|
|
99
|
-
const toolDefs: Record<string, { tool: Tool; create: ToolFactory }> = {
|
|
100
|
-
ask: { tool: askTool, create: createAskTool },
|
|
101
|
-
read: {
|
|
102
|
-
tool: readTool,
|
|
103
|
-
create: (cwd, _ctx, options) => createReadTool(cwd, { autoResizeImages: options?.readAutoResizeImages ?? true }),
|
|
104
|
-
},
|
|
105
|
-
bash: { tool: bashTool, create: createBashTool },
|
|
106
|
-
edit: {
|
|
107
|
-
tool: editTool,
|
|
108
|
-
create: (cwd, _ctx, options) => {
|
|
109
|
-
const enableDiagnostics = options?.lspDiagnosticsOnEdit ?? false;
|
|
110
|
-
const enableFormat = options?.lspFormatOnWrite ?? true;
|
|
111
|
-
const writethrough = createLspWritethrough(cwd, {
|
|
112
|
-
enableFormat,
|
|
113
|
-
enableDiagnostics,
|
|
114
|
-
});
|
|
115
|
-
return createEditTool(cwd, { fuzzyMatch: options?.editFuzzyMatch ?? true, writethrough });
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
write: {
|
|
119
|
-
tool: writeTool,
|
|
120
|
-
create: (cwd, _ctx, options) => {
|
|
121
|
-
const enableFormat = options?.lspFormatOnWrite ?? true;
|
|
122
|
-
const enableDiagnostics = options?.lspDiagnosticsOnWrite ?? true;
|
|
123
|
-
const writethrough = createLspWritethrough(cwd, {
|
|
124
|
-
enableFormat,
|
|
125
|
-
enableDiagnostics,
|
|
126
|
-
});
|
|
127
|
-
return createWriteTool(cwd, { writethrough });
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
grep: { tool: grepTool, create: createGrepTool },
|
|
131
|
-
find: { tool: findTool, create: createFindTool },
|
|
132
|
-
git: { tool: gitTool, create: createGitTool },
|
|
133
|
-
ls: { tool: lsTool, create: createLsTool },
|
|
134
|
-
lsp: { tool: lspTool, create: createLspTool },
|
|
135
|
-
notebook: { tool: notebookTool, create: createNotebookTool },
|
|
136
|
-
output: { tool: outputTool, create: (cwd, ctx) => createOutputTool(cwd, ctx) },
|
|
137
|
-
task: { tool: taskTool, create: (cwd, ctx, opts) => createTaskTool(cwd, ctx, opts) },
|
|
138
|
-
web_fetch: { tool: webFetchTool, create: createWebFetchTool },
|
|
139
|
-
web_search: { tool: webSearchTool, create: createWebSearchTool },
|
|
140
|
-
report_finding: { tool: reportFindingTool, create: createReportFindingTool },
|
|
141
|
-
submit_review: { tool: submitReviewTool, create: createSubmitReviewTool },
|
|
108
|
+
type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool | null>;
|
|
109
|
+
|
|
110
|
+
export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
111
|
+
ask: createAskTool,
|
|
112
|
+
bash: createBashTool,
|
|
113
|
+
edit: createEditTool,
|
|
114
|
+
find: createFindTool,
|
|
115
|
+
git: createGitTool,
|
|
116
|
+
grep: createGrepTool,
|
|
117
|
+
ls: createLsTool,
|
|
118
|
+
lsp: createLspTool,
|
|
119
|
+
notebook: createNotebookTool,
|
|
120
|
+
output: createOutputTool,
|
|
121
|
+
read: createReadTool,
|
|
122
|
+
rulebook: createRulebookTool,
|
|
123
|
+
task: createTaskTool,
|
|
124
|
+
web_fetch: createWebFetchTool,
|
|
125
|
+
web_search: createWebSearchTool,
|
|
126
|
+
write: createWriteTool,
|
|
142
127
|
};
|
|
143
128
|
|
|
144
|
-
export
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Tool sets defined by name (base sets, without UI-only tools)
|
|
150
|
-
export const baseCodingToolNames: ToolName[] = [
|
|
151
|
-
"read",
|
|
152
|
-
"bash",
|
|
153
|
-
"edit",
|
|
154
|
-
"write",
|
|
155
|
-
"grep",
|
|
156
|
-
"find",
|
|
157
|
-
"git",
|
|
158
|
-
"ls",
|
|
159
|
-
"lsp",
|
|
160
|
-
"notebook",
|
|
161
|
-
"output",
|
|
162
|
-
"task",
|
|
163
|
-
"web_fetch",
|
|
164
|
-
"web_search",
|
|
165
|
-
];
|
|
166
|
-
const baseReadOnlyToolNames: ToolName[] = ["read", "grep", "find", "ls"];
|
|
167
|
-
|
|
168
|
-
// Default tools for full access mode (using process.cwd(), no UI)
|
|
169
|
-
export const codingTools: Tool[] = baseCodingToolNames.map((name) => toolDefs[name].tool);
|
|
170
|
-
|
|
171
|
-
// Read-only tools for exploration without modification (using process.cwd(), no UI)
|
|
172
|
-
export const readOnlyTools: Tool[] = baseReadOnlyToolNames.map((name) => toolDefs[name].tool);
|
|
173
|
-
|
|
174
|
-
// All available tools (using process.cwd(), no UI)
|
|
175
|
-
export const allTools = Object.fromEntries(Object.entries(toolDefs).map(([name, def]) => [name, def.tool])) as Record<
|
|
176
|
-
ToolName,
|
|
177
|
-
Tool
|
|
178
|
-
>;
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Create coding tools configured for a specific working directory.
|
|
182
|
-
* @param cwd - Working directory for tools
|
|
183
|
-
* @param hasUI - Whether UI is available (includes ask tool if true)
|
|
184
|
-
* @param sessionContext - Optional session context for tools that need it
|
|
185
|
-
* @param options - Options for tool configuration
|
|
186
|
-
*/
|
|
187
|
-
export async function createCodingTools(
|
|
188
|
-
cwd: string,
|
|
189
|
-
hasUI = false,
|
|
190
|
-
sessionContext?: SessionContext,
|
|
191
|
-
options?: CodingToolsOptions,
|
|
192
|
-
): Promise<Tool[]> {
|
|
193
|
-
const names = hasUI ? [...baseCodingToolNames, ...uiToolNames] : baseCodingToolNames;
|
|
194
|
-
const optionsWithTools = { ...options, availableTools: new Set(names) };
|
|
195
|
-
return Promise.all(names.map((name) => toolDefs[name].create(cwd, sessionContext, optionsWithTools)));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Create read-only tools configured for a specific working directory.
|
|
200
|
-
* @param cwd - Working directory for tools
|
|
201
|
-
* @param hasUI - Whether UI is available (includes ask tool if true)
|
|
202
|
-
* @param sessionContext - Optional session context for tools that need it
|
|
203
|
-
* @param options - Options for tool configuration
|
|
204
|
-
*/
|
|
205
|
-
export async function createReadOnlyTools(
|
|
206
|
-
cwd: string,
|
|
207
|
-
hasUI = false,
|
|
208
|
-
sessionContext?: SessionContext,
|
|
209
|
-
options?: CodingToolsOptions,
|
|
210
|
-
): Promise<Tool[]> {
|
|
211
|
-
const names = hasUI ? [...baseReadOnlyToolNames, ...uiToolNames] : baseReadOnlyToolNames;
|
|
212
|
-
const optionsWithTools = { ...options, availableTools: new Set(names) };
|
|
213
|
-
return Promise.all(names.map((name) => toolDefs[name].create(cwd, sessionContext, optionsWithTools)));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Create all tools configured for a specific working directory.
|
|
218
|
-
* @param cwd - Working directory for tools
|
|
219
|
-
* @param sessionContext - Optional session context for tools that need it
|
|
220
|
-
* @param options - Options for tool configuration
|
|
221
|
-
*/
|
|
222
|
-
export async function createAllTools(
|
|
223
|
-
cwd: string,
|
|
224
|
-
sessionContext?: SessionContext,
|
|
225
|
-
options?: CodingToolsOptions,
|
|
226
|
-
): Promise<Record<ToolName, Tool>> {
|
|
227
|
-
const names = Object.keys(toolDefs);
|
|
228
|
-
const optionsWithTools = { ...options, availableTools: new Set(names) };
|
|
229
|
-
const entries = await Promise.all(
|
|
230
|
-
Object.entries(toolDefs).map(async ([name, def]) => [
|
|
231
|
-
name,
|
|
232
|
-
await def.create(cwd, sessionContext, optionsWithTools),
|
|
233
|
-
]),
|
|
234
|
-
);
|
|
235
|
-
return Object.fromEntries(entries) as Record<ToolName, Tool>;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Wrap a bash tool with interception that redirects common patterns to specialized tools.
|
|
240
|
-
* This helps prevent LLMs from falling back to shell commands when better tools exist.
|
|
241
|
-
*
|
|
242
|
-
* @param bashTool - The bash tool to wrap
|
|
243
|
-
* @param availableTools - Set of tool names that are available (for context-aware blocking)
|
|
244
|
-
* @returns Wrapped bash tool with interception
|
|
245
|
-
*/
|
|
246
|
-
export function wrapBashWithInterception(bashTool: Tool, availableTools: Set<string>): Tool {
|
|
247
|
-
const originalExecute = bashTool.execute;
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
...bashTool,
|
|
251
|
-
execute: async (toolCallId, params, signal, onUpdate, context) => {
|
|
252
|
-
const command = (params as { command: string }).command;
|
|
253
|
-
|
|
254
|
-
// Check for forbidden patterns
|
|
255
|
-
const interception = checkBashInterception(command, availableTools);
|
|
256
|
-
if (interception.block) {
|
|
257
|
-
throw new Error(interception.message);
|
|
258
|
-
}
|
|
129
|
+
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
130
|
+
complete: createCompleteTool,
|
|
131
|
+
report_finding: () => reportFindingTool,
|
|
132
|
+
submit_review: () => submitReviewTool,
|
|
133
|
+
};
|
|
259
134
|
|
|
260
|
-
|
|
261
|
-
const lsInterception = checkSimpleLsInterception(command, availableTools);
|
|
262
|
-
if (lsInterception.block) {
|
|
263
|
-
throw new Error(lsInterception.message);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Pass through to original bash tool
|
|
267
|
-
return originalExecute(toolCallId, params, signal, onUpdate, context);
|
|
268
|
-
},
|
|
269
|
-
};
|
|
270
|
-
}
|
|
135
|
+
export type ToolName = keyof typeof BUILTIN_TOOLS;
|
|
271
136
|
|
|
272
137
|
/**
|
|
273
|
-
*
|
|
274
|
-
* Finds the bash tool and wraps it with interception based on other available tools.
|
|
275
|
-
*
|
|
276
|
-
* @param tools - Array of tools to process
|
|
277
|
-
* @returns Tools with bash interception applied
|
|
138
|
+
* Create tools from BUILTIN_TOOLS registry.
|
|
278
139
|
*/
|
|
279
|
-
export function
|
|
280
|
-
const
|
|
140
|
+
export async function createTools(session: ToolSession, toolNames?: string[]): Promise<Tool[]> {
|
|
141
|
+
const includeComplete = session.requireCompleteTool === true;
|
|
142
|
+
const requestedTools = toolNames && toolNames.length > 0 ? [...new Set(toolNames)] : undefined;
|
|
143
|
+
const allTools: Record<string, ToolFactory> = { ...BUILTIN_TOOLS, ...HIDDEN_TOOLS };
|
|
144
|
+
if (includeComplete && requestedTools && !requestedTools.includes("complete")) {
|
|
145
|
+
requestedTools.push("complete");
|
|
146
|
+
}
|
|
281
147
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
148
|
+
const entries = requestedTools
|
|
149
|
+
? requestedTools.filter((name) => name in allTools).map((name) => [name, allTools[name]] as const)
|
|
150
|
+
: [
|
|
151
|
+
...Object.entries(BUILTIN_TOOLS),
|
|
152
|
+
...(includeComplete ? ([["complete", HIDDEN_TOOLS.complete]] as const) : []),
|
|
153
|
+
];
|
|
154
|
+
const results = await Promise.all(entries.map(([, factory]) => factory(session)));
|
|
155
|
+
const tools = results.filter((t): t is Tool => t !== null);
|
|
156
|
+
|
|
157
|
+
if (requestedTools) {
|
|
158
|
+
const allowed = new Set(requestedTools);
|
|
159
|
+
return tools.filter((tool) => allowed.has(tool.name));
|
|
285
160
|
}
|
|
286
161
|
|
|
287
|
-
return tools
|
|
288
|
-
if (tool.name === "bash") {
|
|
289
|
-
return wrapBashWithInterception(tool, toolNames);
|
|
290
|
-
}
|
|
291
|
-
return tool;
|
|
292
|
-
});
|
|
162
|
+
return tools;
|
|
293
163
|
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert JSON Type Definition (JTD) to JSON Schema.
|
|
3
|
+
*
|
|
4
|
+
* JTD (RFC 8927) is a simpler schema format. This converter allows users to
|
|
5
|
+
* write schemas in JTD and have them converted to JSON Schema for model APIs.
|
|
6
|
+
*
|
|
7
|
+
* @see https://jsontypedef.com/
|
|
8
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8927
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type JTDPrimitive =
|
|
12
|
+
| "boolean"
|
|
13
|
+
| "string"
|
|
14
|
+
| "timestamp"
|
|
15
|
+
| "float32"
|
|
16
|
+
| "float64"
|
|
17
|
+
| "int8"
|
|
18
|
+
| "uint8"
|
|
19
|
+
| "int16"
|
|
20
|
+
| "uint16"
|
|
21
|
+
| "int32"
|
|
22
|
+
| "uint32";
|
|
23
|
+
|
|
24
|
+
interface JTDType {
|
|
25
|
+
type: JTDPrimitive;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface JTDEnum {
|
|
29
|
+
enum: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface JTDElements {
|
|
33
|
+
elements: JTDSchema;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface JTDValues {
|
|
37
|
+
values: JTDSchema;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface JTDProperties {
|
|
41
|
+
properties?: Record<string, JTDSchema>;
|
|
42
|
+
optionalProperties?: Record<string, JTDSchema>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface JTDDiscriminator {
|
|
46
|
+
discriminator: string;
|
|
47
|
+
mapping: Record<string, JTDProperties>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface JTDRef {
|
|
51
|
+
ref: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface JTDEmpty {}
|
|
55
|
+
|
|
56
|
+
type JTDSchema =
|
|
57
|
+
| JTDType
|
|
58
|
+
| JTDEnum
|
|
59
|
+
| JTDElements
|
|
60
|
+
| JTDValues
|
|
61
|
+
| JTDProperties
|
|
62
|
+
| JTDDiscriminator
|
|
63
|
+
| JTDRef
|
|
64
|
+
| JTDEmpty;
|
|
65
|
+
|
|
66
|
+
const primitiveMap: Record<JTDPrimitive, string> = {
|
|
67
|
+
boolean: "boolean",
|
|
68
|
+
string: "string",
|
|
69
|
+
timestamp: "string", // ISO 8601
|
|
70
|
+
float32: "number",
|
|
71
|
+
float64: "number",
|
|
72
|
+
int8: "integer",
|
|
73
|
+
uint8: "integer",
|
|
74
|
+
int16: "integer",
|
|
75
|
+
uint16: "integer",
|
|
76
|
+
int32: "integer",
|
|
77
|
+
uint32: "integer",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function isJTDType(schema: unknown): schema is JTDType {
|
|
81
|
+
return typeof schema === "object" && schema !== null && "type" in schema;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isJTDEnum(schema: unknown): schema is JTDEnum {
|
|
85
|
+
return typeof schema === "object" && schema !== null && "enum" in schema;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isJTDElements(schema: unknown): schema is JTDElements {
|
|
89
|
+
return typeof schema === "object" && schema !== null && "elements" in schema;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isJTDValues(schema: unknown): schema is JTDValues {
|
|
93
|
+
return typeof schema === "object" && schema !== null && "values" in schema;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isJTDProperties(schema: unknown): schema is JTDProperties {
|
|
97
|
+
return (
|
|
98
|
+
typeof schema === "object" &&
|
|
99
|
+
schema !== null &&
|
|
100
|
+
("properties" in schema || "optionalProperties" in schema)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isJTDDiscriminator(schema: unknown): schema is JTDDiscriminator {
|
|
105
|
+
return typeof schema === "object" && schema !== null && "discriminator" in schema;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isJTDRef(schema: unknown): schema is JTDRef {
|
|
109
|
+
return typeof schema === "object" && schema !== null && "ref" in schema;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function convertSchema(schema: unknown): unknown {
|
|
113
|
+
if (schema === null || typeof schema !== "object") {
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Type form: { type: "string" } → { type: "string" }
|
|
118
|
+
if (isJTDType(schema)) {
|
|
119
|
+
const jsonType = primitiveMap[schema.type as JTDPrimitive];
|
|
120
|
+
if (!jsonType) {
|
|
121
|
+
return { type: schema.type };
|
|
122
|
+
}
|
|
123
|
+
const result: Record<string, unknown> = { type: jsonType };
|
|
124
|
+
// Add format for timestamp
|
|
125
|
+
if (schema.type === "timestamp") {
|
|
126
|
+
result.format = "date-time";
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Enum form: { enum: ["a", "b"] } → { enum: ["a", "b"] }
|
|
132
|
+
if (isJTDEnum(schema)) {
|
|
133
|
+
return { enum: schema.enum };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Elements form: { elements: { type: "string" } } → { type: "array", items: ... }
|
|
137
|
+
if (isJTDElements(schema)) {
|
|
138
|
+
return {
|
|
139
|
+
type: "array",
|
|
140
|
+
items: convertSchema(schema.elements),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Values form: { values: { type: "string" } } → { type: "object", additionalProperties: ... }
|
|
145
|
+
if (isJTDValues(schema)) {
|
|
146
|
+
return {
|
|
147
|
+
type: "object",
|
|
148
|
+
additionalProperties: convertSchema(schema.values),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Properties form: { properties: {...}, optionalProperties: {...} }
|
|
153
|
+
if (isJTDProperties(schema)) {
|
|
154
|
+
const properties: Record<string, unknown> = {};
|
|
155
|
+
const required: string[] = [];
|
|
156
|
+
|
|
157
|
+
// Required properties
|
|
158
|
+
if (schema.properties) {
|
|
159
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
160
|
+
properties[key] = convertSchema(value);
|
|
161
|
+
required.push(key);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Optional properties
|
|
166
|
+
if (schema.optionalProperties) {
|
|
167
|
+
for (const [key, value] of Object.entries(schema.optionalProperties)) {
|
|
168
|
+
properties[key] = convertSchema(value);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const result: Record<string, unknown> = {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties,
|
|
175
|
+
additionalProperties: false,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
if (required.length > 0) {
|
|
179
|
+
result.required = required;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Discriminator form: { discriminator: "type", mapping: { ... } }
|
|
186
|
+
if (isJTDDiscriminator(schema)) {
|
|
187
|
+
const oneOf: unknown[] = [];
|
|
188
|
+
|
|
189
|
+
for (const [tag, props] of Object.entries(schema.mapping)) {
|
|
190
|
+
const converted = convertSchema(props) as Record<string, unknown>;
|
|
191
|
+
// Add the discriminator property
|
|
192
|
+
const properties = (converted.properties || {}) as Record<string, unknown>;
|
|
193
|
+
properties[schema.discriminator] = { const: tag };
|
|
194
|
+
|
|
195
|
+
const required = ((converted.required as string[]) || []).slice();
|
|
196
|
+
if (!required.includes(schema.discriminator)) {
|
|
197
|
+
required.push(schema.discriminator);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
oneOf.push({
|
|
201
|
+
...converted,
|
|
202
|
+
properties,
|
|
203
|
+
required,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return { oneOf };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Ref form: { ref: "MyType" } → { $ref: "#/$defs/MyType" }
|
|
211
|
+
if (isJTDRef(schema)) {
|
|
212
|
+
return { $ref: `#/$defs/${schema.ref}` };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Empty form: {} → {} (accepts anything)
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Detect if a schema is JTD format (vs JSON Schema).
|
|
221
|
+
*
|
|
222
|
+
* JTD schemas use: type (primitives), properties, optionalProperties, elements, values, enum, discriminator, ref
|
|
223
|
+
* JSON Schema uses: type: "object", type: "array", items, additionalProperties, etc.
|
|
224
|
+
*/
|
|
225
|
+
export function isJTDSchema(schema: unknown): boolean {
|
|
226
|
+
if (schema === null || typeof schema !== "object") {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const obj = schema as Record<string, unknown>;
|
|
231
|
+
|
|
232
|
+
// JTD-specific keywords
|
|
233
|
+
if ("elements" in obj) return true;
|
|
234
|
+
if ("values" in obj) return true;
|
|
235
|
+
if ("optionalProperties" in obj) return true;
|
|
236
|
+
if ("discriminator" in obj) return true;
|
|
237
|
+
if ("ref" in obj) return true;
|
|
238
|
+
|
|
239
|
+
// JTD type primitives (JSON Schema doesn't have int32, float64, etc.)
|
|
240
|
+
if ("type" in obj) {
|
|
241
|
+
const jtdPrimitives = [
|
|
242
|
+
"timestamp",
|
|
243
|
+
"float32",
|
|
244
|
+
"float64",
|
|
245
|
+
"int8",
|
|
246
|
+
"uint8",
|
|
247
|
+
"int16",
|
|
248
|
+
"uint16",
|
|
249
|
+
"int32",
|
|
250
|
+
"uint32",
|
|
251
|
+
];
|
|
252
|
+
if (jtdPrimitives.includes(obj.type as string)) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// JTD properties form without type: "object" (JSON Schema requires it)
|
|
258
|
+
if ("properties" in obj && !("type" in obj)) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Convert JTD schema to JSON Schema.
|
|
267
|
+
* If already JSON Schema, returns as-is.
|
|
268
|
+
*/
|
|
269
|
+
export function jtdToJsonSchema(schema: unknown): unknown {
|
|
270
|
+
if (!isJTDSchema(schema)) {
|
|
271
|
+
return schema;
|
|
272
|
+
}
|
|
273
|
+
return convertSchema(schema);
|
|
274
|
+
}
|