@oh-my-pi/pi-coding-agent 14.5.11 → 14.5.13

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/package.json +18 -10
  3. package/src/cli/jupyter-cli.ts +1 -1
  4. package/src/config/model-equivalence.ts +49 -16
  5. package/src/config/model-registry.ts +100 -25
  6. package/src/config/model-resolver.ts +29 -15
  7. package/src/config/settings-schema.ts +20 -6
  8. package/src/config/settings.ts +9 -8
  9. package/src/config.ts +9 -0
  10. package/src/eval/backend.ts +43 -0
  11. package/src/eval/eval.lark +43 -0
  12. package/src/eval/index.ts +5 -0
  13. package/src/eval/js/context-manager.ts +717 -0
  14. package/src/eval/js/executor.ts +131 -0
  15. package/src/eval/js/index.ts +46 -0
  16. package/src/eval/js/prelude.ts +2 -0
  17. package/src/eval/js/prelude.txt +84 -0
  18. package/src/eval/js/tool-bridge.ts +124 -0
  19. package/src/eval/parse.ts +337 -0
  20. package/src/{ipy → eval/py}/executor.ts +2 -180
  21. package/src/{ipy → eval/py}/gateway-coordinator.ts +4 -3
  22. package/src/eval/py/index.ts +58 -0
  23. package/src/{ipy → eval/py}/kernel.ts +5 -41
  24. package/src/{ipy → eval/py}/prelude.py +39 -227
  25. package/src/eval/types.ts +48 -0
  26. package/src/export/html/template.generated.ts +1 -1
  27. package/src/export/html/template.js +23 -17
  28. package/src/extensibility/extensions/types.ts +2 -3
  29. package/src/internal-urls/docs-index.generated.ts +5 -5
  30. package/src/lsp/client.ts +9 -0
  31. package/src/lsp/index.ts +395 -0
  32. package/src/lsp/types.ts +15 -4
  33. package/src/main.ts +25 -14
  34. package/src/mcp/oauth-flow.ts +1 -1
  35. package/src/memories/index.ts +1 -1
  36. package/src/modes/acp/acp-event-mapper.ts +1 -1
  37. package/src/modes/components/{python-execution.ts → eval-execution.ts} +11 -4
  38. package/src/modes/components/login-dialog.ts +1 -1
  39. package/src/modes/components/oauth-selector.ts +2 -1
  40. package/src/modes/components/tool-execution.ts +3 -4
  41. package/src/modes/controllers/command-controller.ts +28 -8
  42. package/src/modes/controllers/input-controller.ts +4 -4
  43. package/src/modes/controllers/selector-controller.ts +2 -1
  44. package/src/modes/interactive-mode.ts +4 -5
  45. package/src/modes/types.ts +3 -3
  46. package/src/modes/utils/ui-helpers.ts +2 -2
  47. package/src/prompts/system/system-prompt.md +3 -3
  48. package/src/prompts/tools/atom.md +3 -2
  49. package/src/prompts/tools/browser.md +61 -16
  50. package/src/prompts/tools/eval.md +92 -0
  51. package/src/prompts/tools/lsp.md +7 -3
  52. package/src/sdk.ts +45 -31
  53. package/src/session/agent-session.ts +44 -54
  54. package/src/session/messages.ts +1 -1
  55. package/src/slash-commands/builtin-registry.ts +1 -1
  56. package/src/system-prompt.ts +34 -66
  57. package/src/task/executor.ts +5 -9
  58. package/src/tools/browser/attach.ts +175 -0
  59. package/src/tools/browser/launch.ts +576 -0
  60. package/src/tools/browser/readable.ts +90 -0
  61. package/src/tools/browser/registry.ts +198 -0
  62. package/src/tools/browser/render.ts +212 -0
  63. package/src/tools/browser/tab-protocol.ts +101 -0
  64. package/src/tools/browser/tab-supervisor.ts +429 -0
  65. package/src/tools/browser/tab-worker-entry.ts +21 -0
  66. package/src/tools/browser/tab-worker.ts +1006 -0
  67. package/src/tools/browser.ts +231 -1567
  68. package/src/tools/checkpoint.ts +2 -2
  69. package/src/tools/{python.ts → eval.ts} +324 -315
  70. package/src/tools/exit-plan-mode.ts +1 -1
  71. package/src/tools/index.ts +62 -100
  72. package/src/tools/plan-mode-guard.ts +27 -1
  73. package/src/tools/read.ts +0 -6
  74. package/src/tools/recipe/runners/pkg.ts +34 -32
  75. package/src/tools/renderers.ts +4 -2
  76. package/src/tools/resolve.ts +7 -2
  77. package/src/tools/todo-write.ts +0 -1
  78. package/src/tools/tool-timeouts.ts +2 -2
  79. package/src/utils/markit.ts +15 -7
  80. package/src/utils/tools-manager.ts +5 -5
  81. package/src/web/search/index.ts +5 -5
  82. package/src/web/search/provider.ts +121 -39
  83. package/src/web/search/providers/gemini.ts +2 -2
  84. package/src/web/search/render.ts +2 -2
  85. package/src/ipy/modules.ts +0 -144
  86. package/src/prompts/tools/python.md +0 -57
  87. /package/src/{ipy → eval/py}/cancellation.ts +0 -0
  88. /package/src/{ipy → eval/py}/prelude.ts +0 -0
  89. /package/src/{ipy → eval/py}/runtime.ts +0 -0
