@narumitw/pi-chrome-devtools 0.52.1 → 0.52.3

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.
@@ -2,9 +2,11 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-c
2
2
  import { shutdownManagedBrowser } from "./browser-manager.js";
3
3
  import {
4
4
  availableChromeDevtoolsTools,
5
- configureLazyChromeDevtoolsTools,
5
+ configureChromeDevtoolsToolExposure,
6
6
  createChromeDevtoolsLoadTool,
7
7
  initializeAvailableChromeDevtoolsTools,
8
+ requireEagerChromeDevtoolsToolExposure,
9
+ supportsNativeDeferredToolLoading,
8
10
  } from "./lazy-tools.js";
9
11
  import { applyRuntimeBrowserSettings, state } from "./runtime.js";
10
12
  import { loadSettings, waitForSettingsWrites } from "./settings.js";
@@ -41,7 +43,7 @@ const COMMAND_COMPLETIONS = [
41
43
  { value: "quickstart", label: "quickstart", description: "Show endpoint and launch help" },
42
44
  { value: "status", label: "status", description: "Show tool and settings status" },
43
45
  { value: "settings", label: "settings", description: "Edit browser connection settings" },
44
- { value: "tools", label: "tools", description: "Choose lazy-loadable Chrome DevTools tools" },
46
+ { value: "tools", label: "tools", description: "Choose available Chrome DevTools tools" },
45
47
  { value: "toggle", label: "toggle", description: "Alias for tools" },
46
48
  { value: "select", label: "select", description: "Compatibility alias for tools" },
47
49
  { value: "enable", label: "enable", description: "Make all Chrome DevTools tools available" },
@@ -90,7 +92,13 @@ export default function chromeDevtools(pi: ExtensionAPI) {
90
92
  settings.kind === "loaded" && settings.settings.tools
91
93
  ? settings.settings.tools
92
94
  : availableChromeDevtoolsTools(pi);
93
- configureLazyChromeDevtoolsTools(pi, availableTools);
95
+ configureChromeDevtoolsToolExposure(pi, availableTools, ctx.model);
96
+ });
97
+
98
+ pi.on("model_select", (event) => {
99
+ if (!supportsNativeDeferredToolLoading(event.model)) {
100
+ requireEagerChromeDevtoolsToolExposure(pi);
101
+ }
94
102
  });
95
103
 
96
104
  pi.on("session_shutdown", async (_event, ctx) => {
package/src/lazy-tools.ts CHANGED
@@ -1,4 +1,8 @@
1
- import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
1
+ import {
2
+ defineTool,
3
+ type ExtensionAPI,
4
+ type ExtensionContext,
5
+ } from "@earendil-works/pi-coding-agent";
2
6
  import { Type } from "typebox";
3
7
  import { CHROME_DEVTOOLS_TOOL_NAMES, type ChromeDevToolsToolName } from "./tool-names.js";
4
8
 
@@ -13,6 +17,7 @@ const existingAvailableToolsStore = sharedGlobal[AVAILABLE_TOOLS_STORE];
13
17
  const availableToolsByApi =
14
18
  existingAvailableToolsStore ?? new WeakMap<ExtensionAPI, Set<ChromeDevToolsToolName>>();
15
19
  if (!existingAvailableToolsStore) sharedGlobal[AVAILABLE_TOOLS_STORE] = availableToolsByApi;
20
+ const lazyExposureByApi = new WeakMap<ExtensionAPI, boolean>();
16
21
 
17
22
  const SEARCH_TEXT: Record<ChromeDevToolsToolName, string> = {
18
23
  chrome_devtools_list_pages: "list open inspectable chrome browser pages tabs targets",
@@ -31,15 +36,30 @@ export function initializeAvailableChromeDevtoolsTools(pi: ExtensionAPI) {
31
36
  );
32
37
  }
33
38
 
34
- export function configureLazyChromeDevtoolsTools(
39
+ export function configureChromeDevtoolsToolExposure(
35
40
  pi: ExtensionAPI,
36
41
  availableTools: readonly ChromeDevToolsToolName[],
42
+ model?: ExtensionContext["model"],
37
43
  ) {
38
- setAvailableTools(pi, availableTools);
44
+ const available = setAvailableTools(pi, availableTools);
45
+ const lazyExposure = supportsNativeDeferredToolLoading(model);
46
+ lazyExposureByApi.set(pi, lazyExposure);
47
+ const exposedTools = lazyExposure
48
+ ? []
49
+ : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
39
50
  const nonCapabilityTools = pi
40
51
  .getActiveTools()
41
52
  .filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName));
42
- pi.setActiveTools(unique([...nonCapabilityTools, CHROME_DEVTOOLS_LOAD_TOOL_NAME]));
53
+ pi.setActiveTools(
54
+ unique([...nonCapabilityTools, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...exposedTools]),
55
+ );
56
+ }
57
+
58
+ export function requireEagerChromeDevtoolsToolExposure(pi: ExtensionAPI) {
59
+ lazyExposureByApi.set(pi, false);
60
+ const active = pi.getActiveTools();
61
+ const available = availableChromeDevtoolsTools(pi);
62
+ pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...available]));
43
63
  }
