pi-agent-browser-native 0.2.39 → 0.2.40

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.
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Purpose: Load pi-agent-browser-native package configuration from Pi-scoped global, project, or explicit paths.
3
+ * Responsibilities: Resolve config paths, validate the v1 JSON shape, merge layers, classify credential sources, resolve secrets without exposing values, and provide redacted status for tools/CLIs.
4
+ * Scope: Package-owned configuration only; browser command execution and web-search API calls live in focused modules.
5
+ * Usage: The extension reads this at startup to decide optional tool registration; scripts/config.mjs mirrors the file locations for user setup.
6
+ * Invariants/Assumptions: Raw project-local plaintext credentials are unsafe and rejected; command credentials are resolved lazily at execution time.
7
+ */
8
+
9
+ import { exec as execCallback } from "node:child_process";
10
+ import { readFileSync } from "node:fs";
11
+ import { readFile } from "node:fs/promises";
12
+ import { homedir } from "node:os";
13
+ import { join, resolve } from "node:path";
14
+ import { promisify } from "node:util";
15
+
16
+ const exec = promisify(execCallback);
17
+
18
+ export const AGENT_BROWSER_CONFIG_ENV = "PI_AGENT_BROWSER_CONFIG";
19
+ export const BRAVE_API_KEY_ENV = "BRAVE_API_KEY";
20
+ export const CONFIG_RELATIVE_PATH = [".pi", "config", "pi-agent-browser-native", "config.json"] as const;
21
+ export const GLOBAL_CONFIG_RELATIVE_PATH = [".pi", "config", "pi-agent-browser-native", "config.json"] as const;
22
+ export const SECRET_COMMAND_TIMEOUT_MS = 15_000;
23
+
24
+ export type BrowserDefaultProfilePolicy = "explicit-only" | "authenticated-only" | "always";
25
+ export type AgentBrowserConfigScope = "global" | "project" | "override" | "env-fallback";
26
+ export type CredentialSourceKind = "literal" | "env" | "command";
27
+
28
+ export interface BrowserDefaultProfileConfig {
29
+ name: string;
30
+ policy?: BrowserDefaultProfilePolicy;
31
+ }
32
+
33
+ export interface AgentBrowserConfig {
34
+ version?: 1;
35
+ webSearch?: {
36
+ braveApiKey?: string;
37
+ };
38
+ browser?: {
39
+ defaultProfile?: BrowserDefaultProfileConfig;
40
+ defaultLaunchArgs?: string[];
41
+ };
42
+ }
43
+
44
+ export interface ConfigLayer {
45
+ config: AgentBrowserConfig;
46
+ path: string;
47
+ scope: Exclude<AgentBrowserConfigScope, "env-fallback">;
48
+ }
49
+
50
+ export interface CredentialSource {
51
+ kind: CredentialSourceKind;
52
+ rawValue: string;
53
+ scope: AgentBrowserConfigScope;
54
+ }
55
+
56
+ export interface AgentBrowserConfigState {
57
+ browserDefaultProfile?: Required<BrowserDefaultProfileConfig>;
58
+ config: AgentBrowserConfig;
59
+ credentialSource?: CredentialSource;
60
+ errors: string[];
61
+ layers: ConfigLayer[];
62
+ paths: {
63
+ global: string;
64
+ project: string;
65
+ override?: string;
66
+ };
67
+ warnings: string[];
68
+ }
69
+
70
+ export interface ResolvedCredential {
71
+ source: CredentialSource;
72
+ value: string;
73
+ }
74
+
75
+ function isRecord(value: unknown): value is Record<string, unknown> {
76
+ return typeof value === "object" && value !== null && !Array.isArray(value);
77
+ }
78
+
79
+ function hasOwn(value: Record<string, unknown>, key: string): boolean {
80
+ return Object.prototype.hasOwnProperty.call(value, key);
81
+ }
82
+
83
+ export function getGlobalAgentBrowserConfigPath(env: NodeJS.ProcessEnv = process.env): string {
84
+ const home = env.HOME?.trim() || env.USERPROFILE?.trim() || homedir();
85
+ return join(home, ...GLOBAL_CONFIG_RELATIVE_PATH);
86
+ }
87
+
88
+ export function getProjectAgentBrowserConfigPath(cwd = process.cwd()): string {
89
+ return resolve(cwd, ...CONFIG_RELATIVE_PATH);
90
+ }
91
+
92
+ export function getAgentBrowserConfigPaths(options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}) {
93
+ const env = options.env ?? process.env;
94
+ const override = env[AGENT_BROWSER_CONFIG_ENV]?.trim();
95
+ return {
96
+ global: getGlobalAgentBrowserConfigPath(env),
97
+ project: getProjectAgentBrowserConfigPath(options.cwd),
98
+ override: override ? resolve(override) : undefined,
99
+ };
100
+ }
101
+
102
+ function mergeConfig(base: AgentBrowserConfig, override: AgentBrowserConfig): AgentBrowserConfig {
103
+ return {
104
+ ...base,
105
+ ...override,
106
+ browser: {
107
+ ...(base.browser ?? {}),
108
+ ...(override.browser ?? {}),
109
+ defaultProfile: override.browser?.defaultProfile ?? base.browser?.defaultProfile,
110
+ },
111
+ webSearch: {
112
+ ...(base.webSearch ?? {}),
113
+ ...(override.webSearch ?? {}),
114
+ },
115
+ };
116
+ }
117
+
118
+ function validateString(value: unknown, path: string, errors: string[]): string | undefined {
119
+ if (value === undefined) return undefined;
120
+ if (typeof value !== "string") {
121
+ errors.push(`${path} must be a string.`);
122
+ return undefined;
123
+ }
124
+ return value;
125
+ }
126
+
127
+ function validateStringArray(value: unknown, path: string, errors: string[]): string[] | undefined {
128
+ if (value === undefined) return undefined;
129
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
130
+ errors.push(`${path} must be an array of strings.`);
131
+ return undefined;
132
+ }
133
+ return value;
134
+ }
135
+
136
+ function validateBrowserDefaultProfile(value: unknown, path: string, errors: string[]): BrowserDefaultProfileConfig | undefined {
137
+ if (value === undefined) return undefined;
138
+ if (!isRecord(value)) {
139
+ errors.push(`${path} must be an object.`);
140
+ return undefined;
141
+ }
142
+ const name = validateString(value.name, `${path}.name`, errors)?.trim();
143
+ if (!name) {
144
+ errors.push(`${path}.name must not be blank.`);
145
+ return undefined;
146
+ }
147
+ const rawPolicy = validateString(value.policy, `${path}.policy`, errors);
148
+ const policy = rawPolicy ?? "authenticated-only";
149
+ if (!(["explicit-only", "authenticated-only", "always"] as const).includes(policy as BrowserDefaultProfilePolicy)) {
150
+ errors.push(`${path}.policy must be one of explicit-only, authenticated-only, always.`);
151
+ return undefined;
152
+ }
153
+ return { name, policy: policy as BrowserDefaultProfilePolicy };
154
+ }
155
+
156
+ function validateConfig(value: unknown, path: string, scope: ConfigLayer["scope"], errors: string[], warnings: string[]): AgentBrowserConfig | undefined {
157
+ if (!isRecord(value)) {
158
+ errors.push(`${path} must contain a JSON object.`);
159
+ return undefined;
160
+ }
161
+ if (value.version !== undefined && value.version !== 1) {
162
+ errors.push(`${path}.version must be 1 when present.`);
163
+ }
164
+ const config: AgentBrowserConfig = value.version === 1 ? { version: 1 } : {};
165
+
166
+ if (value.webSearch !== undefined) {
167
+ if (!isRecord(value.webSearch)) {
168
+ errors.push(`${path}.webSearch must be an object.`);
169
+ } else {
170
+ const braveApiKey = validateString(value.webSearch.braveApiKey, `${path}.webSearch.braveApiKey`, errors);
171
+ if (braveApiKey !== undefined) {
172
+ config.webSearch = { braveApiKey };
173
+ if (scope === "project" && !isProjectSafeCredentialValue(braveApiKey)) {
174
+ errors.push(`${path}.webSearch.braveApiKey must be exactly $ENV_VAR or ${"${ENV_VAR}"} in project-local config; plaintext, interpolation literals, malformed env references, and command-backed project secrets are not allowed.`);
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ if (value.browser !== undefined) {
181
+ if (!isRecord(value.browser)) {
182
+ errors.push(`${path}.browser must be an object.`);
183
+ } else {
184
+ config.browser = {};
185
+ const defaultProfile = validateBrowserDefaultProfile(value.browser.defaultProfile, `${path}.browser.defaultProfile`, errors);
186
+ if (defaultProfile) config.browser.defaultProfile = defaultProfile;
187
+ const defaultLaunchArgs = validateStringArray(value.browser.defaultLaunchArgs, `${path}.browser.defaultLaunchArgs`, errors);
188
+ if (defaultLaunchArgs) {
189
+ config.browser.defaultLaunchArgs = defaultLaunchArgs;
190
+ warnings.push(`${path}.browser.defaultLaunchArgs is recorded for future use; current releases do not auto-inject default launch args.`);
191
+ }
192
+ }
193
+ }
194
+
195
+ for (const key of Object.keys(value)) {
196
+ if (!["version", "webSearch", "browser"].includes(key)) {
197
+ warnings.push(`${path}.${key} is not a recognized pi-agent-browser-native config field and was ignored.`);
198
+ }
199
+ }
200
+ return config;
201
+ }
202
+
203
+ function parseConfigLayer(raw: string, path: string, scope: ConfigLayer["scope"], errors: string[], warnings: string[]): ConfigLayer | undefined {
204
+ let parsed: unknown;
205
+ try {
206
+ parsed = JSON.parse(raw);
207
+ } catch (error) {
208
+ errors.push(`Could not parse ${scope} config ${path}: ${error instanceof Error ? error.message : String(error)}`);
209
+ return undefined;
210
+ }
211
+ const config = validateConfig(parsed, path, scope, errors, warnings);
212
+ return config ? { config, path, scope } : undefined;
213
+ }
214
+
215
+ async function readConfigLayer(path: string, scope: ConfigLayer["scope"], errors: string[], warnings: string[]): Promise<ConfigLayer | undefined> {
216
+ let raw: string;
217
+ try {
218
+ raw = await readFile(path, "utf8");
219
+ } catch (error) {
220
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
221
+ return undefined;
222
+ }
223
+ errors.push(`Could not read ${scope} config ${path}: ${error instanceof Error ? error.message : String(error)}`);
224
+ return undefined;
225
+ }
226
+ return parseConfigLayer(raw, path, scope, errors, warnings);
227
+ }
228
+
229
+ function readConfigLayerSync(path: string, scope: ConfigLayer["scope"], errors: string[], warnings: string[]): ConfigLayer | undefined {
230
+ let raw: string;
231
+ try {
232
+ raw = readFileSync(path, "utf8");
233
+ } catch (error) {
234
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
235
+ return undefined;
236
+ }
237
+ errors.push(`Could not read ${scope} config ${path}: ${error instanceof Error ? error.message : String(error)}`);
238
+ return undefined;
239
+ }
240
+ return parseConfigLayer(raw, path, scope, errors, warnings);
241
+ }
242
+
243
+ export function isPlaintextCredentialValue(rawValue: string): boolean {
244
+ const trimmed = rawValue.trim();
245
+ return Boolean(trimmed) && !trimmed.startsWith("!") && !trimmed.startsWith("$");
246
+ }
247
+
248
+ export function isProjectSafeCredentialValue(rawValue: string): boolean {
249
+ const trimmed = rawValue.trim();
250
+ return /^\$[A-Za-z_][A-Za-z0-9_]*$/.test(trimmed) || /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(trimmed);
251
+ }
252
+
253
+ export function classifyCredentialSource(rawValue: string, scope: AgentBrowserConfigScope): CredentialSource | undefined {
254
+ const trimmed = rawValue.trim();
255
+ if (!trimmed) return undefined;
256
+ if (trimmed.startsWith("!")) return { kind: "command", rawValue: trimmed, scope };
257
+ if (trimmed.includes("$")) return { kind: "env", rawValue: trimmed, scope };
258
+ return { kind: "literal", rawValue: trimmed, scope };
259
+ }
260
+
261
+ function getBrowserDefaultProfile(config: AgentBrowserConfig): Required<BrowserDefaultProfileConfig> | undefined {
262
+ const profile = config.browser?.defaultProfile;
263
+ if (!profile?.name.trim()) return undefined;
264
+ return { name: profile.name.trim(), policy: profile.policy ?? "authenticated-only" };
265
+ }
266
+
267
+ function buildConfigState(options: {
268
+ env: NodeJS.ProcessEnv;
269
+ layers: ConfigLayer[];
270
+ mergedConfig: AgentBrowserConfig;
271
+ paths: AgentBrowserConfigState["paths"];
272
+ errors: string[];
273
+ warnings: string[];
274
+ }): AgentBrowserConfigState {
275
+ let credentialScope: AgentBrowserConfigScope = "global";
276
+ for (let index = options.layers.length - 1; index >= 0; index -= 1) {
277
+ const layer = options.layers[index];
278
+ if (layer?.config.webSearch?.braveApiKey !== undefined) {
279
+ credentialScope = layer.scope;
280
+ break;
281
+ }
282
+ }
283
+ let credentialSource = options.mergedConfig.webSearch?.braveApiKey === undefined
284
+ ? undefined
285
+ : classifyCredentialSource(options.mergedConfig.webSearch.braveApiKey, credentialScope);
286
+ if (!credentialSource && options.env[BRAVE_API_KEY_ENV]?.trim()) {
287
+ credentialSource = { kind: "literal", rawValue: options.env[BRAVE_API_KEY_ENV] ?? "", scope: "env-fallback" };
288
+ }
289
+ return {
290
+ browserDefaultProfile: getBrowserDefaultProfile(options.mergedConfig),
291
+ config: options.mergedConfig,
292
+ credentialSource,
293
+ errors: options.errors,
294
+ layers: options.layers,
295
+ paths: options.paths,
296
+ warnings: options.warnings,
297
+ };
298
+ }
299
+
300
+ export async function loadAgentBrowserConfig(options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}): Promise<AgentBrowserConfigState> {
301
+ const env = options.env ?? process.env;
302
+ const paths = getAgentBrowserConfigPaths({ cwd: options.cwd, env });
303
+ const errors: string[] = [];
304
+ const warnings: string[] = [];
305
+ const layerCandidates = [
306
+ { path: paths.global, scope: "global" as const },
307
+ { path: paths.project, scope: "project" as const },
308
+ ...(paths.override ? [{ path: paths.override, scope: "override" as const }] : []),
309
+ ];
310
+ const layers: ConfigLayer[] = [];
311
+ let mergedConfig: AgentBrowserConfig = {};
312
+ for (const candidate of layerCandidates) {
313
+ const layer = await readConfigLayer(candidate.path, candidate.scope, errors, warnings);
314
+ if (!layer) continue;
315
+ layers.push(layer);
316
+ mergedConfig = mergeConfig(mergedConfig, layer.config);
317
+ }
318
+ return buildConfigState({ env, errors, layers, mergedConfig, paths, warnings });
319
+ }
320
+
321
+ export function loadAgentBrowserConfigSync(options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}): AgentBrowserConfigState {
322
+ const env = options.env ?? process.env;
323
+ const paths = getAgentBrowserConfigPaths({ cwd: options.cwd, env });
324
+ const errors: string[] = [];
325
+ const warnings: string[] = [];
326
+ const layerCandidates = [
327
+ { path: paths.global, scope: "global" as const },
328
+ { path: paths.project, scope: "project" as const },
329
+ ...(paths.override ? [{ path: paths.override, scope: "override" as const }] : []),
330
+ ];
331
+ const layers: ConfigLayer[] = [];
332
+ let mergedConfig: AgentBrowserConfig = {};
333
+ for (const candidate of layerCandidates) {
334
+ const layer = readConfigLayerSync(candidate.path, candidate.scope, errors, warnings);
335
+ if (!layer) continue;
336
+ layers.push(layer);
337
+ mergedConfig = mergeConfig(mergedConfig, layer.config);
338
+ }
339
+ return buildConfigState({ env, errors, layers, mergedConfig, paths, warnings });
340
+ }
341
+
342
+ function resolveEnvInterpolations(rawValue: string, env: NodeJS.ProcessEnv): string | undefined {
343
+ let output = "";
344
+ for (let index = 0; index < rawValue.length; index += 1) {
345
+ const char = rawValue[index];
346
+ if (char !== "$") {
347
+ output += char;
348
+ continue;
349
+ }
350
+ const next = rawValue[index + 1];
351
+ if (next === "$") {
352
+ output += "$";
353
+ index += 1;
354
+ continue;
355
+ }
356
+ if (next === "!") {
357
+ output += "!";
358
+ index += 1;
359
+ continue;
360
+ }
361
+ let name = "";
362
+ if (next === "{") {
363
+ const end = rawValue.indexOf("}", index + 2);
364
+ if (end === -1) return undefined;
365
+ name = rawValue.slice(index + 2, end);
366
+ index = end;
367
+ } else {
368
+ const match = rawValue.slice(index + 1).match(/^([A-Za-z_][A-Za-z0-9_]*)/);
369
+ if (!match) {
370
+ output += "$";
371
+ continue;
372
+ }
373
+ name = match[1] ?? "";
374
+ index += name.length;
375
+ }
376
+ if (!name) return undefined;
377
+ const value = env[name];
378
+ if (value === undefined) return undefined;
379
+ output += value;
380
+ }
381
+ return output;
382
+ }
383
+
384
+ async function resolveCommandCredential(rawValue: string, signal?: AbortSignal): Promise<string | undefined> {
385
+ const command = rawValue.slice(1).trim();
386
+ if (!command) return undefined;
387
+ try {
388
+ const result = await exec(command, {
389
+ signal,
390
+ timeout: SECRET_COMMAND_TIMEOUT_MS,
391
+ maxBuffer: 1024 * 1024,
392
+ });
393
+ const value = result.stdout.trim();
394
+ return value.length > 0 ? value : undefined;
395
+ } catch (error) {
396
+ if (signal?.aborted) throw error;
397
+ throw new Error("Credential command failed without exposing command output. Check pi-agent-browser-config web-search status and the configured secret manager command.");
398
+ }
399
+ }
400
+
401
+ export async function resolveCredentialSource(
402
+ source: CredentialSource | undefined,
403
+ options: { env?: NodeJS.ProcessEnv; signal?: AbortSignal } = {},
404
+ ): Promise<ResolvedCredential | undefined> {
405
+ if (!source) return undefined;
406
+ let value: string | undefined;
407
+ if (source.kind === "command") {
408
+ value = await resolveCommandCredential(source.rawValue, options.signal);
409
+ } else if (source.kind === "env") {
410
+ value = resolveEnvInterpolations(source.rawValue, options.env ?? process.env)?.trim();
411
+ } else {
412
+ value = source.rawValue.trim();
413
+ }
414
+ return value ? { source, value } : undefined;
415
+ }
416
+
417
+ export async function resolveBraveApiKey(
418
+ state: AgentBrowserConfigState,
419
+ options: { env?: NodeJS.ProcessEnv; signal?: AbortSignal } = {},
420
+ ): Promise<ResolvedCredential | undefined> {
421
+ return resolveCredentialSource(state.credentialSource, options);
422
+ }
423
+
424
+ export function canRegisterWebSearchTool(state: AgentBrowserConfigState, env: NodeJS.ProcessEnv = process.env): boolean {
425
+ if (!state.credentialSource || state.errors.length > 0) return false;
426
+ if (state.credentialSource.kind === "command") return true;
427
+ if (state.credentialSource.kind === "env") return Boolean(resolveEnvInterpolations(state.credentialSource.rawValue, env)?.trim());
428
+ return Boolean(state.credentialSource.rawValue.trim());
429
+ }
430
+
431
+ export async function hasResolvableCredentialSource(
432
+ state: AgentBrowserConfigState,
433
+ options: { env?: NodeJS.ProcessEnv } = {},
434
+ ): Promise<boolean> {
435
+ if (!state.credentialSource || state.errors.length > 0) return false;
436
+ if (state.credentialSource.kind === "command") return true;
437
+ return Boolean((await resolveCredentialSource(state.credentialSource, options))?.value);
438
+ }
439
+
440
+ export function getCredentialSourceSummary(source: CredentialSource | undefined): string {
441
+ if (!source) return "not configured";
442
+ if (source.kind === "command") return `configured via command (${source.scope})`;
443
+ if (source.kind === "env") return `configured via environment interpolation (${source.scope})`;
444
+ if (source.scope === "env-fallback") return `configured via ${BRAVE_API_KEY_ENV} environment fallback`;
445
+ return `configured as plaintext ${source.scope} value [redacted]`;
446
+ }
@@ -30,7 +30,7 @@ export const QUICK_START_GUIDELINES = [
30
30
  ] as const;