@@ -1,39 +1,121 @@
1
- import { AnthropicProvider } from "./providers/anthropic";
1
+ // Lazy registry of web search providers.
2
+ //
3
+ // Each provider is loaded on first use; importing this module loads zero
4
+ // provider implementations. Provider modules are heavy (each pulls in
5
+ // fetch/parse/format helpers) and only one — at most — is needed per session,
6
+ // so eager construction was wasted work at startup.
7
+ //
8
+ // The `label`/`id` metadata is kept inline so callers needing a display name
9
+ // (error formatting, UI listings) do not force a load.
10
+
2
11
  import type { SearchProvider } from "./providers/base";
3
- import { BraveProvider } from "./providers/brave";
4
- import { CodexProvider } from "./providers/codex";
5
- import { ExaProvider } from "./providers/exa";
6
- import { GeminiProvider } from "./providers/gemini";
7
- import { JinaProvider } from "./providers/jina";
8
- import { KagiProvider } from "./providers/kagi";
9
- import { KimiProvider } from "./providers/kimi";
10
- import { ParallelProvider } from "./providers/parallel";
11
- import { PerplexityProvider } from "./providers/perplexity";
12
- import { SearXNGProvider } from "./providers/searxng";
13
- import { SyntheticProvider } from "./providers/synthetic";
14
- import { TavilyProvider } from "./providers/tavily";
15
- import { ZaiProvider } from "./providers/zai";
16
12
  import type { SearchProviderId } from "./types";
17
13
 
18
14
  export type { SearchParams } from "./providers/base";
19
15
  export { SearchProvider } from "./providers/base";
20
16
 
