Setting the file. One moment.
Optimize Agent Prompt · Optimize Agent Prompt · browserbase/skills · Skills Docs
ContentsBack to the top of the page 115
async function poll
— line 115
This file
Number 14.3
Position 3 of 6
Type JavaScript
Size 12 KB
Lines 251 scripts/ optimize_agent_prompt.mjs
JavaScript · 251 lines · 12 KB
,
"FAILED"
,
"STOPPED"
,
"TIMED_OUT"
]);
9
10 function parseArgs ( argv ) {
11 const [ command , ... rest ] = argv;
12 const values = { command };
13 const boolean = new Set ([ "proxies" , "verified" ]);
14 for ( let i = 0 ; i < rest. length ; i += 1 ) {
15 const token = rest[i];
16 if ( ! token. startsWith ( "--" )) throw new Error ( `Unexpected argument: ${ token }` );
17 const key = token. slice ( 2 ). replace ( /-( [a-z] )/ g , ( _ , letter ) => letter. toUpperCase ());
18 values[key] = boolean. has (key) ? true : rest[ ++ i];
19 }
20 return values;
21 }
22
23 function requireArg ( args , key ) {
24 if ( ! args[key]) throw new Error ( `--${ key . replace ( / [A-Z] / g , ( letter ) => `-${ letter . toLowerCase () }` ) } is required` );
25 return args[key];
26 }
27
28 async function readJson ( file ) {
29 return JSON . parse ( await fs. readFile (file, "utf8" ));
30 }
31
32 async function writeJson ( file , value ) {
33 await fs. writeFile (file, `${ JSON . stringify ( value , null , 2 ) } \n ` );
34 }
35
36 async function api ( apiKey , pathname , init = {}) {
37 const response = await fetch ( `${ API_BASE }${ pathname }` , {
38 ... init,
39 headers: {
40 "X-BB-API-Key" : apiKey,
41 ... (init.body ? { "Content-Type" : "application/json" } : {}),
42 ... init.headers,
43 },
44 });
45 const text = await response. text ();
46 let body;
47 try { body = text ? JSON . parse (text) : null ; } catch { body = text; }
48 if ( ! response.ok) throw new Error ( `${ init . method ?? "GET"} ${ pathname } returned ${ response . status }: ${ text . slice ( 0 , 1000 ) }` );
49 return body;
50 }
51
52 async function optionalApi ( apiKey , pathname ) {
53 try { return await api (apiKey, pathname); } catch (error) { return { _error: error.message }; }
54 }
55
56 function score ( run , taskConfig ) {
57 const output = run.result?.output ?? run.result ?? {};
58 const required = taskConfig.resultSchema?.required ?? [];
59 const present = required. filter (( key ) => Object. hasOwn (output, key) && output[key] !== undefined && output[key] !== "" );
60 const coverage = required. length ? present. length / required. length : 1 ;
61 const checks = Object. entries (taskConfig.evaluation?.fieldPatterns ?? {}). map (([ key , pattern ]) => ({
62 key,
63 passed: new RegExp (pattern, "i" ). test ( String (output[key] ?? "" )),
64 }));
65 const warnings = (taskConfig.evaluation?.factualityWarnings ?? []). filter (( pattern ) => new RegExp (pattern, "i" ). test ( JSON . stringify (output)));
66 const accuracy = checks. length ? checks. filter (( item ) => item.passed). length / checks. length : 1 ;
67 const total = Math. max ( 0 , Math. round ( 100 * ( 0.45 * coverage + 0.4 * accuracy + 0.15 * (run.status === "COMPLETED" ? 1 : 0 )) - warnings. length * 10 ));
68 return { total, requiredPresent: present, requiredMissing: required. filter (( key ) => ! present. includes (key)), accuracyChecks: checks, factualityWarnings: warnings, completed: run.status === "COMPLETED" };
69 }
70
71 function summarizeMessages ( messages ) {
72 const roles = {};
73 let toolCalls = 0 ;
74 let toolResults = 0 ;
75 let readableReasoningParts = 0 ;
76 for ( const entry of messages) {
77 const role = entry.message?.role ?? "unknown" ;
78 roles[role] = (roles[role] ?? 0 ) + 1 ;
79 for ( const part of entry.message?.content ?? []) {
80 if (part.type === "tool-call" ) toolCalls += 1 ;
81 if (part.type === "tool-result" ) toolResults += 1 ;
82 if (part.type === "reasoning" && part.text?. trim ()) readableReasoningParts += 1 ;
83 }
84 }
85 return { count: messages. length , roles, toolCalls, toolResults, readableReasoningParts };
86 }
87
88 function summarizeLogs ( logs ) {
89 if ( ! Array. isArray (logs)) return { count: 0 , methods: {}, retrievalError: logs?._error ?? null };
90 const methods = {};
91 for ( const entry of logs) methods[entry.method] = (methods[entry.method] ?? 0 ) + 1 ;
92 return { count: logs. length , methods: Object. fromEntries (Object. entries (methods). sort (( a , b ) => b[ 1 ] - a[ 1 ])) };
93 }
94
95 function runDurationMs ( run ) {
96 const taskDuration = run.result?.taskDuration;
97 if (Number. isFinite (taskDuration) && taskDuration >= 0 ) return taskDuration;
98 if ( ! run.startedAt || ! run.endedAt) return null ;
99 const duration = Date. parse (run.endedAt) - Date. parse (run.startedAt);
100 return Number. isFinite (duration) && duration >= 0 ? duration : null ;
101 }
102
103 async function loadOrCreateAgent ( apiKey , workspace , prompt , config , name ) {
104 const stateFile = path. join (workspace, "state.json" );
105 let state;
106 try { state = await readJson (stateFile); } catch (error) { if (error.code !== "ENOENT" ) throw error; }
107 if ( ! state?.agentId) {
108 const agent = await api (apiKey, "/agents" , { method: "POST" , body: JSON . stringify ({ name, systemPrompt: prompt, resultSchema: config.resultSchema }) });
109 await writeJson (stateFile, { agentId: agent.agentId, createdAt: new Date (). toISOString () });
110 return agent;
111 }
112 return api (apiKey, `/agents/${ state . agentId }` , { method: "PATCH" , body: JSON . stringify ({ systemPrompt: prompt, resultSchema: config.resultSchema }) });
113 }
114
115 async function poll ( apiKey , runId , { pollMs , timeoutMs , maxMessages }) {
116 const started = Date. now ();
117 const messages = [];
118 let since;
119 let stopRequested = false ;
120 while ( true ) {
121 const query = new URLSearchParams ({ all: "true" });
122 if (since) query. set ( "since" , since);
123 const page = await api (apiKey, `/agents/runs/${ runId }/messages?${ query }` );
124 if (page.data?. length ) messages. push ( ... page.data);
125 if (page.nextSince) since = page.nextSince;
126 const run = await api (apiKey, `/agents/runs/${ runId }` );
127 process.stderr. write ( ` \r ${ run . status . padEnd ( 10 ) } messages=${ messages . length }` );
128 if ( TERMINAL . has (run.status)) { process.stderr. write ( " \n " ); return { run, messages }; }
129 if ( ! stopRequested && (messages. length >= maxMessages || Date. now () - started >= timeoutMs)) {
130 await api (apiKey, `/agents/runs/${ runId }/stop` , { method: "POST" });
131 stopRequested = true ;
132 process.stderr. write ( " stop=requested" );
133 }
134 await new Promise (( resolve ) => setTimeout (resolve, pollMs));
135 }
136 }
137
138 async function initWorkspace ( args ) {
139 const workspace = path. resolve ( requireArg (args, "workspace" ));
140 const name = requireArg (args, "name" );
141 await fs. mkdir (path. join (workspace, "prompts" ), { recursive: true });
142 await fs. mkdir (path. join (workspace, "runs" ), { recursive: true });
143 const taskFile = path. join (workspace, "task.json" );
144 const promptFile = path. join (workspace, "prompts" , "iteration-001.md" );
145 try { await fs. access (taskFile); } catch {
146 await writeJson (taskFile, {
147 name,
148 task: "TODO: Describe one fixed browser research or workflow task." ,
149 resultSchema: { type: "object" , additionalProperties: false , required: [ "outcome" , "evidence" ], properties: { outcome: { type: "string" }, evidence: { type: "array" , items: { type: "string" } } } },
150 variables: {},
151 browserSettings: { proxies: true , verified: true },
152 evaluation: { fieldPatterns: {}, factualityWarnings: [] },
153 });
154 }
155 try { await fs. access (promptFile); } catch {
156 await fs. writeFile (promptFile, "You are a careful browser agent. Complete the user's task and return only evidence-backed facts. \n " );
157 }
158 console. log ( JSON . stringify ({ workspace, name, taskFile, promptFile }, null , 2 ));
159 }
160
161 async function runIteration ( args ) {
162 const apiKey = process.env. BROWSERBASE_API_KEY ;
163 if ( ! apiKey) throw new Error ( "BROWSERBASE_API_KEY is required" );
164 const workspace = path. resolve ( requireArg (args, "workspace" ));
165 const config = await readJson (path. join (workspace, "task.json" ));
166 const promptPath = path. resolve (workspace, requireArg (args, "prompt" ));
167 const prompt = ( await fs. readFile (promptPath, "utf8" )). trim ();
168 const label = args.label ?? path. basename (promptPath, path. extname (promptPath));
169 const runDir = path. join (workspace, "runs" , label);
170 await fs. mkdir (runDir, { recursive: true });
171 try { await fs. access (path. join (runDir, "created-run.json" )); throw new Error ( `Label already has a run: ${ label }` ); } catch (error) { if (error.code !== "ENOENT" ) throw error; }
172 await fs. writeFile (path. join (runDir, "system-prompt.md" ), `${ prompt } \n ` );
173 const agent = await loadOrCreateAgent (apiKey, workspace, prompt, config, args.agentName ?? `Prompt optimization: ${ config . name ?? path . basename ( workspace ) }` );
174 const browserSettings = { ... (config.browserSettings ?? {}) };
175 if (args.proxies) browserSettings.proxies = true ;
176 if (args.verified) browserSettings.verified = true ;
177 const body = { agentId: agent.agentId, task: config.task, resultSchema: config.resultSchema };
178 if (Object. keys (config.variables ?? {}). length ) body.variables = config.variables;
179 if (Object. keys (browserSettings). length ) body.browserSettings = browserSettings;
180 const created = await api (apiKey, "/agents/runs" , { method: "POST" , body: JSON . stringify (body) });
181 await writeJson (path. join (runDir, "created-run.json" ), created);
182 console. log ( `run=${ created . runId }` );
183 const { run , messages } = await poll (apiKey, created.runId, {
184 pollMs: Number (args.pollMs ?? 3000 ),
185 timeoutMs: Number (args.timeoutMs ?? 12 * 60_000 ),
186 maxMessages: Number (args.maxMessages ?? 100 ),
187 });
188 const logs = run.sessionId ? await optionalApi (apiKey, `/sessions/${ run . sessionId }/logs` ) : [];
189 const summary = {
190 label,
191 status: run.status,
192 durationMs: runDurationMs (run),
193 normalizedResult: run.result?.output ?? run.result ?? null ,
194 cause: run.cause ?? null ,
195 score: score (run, config),
196 messages: summarizeMessages (messages),
197 sessionLogs: summarizeLogs (logs),
198 };
199 await Promise . all ([
200 writeJson (path. join (runDir, "run.json" ), run),
201 writeJson (path. join (runDir, "messages.json" ), messages),
202 writeJson (path. join (runDir, "session-logs.json" ), logs),
203 writeJson (path. join (runDir, "summary.json" ), summary),
204 ]);
205 console. log ( JSON . stringify (summary, null , 2 ));
206 }
207
208 async function inspectRun ( args ) {
209 const workspace = path. resolve ( requireArg (args, "workspace" ));
210 const label = requireArg (args, "label" );
211 const messages = await readJson (path. join (workspace, "runs" , label, "messages.json" ));
212 let index = 0 ;
213 for ( const entry of messages) {
214 for ( const part of entry.message?.content ?? []) {
215 if (part.type === "tool-call" ) console. log ( `${ ++ index } \t CALL \t ${ part . toolName } \t ${ JSON . stringify ( part . input ). slice ( 0 , 500 ) }` );
216 if (part.type === "tool-result" ) console. log ( `${ ++ index } \t RESULT \t ${ part . toolName } \t ${ JSON . stringify ( part . output ). slice ( 0 , 1000 ) }` );
217 }
218 }
219 }
220
221 async function report ( args ) {
222 const workspace = path. resolve ( requireArg (args, "workspace" ));
223 const entries = await fs. readdir (path. join (workspace, "runs" ), { withFileTypes: true });
224 const rows = [];
225 for ( const entry of entries. filter (( item ) => item. isDirectory ()). sort (( a , b ) => a.name. localeCompare (b.name))) {
226 try { rows. push ( await readJson (path. join (workspace, "runs" , entry.name, "summary.json" ))); } catch (error) { if (error.code !== "ENOENT" ) throw error; }
227 }
228 const lines = [
229 "| Run | Status | Score | Duration | Messages | Browser logs | Missing required fields |" ,
230 "|---|---:|---:|---:|---:|---:|---|" ,
231 ... rows. map (( row ) => `| ${ row . label } | ${ row . status } | ${ row . score . total } | ${ row . durationMs == null ? "n/a" : `${ Math . round ( row . durationMs / 1000 ) }s`} | ${ row . messages . count } | ${ row . sessionLogs . count } | ${ row . score . requiredMissing . join ( ", " ) || "none"} |` ),
232 ];
233 const markdown = `${ lines . join ( " \n " ) } \n ` ;
234 await fs. writeFile (path. join (workspace, "REPORT.md" ), markdown);
235 console. log (markdown);
236 }
237
238 function usage () {
239 console. log ( `Usage:
240 optimize_agent_prompt.mjs init --workspace PATH --name NAME
241 optimize_agent_prompt.mjs run --workspace PATH --prompt FILE [--label NAME] [--max-messages N]
242 optimize_agent_prompt.mjs inspect --workspace PATH --label NAME
243 optimize_agent_prompt.mjs report --workspace PATH` );
244 }
245
246 const args = parseArgs (process.argv. slice ( 2 ));
247 if (args.command === "init" ) await initWorkspace (args);
248 else if (args.command === "run" ) await runIteration (args);
249 else if (args.command === "inspect" ) await inspectRun (args);
250 else if (args.command === "report" ) await report (args);
251 else usage ();