44
64
 
45
65
  export function applyAvailableChromeDevtoolsTools(
@@ -47,14 +67,46 @@ export function applyAvailableChromeDevtoolsTools(
47
67
  availableTools: readonly ChromeDevToolsToolName[],
48
68
  ) {
49
69
  const available = setAvailableTools(pi, availableTools);
70
+ const lazyExposure = lazyExposureByApi.get(pi) === true;
50
71
  const active = pi
51
72
  .getActiveTools()
52
73
  .filter(
53
74
  (name) =>
54
75
  !CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName) ||
55
- available.has(name as ChromeDevToolsToolName),
76
+ (lazyExposure && available.has(name as ChromeDevToolsToolName)),
56
77
  );
57
- pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME]));
78
+ const eagerTools = lazyExposure
79
+ ? []
80
+ : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
81
+ pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...eagerTools]));
82
+ }
83
+
84
+ export function chromeDevtoolsToolExposureMode(pi: ExtensionAPI) {
85
+ return lazyExposureByApi.get(pi) === true ? "native deferred" : "eager";
86
+ }
87
+
88
+ export function supportsNativeDeferredToolLoading(model: ExtensionContext["model"]): boolean {
89
+ if (!model) return false;
90
+ if (model.api === "anthropic-messages") {
91
+ const configured = compatBoolean(model.compat, "supportsToolReferences");
92
+ if (configured !== undefined) return configured;
93
+ if (model.provider !== "anthropic" || model.id.includes("haiku")) return false;
94
+ const version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
95
+ if (!version) return false;
96
+ const major = Number(version[1]);
97
+ const minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;
98
+ return major > 4 || (major === 4 && minor >= 5);
99
+ }
100
+ if (model.api === "openai-completions") {
101
+ return compatString(model.compat, "deferredToolsMode") === "kimi";
102
+ }
103
+ if (model.api === "openai-responses" || model.api === "openai-codex-responses") {
104
+ return (
105
+ compatBoolean(model.compat, "supportsAdditionalTools") === true ||
106
+ compatBoolean(model.compat, "supportsToolSearch") === true
107
+ );
108
+ }
109
+ return false;
58
110
  }
59
111
 