21
- const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
22
- exa: new ExaProvider(),
23
- brave: new BraveProvider(),
24
- jina: new JinaProvider(),
25
- perplexity: new PerplexityProvider(),
26
- kimi: new KimiProvider(),
27
- zai: new ZaiProvider(),
28
- anthropic: new AnthropicProvider(),
29
- gemini: new GeminiProvider(),
30
- codex: new CodexProvider(),
31
- tavily: new TavilyProvider(),
32
- parallel: new ParallelProvider(),
33
- kagi: new KagiProvider(),
34
- synthetic: new SyntheticProvider(),
35
- searxng: new SearXNGProvider(),
36
- } as const;
17
+ interface ProviderMeta {
18
+ id: SearchProviderId;
19
+ label: string;
20
+ load: () => Promise<SearchProvider>;
21
+ }
22
+
23
+ /** Lazy factories. Each `load()` dynamic-imports its provider module on first call. */
24
+ const PROVIDER_META: Record<SearchProviderId, ProviderMeta> = {
25
+ exa: {
26
+ id: "exa",
27
+ label: "Exa",
28
+ load: async () => new (await import("./providers/exa")).ExaProvider(),
29
+ },
30
+ brave: {
31
+ id: "brave",
32
+ label: "Brave",
33
+ load: async () => new (await import("./providers/brave")).BraveProvider(),
34
+ },
35
+ jina: {
36
+ id: "jina",
37
+ label: "Jina",
38
+ load: async () => new (await import("./providers/jina")).JinaProvider(),
39
+ },
40
+ perplexity: {
41
+ id: "perplexity",
42
+ label: "Perplexity",
43
+ load: async () => new (await import("./providers/perplexity")).PerplexityProvider(),
44
+ },
45
+ kimi: {
46
+ id: "kimi",
47
+ label: "Kimi",
48
+ load: async () => new (await import("./providers/kimi")).KimiProvider(),
49
+ },
50
+ zai: {
51
+ id: "zai",
52
+ label: "Z.AI",
53
+ load: async () => new (await import("./providers/zai")).ZaiProvider(),
54
+ },
55
+ anthropic: {
56
+ id: "anthropic",
57
+ label: "Anthropic",
58
+ load: async () => new (await import("./providers/anthropic")).AnthropicProvider(),
59
+ },
60
+ gemini: {
61
+ id: "gemini",
62
+ label: "Gemini",
63
+ load: async () => new (await import("./providers/gemini")).GeminiProvider(),
64
+ },
65
+ codex: {
66
+ id: "codex",
67
+ label: "Codex",
68
+ load: async () => new (await import("./providers/codex")).CodexProvider(),
69
+ },
70
+ tavily: {
71
+ id: "tavily",
72
+ label: "Tavily",
73
+ load: async () => new (await import("./providers/tavily")).TavilyProvider(),
74
+ },
75
+ parallel: {
76
+ id: "parallel",
77
+ label: "Parallel",
78
+ load: async () => new (await import("./providers/parallel")).ParallelProvider(),
79
+ },
80
+ kagi: {
81
+ id: "kagi",
82
+ label: "Kagi",
83
+ load: async () => new (await import("./providers/kagi")).KagiProvider(),
84
+ },
85
+ synthetic: {
86
+ id: "synthetic",
87
+ label: "Synthetic",
88
+ load: async () => new (await import("./providers/synthetic")).SyntheticProvider(),
89
+ },
90
+ searxng: {
91
+ id: "searxng",
92
+ label: "SearXNG",
93
+ load: async () => new (await import("./providers/searxng")).SearXNGProvider(),
94
+ },
95
+ };
96
+
97
+ const instanceCache = new Map<SearchProviderId, SearchProvider>();
98
+
99
+ /** Cheap, sync metadata accessor — never triggers a provider load. */
100
+ export function getSearchProviderLabel(id: SearchProviderId): string {
101
+ return PROVIDER_META[id]?.label ?? id;
102
+ }
103
+
104
+ /**
105
+ * Resolve and cache a provider instance. First call for a given id loads the
106
+ * underlying module; subsequent calls return the cached singleton.
107
+ */
108
+ export async function getSearchProvider(id: SearchProviderId): Promise<SearchProvider> {
109
+ const cached = instanceCache.get(id);
110
+ if (cached) return cached;
111
+ const meta = PROVIDER_META[id];
112
+ if (!meta) {
113
+ throw new Error(`Unknown search provider: ${id}`);
114
+ }
115
+ const provider = await meta.load();
116
+ instanceCache.set(id, provider);
117
+ return provider;
118
+ }
37
119
 
