@oh-my-pi/pi-coding-agent 14.5.12 → 14.5.14
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 +45 -0
- package/package.json +18 -10
- package/src/cli/jupyter-cli.ts +1 -1
- package/src/commit/pipeline.ts +4 -3
- package/src/config/model-equivalence.ts +49 -16
- package/src/config/model-registry.ts +100 -25
- package/src/config/model-resolver.ts +29 -15
- package/src/config/settings-schema.ts +20 -6
- package/src/config/settings.ts +9 -8
- package/src/config.ts +18 -6
- package/src/eval/backend.ts +43 -0
- package/src/eval/eval.lark +43 -0
- package/src/eval/index.ts +5 -0
- package/src/eval/js/context-manager.ts +717 -0
- package/src/eval/js/executor.ts +131 -0
- package/src/eval/js/index.ts +46 -0
- package/src/eval/js/prelude.ts +2 -0
- package/src/eval/js/prelude.txt +84 -0
- package/src/eval/js/tool-bridge.ts +124 -0
- package/src/eval/parse.ts +337 -0
- package/src/{ipy → eval/py}/executor.ts +2 -180
- package/src/{ipy → eval/py}/gateway-coordinator.ts +2 -2
- package/src/eval/py/index.ts +58 -0
- package/src/{ipy → eval/py}/kernel.ts +9 -45
- package/src/{ipy → eval/py}/prelude.py +39 -227
- package/src/eval/types.ts +48 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +8 -10
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/lsp/client.ts +9 -0
- package/src/lsp/index.ts +395 -0
- package/src/lsp/types.ts +15 -4
- package/src/main.ts +35 -14
- package/src/mcp/manager.ts +22 -0
- package/src/mcp/oauth-flow.ts +1 -1
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/components/{python-execution.ts → eval-execution.ts} +11 -4
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/oauth-selector.ts +2 -1
- package/src/modes/components/tool-execution.ts +3 -4
- package/src/modes/controllers/command-controller.ts +28 -8
- package/src/modes/controllers/input-controller.ts +4 -4
- package/src/modes/controllers/selector-controller.ts +2 -1
- package/src/modes/interactive-mode.ts +4 -5
- package/src/modes/rpc/rpc-client.ts +9 -0
- package/src/modes/rpc/rpc-mode.ts +6 -0
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/types.ts +3 -3
- package/src/modes/utils/ui-helpers.ts +2 -2
- package/src/prompts/system/system-prompt.md +3 -3
- package/src/prompts/tools/eval.md +92 -0
- package/src/prompts/tools/lsp.md +7 -3
- package/src/sdk.ts +64 -35
- package/src/session/agent-session.ts +152 -46
- package/src/session/messages.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/system-prompt.ts +34 -66
- package/src/task/agents.ts +4 -5
- package/src/task/executor.ts +5 -9
- package/src/tools/archive-reader.ts +9 -3
- package/src/tools/browser/launch.ts +22 -0
- package/src/tools/browser/readable.ts +11 -6
- package/src/tools/browser/registry.ts +25 -244
- package/src/tools/browser/render.ts +1 -1
- package/src/tools/browser/tab-protocol.ts +101 -0
- package/src/tools/browser/tab-supervisor.ts +429 -0
- package/src/tools/browser/tab-worker-entry.ts +21 -0
- package/src/tools/browser/tab-worker.ts +1006 -0
- package/src/tools/browser.ts +17 -32
- package/src/tools/checkpoint.ts +2 -2
- package/src/tools/{python.ts → eval.ts} +324 -315
- package/src/tools/exit-plan-mode.ts +1 -1
- package/src/tools/image-gen.ts +2 -2
- package/src/tools/index.ts +62 -100
- package/src/tools/read.ts +0 -6
- package/src/tools/recipe/runners/pkg.ts +34 -32
- package/src/tools/renderers.ts +2 -2
- package/src/tools/resolve.ts +7 -2
- package/src/tools/todo-write.ts +0 -1
- package/src/tools/tool-timeouts.ts +2 -2
- package/src/tools/write.ts +8 -1
- package/src/utils/markit.ts +15 -7
- package/src/utils/tools-manager.ts +5 -5
- package/src/web/scrapers/crossref.ts +3 -3
- package/src/web/scrapers/devto.ts +1 -1
- package/src/web/scrapers/discourse.ts +5 -5
- package/src/web/scrapers/firefox-addons.ts +1 -1
- package/src/web/scrapers/flathub.ts +2 -2
- package/src/web/scrapers/gitlab.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
- package/src/web/scrapers/mastodon.ts +9 -9
- package/src/web/scrapers/mdn.ts +11 -7
- package/src/web/scrapers/pub-dev.ts +1 -1
- package/src/web/scrapers/rawg.ts +3 -3
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/spdx.ts +1 -1
- package/src/web/scrapers/stackoverflow.ts +2 -2
- package/src/web/scrapers/types.ts +53 -39
- package/src/web/scrapers/w3c.ts +1 -1
- package/src/web/search/index.ts +5 -5
- package/src/web/search/provider.ts +121 -39
- package/src/web/search/providers/gemini.ts +4 -4
- package/src/web/search/render.ts +2 -2
- package/src/ipy/modules.ts +0 -144
- package/src/prompts/tools/python.md +0 -57
- package/src/tools/browser/vm.ts +0 -792
- /package/src/{ipy → eval/py}/cancellation.ts +0 -0
- /package/src/{ipy → eval/py}/prelude.ts +0 -0
- /package/src/{ipy → eval/py}/runtime.ts +0 -0
|
@@ -46,7 +46,7 @@ export class ExitPlanModeTool implements AgentTool<typeof exitPlanModeSchema, Ex
|
|
|
46
46
|
readonly parameters = exitPlanModeSchema;
|
|
47
47
|
readonly strict = true;
|
|
48
48
|
readonly concurrency = "exclusive";
|
|
49
|
-
readonly intent = (): string => "
|
|
49
|
+
readonly intent = (): string => "present plan";
|
|
50
50
|
|
|
51
51
|
constructor(private readonly session: ToolSession) {
|
|
52
52
|
this.description = prompt.render(exitPlanModeDescription);
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { getAntigravityUserAgent, getEnvApiKey, type Model, StringEnum } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import {
|
|
5
5
|
CODEX_BASE_URL,
|
|
6
6
|
getCodexAccountId,
|
|
@@ -1055,7 +1055,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1055
1055
|
Authorization: `Bearer ${apiKey.apiKey}`,
|
|
1056
1056
|
"Content-Type": "application/json",
|
|
1057
1057
|
Accept: "text/event-stream",
|
|
1058
|
-
|
|
1058
|
+
"User-Agent": getAntigravityUserAgent(),
|
|
1059
1059
|
},
|
|
1060
1060
|
body: JSON.stringify(requestBody),
|
|
1061
1061
|
signal: requestSignal,
|
package/src/tools/index.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { ToolChoice } from "@oh-my-pi/pi-ai";
|
|
3
|
-
import { $env, $flag,
|
|
3
|
+
import { $env, $flag, logger } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import type { AsyncJobManager } from "../async";
|
|
5
5
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
6
6
|
import type { Settings } from "../config/settings";
|
|
7
7
|
import { EditTool } from "../edit";
|
|
8
|
+
import { checkPythonKernelAvailability } from "../eval/py/kernel";
|
|
8
9
|
import type { Skill } from "../extensibility/skills";
|
|
9
10
|
import type { InternalUrlRouter } from "../internal-urls";
|
|
10
|
-
import { getPreludeDocs, resetPreludeDocsCache, warmPythonEnvironment } from "../ipy/executor";
|
|
11
|
-
import { checkPythonKernelAvailability } from "../ipy/kernel";
|
|
12
11
|
import { LspTool } from "../lsp";
|
|
13
12
|
import type { DiscoverableMCPSearchIndex, DiscoverableMCPTool } from "../mcp/discoverable-tool-metadata";
|
|
14
13
|
import type { PlanModeState } from "../plan-mode/state";
|
|
@@ -27,6 +26,7 @@ import { BrowserTool } from "./browser";
|
|
|
27
26
|
import { CalculatorTool } from "./calculator";
|
|
28
27
|
import { type CheckpointState, CheckpointTool, RewindTool } from "./checkpoint";
|
|
29
28
|
import { DebugTool } from "./debug";
|
|
29
|
+
import { EvalTool } from "./eval";
|
|
30
30
|
import { ExitPlanModeTool } from "./exit-plan-mode";
|
|
31
31
|
import { FindTool } from "./find";
|
|
32
32
|
import { GithubTool } from "./gh";
|
|
@@ -35,7 +35,6 @@ import { IrcTool } from "./irc";
|
|
|
35
35
|
import { JobTool } from "./job";
|
|
36
36
|
import { NotebookTool } from "./notebook";
|
|
37
37
|
import { wrapToolWithMetaNotice } from "./output-meta";
|
|
38
|
-
import { PythonTool } from "./python";
|
|
39
38
|
import { ReadTool } from "./read";
|
|
40
39
|
import { RecipeTool } from "./recipe";
|
|
41
40
|
import { RenderMermaidTool } from "./render-mermaid";
|
|
@@ -66,6 +65,7 @@ export * from "./browser";
|
|
|
66
65
|
export * from "./calculator";
|
|
67
66
|
export * from "./checkpoint";
|
|
68
67
|
export * from "./debug";
|
|
68
|
+
export * from "./eval";
|
|
69
69
|
export * from "./exit-plan-mode";
|
|
70
70
|
export * from "./find";
|
|
71
71
|
export * from "./gh";
|
|
@@ -74,7 +74,6 @@ export * from "./inspect-image";
|
|
|
74
74
|
export * from "./irc";
|
|
75
75
|
export * from "./job";
|
|
76
76
|
export * from "./notebook";
|
|
77
|
-
export * from "./python";
|
|
78
77
|
export * from "./read";
|
|
79
78
|
export * from "./recipe";
|
|
80
79
|
export * from "./render-mermaid";
|
|
@@ -108,8 +107,6 @@ export interface ToolSession {
|
|
|
108
107
|
hasUI: boolean;
|
|
109
108
|
/** Skip Python kernel availability check and warmup */
|
|
110
109
|
skipPythonPreflight?: boolean;
|
|
111
|
-
/** Force Python prelude warmup even when test env would normally skip it */
|
|
112
|
-
forcePythonWarmup?: boolean;
|
|
113
110
|
/** Pre-loaded context files (AGENTS.md, etc) */
|
|
114
111
|
contextFiles?: ContextFileEntry[];
|
|
115
112
|
/** Pre-loaded skills */
|
|
@@ -130,16 +127,18 @@ export interface ToolSession {
|
|
|
130
127
|
taskDepth?: number;
|
|
131
128
|
/** Get session file */
|
|
132
129
|
getSessionFile: () => string | null;
|
|
133
|
-
/** Get
|
|
134
|
-
|
|
135
|
-
/** Reject new
|
|
136
|
-
|
|
137
|
-
/** Track tool-owned
|
|
138
|
-
|
|
130
|
+
/** Get eval kernel owner ID for session-scoped retained-kernel cleanup. */
|
|
131
|
+
getEvalKernelOwnerId?: () => string | null;
|
|
132
|
+
/** Reject new eval (python or js) work once session disposal has started. */
|
|
133
|
+
assertEvalExecutionAllowed?: () => void;
|
|
134
|
+
/** Track tool-owned eval work so session disposal can await/abort it like direct session eval runs. */
|
|
135
|
+
trackEvalExecution?<T>(execution: Promise<T>, abortController: AbortController): Promise<T>;
|
|
139
136
|
/** Get session ID */
|
|
140
137
|
getSessionId?: () => string | null;
|
|
141
138
|
/** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
|
|
142
139
|
getAgentId?: () => string | null;
|
|
140
|
+
/** Look up a registered tool by name (used by the eval js backend's tool bridge). */
|
|
141
|
+
getToolByName?: (name: string) => AgentTool | undefined;
|
|
143
142
|
/** Agent registry for IRC routing across live sessions. */
|
|
144
143
|
agentRegistry?: AgentRegistry;
|
|
145
144
|
/** Get artifacts directory for artifact:// URLs */
|
|
@@ -210,7 +209,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
210
209
|
ask: AskTool.createIf,
|
|
211
210
|
bash: s => new BashTool(s),
|
|
212
211
|
debug: DebugTool.createIf,
|
|
213
|
-
|
|
212
|
+
eval: s => new EvalTool(s),
|
|
214
213
|
calc: s => new CalculatorTool(s),
|
|
215
214
|
ssh: loadSshTool,
|
|
216
215
|
edit: s => new EditTool(s),
|
|
@@ -244,34 +243,40 @@ export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
|
244
243
|
|
|
245
244
|
export type ToolName = keyof typeof BUILTIN_TOOLS;
|
|
246
245
|
|
|
247
|
-
export
|
|
246
|
+
export interface EvalBackendsAllowance {
|
|
247
|
+
python: boolean;
|
|
248
|
+
js: boolean;
|
|
249
|
+
}
|
|
248
250
|
|
|
249
251
|
/**
|
|
250
|
-
* Parse PI_PY environment
|
|
251
|
-
* Returns null
|
|
252
|
-
*
|
|
253
|
-
* Values:
|
|
254
|
-
* - "0" or "bash" → bash-only
|
|
255
|
-
* - "1" or "py" → ipy-only
|
|
256
|
-
* - "mix" or "both" → both
|
|
252
|
+
* Parse PI_PY / PI_JS environment variables. Each is a boolean flag; unset
|
|
253
|
+
* means "not specified, defer to settings". Returns null when neither is set
|
|
254
|
+
* so the caller can fall through to `readEvalBackendsAllowance` per key.
|
|
257
255
|
*/
|
|
258
|
-
function
|
|
259
|
-
const
|
|
260
|
-
|
|
256
|
+
function getEvalBackendsFromEnv(): EvalBackendsAllowance | null {
|
|
257
|
+
const pyEnv = $env.PI_PY;
|
|
258
|
+
const jsEnv = $env.PI_JS;
|
|
259
|
+
if (pyEnv === undefined && jsEnv === undefined) return null;
|
|
260
|
+
return {
|
|
261
|
+
python: pyEnv === undefined ? true : $flag("PI_PY"),
|
|
262
|
+
js: jsEnv === undefined ? true : $flag("PI_JS"),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
261
265
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
266
|
+
/** Read per-backend allowance from settings (defaults true). */
|
|
267
|
+
export function readEvalBackendsAllowance(session: ToolSession): EvalBackendsAllowance {
|
|
268
|
+
return {
|
|
269
|
+
python: session.settings.get("eval.py") ?? true,
|
|
270
|
+
js: session.settings.get("eval.js") ?? true,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Materialize the active eval backend allowance: PI_PY / PI_JS env flags
|
|
276
|
+
* override the per-key settings; otherwise settings (defaults true) win.
|
|
277
|
+
*/
|
|
278
|
+
export function resolveEvalBackends(session: ToolSession): EvalBackendsAllowance {
|
|
279
|
+
return getEvalBackendsFromEnv() ?? readEvalBackendsAllowance(session);
|
|
275
280
|
}
|
|
276
281
|
|
|
277
282
|
/**
|
|
@@ -285,77 +290,34 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
285
290
|
if (requestedTools && !requestedTools.includes("exit_plan_mode")) {
|
|
286
291
|
requestedTools.push("exit_plan_mode");
|
|
287
292
|
}
|
|
288
|
-
const
|
|
293
|
+
const backends = resolveEvalBackends(session);
|
|
294
|
+
const allowPython = backends.python;
|
|
295
|
+
const allowJs = backends.js;
|
|
289
296
|
const skipPythonPreflight = session.skipPythonPreflight === true;
|
|
297
|
+
// Eval tool is enabled if EITHER backend is reachable. We only need to know
|
|
298
|
+
// whether python is reachable when JS is disabled — otherwise allowEval is
|
|
299
|
+
// already true and the python-availability check can be deferred to first
|
|
300
|
+
// invocation of the python backend (already handled inside the executor).
|
|
290
301
|
let pythonAvailable = true;
|
|
291
|
-
|
|
302
|
+
if (
|
|
292
303
|
!skipPythonPreflight &&
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const skipPythonWarm = (isTestEnv && !forcePythonWarmup) || $flag("PI_PYTHON_SKIP_CHECK");
|
|
298
|
-
const cachedPreludeDocs = getPreludeDocs();
|
|
299
|
-
const shouldWarmPython = !skipPythonWarm && (forcePythonWarmup || cachedPreludeDocs.length === 0);
|
|
300
|
-
if (shouldCheckPython) {
|
|
304
|
+
allowPython &&
|
|
305
|
+
!allowJs &&
|
|
306
|
+
(requestedTools === undefined || requestedTools.includes("eval"))
|
|
307
|
+
) {
|
|
301
308
|
const availability = await logger.time("createTools:pythonCheck", checkPythonKernelAvailability, session.cwd);
|
|
302
309
|
pythonAvailable = availability.ok;
|
|
303
310
|
if (!availability.ok) {
|
|
304
|
-
logger.warn("Python kernel unavailable
|
|
311
|
+
logger.warn("Python kernel unavailable and JS backend disabled; eval will be unavailable", {
|
|
305
312
|
reason: availability.reason,
|
|
306
313
|
});
|
|
307
|
-
} else if (shouldWarmPython) {
|
|
308
|
-
const sessionFile = session.getSessionFile?.() ?? undefined;
|
|
309
|
-
const kernelOwnerId = session.getPythonKernelOwnerId?.() ?? undefined;
|
|
310
|
-
const warmSessionId = sessionFile ? `session:${sessionFile}:cwd:${session.cwd}` : `cwd:${session.cwd}`;
|
|
311
|
-
const warmupAbortController = new AbortController();
|
|
312
|
-
try {
|
|
313
|
-
session.assertPythonExecutionAllowed?.();
|
|
314
|
-
if (forcePythonWarmup && cachedPreludeDocs.length > 0) {
|
|
315
|
-
resetPreludeDocsCache();
|
|
316
|
-
}
|
|
317
|
-
const warmupExecution = session.trackPythonExecution
|
|
318
|
-
? logger.time(
|
|
319
|
-
"createTools:warmPython",
|
|
320
|
-
warmPythonEnvironment,
|
|
321
|
-
session.cwd,
|
|
322
|
-
warmSessionId,
|
|
323
|
-
session.settings.get("python.sharedGateway"),
|
|
324
|
-
sessionFile,
|
|
325
|
-
kernelOwnerId,
|
|
326
|
-
warmupAbortController.signal,
|
|
327
|
-
)
|
|
328
|
-
: logger.time(
|
|
329
|
-
"createTools:warmPython",
|
|
330
|
-
warmPythonEnvironment,
|
|
331
|
-
session.cwd,
|
|
332
|
-
warmSessionId,
|
|
333
|
-
session.settings.get("python.sharedGateway"),
|
|
334
|
-
sessionFile,
|
|
335
|
-
kernelOwnerId,
|
|
336
|
-
);
|
|
337
|
-
await (session.trackPythonExecution?.(warmupExecution, warmupAbortController) ?? warmupExecution);
|
|
338
|
-
session.assertPythonExecutionAllowed?.();
|
|
339
|
-
} catch (err) {
|
|
340
|
-
logger.warn("Failed to warm Python environment", {
|
|
341
|
-
error: err instanceof Error ? err.message : String(err),
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
314
|
}
|
|
345
315
|
}
|
|
346
316
|
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
requestedTools &&
|
|
352
|
-
allowBash &&
|
|
353
|
-
!allowPython &&
|
|
354
|
-
requestedTools.includes("python") &&
|
|
355
|
-
!requestedTools.includes("bash")
|
|
356
|
-
) {
|
|
357
|
-
requestedTools.push("bash");
|
|
358
|
-
}
|
|
317
|
+
const effectivePythonAllowed = allowPython && pythonAvailable;
|
|
318
|
+
// Eval is exposed whenever any backend is reachable. The python backend may
|
|
319
|
+
// be unreachable, in which case eval dispatches exclusively to js.
|
|
320
|
+
const allowEval = effectivePythonAllowed || allowJs;
|
|
359
321
|
|
|
360
322
|
// Auto-include AST counterparts when their text-based sibling is present
|
|
361
323
|
if (requestedTools) {
|
|
@@ -384,8 +346,8 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
384
346
|
const allTools: Record<string, ToolFactory> = { ...BUILTIN_TOOLS, ...HIDDEN_TOOLS };
|
|
385
347
|
const isToolAllowed = (name: string) => {
|
|
386
348
|
if (name === "lsp") return enableLsp && session.settings.get("lsp.enabled");
|
|
387
|
-
if (name === "bash") return
|
|
388
|
-
if (name === "
|
|
349
|
+
if (name === "bash") return true;
|
|
350
|
+
if (name === "eval") return allowEval;
|
|
389
351
|
if (name === "debug") return session.settings.get("debug.enabled");
|
|
390
352
|
if (name === "todo_write") return !includeYield && session.settings.get("todo.enabled");
|
|
391
353
|
if (name === "find") return session.settings.get("find.enabled");
|
package/src/tools/read.ts
CHANGED
|
@@ -456,12 +456,6 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
456
456
|
readonly parameters = readSchema;
|
|
457
457
|
readonly nonAbortable = true;
|
|
458
458
|
readonly strict = true;
|
|
459
|
-
readonly intent = (args: Partial<ReadParams>): string => {
|
|
460
|
-
const p = typeof args.path === "string" ? args.path.trim() : "";
|
|
461
|
-
if (!p) return "Reading";
|
|
462
|
-
const isUrl = /^(https?|ftp):\/\//i.test(p);
|
|
463
|
-
return isUrl ? `Fetching ${p}` : `Reading ${p}`;
|
|
464
|
-
};
|
|
465
459
|
|
|
466
460
|
readonly #autoResizeImages: boolean;
|
|
467
461
|
readonly #defaultLimit: number;
|
|
@@ -10,21 +10,19 @@ interface PackageJsonInfo {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
async function resolvePackageRunner(cwd: string): Promise<string> {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if ($which("bun"))
|
|
26
|
-
return "bun run";
|
|
27
|
-
}
|
|
13
|
+
const [bunLock, bunLockb, pnpmLock, yarnLock, npmLock, npmShrink] = await Promise.all([
|
|
14
|
+
isFile(path.join(cwd, "bun.lock")),
|
|
15
|
+
isFile(path.join(cwd, "bun.lockb")),
|
|
16
|
+
isFile(path.join(cwd, "pnpm-lock.yaml")),
|
|
17
|
+
isFile(path.join(cwd, "yarn.lock")),
|
|
18
|
+
isFile(path.join(cwd, "package-lock.json")),
|
|
19
|
+
isFile(path.join(cwd, "npm-shrinkwrap.json")),
|
|
20
|
+
]);
|
|
21
|
+
if (bunLock || bunLockb) return "bun run";
|
|
22
|
+
if (pnpmLock) return "pnpm run";
|
|
23
|
+
if (yarnLock) return "yarn";
|
|
24
|
+
if (npmLock || npmShrink) return "npm run";
|
|
25
|
+
if ($which("bun")) return "bun run";
|
|
28
26
|
return "npm run";
|
|
29
27
|
}
|
|
30
28
|
|
|
@@ -86,18 +84,23 @@ async function readPackageJson(filePath: string): Promise<PackageJsonInfo | null
|
|
|
86
84
|
async function findWorkspacePackageJsons(cwd: string, patterns: string[]): Promise<string[]> {
|
|
87
85
|
const includePatterns = patterns.filter(pattern => !pattern.startsWith("!")).map(normalizeWorkspacePattern);
|
|
88
86
|
const excludePatterns = patterns.filter(pattern => pattern.startsWith("!")).map(normalizeWorkspacePattern);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
excluded.add(path.normalize(String(entry)));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const files = new Set<string>();
|
|
96
|
-
for (const pattern of includePatterns) {
|
|
87
|
+
|
|
88
|
+
const collect = async (pattern: string): Promise<string[]> => {
|
|
89
|
+
const out: string[] = [];
|
|
97
90
|
for await (const entry of new Bun.Glob(pattern).scan({ cwd, onlyFiles: true })) {
|
|
98
|
-
|
|
99
|
-
if (normalized !== "package.json" && !excluded.has(normalized)) files.add(normalized);
|
|
91
|
+
out.push(path.normalize(String(entry)));
|
|
100
92
|
}
|
|
93
|
+
return out;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const [excludedLists, includedLists] = await Promise.all([
|
|
97
|
+
Promise.all(excludePatterns.map(pattern => collect(pattern.slice(1)))),
|
|
98
|
+
Promise.all(includePatterns.map(pattern => collect(pattern))),
|
|
99
|
+
]);
|
|
100
|
+
const excluded = new Set<string>(excludedLists.flat());
|
|
101
|
+
const files = new Set<string>();
|
|
102
|
+
for (const entry of includedLists.flat()) {
|
|
103
|
+
if (entry !== "package.json" && !excluded.has(entry)) files.add(entry);
|
|
101
104
|
}
|
|
102
105
|
return [...files].sort((left, right) => left.localeCompare(right));
|
|
103
106
|
}
|
|
@@ -132,10 +135,10 @@ async function readPackageTasks(cwd: string): Promise<RunnerTask[] | null> {
|
|
|
132
135
|
);
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (!pkg || pkg.scripts.length === 0)
|
|
138
|
-
const packageDir = path.dirname(
|
|
138
|
+
const pkgs = await Promise.all(workspacePackageJsons.map(p => readPackageJson(path.join(cwd, p))));
|
|
139
|
+
pkgs.forEach((pkg, index) => {
|
|
140
|
+
if (!pkg || pkg.scripts.length === 0) return;
|
|
141
|
+
const packageDir = path.dirname(workspacePackageJsons[index]);
|
|
139
142
|
tasks.push(
|
|
140
143
|
...tasksForPackage({
|
|
141
144
|
pkg,
|
|
@@ -143,7 +146,7 @@ async function readPackageTasks(cwd: string): Promise<RunnerTask[] | null> {
|
|
|
143
146
|
namespaced: true,
|
|
144
147
|
}),
|
|
145
148
|
);
|
|
146
|
-
}
|
|
149
|
+
});
|
|
147
150
|
|
|
148
151
|
return tasks.length > 0 ? tasks : null;
|
|
149
152
|
}
|
|
@@ -153,8 +156,7 @@ export const pkgRunner: TaskRunner = {
|
|
|
153
156
|
label: "Pkg",
|
|
154
157
|
async detect(cwd: string): Promise<DetectedRunner | null> {
|
|
155
158
|
try {
|
|
156
|
-
const commandPrefix = await resolvePackageRunner(cwd);
|
|
157
|
-
const tasks = await readPackageTasks(cwd);
|
|
159
|
+
const [commandPrefix, tasks] = await Promise.all([resolvePackageRunner(cwd), readPackageTasks(cwd)]);
|
|
158
160
|
if (!tasks || tasks.length === 0) return null;
|
|
159
161
|
return { id: "pkg", label: "Pkg", commandPrefix, tasks };
|
|
160
162
|
} catch (err) {
|
package/src/tools/renderers.ts
CHANGED
|
@@ -17,12 +17,12 @@ import { bashToolRenderer } from "./bash";
|
|
|
17
17
|
import { browserToolRenderer } from "./browser/render";
|
|
18
18
|
import { calculatorToolRenderer } from "./calculator";
|
|
19
19
|
import { debugToolRenderer } from "./debug";
|
|
20
|
+
import { evalToolRenderer } from "./eval";
|
|
20
21
|
import { findToolRenderer } from "./find";
|
|
21
22
|
import { githubToolRenderer } from "./gh-renderer";
|
|
22
23
|
import { inspectImageToolRenderer } from "./inspect-image-renderer";
|
|
23
24
|
import { jobToolRenderer } from "./job";
|
|
24
25
|
import { notebookToolRenderer } from "./notebook";
|
|
25
|
-
import { pythonToolRenderer } from "./python";
|
|
26
26
|
import { readToolRenderer } from "./read";
|
|
27
27
|
import { recipeToolRenderer } from "./recipe/render";
|
|
28
28
|
import { resolveToolRenderer } from "./resolve";
|
|
@@ -53,7 +53,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
53
53
|
browser: browserToolRenderer as ToolRenderer,
|
|
54
54
|
recipe: recipeToolRenderer as ToolRenderer,
|
|
55
55
|
debug: debugToolRenderer as ToolRenderer,
|
|
56
|
-
|
|
56
|
+
eval: evalToolRenderer as ToolRenderer,
|
|
57
57
|
calc: calculatorToolRenderer as ToolRenderer,
|
|
58
58
|
edit: editToolRenderer as ToolRenderer,
|
|
59
59
|
apply_patch: editToolRenderer as ToolRenderer,
|
package/src/tools/resolve.ts
CHANGED
|
@@ -110,8 +110,13 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
|
|
|
110
110
|
readonly description: string;
|
|
111
111
|
readonly parameters = resolveSchema;
|
|
112
112
|
readonly strict = true;
|
|
113
|
-
readonly intent = (args: Partial<ResolveParams>) =>
|
|
114
|
-
args.action === "discard"
|
|
113
|
+
readonly intent = (args: Partial<ResolveParams>) => {
|
|
114
|
+
if (args.action === "discard") {
|
|
115
|
+
return args.reason ? `discarding: ${args.reason}` : "aiscarding changes";
|
|
116
|
+
} else {
|
|
117
|
+
return args.reason ? `accepting: ${args.reason}` : "accepting changes";
|
|
118
|
+
}
|
|
119
|
+
};
|
|
115
120
|
|
|
116
121
|
constructor(private readonly session: ToolSession) {
|
|
117
122
|
this.description = prompt.render(resolveDescription);
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -507,7 +507,6 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
507
507
|
readonly parameters = todoWriteSchema;
|
|
508
508
|
readonly concurrency = "exclusive";
|
|
509
509
|
readonly strict = true;
|
|
510
|
-
readonly intent = "omit" as const;
|
|
511
510
|
|
|
512
511
|
constructor(private readonly session: ToolSession) {
|
|
513
512
|
this.description = prompt.render(todoWriteDescription);
|
|
@@ -9,8 +9,8 @@ export interface ToolTimeoutConfig {
|
|
|
9
9
|
|
|
10
10
|
export const TOOL_TIMEOUTS = {
|
|
11
11
|
bash: { default: 300, min: 1, max: 3600 },
|
|
12
|
-
|
|
13
|
-
browser: { default: 30, min: 1, max:
|
|
12
|
+
eval: { default: 30, min: 1, max: 600 },
|
|
13
|
+
browser: { default: 30, min: 1, max: 30 },
|
|
14
14
|
ssh: { default: 60, min: 1, max: 3600 },
|
|
15
15
|
fetch: { default: 20, min: 1, max: 45 },
|
|
16
16
|
lsp: { default: 20, min: 5, max: 60 },
|
package/src/tools/write.ts
CHANGED
|
@@ -6,7 +6,6 @@ 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
8
|
import { type Static, Type } from "@sinclair/typebox";
|
|
9
|
-
import { unzipSync, zipSync } from "fflate";
|
|
10
9
|
import { stripHashlinePrefixes } from "../edit";
|
|
11
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
12
11
|
import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
|
|
@@ -44,6 +43,12 @@ import {
|
|
|
44
43
|
import { ToolError } from "./tool-errors";
|
|
45
44
|
import { toolResult } from "./tool-result";
|
|
46
45
|
|
|
46
|
+
let fflateModulePromise: Promise<typeof import("fflate")> | undefined;
|
|
47
|
+
async function loadFflate(): Promise<typeof import("fflate")> {
|
|
48
|
+
if (!fflateModulePromise) fflateModulePromise = import("fflate");
|
|
49
|
+
return fflateModulePromise;
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
const writeSchema = Type.Object({
|
|
48
53
|
path: Type.String({ description: "file path", examples: ["src/new.ts"] }),
|
|
49
54
|
content: Type.String({ description: "file content" }),
|
|
@@ -229,6 +234,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
229
234
|
if (resolvedArchivePath.exists) {
|
|
230
235
|
try {
|
|
231
236
|
const bytes = await Bun.file(resolvedArchivePath.absolutePath).bytes();
|
|
237
|
+
const { unzipSync } = await loadFflate();
|
|
232
238
|
const existing = unzipSync(new Uint8Array(bytes));
|
|
233
239
|
for (const [entryPath, data] of Object.entries(existing)) {
|
|
234
240
|
zipEntries[entryPath.replace(/\\/g, "/")] = data;
|
|
@@ -241,6 +247,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
241
247
|
zipEntries[resolvedArchivePath.archiveSubPath] = new TextEncoder().encode(content);
|
|
242
248
|
|
|
243
249
|
try {
|
|
250
|
+
const { zipSync } = await loadFflate();
|
|
244
251
|
const zipBuffer = zipSync(zipEntries);
|
|
245
252
|
await Bun.write(resolvedArchivePath.absolutePath, zipBuffer);
|
|
246
253
|
} catch (error) {
|
package/src/utils/markit.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
2
|
-
import type { StreamInfo } from "markit-ai";
|
|
3
|
-
import { Markit } from "markit-ai";
|
|
2
|
+
import type { Markit, StreamInfo } from "markit-ai";
|
|
4
3
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
5
4
|
|
|
6
5
|
export interface MarkitConversionResult {
|
|
@@ -9,7 +8,15 @@ export interface MarkitConversionResult {
|
|
|
9
8
|
error?: string;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
let markit: () => Markit | Promise<Markit> = async () => {
|
|
12
|
+
const promise = import("markit-ai").then(({ Markit }) => {
|
|
13
|
+
const instance = new Markit();
|
|
14
|
+
markit = () => instance;
|
|
15
|
+
return instance;
|
|
16
|
+
});
|
|
17
|
+
markit = () => promise;
|
|
18
|
+
return promise;
|
|
19
|
+
};
|
|
13
20
|
|
|
14
21
|
function normalizeExtension(extension: string): string {
|
|
15
22
|
const trimmed = extension.trim().toLowerCase();
|
|
@@ -24,9 +31,10 @@ function normalizeError(error: unknown): string {
|
|
|
24
31
|
return "Conversion failed";
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
async function runMarkitConversion<T>(task: () => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
34
|
+
async function runMarkitConversion<T>(task: (markit: Markit) => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
28
35
|
try {
|
|
29
|
-
|
|
36
|
+
const instance = await markit();
|
|
37
|
+
return signal ? await untilAborted(signal, () => task(instance)) : await task(instance);
|
|
30
38
|
} catch (error) {
|
|
31
39
|
if (error instanceof ToolAbortError) {
|
|
32
40
|
throw error;
|
|
@@ -48,7 +56,7 @@ function finalizeConversion(markdown?: string): MarkitConversionResult {
|
|
|
48
56
|
|
|
49
57
|
export async function convertFileWithMarkit(filePath: string, signal?: AbortSignal): Promise<MarkitConversionResult> {
|
|
50
58
|
try {
|
|
51
|
-
const result = await runMarkitConversion(
|
|
59
|
+
const result = await runMarkitConversion(markit => markit.convertFile(filePath), signal);
|
|
52
60
|
return finalizeConversion(result.markdown);
|
|
53
61
|
} catch (error) {
|
|
54
62
|
if (error instanceof ToolAbortError) {
|
|
@@ -70,7 +78,7 @@ export async function convertBufferWithMarkit(
|
|
|
70
78
|
};
|
|
71
79
|
|
|
72
80
|
try {
|
|
73
|
-
const result = await runMarkitConversion(
|
|
81
|
+
const result = await runMarkitConversion(markit => markit.convert(Buffer.from(buffer), streamInfo), signal);
|
|
74
82
|
return finalizeConversion(result.markdown);
|
|
75
83
|
} catch (error) {
|
|
76
84
|
if (error instanceof ToolAbortError) {
|
|
@@ -74,14 +74,14 @@ const TOOLS: Record<string, ToolConfig> = {
|
|
|
74
74
|
},
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
//
|
|
78
|
-
interface
|
|
77
|
+
// CLI packages installed via uv/pip
|
|
78
|
+
interface PythonPackageToolConfig {
|
|
79
79
|
name: string;
|
|
80
80
|
package: string; // PyPI package name
|
|
81
81
|
binaryName: string; // CLI command name after install
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const PYTHON_TOOLS: Record<string,
|
|
84
|
+
const PYTHON_TOOLS: Record<string, PythonPackageToolConfig> = {
|
|
85
85
|
trafilatura: {
|
|
86
86
|
name: "trafilatura",
|
|
87
87
|
package: "trafilatura",
|
|
@@ -93,7 +93,7 @@ export type ToolName = "sd" | "sg" | "yt-dlp" | "trafilatura";
|
|
|
93
93
|
|
|
94
94
|
// Get the path to a tool (system-wide or in our tools dir)
|
|
95
95
|
export function getToolPath(tool: ToolName): string | null {
|
|
96
|
-
// Check
|
|
96
|
+
// Check uv/pip-installed CLI packages first
|
|
97
97
|
const pythonConfig = PYTHON_TOOLS[tool];
|
|
98
98
|
if (pythonConfig) {
|
|
99
99
|
return $which(pythonConfig.binaryName);
|
|
@@ -306,7 +306,7 @@ export async function ensureTool(tool: ToolName, silentOrOptions?: EnsureToolOpt
|
|
|
306
306
|
return undefined;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
// Handle
|
|
309
|
+
// Handle uv/pip-installed CLI packages
|
|
310
310
|
const pythonConfig = PYTHON_TOOLS[tool];
|
|
311
311
|
if (pythonConfig) {
|
|
312
312
|
if (!silent) {
|
|
@@ -66,10 +66,10 @@ function formatDate(date?: CrossrefDate): string | null {
|
|
|
66
66
|
return formatted.join("-");
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function formatAbstract(abstract?: string): string | null {
|
|
69
|
+
async function formatAbstract(abstract?: string): Promise<string | null> {
|
|
70
70
|
if (!abstract) return null;
|
|
71
71
|
const normalized = abstract.replace(/<\/?jats:p[^>]*>/g, match => (match.startsWith("</") ? "</p>" : "<p>"));
|
|
72
|
-
const markdown = htmlToBasicMarkdown(normalized);
|
|
72
|
+
const markdown = await htmlToBasicMarkdown(normalized);
|
|
73
73
|
return markdown.trim().length > 0 ? markdown : null;
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -114,7 +114,7 @@ export const handleCrossref: SpecialHandler = async (
|
|
|
114
114
|
formatDate(message.issued) ||
|
|
115
115
|
formatDate(message.created);
|
|
116
116
|
const doiValue = message.DOI || doi;
|
|
117
|
-
const abstract = formatAbstract(message.abstract);
|
|
117
|
+
const abstract = await formatAbstract(message.abstract);
|
|
118
118
|
const type = message.type?.replace(/-/g, " ");
|
|
119
119
|
|
|
120
120
|
let md = `# ${title}\n\n`;
|
|
@@ -133,7 +133,7 @@ export const handleDevTo: SpecialHandler = async (
|
|
|
133
133
|
if (article.body_markdown) {
|
|
134
134
|
md += article.body_markdown;
|
|
135
135
|
} else if (article.body_html) {
|
|
136
|
-
md += htmlToBasicMarkdown(article.body_html);
|
|
136
|
+
md += await htmlToBasicMarkdown(article.body_html);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
notes.push("Fetched via dev.to API");
|