60
112
  export function availableChromeDevtoolsTools(pi: ExtensionAPI) {
@@ -138,6 +190,18 @@ function setAvailableTools(pi: ExtensionAPI, availableTools: readonly ChromeDevT
138
190
  return available;
139
191
  }
140
192
 
193
+ function compatBoolean(value: unknown, key: string) {
194
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
195
+ const record = value as Record<string, unknown>;
196
+ return typeof record[key] === "boolean" ? record[key] : undefined;
197
+ }
198
+
199
+ function compatString(value: unknown, key: string) {
200
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
201
+ const record = value as Record<string, unknown>;
202
+ return typeof record[key] === "string" ? record[key] : undefined;
203
+ }
204
+
141
205
  function unique(values: readonly string[]) {
142
206
  return [...new Set(values)];
143
207
  }
package/src/menu.ts CHANGED
@@ -306,7 +306,7 @@ export async function showChromeDevtoolsToolWorkflow(
306
306
  current.draft = new Set(selectedTools);
307
307
  current.applied = true;
308
308
  ctx.ui.notify(
309
- `Saved: ${selectedTools.length} of ${CHROME_DEVTOOLS_TOOL_NAMES.length} browser tools available to lazy-load.`,
309
+ `Saved: ${selectedTools.length} of ${CHROME_DEVTOOLS_TOOL_NAMES.length} browser tools available.`,
310
310
  "info",
311
311
  );
312
312
  return { kind: "close" };
@@ -378,7 +378,7 @@ function buildMenuSnapshot(pi: ExtensionAPI, settings: SettingsLoadResult): Menu
378
378
 
379
379
  function mainStateLines(snapshot: MenuSnapshot) {
380
380
  return sanitizeLines([
381
- `Lazy catalog: ${snapshot.activeTools.length} of ${CHROME_DEVTOOLS_TOOL_NAMES.length} available · ${snapshot.persistenceLabel}`,
381
+ `Tool catalog: ${snapshot.activeTools.length} of ${CHROME_DEVTOOLS_TOOL_NAMES.length} available · ${snapshot.persistenceLabel}`,
382
382
  `Browser: ${browserLifecycleSummary()}`,
383
383
  `Endpoint: ${devToolsEndpoint()}`,
384
384
  ...(snapshot.settingsWarning ? [`Settings warning: ${snapshot.settingsWarning}`] : []),
@@ -421,7 +421,7 @@ function buildToolReview(current: ToolWorkflowState) {
421
421
  `Current available browser tools: ${current.accepted.size}/${CHROME_DEVTOOLS_TOOL_NAMES.length}`,
422
422
  `Proposed available browser tools: ${current.draft.size}/${CHROME_DEVTOOLS_TOOL_NAMES.length}`,
423
423
  "",
424
- "Available to lazy-load after apply:",
424
+ "Available after apply:",
425
425
  ...(available.length > 0
426
426
  ? available.map((name) => ` - ${TOOL_PRESENTATION[name].label} (${name})`)
427
427
  : [" - none"]),
@@ -432,7 +432,8 @@ function buildToolReview(current: ToolWorkflowState) {
432
432
  : [" - none"]),
433
433
  "",
434
434
  "Other active Pi tools remain unchanged.",
435
- "Newly available tools remain deferred until chrome_devtools_load selects them.",
435
+ "Native-capable models defer these tools until chrome_devtools_load selects them.",
436
+ "Other models expose available tools eagerly before the next model request.",
436
437
  "The accepted availability policy is saved for future sessions.",
437
438
  ].join("\n"),
438
439
  );
@@ -14,6 +14,7 @@ import {
14
14
  applyAvailableChromeDevtoolsTools,
15
15
  availableChromeDevtoolsTools,
16
16
  CHROME_DEVTOOLS_LOAD_TOOL_NAME,
17
+ chromeDevtoolsToolExposureMode,
17
18
  } from "./lazy-tools.js";
18
19
  import { state } from "./runtime.js";
19
20
  import { loadSettings, saveSettings, settingsFilePath } from "./settings.js";
@@ -49,7 +50,7 @@ export async function updateChromeDevtoolsTools(
49
50
  if (result !== "saved" || generation !== state.sessionGeneration) return;
50
51
  const status = await buildToolStatusMessage(pi);
51
52
  if (generation !== state.sessionGeneration) return;
52
- ctx.ui.notify(`Chrome DevTools lazy catalog ${action}.\n\n${status}`, "info");
53
+ ctx.ui.notify(`Chrome DevTools tool catalog ${action}.\n\n${status}`, "info");
53
54
  }
54
55
 
55
56
  export async function setSelectedChromeDevtoolsTools(
@@ -121,7 +122,11 @@ async function transactSelectedToolsNow(
121
122
  const previousLoadedChromeTools = previousActiveTools.filter((name) =>
122
123
  CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName),
123
124
  );
124
- pi.setActiveTools(unique([...currentNonChromeTools, ...previousLoadedChromeTools]));
125
+ const restoredChromeTools =
126
+ chromeDevtoolsToolExposureMode(pi) === "eager"
127
+ ? previousAvailableTools
128
+ : previousLoadedChromeTools;
129
+ pi.setActiveTools(unique([...currentNonChromeTools, ...restoredChromeTools]));
125
130
  } catch (caught) {
126
131
  rollbackError = caught;
127
132
  }
@@ -179,10 +184,11 @@ export async function buildToolStatusMessage(pi: ExtensionAPI) {
179
184
  const persistedSetting = await persistedSettingLabel();
180
185
  return sanitizeChromeDevtoolsDisplay(
181
186
  [
182
- `Chrome DevTools tools available to lazy-load: ${formatRuntimeStatus(summary)}`,
187
+ `Chrome DevTools tools available: ${formatRuntimeStatus(summary)}`,
188
+ `Tool exposure: ${chromeDevtoolsToolExposureMode(pi)}`,
183
189
  `Loaded capability tools this session: ${summary.loadedChromeToolCount}/${CHROME_DEVTOOLS_TOOL_NAMES.length}`,
184
190
  `Loader: ${pi.getActiveTools().includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME) ? "active" : "inactive"}`,
185
- `Persisted lazy catalog: ${persistedSetting}`,
191
+ `Persisted tool catalog: ${persistedSetting}`,
186
192
  ...browserSettingsStatusLines(),
187
193
  ...(state.settingsNotice ? [`Settings note: ${state.settingsNotice}`] : []),
188
194
  `Other active tools preserved: ${summary.activeNonChromeToolCount}`,
@@ -289,9 +295,9 @@ export function buildCommandGuide() {
289
295
  "/chrome-devtools quickstart — show endpoint and launch help",
290
296
  "/chrome-devtools status — show tool and settings status",
291
297
  "/chrome-devtools settings — edit browser connection settings",
292
- "/chrome-devtools tools — choose tools available to lazy-load",
298
+ "/chrome-devtools tools — choose available Chrome DevTools tools",
293
299
  "/chrome-devtools toggle|select — compatibility aliases for tools",
294
- "/chrome-devtools enable|on — make all Chrome DevTools tools available to lazy-load",
300
+ "/chrome-devtools enable|on — make all Chrome DevTools tools available",
295
301
  "/chrome-devtools disable|off — make all Chrome DevTools capability tools unavailable",
296
302
  ].join("\n");
297
303
  }