38
120
  export const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
39
121
  "tavily",
@@ -52,10 +134,6 @@ export const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
52
134
  "searxng",
53
135
  ];
54
136
 
55
- export function getSearchProvider(provider: SearchProviderId): SearchProvider {
56
- return SEARCH_PROVIDERS[provider];
57
- }
58
-
59
137
  /** Preferred provider set via settings (default: auto) */
60
138
  let preferredProvId: SearchProviderId | "auto" = "auto";
61
139
 
@@ -64,22 +142,26 @@ export function setPreferredSearchProvider(provider: SearchProviderId | "auto"):
64
142
  preferredProvId = provider;
65
143
  }
66
144
 
67
- /** Determine which providers are configured */
145
+ /**
146
+ * Determine which providers are configured and currently available.
147
+ * Each candidate is loaded (and its `isAvailable()` called) only as the chain
148
+ * is walked, so unconfigured providers never pay the load cost.
149
+ */
68
150
  export async function resolveProviderChain(
69
151
  preferredProvider: SearchProviderId | "auto" = preferredProvId,
70
152
  ): Promise<SearchProvider[]> {
71
153
  const providers: SearchProvider[] = [];
72
154
 
73
155
  if (preferredProvider !== "auto") {
74
- if (await getSearchProvider(preferredProvider).isAvailable()) {
75
- providers.push(getSearchProvider(preferredProvider));
156
+ const provider = await getSearchProvider(preferredProvider);
157
+ if (await provider.isAvailable()) {
158
+ providers.push(provider);
76
159
  }
77
160
  }
78
161
 
79
162
  for (const id of SEARCH_PROVIDER_ORDER) {
80
163
  if (id === preferredProvider) continue;
81
-
82
- const provider = getSearchProvider(id);
164
+ const provider = await getSearchProvider(id);
83
165
  if (await provider.isAvailable()) {
84
166
  providers.push(provider);
85
167
  }
@@ -10,9 +10,9 @@ import {
10
10
  extractRetryDelay,
11
11
  getAntigravityHeaders,
12
12
  getGeminiCliHeaders,
13
- refreshAntigravityToken,
14
- refreshGoogleCloudToken,
15
13
  } from "@oh-my-pi/pi-ai";
14
+ import { refreshAntigravityToken } from "@oh-my-pi/pi-ai/utils/oauth/google-antigravity";
15
+ import { refreshGoogleCloudToken } from "@oh-my-pi/pi-ai/utils/oauth/google-gemini-cli";
16
16
  import { getAgentDbPath } from "@oh-my-pi/pi-utils";
17
17
  import { AgentStorage } from "../../../session/agent-storage";
18
18
  import type { SearchCitation, SearchResponse, SearchSource } from "../../../web/search/types";
@@ -22,7 +22,7 @@ import {
22
22
  } from "../../tools/render-utils";
23
23
  import { renderStatusLine, renderTreeList } from "../../tui";
24
24
  import { CachedOutputBlock } from "../../tui/output-block";
25
- import { getSearchProvider } from "./provider";
25
+ import { getSearchProviderLabel } from "./provider";
26
26
  import type { SearchResponse } from "./types";
27
27
 
28
28
  const MAX_COLLAPSED_ANSWER_LINES = PREVIEW_LIMITS.COLLAPSED_LINES;
@@ -112,7 +112,7 @@ export function renderSearchResult(
112
112
  : [];
113
113
  const totalAnswerLines = answerLines.length;
114
114
 
115
- const providerLabel = provider !== "none" ? getSearchProvider(provider).label : "None";
115
+ const providerLabel = provider !== "none" ? getSearchProviderLabel(provider) : "None";
116
116
  const queryPreview = args?.query
117
117
  ? truncateToWidth(args.query, 80)
118
118
  : searchQueries[0]
@@ -1,144 +0,0 @@
1
- import * as fs from "node:fs/promises";
2
- import * as path from "node:path";
3
- import { getAgentModulesDir, getProjectDir, getProjectModulesDir } from "@oh-my-pi/pi-utils";
4
- import { getExecutionCancellationError } from "./cancellation";
5
-
6
- export type PythonModuleSource = "user" | "project";
7
-
8
- export interface PythonModuleEntry {
9
- path: string;
10
- content: string;
11
- source: PythonModuleSource;
12
- }
13
-
14
- export interface PythonModuleExecuteResult {
15
- status: "ok" | "error";
16
- cancelled: boolean;
17
- timedOut?: boolean;
18
- error?: { name: string; value: string; traceback: string[] };
19
- }
20
-
21
- export interface PythonModuleExecutor {
22
- execute: (
23
- code: string,
24
- options?: { signal?: AbortSignal; timeoutMs?: number; silent?: boolean; storeHistory?: boolean },
25
- ) => Promise<PythonModuleExecuteResult>;
26
- }
27
-
28
- export interface DiscoverPythonModulesOptions {
29
- /** Working directory for project-level modules. Default: getProjectDir() */
30
- cwd?: string;
31
- /** Agent directory for user-level modules. Default: from getAgentDir() */
32
- agentDir?: string;
33
- }
34
-
35
- export interface LoadPythonModulesOptions extends DiscoverPythonModulesOptions {
36
- signal?: AbortSignal;
37
- timeoutMs?: number;
38
- deadlineMs?: number;
39
- }
40
-
41
- interface ModuleCandidate {
42
- name: string;
43
- path: string;
44
- source: PythonModuleSource;
45
- }
46
-
47
- async function listModuleCandidates(dir: string, source: PythonModuleSource): Promise<ModuleCandidate[]> {
48
- try {
49
- const entries = await fs.readdir(dir, { withFileTypes: true });
50
- return entries
51
- .filter(entry => entry.isFile() && entry.name.endsWith(".py"))
52
- .map(entry => ({
53
- name: entry.name,
54
- path: path.resolve(dir, entry.name),
55
- source,
56
- }));
57
- } catch {
58
- return [];
59
- }
60
- }
61
-
62
- async function readModuleContent(candidate: ModuleCandidate): Promise<PythonModuleEntry> {
63
- try {
64
- const content = await Bun.file(candidate.path).text();
65
- return { path: candidate.path, content, source: candidate.source };
66
- } catch (err) {
67
- const message = err instanceof Error ? err.message : String(err);
68
- throw new Error(`Failed to read Python module ${candidate.path}: ${message}`);
69
- }
70
- }
71
-
72
- function createTimeoutError(message: string): Error {
73
- const error = new Error(message);
74
- error.name = "TimeoutError";
75
- return error;
76
- }
77
-
78
- function requireModuleExecutionTimeoutMs(options: LoadPythonModulesOptions): number | undefined {
79
- if (options.deadlineMs === undefined) {
80
- return options.timeoutMs;
81
- }
82
-
83
- const remainingMs = options.deadlineMs - Date.now();
84
- if (remainingMs <= 0) {
85
- throw createTimeoutError("Python module loading timed out");
86
- }
87
-
88
- return remainingMs;
89
- }
90
-
91
- /**
92
- * Discover Python prelude extension modules from user and project directories.
93
- */
94
- export async function discoverPythonModules(options: DiscoverPythonModulesOptions = {}): Promise<PythonModuleEntry[]> {
95
- const cwd = options.cwd ?? getProjectDir();
96
-
97
- const userDir = getAgentModulesDir(options.agentDir);
98
- const projectDir = getProjectModulesDir(cwd);
99
-
100
- const userCandidates = await listModuleCandidates(userDir, "user");
101
- const projectCandidates = await listModuleCandidates(projectDir, "project");
102
-
103
- const byName = new Map<string, ModuleCandidate>();
104
- for (const candidate of userCandidates) {
105
- if (!byName.has(candidate.name)) {
106
- byName.set(candidate.name, candidate);
107
- }
108
- }
109
- for (const candidate of projectCandidates) {
110
- const existing = byName.get(candidate.name);
111
- if (!existing || existing.source === "user") {
112
- byName.set(candidate.name, candidate);
113
- }
114
- }
115
-
116
- const sorted = Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
117
- return Promise.all(sorted.map(candidate => readModuleContent(candidate)));
118
- }
119
-
120
- /**
121
- * Load Python prelude extension modules into an active kernel.
122
- */
123
- export async function loadPythonModules(
124
- executor: PythonModuleExecutor,
125
- options: LoadPythonModulesOptions = {},
126
- ): Promise<PythonModuleEntry[]> {
127
- const modules = await discoverPythonModules(options);
128
- for (const module of modules) {
129
- const result = await executor.execute(module.content, {
130
- signal: options.signal,
131
- timeoutMs: requireModuleExecutionTimeoutMs(options),
132
- silent: true,
133
- storeHistory: false,
134
- });
135
- if (result.cancelled) {
136
- throw getExecutionCancellationError(result, options.signal, `Failed to load Python module ${module.path}`);
137
- }
138
- if (result.status === "error") {
139
- const details = result.error ? `${result.error.name}: ${result.error.value}` : "unknown error";
140
- throw new Error(`Failed to load Python module ${module.path}: ${details}`);
141
- }
142
- }
143
- return modules;
144
- }
@@ -1,57 +0,0 @@
1
- Runs Python cells sequentially in persistent IPython kernel.
2
-
3
- <instruction>
4
- Kernel persists across calls and cells; **imports, variables, and functions survive — use this.**
5
-
6
- **Work incrementally:** one logical step per cell (imports, define, test, use). Pass multiple small cells in one call. Define small reusable functions you can debug individually. You **MUST** put workflow explanations in the assistant message or cell title — never inside cell code.
7
-
8
- **On failure:** errors identify the failing cell (e.g., "Cell 3 failed"). Resubmit only the fixed cell (or fixed cell + remaining cells).
9
- </instruction>
10
-
11
- {{#if categories.length}}
12
- <prelude>
13
- All helpers auto-print results and return values for chaining.
14
-
15
- {{#each categories}}
16
- ### {{name}}
17
-
18
- ```
19
- {{#each functions}}
20
- {{name}}{{signature}}
21
- {{docstring}}
22
- {{/each}}
23
- ```
24
- {{/each}}
25
- </prelude>
26
- {{/if}}
27
-
28
- <output>
29
- User sees output like Jupyter notebook; rich displays render fully:
30
- - `display(JSON(data))` → interactive JSON tree
31
- - `display(HTML(…))` → rendered HTML
32
- - `display(Markdown(…))` → formatted markdown
33
- - `plt.show()` → inline figures
34
-
35
- **You will see object repr** (e.g., `<IPython.core.display.JSON object>`). Trust `display()`; you **MUST NOT** assume the user sees only the repr.
36
- </output>
37
-
38
- <caution>
39
- - Per-call mode uses a fresh kernel each call
40
- - You **MUST** use `reset: true` to clear state when session mode is active
41
- </caution>
42
-
43
- <critical>
44
- - You **MUST** use `run()` for shell commands; you **MUST NOT** use raw `subprocess`
45
- </critical>
46
-
47
- <examples>
48
- # Multiple small cells
49
- ```python
50
- cells: [
51
- {"title": "imports", "code": "import json\nfrom pathlib import Path"},
52
- {"title": "parse helper", "code": "def parse_config(path):\n return json.loads(Path(path).read_text())"},
53
- {"title": "test helper", "code": "parse_config('config.json')"},
54
- {"title": "use helper", "code": "configs = [parse_config(p) for p in Path('.').glob('*.json')]"}
55
- ]
56
- ```
57
- </examples>
File without changes
File without changes
File without changes