31
31
 
32
32
  export const BRAVE_SEARCH_PROMPT_GUIDELINE =
33
- "With BRAVE_API_KEY set, use Brave Search via bash/curl to find exact destination URLs, then open the chosen URL with agent_browser; do not browse search results just to locate a target.";
33
+ "When agent_browser_web_search is available, use it for live web search when current or external web information would help; use agent_browser when the task needs browser interaction, page inspection, screenshots, authenticated/profile content, or DOM work.";
34
34
 
35
35
  export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
36
36
  "Standard workflow: open the page, snapshot -i, interact using current @refs from that snapshot, and re-snapshot after navigation, scrolling, rerendering, or other major DOM changes because refs are page-scoped; the wrapper fails mutation-prone stale/recycled refs before upstream can silently target a different current-page element.",
@@ -105,11 +105,25 @@ export const RUNTIME_PROMPT_GUIDELINES = [
105
105
  "For extraction, prefer get title/url/text/html/value/attr/count or eval --stdin with plain expression, not console.log. Batch three or more known refs/selectors (e.g. [[\"get\",\"text\",\"@e1\"],[\"get\",\"text\",\"@e2\"]]); selector visibility warnings → visible @refs/nextActions.",
106
106
  ] as const;
107
107
 
108
- export function buildToolPromptGuidelines(options: { includeBraveSearch: boolean; docs?: { readmePath: string; commandReferencePath: string; toolContractPath: string } }): string[] {
108
+ export function buildBrowserDefaultProfileGuideline(profile: { name: string; policy: "explicit-only" | "authenticated-only" | "always" } | undefined): string | undefined {
109
+ if (!profile || profile.policy === "explicit-only") return undefined;
110
+ if (profile.policy === "always") {
111
+ return `Agent-browser config sets browser.defaultProfile.name to ${JSON.stringify(profile.name)} with policy always; use --profile ${JSON.stringify(profile.name)} with sessionMode:fresh when a fresh browser launch should use the configured profile, and treat profile content as model-visible user data.`;
112
+ }
113
+ return `Agent-browser config sets browser.defaultProfile.name to ${JSON.stringify(profile.name)}; for signed-in/account-specific browser tasks, start with --profile ${JSON.stringify(profile.name)} plus sessionMode:fresh unless the user asks for a different profile.`;
114
+ }
115
+
116
+ export function buildToolPromptGuidelines(options: {
117
+ browserDefaultProfile?: { name: string; policy: "explicit-only" | "authenticated-only" | "always" };
118
+ docs?: { readmePath: string; commandReferencePath: string; toolContractPath: string };
119
+ includeBraveSearch: boolean;
120
+ }): string[] {
121
+ const browserDefaultProfileGuideline = buildBrowserDefaultProfileGuideline(options.browserDefaultProfile);
109
122
  return [
110
123
  ...TOOL_PROMPT_GUIDELINES_PREFIX,
111
124
  ...(options.docs ? [buildInstalledDocsGuideline(options.docs)] : []),
112
125
  ...RUNTIME_PROMPT_GUIDELINES,
126
+ ...(browserDefaultProfileGuideline ? [browserDefaultProfileGuideline] : []),
113
127
  ...(options.includeBraveSearch ? [BRAVE_SEARCH_PROMPT_GUIDELINE] : []),
114
128
  TOOL_PROMPT_GUIDELINES_SUFFIX[0],
115
129
  TOOL_PROMPT_GUIDELINES_SUFFIX[1],