@browserbasehq/orca 3.7.0-preview.1 → 3.7.2-gemini-cua
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/dist/cjs/lib/modelUtils.d.ts +10 -0
- package/dist/cjs/lib/modelUtils.js +11 -0
- package/dist/cjs/lib/modelUtils.js.map +1 -1
- package/dist/cjs/lib/v3/agent/AgentProvider.js +3 -0
- package/dist/cjs/lib/v3/agent/AgentProvider.js.map +1 -1
- package/dist/cjs/lib/v3/agent/GoogleCUAClient.d.ts +23 -0
- package/dist/cjs/lib/v3/agent/GoogleCUAClient.js +363 -206
- package/dist/cjs/lib/v3/agent/GoogleCUAClient.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/click.js +16 -5
- package/dist/cjs/lib/v3/agent/tools/click.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/clickAndHold.js +1 -1
- package/dist/cjs/lib/v3/agent/tools/clickAndHold.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/dragAndDrop.js +2 -2
- package/dist/cjs/lib/v3/agent/tools/dragAndDrop.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/fillFormVision.js +18 -7
- package/dist/cjs/lib/v3/agent/tools/fillFormVision.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/scroll.js +1 -1
- package/dist/cjs/lib/v3/agent/tools/scroll.js.map +1 -1
- package/dist/cjs/lib/v3/agent/tools/type.js +13 -4
- package/dist/cjs/lib/v3/agent/tools/type.js.map +1 -1
- package/dist/cjs/lib/v3/agent/utils/coordinateNormalization.d.ts +5 -2
- package/dist/cjs/lib/v3/agent/utils/coordinateNormalization.js +5 -7
- package/dist/cjs/lib/v3/agent/utils/coordinateNormalization.js.map +1 -1
- package/dist/cjs/lib/v3/agent/utils/handleDoneToolCall.js +3 -1
- package/dist/cjs/lib/v3/agent/utils/handleDoneToolCall.js.map +1 -1
- package/dist/cjs/lib/v3/api.d.ts +1 -1
- package/dist/cjs/lib/v3/api.js +11 -1
- package/dist/cjs/lib/v3/api.js.map +1 -1
- package/dist/cjs/lib/v3/cache/AgentCache.js +6 -1
- package/dist/cjs/lib/v3/cache/AgentCache.js.map +1 -1
- package/dist/cjs/lib/v3/external_clients/aisdk.js +2 -0
- package/dist/cjs/lib/v3/external_clients/aisdk.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js +20 -0
- package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.js +5 -4
- package/dist/cjs/lib/v3/handlers/v3AgentHandler.js.map +1 -1
- package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.d.ts +4 -0
- package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js +126 -20
- package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
- package/dist/cjs/lib/v3/index.d.ts +1 -1
- package/dist/cjs/lib/v3/launch/local.js +1 -1
- package/dist/cjs/lib/v3/launch/local.js.map +1 -1
- package/dist/cjs/lib/v3/llm/LLMProvider.js +9 -2
- package/dist/cjs/lib/v3/llm/LLMProvider.js.map +1 -1
- package/dist/cjs/lib/v3/llm/aisdk.js +5 -3
- package/dist/cjs/lib/v3/llm/aisdk.js.map +1 -1
- package/dist/cjs/lib/v3/llm/providerOptions.d.ts +7 -0
- package/dist/cjs/lib/v3/llm/providerOptions.js +9 -0
- package/dist/cjs/lib/v3/llm/providerOptions.js.map +1 -0
- package/dist/cjs/lib/v3/runtimePaths.js +0 -1
- package/dist/cjs/lib/v3/runtimePaths.js.map +1 -1
- package/dist/cjs/lib/v3/types/private/agent.d.ts +1 -1
- package/dist/cjs/lib/v3/types/private/agent.js +1 -0
- package/dist/cjs/lib/v3/types/private/agent.js.map +1 -1
- package/dist/cjs/lib/v3/types/public/agent.d.ts +1 -1
- package/dist/cjs/lib/v3/types/public/agent.js +3 -0
- package/dist/cjs/lib/v3/types/public/agent.js.map +1 -1
- package/dist/cjs/lib/v3/types/public/api.d.ts +53 -0
- package/dist/cjs/lib/v3/types/public/api.js +8 -0
- package/dist/cjs/lib/v3/types/public/api.js.map +1 -1
- package/dist/cjs/lib/v3/types/public/model.d.ts +12 -0
- package/dist/cjs/lib/v3/types/public/model.js.map +1 -1
- package/dist/cjs/lib/v3/types/public/options.d.ts +13 -0
- package/dist/cjs/lib/v3/types/public/options.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/a11y/snapshot/capture.js +4 -3
- package/dist/cjs/lib/v3/understudy/a11y/snapshot/capture.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/a11y/snapshot/coordinateResolver.js +39 -1
- package/dist/cjs/lib/v3/understudy/a11y/snapshot/coordinateResolver.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/locator.d.ts +12 -0
- package/dist/cjs/lib/v3/understudy/locator.js +44 -0
- package/dist/cjs/lib/v3/understudy/locator.js.map +1 -1
- package/dist/cjs/lib/v3/understudy/page.d.ts +22 -3
- package/dist/cjs/lib/v3/understudy/page.js +55 -1
- package/dist/cjs/lib/v3/understudy/page.js.map +1 -1
- package/dist/cjs/lib/v3/v3.d.ts +14 -0
- package/dist/cjs/lib/v3/v3.js +81 -16
- package/dist/cjs/lib/v3/v3.js.map +1 -1
- package/dist/cjs/lib/v3/verifier/types.d.ts +3 -2
- package/dist/cjs/lib/v3/verifier/types.js.map +1 -1
- package/dist/cjs/lib/version.d.ts +1 -1
- package/dist/cjs/lib/version.js +1 -1
- package/dist/cjs/lib/version.js.map +1 -1
- package/dist/esm/lib/modelUtils.d.ts +10 -0
- package/dist/esm/lib/modelUtils.js +9 -0
- package/dist/esm/lib/modelUtils.js.map +1 -1
- package/dist/esm/lib/v3/agent/AgentProvider.js +3 -0
- package/dist/esm/lib/v3/agent/AgentProvider.js.map +1 -1
- package/dist/esm/lib/v3/agent/GoogleCUAClient.d.ts +23 -0
- package/dist/esm/lib/v3/agent/GoogleCUAClient.js +363 -206
- package/dist/esm/lib/v3/agent/GoogleCUAClient.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/click.js +16 -5
- package/dist/esm/lib/v3/agent/tools/click.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/clickAndHold.js +1 -1
- package/dist/esm/lib/v3/agent/tools/clickAndHold.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/dragAndDrop.js +2 -2
- package/dist/esm/lib/v3/agent/tools/dragAndDrop.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/fillFormVision.js +19 -8
- package/dist/esm/lib/v3/agent/tools/fillFormVision.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/scroll.js +1 -1
- package/dist/esm/lib/v3/agent/tools/scroll.js.map +1 -1
- package/dist/esm/lib/v3/agent/tools/type.js +13 -4
- package/dist/esm/lib/v3/agent/tools/type.js.map +1 -1
- package/dist/esm/lib/v3/agent/utils/coordinateNormalization.d.ts +5 -2
- package/dist/esm/lib/v3/agent/utils/coordinateNormalization.js +5 -7
- package/dist/esm/lib/v3/agent/utils/coordinateNormalization.js.map +1 -1
- package/dist/esm/lib/v3/agent/utils/handleDoneToolCall.js +3 -1
- package/dist/esm/lib/v3/agent/utils/handleDoneToolCall.js.map +1 -1
- package/dist/esm/lib/v3/api.d.ts +1 -1
- package/dist/esm/lib/v3/api.js +11 -1
- package/dist/esm/lib/v3/api.js.map +1 -1
- package/dist/esm/lib/v3/cache/AgentCache.js +6 -1
- package/dist/esm/lib/v3/cache/AgentCache.js.map +1 -1
- package/dist/esm/lib/v3/external_clients/aisdk.js +2 -0
- package/dist/esm/lib/v3/external_clients/aisdk.js.map +1 -1
- package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js +20 -0
- package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
- package/dist/esm/lib/v3/handlers/v3AgentHandler.js +5 -4
- package/dist/esm/lib/v3/handlers/v3AgentHandler.js.map +1 -1
- package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.d.ts +4 -0
- package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js +126 -20
- package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
- package/dist/esm/lib/v3/index.d.ts +1 -1
- package/dist/esm/lib/v3/launch/local.js +1 -1
- package/dist/esm/lib/v3/launch/local.js.map +1 -1
- package/dist/esm/lib/v3/llm/LLMProvider.js +9 -2
- package/dist/esm/lib/v3/llm/LLMProvider.js.map +1 -1
- package/dist/esm/lib/v3/llm/aisdk.js +5 -3
- package/dist/esm/lib/v3/llm/aisdk.js.map +1 -1
- package/dist/esm/lib/v3/llm/providerOptions.d.ts +7 -0
- package/dist/esm/lib/v3/llm/providerOptions.js +6 -0
- package/dist/esm/lib/v3/llm/providerOptions.js.map +1 -0
- package/dist/esm/lib/v3/runtimePaths.js +0 -1
- package/dist/esm/lib/v3/runtimePaths.js.map +1 -1
- package/dist/esm/lib/v3/types/private/agent.d.ts +1 -1
- package/dist/esm/lib/v3/types/private/agent.js +1 -0
- package/dist/esm/lib/v3/types/private/agent.js.map +1 -1
- package/dist/esm/lib/v3/types/public/agent.d.ts +1 -1
- package/dist/esm/lib/v3/types/public/agent.js +3 -0
- package/dist/esm/lib/v3/types/public/agent.js.map +1 -1
- package/dist/esm/lib/v3/types/public/api.d.ts +53 -0
- package/dist/esm/lib/v3/types/public/api.js +8 -0
- package/dist/esm/lib/v3/types/public/api.js.map +1 -1
- package/dist/esm/lib/v3/types/public/model.d.ts +12 -0
- package/dist/esm/lib/v3/types/public/model.js.map +1 -1
- package/dist/esm/lib/v3/types/public/options.d.ts +13 -0
- package/dist/esm/lib/v3/types/public/options.js.map +1 -1
- package/dist/esm/lib/v3/understudy/a11y/snapshot/capture.js +4 -3
- package/dist/esm/lib/v3/understudy/a11y/snapshot/capture.js.map +1 -1
- package/dist/esm/lib/v3/understudy/a11y/snapshot/coordinateResolver.js +39 -1
- package/dist/esm/lib/v3/understudy/a11y/snapshot/coordinateResolver.js.map +1 -1
- package/dist/esm/lib/v3/understudy/locator.d.ts +12 -0
- package/dist/esm/lib/v3/understudy/locator.js +44 -0
- package/dist/esm/lib/v3/understudy/locator.js.map +1 -1
- package/dist/esm/lib/v3/understudy/page.d.ts +22 -3
- package/dist/esm/lib/v3/understudy/page.js +55 -1
- package/dist/esm/lib/v3/understudy/page.js.map +1 -1
- package/dist/esm/lib/v3/v3.d.ts +14 -0
- package/dist/esm/lib/v3/v3.js +82 -17
- package/dist/esm/lib/v3/v3.js.map +1 -1
- package/dist/esm/lib/v3/verifier/types.d.ts +3 -2
- package/dist/esm/lib/v3/verifier/types.js.map +1 -1
- package/dist/esm/lib/version.d.ts +1 -1
- package/dist/esm/lib/version.js +1 -1
- package/dist/esm/lib/version.js.map +1 -1
- package/package.json +3 -3
|
@@ -10,6 +10,15 @@ const cuaKeyMapping_js_1 = require("./utils/cuaKeyMapping.js");
|
|
|
10
10
|
const googleCustomToolHandler_js_1 = require("./utils/googleCustomToolHandler.js");
|
|
11
11
|
const FlowLogger_js_1 = require("../flowlogger/FlowLogger.js");
|
|
12
12
|
const uuid_1 = require("uuid");
|
|
13
|
+
/** Errors worth retrying: rate limits, server errors, network failures. */
|
|
14
|
+
function isTransientError(error) {
|
|
15
|
+
const status = error?.status;
|
|
16
|
+
if (typeof status === "number") {
|
|
17
|
+
return status === 429 || status >= 500;
|
|
18
|
+
}
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
return /network|timeout|timed out|fetch failed|econn|socket hang up/i.test(message);
|
|
21
|
+
}
|
|
13
22
|
/**
|
|
14
23
|
* Client for Google's Computer Use Assistant API
|
|
15
24
|
* This implementation uses the Google Generative AI SDK for Computer Use
|
|
@@ -27,6 +36,13 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
27
36
|
tools;
|
|
28
37
|
baseURL;
|
|
29
38
|
safetyConfirmationHandler;
|
|
39
|
+
/**
|
|
40
|
+
* Request API. "interactions" uses the stateful Interactions API (server-side
|
|
41
|
+
* history via previous_interaction_id) instead of generateContent.
|
|
42
|
+
*/
|
|
43
|
+
api = "generateContent";
|
|
44
|
+
previousInteractionId;
|
|
45
|
+
pendingInteractionInput;
|
|
30
46
|
constructor(type, modelName, userProvidedInstructions, clientOptions, tools) {
|
|
31
47
|
super(type, modelName, userProvidedInstructions);
|
|
32
48
|
this.tools = tools;
|
|
@@ -44,19 +60,23 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
44
60
|
...(this.baseURL ? { httpOptions: { baseUrl: this.baseURL } } : {}),
|
|
45
61
|
};
|
|
46
62
|
this.client = new genai_1.GoogleGenAI(genAIOptions);
|
|
63
|
+
if (clientOptions?.googleApi === "interactions") {
|
|
64
|
+
this.api = "interactions";
|
|
65
|
+
}
|
|
47
66
|
// Get environment if specified
|
|
48
67
|
if (clientOptions?.environment &&
|
|
49
68
|
typeof clientOptions.environment === "string") {
|
|
50
69
|
this.environment = clientOptions.environment;
|
|
51
70
|
}
|
|
71
|
+
// No maxOutputTokens: thinking tokens count against it on gemini-3.x, so a
|
|
72
|
+
// fixed cap truncates turns (finishReason MAX_TOKENS). Use the model max.
|
|
52
73
|
this.generateContentConfig = {
|
|
53
74
|
temperature: 1,
|
|
54
75
|
topP: 0.95,
|
|
55
76
|
topK: 40,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// : { parts: [{ text: buildGoogleCUASystemPrompt() }] },
|
|
77
|
+
systemInstruction: this.userProvidedInstructions
|
|
78
|
+
? this.userProvidedInstructions
|
|
79
|
+
: (0, prompt_js_1.buildGoogleCUASystemPrompt)().content,
|
|
60
80
|
tools: [
|
|
61
81
|
{
|
|
62
82
|
computerUse: {
|
|
@@ -166,6 +186,7 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
166
186
|
const maxSteps = options.maxSteps || 10;
|
|
167
187
|
let currentStep = 0;
|
|
168
188
|
let completed = false;
|
|
189
|
+
let declined = false;
|
|
169
190
|
const actions = [];
|
|
170
191
|
const messageList = [];
|
|
171
192
|
let finalMessage = "";
|
|
@@ -196,6 +217,9 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
196
217
|
actions.push(...result.actions);
|
|
197
218
|
// Update completion status
|
|
198
219
|
completed = result.completed;
|
|
220
|
+
if (result.stopped) {
|
|
221
|
+
declined = true;
|
|
222
|
+
}
|
|
199
223
|
// Record any message for this step
|
|
200
224
|
if (result.message) {
|
|
201
225
|
messageList.push(result.message);
|
|
@@ -206,7 +230,7 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
206
230
|
}
|
|
207
231
|
// Return the final result
|
|
208
232
|
return {
|
|
209
|
-
success: completed,
|
|
233
|
+
success: completed && !declined,
|
|
210
234
|
actions,
|
|
211
235
|
message: finalMessage,
|
|
212
236
|
completed,
|
|
@@ -246,20 +270,12 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
246
270
|
*/
|
|
247
271
|
async initializeHistory(instruction) {
|
|
248
272
|
const parts = [{ text: instruction }];
|
|
273
|
+
this.previousInteractionId = undefined;
|
|
274
|
+
this.pendingInteractionInput = instruction;
|
|
249
275
|
// Note: The Python implementation doesn't include the initial screenshot
|
|
250
|
-
// Following the same pattern here
|
|
251
|
-
|
|
252
|
-
? this.userProvidedInstructions
|
|
253
|
-
: (0, prompt_js_1.buildGoogleCUASystemPrompt)().content;
|
|
276
|
+
// Following the same pattern here. The system prompt travels in
|
|
277
|
+
// generateContentConfig.systemInstruction.
|
|
254
278
|
this.history = [
|
|
255
|
-
{
|
|
256
|
-
role: "user",
|
|
257
|
-
parts: [
|
|
258
|
-
{
|
|
259
|
-
text: "System prompt: " + systemPromptContent,
|
|
260
|
-
},
|
|
261
|
-
],
|
|
262
|
-
},
|
|
263
279
|
{
|
|
264
280
|
role: "user",
|
|
265
281
|
parts,
|
|
@@ -275,11 +291,14 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
275
291
|
// Compress images in conversation history before sending to the model
|
|
276
292
|
const compressedResult = (0, imageCompression_js_1.compressGoogleConversationImages)(this.history, 2);
|
|
277
293
|
const compressedHistory = compressedResult.items;
|
|
278
|
-
// Use the SDK's generateContent method
|
|
294
|
+
// Use the SDK's generateContent method, retrying only transient failures
|
|
295
|
+
// (rate limits, 5xx, network, empty candidates). Anything else — a 400,
|
|
296
|
+
// or a candidate that stopped for a non-STOP reason — fails fast.
|
|
279
297
|
const maxRetries = 5;
|
|
280
298
|
const baseDelayS = 1;
|
|
281
299
|
let lastError = null;
|
|
282
300
|
let response = null;
|
|
301
|
+
let emptyResponse = false;
|
|
283
302
|
// Log LLM request
|
|
284
303
|
const llmRequestId = (0, uuid_1.v7)();
|
|
285
304
|
FlowLogger_js_1.FlowLogger.logLlmRequest({
|
|
@@ -300,21 +319,20 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
300
319
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
301
320
|
}
|
|
302
321
|
// Use the SDK's generateContent method - following Python SDK pattern
|
|
303
|
-
response = await this.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
config: this.generateContentConfig,
|
|
307
|
-
});
|
|
308
|
-
// Check if we have valid response content
|
|
322
|
+
response = await this.requestModel(compressedHistory);
|
|
323
|
+
// An empty response is treated as transient and retried; an early
|
|
324
|
+
// stop (non-STOP finish reason) is not — it is rejected below.
|
|
309
325
|
if (!response.candidates || response.candidates.length === 0) {
|
|
326
|
+
emptyResponse = true;
|
|
310
327
|
throw new sdkErrors_js_1.LLMResponseError("agent", "Response has no candidates!");
|
|
311
328
|
}
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
329
|
+
const first = response.candidates[0];
|
|
330
|
+
const stoppedEarly = first.finishReason && first.finishReason !== "STOP";
|
|
331
|
+
if (!stoppedEarly && !first.content?.parts?.length) {
|
|
332
|
+
emptyResponse = true;
|
|
333
|
+
throw new sdkErrors_js_1.LLMResponseError("agent", `Response has no content (finish reason: ${first.finishReason || "unknown"})`);
|
|
316
334
|
}
|
|
317
|
-
// Success -
|
|
335
|
+
// Success - validated below, outside the retry loop
|
|
318
336
|
break;
|
|
319
337
|
}
|
|
320
338
|
catch (error) {
|
|
@@ -324,6 +342,11 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
324
342
|
message: `API call error: ${lastError.message}`,
|
|
325
343
|
level: 2,
|
|
326
344
|
});
|
|
345
|
+
const retryable = emptyResponse || isTransientError(error);
|
|
346
|
+
emptyResponse = false;
|
|
347
|
+
if (!retryable) {
|
|
348
|
+
throw lastError;
|
|
349
|
+
}
|
|
327
350
|
// If this was the last attempt, throw the error
|
|
328
351
|
if (attempt === maxRetries - 1) {
|
|
329
352
|
logger({
|
|
@@ -338,6 +361,13 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
338
361
|
if (!response) {
|
|
339
362
|
throw (lastError || new Error("Failed to get response after all retries"));
|
|
340
363
|
}
|
|
364
|
+
// A candidate that stopped for any reason other than STOP (MAX_TOKENS,
|
|
365
|
+
// SAFETY, MALFORMED_FUNCTION_CALL, ...) is incomplete: fail the run
|
|
366
|
+
// rather than executing its calls or reporting its text as the answer.
|
|
367
|
+
const candidate = response.candidates[0];
|
|
368
|
+
if (candidate.finishReason && candidate.finishReason !== "STOP") {
|
|
369
|
+
throw new sdkErrors_js_1.LLMResponseError("agent", `Model response stopped early (finish reason: ${candidate.finishReason})`);
|
|
370
|
+
}
|
|
341
371
|
const endTime = Date.now();
|
|
342
372
|
const elapsedMs = endTime - startTime;
|
|
343
373
|
const { usageMetadata } = response;
|
|
@@ -371,63 +401,58 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
371
401
|
}
|
|
372
402
|
this.history.push(sanitizedContent);
|
|
373
403
|
}
|
|
374
|
-
// Execute
|
|
404
|
+
// Execute each function call and answer it with its own function
|
|
405
|
+
// response: the call's outcome (output or error text), the URL, and a
|
|
406
|
+
// fresh screenshot taken after that call. The model expects exactly one
|
|
407
|
+
// response per call, including calls with nothing to execute (e.g. a
|
|
408
|
+
// standalone take_screenshot) and calls that could not be converted.
|
|
375
409
|
const functionResponses = [];
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
category: "agent",
|
|
397
|
-
message: "Skipping open_web_browser action",
|
|
398
|
-
level: 2,
|
|
399
|
-
});
|
|
410
|
+
let stopped;
|
|
411
|
+
for (const call of result.calls) {
|
|
412
|
+
const { functionCall } = call;
|
|
413
|
+
if (call.actions[0]?.type === "custom_tool") {
|
|
414
|
+
const action = call.actions[0];
|
|
415
|
+
const toolName = action.name;
|
|
416
|
+
if (this.tools && toolName in this.tools) {
|
|
417
|
+
const executionResult = await (0, googleCustomToolHandler_js_1.executeGoogleCustomTool)(toolName, action.arguments, this.tools, functionCall, logger);
|
|
418
|
+
functionResponses.push(executionResult.functionResponse);
|
|
419
|
+
}
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
// Ask for confirmation BEFORE acting. If the user declines, the
|
|
423
|
+
// action is not executed and the run stops.
|
|
424
|
+
let safetyAcknowledgement;
|
|
425
|
+
if (functionCall.args?.safety_decision) {
|
|
426
|
+
safetyAcknowledgement = await this.handleSafetyConfirmation(functionCall.args.safety_decision, logger);
|
|
427
|
+
if (safetyAcknowledgement === undefined) {
|
|
428
|
+
stopped = `Stopped: the user declined to confirm "${functionCall.name}".`;
|
|
429
|
+
break;
|
|
400
430
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
431
|
+
}
|
|
432
|
+
let error = call.error;
|
|
433
|
+
if (!error) {
|
|
434
|
+
for (let i = 0; i < call.actions.length; i++) {
|
|
435
|
+
const action = call.actions[i];
|
|
404
436
|
logger({
|
|
405
437
|
category: "agent",
|
|
406
|
-
message:
|
|
438
|
+
message: `Executing action ${i + 1}/${call.actions.length} for ${functionCall.name}: ${action.type}`,
|
|
407
439
|
level: 2,
|
|
408
440
|
});
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
functionResponses.push(executionResult.functionResponse);
|
|
418
|
-
if (!executionResult.success) {
|
|
419
|
-
hasError = true;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
441
|
+
// open_web_browser and take_screenshot have nothing to execute;
|
|
442
|
+
// the screenshot captured below is the call's result.
|
|
443
|
+
if (action.type === "open_web_browser") {
|
|
444
|
+
action.pageUrl = this.currentUrl;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (action.type === "screenshot" || !this.actionHandler) {
|
|
448
|
+
continue;
|
|
422
449
|
}
|
|
423
|
-
}
|
|
424
|
-
else if (this.actionHandler) {
|
|
425
450
|
try {
|
|
426
451
|
await this.actionHandler(action);
|
|
427
452
|
// Add a delay between actions to ensure they complete properly
|
|
428
453
|
// Longer delay for typing actions to ensure fields are ready
|
|
429
|
-
if (i <
|
|
430
|
-
const nextAction =
|
|
454
|
+
if (i < call.actions.length - 1) {
|
|
455
|
+
const nextAction = call.actions[i + 1];
|
|
431
456
|
const isTypingAction = action.type === "type" || nextAction.type === "type";
|
|
432
457
|
const delay = isTypingAction ? 500 : 200;
|
|
433
458
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
@@ -437,87 +462,88 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
437
462
|
if (actionError instanceof sdkErrors_js_1.StagehandClosedError) {
|
|
438
463
|
throw actionError;
|
|
439
464
|
}
|
|
465
|
+
error =
|
|
466
|
+
actionError instanceof Error
|
|
467
|
+
? actionError.message
|
|
468
|
+
: String(actionError);
|
|
440
469
|
logger({
|
|
441
470
|
category: "agent",
|
|
442
|
-
message: `Error executing action ${action.type}: ${
|
|
471
|
+
message: `Error executing action ${action.type}: ${error}`,
|
|
443
472
|
level: 0,
|
|
444
473
|
});
|
|
445
|
-
|
|
446
|
-
|
|
474
|
+
// Skip the rest of this call; report the failure to the model.
|
|
475
|
+
break;
|
|
447
476
|
}
|
|
448
477
|
}
|
|
449
478
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const computerUseFunctionCalls = result.functionCalls.filter((fc) => !(0, googleCustomToolHandler_js_1.isCustomTool)(fc, this.tools));
|
|
455
|
-
if (computerUseFunctionCalls.length > 0) {
|
|
456
|
-
try {
|
|
457
|
-
logger({
|
|
458
|
-
category: "agent",
|
|
459
|
-
message: `Taking screenshot after executing ${result.actions.length} actions${hasError ? " (with errors)" : ""}`,
|
|
460
|
-
level: 2,
|
|
461
|
-
});
|
|
462
|
-
const screenshot = await this.captureScreenshot();
|
|
463
|
-
const base64Data = screenshot.base64;
|
|
464
|
-
// Create one function response for each computer use function call
|
|
465
|
-
// Following Python SDK pattern: FunctionResponse with parts containing inline_data
|
|
466
|
-
for (const functionCall of computerUseFunctionCalls) {
|
|
467
|
-
let safetyAcknowledgement;
|
|
468
|
-
if (functionCall.args?.safety_decision) {
|
|
469
|
-
safetyAcknowledgement = await this.handleSafetyConfirmation(functionCall.args.safety_decision, logger);
|
|
470
|
-
}
|
|
471
|
-
const functionResponsePart = {
|
|
472
|
-
functionResponse: {
|
|
473
|
-
name: functionCall.name,
|
|
474
|
-
response: {
|
|
475
|
-
url: this.currentUrl || "",
|
|
476
|
-
...(safetyAcknowledgement !== undefined
|
|
477
|
-
? {
|
|
478
|
-
safety_acknowledgement: safetyAcknowledgement,
|
|
479
|
-
}
|
|
480
|
-
: {}),
|
|
481
|
-
},
|
|
482
|
-
parts: [
|
|
483
|
-
{
|
|
484
|
-
inlineData: {
|
|
485
|
-
mimeType: screenshot.mediaType,
|
|
486
|
-
data: base64Data,
|
|
487
|
-
},
|
|
488
|
-
},
|
|
489
|
-
],
|
|
490
|
-
},
|
|
491
|
-
};
|
|
492
|
-
functionResponses.push(functionResponsePart);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
496
|
-
logger({
|
|
497
|
-
category: "agent",
|
|
498
|
-
message: `Error capturing screenshot: ${error}`,
|
|
499
|
-
level: 0,
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
479
|
+
let screenshot;
|
|
480
|
+
let screenshotError;
|
|
481
|
+
try {
|
|
482
|
+
screenshot = await this.captureScreenshot();
|
|
503
483
|
}
|
|
504
|
-
|
|
505
|
-
|
|
484
|
+
catch (screenshotFailure) {
|
|
485
|
+
if (screenshotFailure instanceof sdkErrors_js_1.StagehandClosedError) {
|
|
486
|
+
throw screenshotFailure;
|
|
487
|
+
}
|
|
488
|
+
screenshotError =
|
|
489
|
+
screenshotFailure instanceof Error
|
|
490
|
+
? screenshotFailure.message
|
|
491
|
+
: String(screenshotFailure);
|
|
506
492
|
logger({
|
|
507
493
|
category: "agent",
|
|
508
|
-
message: `
|
|
509
|
-
level:
|
|
510
|
-
});
|
|
511
|
-
this.history.push({
|
|
512
|
-
role: "user",
|
|
513
|
-
parts: functionResponses,
|
|
494
|
+
message: `Error capturing screenshot: ${screenshotError}`,
|
|
495
|
+
level: 0,
|
|
514
496
|
});
|
|
515
497
|
}
|
|
498
|
+
functionResponses.push({
|
|
499
|
+
functionResponse: {
|
|
500
|
+
...(functionCall.id ? { id: functionCall.id } : {}),
|
|
501
|
+
name: functionCall.name,
|
|
502
|
+
response: {
|
|
503
|
+
url: this.currentUrl || "",
|
|
504
|
+
...(error ? { error } : {}),
|
|
505
|
+
...(screenshotError ? { screenshot_error: screenshotError } : {}),
|
|
506
|
+
...(safetyAcknowledgement !== undefined
|
|
507
|
+
? { safety_acknowledgement: safetyAcknowledgement }
|
|
508
|
+
: {}),
|
|
509
|
+
},
|
|
510
|
+
...(screenshot
|
|
511
|
+
? {
|
|
512
|
+
parts: [
|
|
513
|
+
{
|
|
514
|
+
inlineData: {
|
|
515
|
+
mimeType: screenshot.mediaType,
|
|
516
|
+
data: screenshot.base64,
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
],
|
|
520
|
+
}
|
|
521
|
+
: {}),
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
// The Interactions API keeps history server-side: the next request sends
|
|
526
|
+
// only these results, chained by previous_interaction_id.
|
|
527
|
+
if (this.api === "interactions" && !stopped) {
|
|
528
|
+
this.pendingInteractionInput = functionResponses.map(GoogleCUAClient.toFunctionResult);
|
|
529
|
+
}
|
|
530
|
+
// Add all function responses to history in a single user message
|
|
531
|
+
if (!stopped && functionResponses.length > 0) {
|
|
532
|
+
logger({
|
|
533
|
+
category: "agent",
|
|
534
|
+
message: `Adding ${functionResponses.length} function responses to history`,
|
|
535
|
+
level: 2,
|
|
536
|
+
});
|
|
537
|
+
this.history.push({
|
|
538
|
+
role: "user",
|
|
539
|
+
parts: functionResponses,
|
|
540
|
+
});
|
|
516
541
|
}
|
|
517
542
|
return {
|
|
518
543
|
actions: result.actions,
|
|
519
|
-
message: result.message,
|
|
520
|
-
completed: result.completed,
|
|
544
|
+
message: stopped ?? result.message,
|
|
545
|
+
completed: stopped ? true : result.completed,
|
|
546
|
+
stopped: stopped !== undefined,
|
|
521
547
|
usage: {
|
|
522
548
|
// promptTokenCount is the TOTAL input and already includes the
|
|
523
549
|
// cached portion; cachedContentTokenCount is the cache-hit subset
|
|
@@ -541,19 +567,128 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
541
567
|
throw error;
|
|
542
568
|
}
|
|
543
569
|
}
|
|
570
|
+
/** Send one model request on the configured API. */
|
|
571
|
+
async requestModel(contents) {
|
|
572
|
+
if (this.api === "interactions") {
|
|
573
|
+
return this.createInteraction();
|
|
574
|
+
}
|
|
575
|
+
return this.client.models.generateContent({
|
|
576
|
+
model: this.modelName,
|
|
577
|
+
contents,
|
|
578
|
+
config: this.generateContentConfig,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Create an interaction (Interactions API, REST) continuing the previous one,
|
|
583
|
+
* and map it onto the generateContent response shape the step loop expects.
|
|
584
|
+
*/
|
|
585
|
+
async createInteraction() {
|
|
586
|
+
const baseUrl = (this.baseURL ?? "https://generativelanguage.googleapis.com").replace(/\/+$/, "");
|
|
587
|
+
const body = {
|
|
588
|
+
model: this.modelName.replace(/^models\//, ""),
|
|
589
|
+
input: this.pendingInteractionInput,
|
|
590
|
+
system_instruction: this.generateContentConfig.systemInstruction,
|
|
591
|
+
tools: [
|
|
592
|
+
{
|
|
593
|
+
type: "computer_use",
|
|
594
|
+
environment: this.environment === "ENVIRONMENT_DESKTOP" ? "desktop" : "browser",
|
|
595
|
+
},
|
|
596
|
+
],
|
|
597
|
+
...(this.previousInteractionId
|
|
598
|
+
? { previous_interaction_id: this.previousInteractionId }
|
|
599
|
+
: {}),
|
|
600
|
+
};
|
|
601
|
+
const res = await fetch(`${baseUrl}/v1beta/interactions`, {
|
|
602
|
+
method: "POST",
|
|
603
|
+
headers: {
|
|
604
|
+
"content-type": "application/json",
|
|
605
|
+
"x-goog-api-key": this.apiKey,
|
|
606
|
+
},
|
|
607
|
+
body: JSON.stringify(body),
|
|
608
|
+
});
|
|
609
|
+
const json = (await res.json().catch(() => ({})));
|
|
610
|
+
if (!res.ok || !json.id) {
|
|
611
|
+
const { message = res.statusText, code } = json.error ?? {};
|
|
612
|
+
// code "safety": Google's input classifier rejected the turn. Not
|
|
613
|
+
// transient (status 400), so it fails the run instead of retrying.
|
|
614
|
+
throw Object.assign(new Error(code === "safety"
|
|
615
|
+
? `Gemini blocked the request (safety): ${message}`
|
|
616
|
+
: `Interactions API error ${res.status}: ${message}`), { status: res.status });
|
|
617
|
+
}
|
|
618
|
+
const parts = [];
|
|
619
|
+
for (const step of json.steps ?? []) {
|
|
620
|
+
if (step.type === "function_call" && step.name) {
|
|
621
|
+
parts.push({
|
|
622
|
+
functionCall: { id: step.id, name: step.name, args: step.arguments },
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
else if (step.type === "model_output") {
|
|
626
|
+
for (const content of step.content ?? []) {
|
|
627
|
+
if (content.type === "text" && content.text) {
|
|
628
|
+
parts.push({ text: content.text });
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (parts.length === 0 && json.output_text) {
|
|
634
|
+
parts.push({ text: json.output_text });
|
|
635
|
+
}
|
|
636
|
+
this.previousInteractionId = json.id;
|
|
637
|
+
// requires_action (calls to answer) and completed are normal stops; any
|
|
638
|
+
// other status (incomplete, failed, budget_exceeded, ...) is an early stop.
|
|
639
|
+
const finishReason = json.status === "requires_action" || json.status === "completed"
|
|
640
|
+
? "STOP"
|
|
641
|
+
: String(json.status).toUpperCase();
|
|
642
|
+
return {
|
|
643
|
+
candidates: [{ content: { role: "model", parts }, finishReason }],
|
|
644
|
+
usageMetadata: {
|
|
645
|
+
promptTokenCount: json.usage?.total_input_tokens,
|
|
646
|
+
candidatesTokenCount: json.usage?.total_output_tokens,
|
|
647
|
+
thoughtsTokenCount: json.usage?.total_thought_tokens,
|
|
648
|
+
cachedContentTokenCount: json.usage?.total_cached_tokens,
|
|
649
|
+
},
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
/** Convert a generateContent function response into an Interactions result. */
|
|
653
|
+
static toFunctionResult(part) {
|
|
654
|
+
const fr = part.functionResponse;
|
|
655
|
+
const response = { ...(fr.response ?? {}) };
|
|
656
|
+
// The Interactions API takes a boolean acknowledgement.
|
|
657
|
+
if (response.safety_acknowledgement !== undefined) {
|
|
658
|
+
response.safety_acknowledgement = true;
|
|
659
|
+
}
|
|
660
|
+
const image = fr.parts?.[0]?.inlineData;
|
|
661
|
+
return {
|
|
662
|
+
type: "function_result",
|
|
663
|
+
call_id: fr.id ?? "",
|
|
664
|
+
name: fr.name,
|
|
665
|
+
...(response.error ? { is_error: true } : {}),
|
|
666
|
+
result: [
|
|
667
|
+
{ type: "text", text: JSON.stringify(response) },
|
|
668
|
+
...(image?.data
|
|
669
|
+
? [
|
|
670
|
+
{
|
|
671
|
+
type: "image",
|
|
672
|
+
data: image.data,
|
|
673
|
+
mime_type: image.mimeType,
|
|
674
|
+
},
|
|
675
|
+
]
|
|
676
|
+
: []),
|
|
677
|
+
],
|
|
678
|
+
};
|
|
679
|
+
}
|
|
544
680
|
/**
|
|
545
681
|
* Process the response from Google's API
|
|
546
682
|
*/
|
|
547
683
|
async processResponse(response, logger) {
|
|
548
|
-
const
|
|
684
|
+
const calls = [];
|
|
549
685
|
let message = "";
|
|
550
|
-
const functionCalls = [];
|
|
551
686
|
if (!response.candidates || response.candidates.length === 0) {
|
|
552
687
|
return {
|
|
553
688
|
actions: [],
|
|
554
689
|
message: "No candidates in response",
|
|
555
690
|
completed: true,
|
|
556
|
-
|
|
691
|
+
calls: [],
|
|
557
692
|
};
|
|
558
693
|
}
|
|
559
694
|
const candidate = response.candidates[0];
|
|
@@ -574,83 +709,71 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
574
709
|
});
|
|
575
710
|
}
|
|
576
711
|
if (part.functionCall) {
|
|
577
|
-
|
|
712
|
+
const functionCall = part.functionCall;
|
|
578
713
|
logger({
|
|
579
714
|
category: "agent",
|
|
580
|
-
message: `Found function call: ${
|
|
715
|
+
message: `Found function call: ${functionCall.name} with args: ${JSON.stringify(functionCall.args)}`,
|
|
581
716
|
level: 2,
|
|
582
717
|
});
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (action) {
|
|
586
|
-
// Special handling for type actions. gemini-2.5 type_text_at carries
|
|
587
|
-
// coordinates (click the field first); gemini-3.x `type` has none and
|
|
588
|
-
// types into the element the model already focused.
|
|
589
|
-
if (action.type === "type") {
|
|
590
|
-
logger({
|
|
591
|
-
category: "agent",
|
|
592
|
-
message: `Adding action: ${JSON.stringify(action)}`,
|
|
593
|
-
level: 2,
|
|
594
|
-
});
|
|
595
|
-
// Click the target first only when coordinates are provided.
|
|
596
|
-
if (typeof action.x === "number" && typeof action.y === "number") {
|
|
597
|
-
actions.push({
|
|
598
|
-
type: "click",
|
|
599
|
-
x: action.x,
|
|
600
|
-
y: action.y,
|
|
601
|
-
button: "left",
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
// If clear_before_typing is true (default), add a select all
|
|
605
|
-
if (action.clearBeforeTyping) {
|
|
606
|
-
// Select all text in the field
|
|
607
|
-
actions.push({
|
|
608
|
-
type: "keypress",
|
|
609
|
-
keys: ["ControlOrMeta+A"],
|
|
610
|
-
});
|
|
611
|
-
actions.push({
|
|
612
|
-
type: "keypress",
|
|
613
|
-
keys: ["Backspace"],
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
// Then add the type action
|
|
617
|
-
actions.push(action);
|
|
618
|
-
if (action.pressEnter) {
|
|
619
|
-
actions.push({
|
|
620
|
-
type: "keypress",
|
|
621
|
-
keys: ["Enter"],
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
else {
|
|
626
|
-
actions.push(action);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
718
|
+
const action = this.convertFunctionCallToAction(functionCall, logger);
|
|
719
|
+
if (!action) {
|
|
630
720
|
logger({
|
|
631
721
|
category: "agent",
|
|
632
|
-
message: `Warning: Could not convert function call ${
|
|
722
|
+
message: `Warning: Could not convert function call ${functionCall.name} to action`,
|
|
633
723
|
level: 1,
|
|
634
724
|
});
|
|
725
|
+
calls.push({
|
|
726
|
+
functionCall,
|
|
727
|
+
actions: [],
|
|
728
|
+
error: `Unsupported or malformed computer-use action "${functionCall.name}" with args ${JSON.stringify(functionCall.args ?? {})}`,
|
|
729
|
+
});
|
|
730
|
+
continue;
|
|
635
731
|
}
|
|
732
|
+
calls.push({ functionCall, actions: this.expandAction(action) });
|
|
636
733
|
}
|
|
637
734
|
}
|
|
735
|
+
const actions = calls.flatMap((call) => call.actions);
|
|
638
736
|
// Log summary of what we found
|
|
639
737
|
logger({
|
|
640
738
|
category: "agent",
|
|
641
|
-
message: `Found ${
|
|
739
|
+
message: `Found ${calls.length} function calls, converted to ${actions.length} actions`,
|
|
642
740
|
level: 2,
|
|
643
741
|
});
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
742
|
+
if (calls.length === 0 && !message.trim()) {
|
|
743
|
+
throw new sdkErrors_js_1.LLMResponseError("agent", "Response has neither function calls nor text");
|
|
744
|
+
}
|
|
745
|
+
// Early stops (non-STOP finish reasons) are rejected in executeStep, so a
|
|
746
|
+
// turn is complete exactly when the model made no function calls.
|
|
647
747
|
return {
|
|
648
748
|
actions,
|
|
649
749
|
message: message.trim(),
|
|
650
|
-
completed,
|
|
651
|
-
|
|
750
|
+
completed: calls.length === 0,
|
|
751
|
+
calls,
|
|
652
752
|
};
|
|
653
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Expand a converted action into the executor steps it needs. Typing may
|
|
756
|
+
* click the field first, clear it, and press Enter afterwards.
|
|
757
|
+
*/
|
|
758
|
+
expandAction(action) {
|
|
759
|
+
if (action.type !== "type") {
|
|
760
|
+
return [action];
|
|
761
|
+
}
|
|
762
|
+
const steps = [];
|
|
763
|
+
// Click the target first only when coordinates are provided.
|
|
764
|
+
if (typeof action.x === "number" && typeof action.y === "number") {
|
|
765
|
+
steps.push({ type: "click", x: action.x, y: action.y, button: "left" });
|
|
766
|
+
}
|
|
767
|
+
if (action.clearBeforeTyping) {
|
|
768
|
+
steps.push({ type: "keypress", keys: ["ControlOrMeta+A"] });
|
|
769
|
+
steps.push({ type: "keypress", keys: ["Backspace"] });
|
|
770
|
+
}
|
|
771
|
+
steps.push(action);
|
|
772
|
+
if (action.pressEnter) {
|
|
773
|
+
steps.push({ type: "keypress", keys: ["Enter"] });
|
|
774
|
+
}
|
|
775
|
+
return steps;
|
|
776
|
+
}
|
|
654
777
|
/**
|
|
655
778
|
* Convert Google function call to Stagehand action
|
|
656
779
|
*/
|
|
@@ -692,7 +815,6 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
692
815
|
press_keys: "key_combination",
|
|
693
816
|
hotkey: "key_combination",
|
|
694
817
|
screenshot: "take_screenshot",
|
|
695
|
-
wait: "wait_5_seconds",
|
|
696
818
|
};
|
|
697
819
|
const name = NAME_ALIASES[rawName] ?? rawName;
|
|
698
820
|
// Predefined computer-use tools take precedence over custom tools. If a
|
|
@@ -764,15 +886,37 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
764
886
|
button: name === "right_click" ? "right" : "middle",
|
|
765
887
|
};
|
|
766
888
|
}
|
|
889
|
+
// gemini-3.x press/release primitives (e.g. a manual drag or a
|
|
890
|
+
// modifier held across clicks).
|
|
891
|
+
case "mouse_down":
|
|
892
|
+
case "mouse_up": {
|
|
893
|
+
if (!GoogleCUAClient.isFiniteCoord(args.x) ||
|
|
894
|
+
!GoogleCUAClient.isFiniteCoord(args.y)) {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
const { x, y } = this.normalizeCoordinates(args.x, args.y);
|
|
898
|
+
return { type: name, x, y };
|
|
899
|
+
}
|
|
900
|
+
case "key_down":
|
|
901
|
+
case "key_up": {
|
|
902
|
+
if (typeof args.key !== "string" || !args.key.trim()) {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
return { type: name, key: args.key.trim() };
|
|
906
|
+
}
|
|
767
907
|
case "type_text_at": {
|
|
768
908
|
// text is required; reject a malformed call rather than typing
|
|
769
909
|
// "undefined". An empty string is valid (e.g. clear the field).
|
|
770
910
|
if (typeof args.text !== "string") {
|
|
771
911
|
return null;
|
|
772
912
|
}
|
|
773
|
-
//
|
|
913
|
+
// gemini-2.5 type_text_at clears the field by default
|
|
914
|
+
// (clear_before_typing). gemini-3.x `type` has no such arg and types
|
|
915
|
+
// into the focused element, so only clear when asked or when the call
|
|
916
|
+
// targets a field by coordinates.
|
|
917
|
+
const hasCoords = typeof args.x === "number" && typeof args.y === "number";
|
|
774
918
|
const pressEnter = args.press_enter ?? false;
|
|
775
|
-
const clearBeforeTyping = args.clear_before_typing ??
|
|
919
|
+
const clearBeforeTyping = args.clear_before_typing ?? hasCoords;
|
|
776
920
|
const base = {
|
|
777
921
|
type: "type",
|
|
778
922
|
text: args.text,
|
|
@@ -783,7 +927,7 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
783
927
|
// gemini-3.x `type` has no coordinates and types into the element the
|
|
784
928
|
// model already focused with a preceding click. Only attach coords when
|
|
785
929
|
// present so the executor doesn't click at NaN.
|
|
786
|
-
if (
|
|
930
|
+
if (hasCoords) {
|
|
787
931
|
const { x, y } = this.normalizeCoordinates(args.x, args.y);
|
|
788
932
|
return { ...base, x, y };
|
|
789
933
|
}
|
|
@@ -829,12 +973,15 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
829
973
|
}
|
|
830
974
|
const { x, y } = this.normalizeCoordinates(args.x, args.y);
|
|
831
975
|
const direction = (args.direction || "down").toLowerCase();
|
|
832
|
-
// 2.5 uses `magnitude
|
|
976
|
+
// 2.5 uses `magnitude` (default 800); gemini-3.x `scroll` uses
|
|
977
|
+
// `magnitude_in_pixels` (default 300).
|
|
833
978
|
const magnitude = GoogleCUAClient.isFiniteCoord(args.magnitude)
|
|
834
979
|
? args.magnitude
|
|
835
980
|
: GoogleCUAClient.isFiniteCoord(args.magnitude_in_pixels)
|
|
836
981
|
? args.magnitude_in_pixels
|
|
837
|
-
:
|
|
982
|
+
: rawName === "scroll"
|
|
983
|
+
? 300
|
|
984
|
+
: 800;
|
|
838
985
|
let scroll_x = 0;
|
|
839
986
|
let scroll_y = 0;
|
|
840
987
|
if (direction === "up") {
|
|
@@ -884,6 +1031,16 @@ class GoogleCUAClient extends AgentClient_js_1.AgentClient {
|
|
|
884
1031
|
type: "wait",
|
|
885
1032
|
timeMs: 5000, // Google CUA waits for 5 seconds
|
|
886
1033
|
};
|
|
1034
|
+
case "wait": {
|
|
1035
|
+
// gemini-3.x `wait` takes optional `seconds` (default 1).
|
|
1036
|
+
const seconds = GoogleCUAClient.isFiniteCoord(args.seconds) && args.seconds >= 0
|
|
1037
|
+
? args.seconds
|
|
1038
|
+
: 1;
|
|
1039
|
+
return {
|
|
1040
|
+
type: "wait",
|
|
1041
|
+
timeMs: Math.round(seconds * 1000),
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
887
1044
|
case "hover_at": {
|
|
888
1045
|
const { x, y } = this.normalizeCoordinates(args.x, args.y);
|
|
889
1046
|
return {
|