@opengeni/runtime 4.0.1 → 4.0.3-canary.36199476632001
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/{agent-RJQLKFIW.js → agent-ME6HT4DH.js} +2 -2
- package/dist/assets/codemode-client.json +1 -1
- package/dist/browser-action-json-schema.d.ts +4 -0
- package/dist/{chunk-J5AR5XAJ.js → chunk-FAFZCSN7.js} +695 -210
- package/dist/chunk-FAFZCSN7.js.map +1 -0
- package/dist/{chunk-ZG2YH25H.js → chunk-JAYHA6BF.js} +2 -2
- package/dist/{chunk-UNICCRW5.js → chunk-LZIXCZUV.js} +6 -3
- package/dist/{chunk-UNICCRW5.js.map → chunk-LZIXCZUV.js.map} +1 -1
- package/dist/{chunk-J6J3VYFW.js → chunk-P4V7JGQ2.js} +1037 -105
- package/dist/chunk-P4V7JGQ2.js.map +1 -0
- package/dist/{chunk-SPT3PLMX.js → chunk-PRHMSM6C.js} +2 -2
- package/dist/codex-tool-search.d.ts +12 -0
- package/dist/context-compaction.d.ts +4 -2
- package/dist/index.d.ts +27 -8
- package/dist/index.js +23 -7
- package/dist/interaction-tools.d.ts +1 -1
- package/dist/{lazy-manifest-OEYEHNVY.js → lazy-manifest-GCGUFIWP.js} +3 -3
- package/dist/{localSources-TWHTEKGO.js → localSources-FDH65AFI.js} +4 -4
- package/dist/metrics.d.ts +17 -0
- package/dist/model-provider-routing.d.ts +14 -0
- package/dist/operational-instructions.d.ts +1 -1
- package/dist/provider-quota.d.ts +76 -0
- package/dist/sandbox/browser-control-client.d.ts +28 -1
- package/dist/sandbox/channel-a.d.ts +43 -0
- package/dist/sandbox/index.d.ts +3 -3
- package/dist/sandbox/index.js +10 -6
- package/dist/sandbox/providers/modal-command-router-wire.d.ts +8 -0
- package/dist/sandbox/providers/modal.d.ts +5 -6
- package/dist/workspace-tool-gateway.js +5 -5
- package/package.json +11 -11
- package/src/browser-action-json-schema.ts +10 -0
- package/src/bundled_artifact_skills/opengeni-documents/SKILL.md +4 -1
- package/src/bundled_artifact_skills/opengeni-documents/references/api.md +20 -0
- package/src/bundled_default_skills/opengeni-client/SKILL.md +4 -1
- package/src/bundled_default_skills/opengeni-client/references/discovery-and-autonomy.md +31 -0
- package/src/bundled_default_skills/opengeni-client/references/runtime-profile-and-verification.md +32 -0
- package/src/codex-tool-search.ts +54 -6
- package/src/context-compaction.ts +19 -3
- package/src/index.ts +285 -56
- package/src/interaction-tools.ts +656 -48
- package/src/lazy-tool-transport.ts +4 -0
- package/src/metrics.ts +26 -0
- package/src/model-context-inspector.ts +1 -0
- package/src/model-provider-client.ts +37 -16
- package/src/model-provider-routing.ts +32 -6
- package/src/operational-instructions.ts +1 -1
- package/src/provider-quota.ts +412 -0
- package/src/sandbox/browser-control-client.ts +241 -26
- package/src/sandbox/channel-a.ts +370 -0
- package/src/sandbox/index.ts +6 -1
- package/src/sandbox/providers/modal-command-control.ts +7 -1
- package/src/sandbox/providers/modal-command-router-wire.ts +47 -6
- package/src/sandbox/providers/modal.ts +16 -24
- package/src/sandbox/routing/routing-session.ts +29 -6
- package/dist/chunk-J5AR5XAJ.js.map +0 -1
- package/dist/chunk-J6J3VYFW.js.map +0 -1
- /package/dist/{agent-RJQLKFIW.js.map → agent-ME6HT4DH.js.map} +0 -0
- /package/dist/{chunk-ZG2YH25H.js.map → chunk-JAYHA6BF.js.map} +0 -0
- /package/dist/{chunk-SPT3PLMX.js.map → chunk-PRHMSM6C.js.map} +0 -0
- /package/dist/{lazy-manifest-OEYEHNVY.js.map → lazy-manifest-GCGUFIWP.js.map} +0 -0
- /package/dist/{localSources-TWHTEKGO.js.map → localSources-FDH65AFI.js.map} +0 -0
package/src/interaction-tools.ts
CHANGED
|
@@ -9,10 +9,15 @@ import {
|
|
|
9
9
|
BrowserActionReceipt,
|
|
10
10
|
BrowserClipboard,
|
|
11
11
|
BrowserDiagnosticBatch,
|
|
12
|
+
BrowserDomReadResponse,
|
|
13
|
+
BrowserDomReadLocator,
|
|
14
|
+
BrowserDomReadSelector,
|
|
15
|
+
BrowserDomSafeAttribute,
|
|
12
16
|
BrowserIdentity,
|
|
13
17
|
BrowserIdentityListResponse,
|
|
14
18
|
BrowserIdentityMutationResponse,
|
|
15
19
|
BrowserObservation,
|
|
20
|
+
InteractionSemanticNode,
|
|
16
21
|
BrowserRevisionListResponse,
|
|
17
22
|
BrowserSession,
|
|
18
23
|
BrowserSessionMutationResponse,
|
|
@@ -61,6 +66,7 @@ import type {
|
|
|
61
66
|
} from "@opengeni/codemode";
|
|
62
67
|
import { OpenGeniApiError, OpenGeniClient, type InteractionTransport } from "@opengeni/sdk";
|
|
63
68
|
import { z } from "zod";
|
|
69
|
+
import { browserActionInputJsonSchema } from "./browser-action-json-schema";
|
|
64
70
|
import { guardedMcpFetch } from "./mcp-network";
|
|
65
71
|
|
|
66
72
|
export const INTERACTION_ATTEMPT_TOOL_NAMES = FIRST_PARTY_IN_PROCESS_TOOL_NAMES;
|
|
@@ -74,11 +80,200 @@ class InteractionExecutionResult<T> {
|
|
|
74
80
|
) {}
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
const BROWSER_AGENT_MAX_NODES = 60;
|
|
84
|
+
const BROWSER_AGENT_MAX_NODE_BYTES = 12_000;
|
|
85
|
+
const BROWSER_AGENT_MAX_FIELD_LENGTH = 180;
|
|
86
|
+
const BROWSER_READ_MAX_NODES = 80;
|
|
87
|
+
// A base64 image is journaled inside a 16 MiB Code Mode result. Leave room
|
|
88
|
+
// for JSON framing, metadata, and other content instead of failing after capture.
|
|
89
|
+
const BROWSER_TOOL_IMAGE_MAX_BYTES = 8 * 1024 * 1024;
|
|
90
|
+
|
|
91
|
+
const BrowserAgentNode = z
|
|
92
|
+
.object({
|
|
93
|
+
ref: z.string().max(512),
|
|
94
|
+
role: z.string().max(256),
|
|
95
|
+
depth: z.number().int().nonnegative(),
|
|
96
|
+
name: z.string().max(BROWSER_AGENT_MAX_FIELD_LENGTH).optional(),
|
|
97
|
+
description: z.string().max(BROWSER_AGENT_MAX_FIELD_LENGTH).optional(),
|
|
98
|
+
value: z
|
|
99
|
+
.union([
|
|
100
|
+
z.string().max(BROWSER_AGENT_MAX_FIELD_LENGTH),
|
|
101
|
+
z.object({ redacted: z.literal(true), reason: z.string().max(32) }).strict(),
|
|
102
|
+
])
|
|
103
|
+
.optional(),
|
|
104
|
+
states: z.array(z.string().max(BROWSER_AGENT_MAX_FIELD_LENGTH)).max(6),
|
|
105
|
+
actions: z.array(z.string().max(BROWSER_AGENT_MAX_FIELD_LENGTH)).max(6),
|
|
106
|
+
})
|
|
107
|
+
.strict();
|
|
108
|
+
const BrowserAgentView = z
|
|
109
|
+
.object({
|
|
110
|
+
kind: z.literal("compact"),
|
|
111
|
+
sourceKind: z.enum(["snapshot", "diff", "none"]),
|
|
112
|
+
nodes: z.array(BrowserAgentNode).max(BROWSER_AGENT_MAX_NODES),
|
|
113
|
+
sourceNodeCount: z.number().int().nonnegative(),
|
|
114
|
+
omittedNodeCount: z.number().int().nonnegative(),
|
|
115
|
+
removedRefCount: z.number().int().nonnegative(),
|
|
116
|
+
clippedFieldCount: z.number().int().nonnegative(),
|
|
117
|
+
maxNodes: z.number().int().positive(),
|
|
118
|
+
maxNodeBytes: z.number().int().positive(),
|
|
119
|
+
})
|
|
120
|
+
.strict();
|
|
121
|
+
const CompactBrowserObservation = BrowserObservation.safeExtend({
|
|
122
|
+
semantic: z.null(),
|
|
123
|
+
agentView: BrowserAgentView,
|
|
124
|
+
});
|
|
125
|
+
const BrowserAgentObservation = z.union([BrowserObservation, CompactBrowserObservation]);
|
|
126
|
+
const BrowserAgentActionReceipt = BrowserActionReceipt.safeExtend({
|
|
127
|
+
observation: BrowserAgentObservation.nullable(),
|
|
128
|
+
});
|
|
129
|
+
const BrowserScreenshotInput = z
|
|
130
|
+
.object({
|
|
131
|
+
browserSessionId: z.string().uuid(),
|
|
132
|
+
targetId: z.string().min(1).max(512),
|
|
133
|
+
fullPage: z.boolean().optional(),
|
|
134
|
+
quality: z.number().int().min(1).max(100).optional(),
|
|
135
|
+
})
|
|
136
|
+
.strict();
|
|
137
|
+
const BrowserScreenshotOutput = z
|
|
138
|
+
.object({
|
|
139
|
+
kind: z.literal("browser_screenshot"),
|
|
140
|
+
browserSessionId: z.string().uuid(),
|
|
141
|
+
targetId: z.string(),
|
|
142
|
+
frameId: z.string(),
|
|
143
|
+
targetGeneration: z.string(),
|
|
144
|
+
documentGeneration: z.string(),
|
|
145
|
+
mediaType: z.enum(["image/jpeg", "image/png"]),
|
|
146
|
+
width: z.number().int().positive(),
|
|
147
|
+
height: z.number().int().positive(),
|
|
148
|
+
deviceScaleFactor: z.number().positive(),
|
|
149
|
+
scrollX: z.number(),
|
|
150
|
+
scrollY: z.number(),
|
|
151
|
+
capturedAt: z.string(),
|
|
152
|
+
fullPage: z.boolean(),
|
|
153
|
+
})
|
|
154
|
+
.strict();
|
|
155
|
+
const BrowserReadInput = z
|
|
156
|
+
.object({
|
|
157
|
+
browserSessionId: z.string().uuid(),
|
|
158
|
+
targetId: z.string().min(1).max(512),
|
|
159
|
+
mode: z.enum(["matches", "count", "subtree", "dom"]).optional(),
|
|
160
|
+
dom: z
|
|
161
|
+
.discriminatedUnion("kind", [
|
|
162
|
+
z
|
|
163
|
+
.object({
|
|
164
|
+
kind: z.literal("element"),
|
|
165
|
+
locator: BrowserDomReadLocator,
|
|
166
|
+
attributes: z.array(BrowserDomSafeAttribute).max(6).optional(),
|
|
167
|
+
maxChars: z.number().int().min(1).max(4_096).optional(),
|
|
168
|
+
})
|
|
169
|
+
.strict(),
|
|
170
|
+
z
|
|
171
|
+
.object({
|
|
172
|
+
kind: z.literal("count"),
|
|
173
|
+
selector: BrowserDomReadSelector,
|
|
174
|
+
})
|
|
175
|
+
.strict(),
|
|
176
|
+
])
|
|
177
|
+
.optional(),
|
|
178
|
+
expectedTargetGeneration: z.string().min(1).max(256).optional(),
|
|
179
|
+
expectedDocumentGeneration: z.string().min(1).max(256).nullable().optional(),
|
|
180
|
+
expectedFrameId: z.string().min(1).max(256).nullable().optional(),
|
|
181
|
+
ref: z.string().min(1).max(512).optional(),
|
|
182
|
+
scopeRef: z.string().min(1).max(512).optional(),
|
|
183
|
+
role: z.string().min(1).max(256).optional(),
|
|
184
|
+
nameContains: z.string().min(1).max(512).optional(),
|
|
185
|
+
textContains: z.string().min(1).max(512).optional(),
|
|
186
|
+
state: z.string().min(1).max(128).optional(),
|
|
187
|
+
action: z.string().min(1).max(128).optional(),
|
|
188
|
+
limit: z.number().int().min(1).max(BROWSER_READ_MAX_NODES).optional(),
|
|
189
|
+
})
|
|
190
|
+
.strict()
|
|
191
|
+
.superRefine((value, context) => {
|
|
192
|
+
if (value.mode === "subtree" && !value.ref) {
|
|
193
|
+
context.addIssue({ code: "custom", path: ["ref"], message: "subtree requires a ref" });
|
|
194
|
+
}
|
|
195
|
+
if ((value.mode === "dom") !== (value.dom !== undefined)) {
|
|
196
|
+
context.addIssue({
|
|
197
|
+
code: "custom",
|
|
198
|
+
path: ["dom"],
|
|
199
|
+
message: "dom query is required only with mode=dom",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (
|
|
203
|
+
value.mode === "dom" &&
|
|
204
|
+
[
|
|
205
|
+
value.ref,
|
|
206
|
+
value.scopeRef,
|
|
207
|
+
value.role,
|
|
208
|
+
value.nameContains,
|
|
209
|
+
value.textContains,
|
|
210
|
+
value.state,
|
|
211
|
+
value.action,
|
|
212
|
+
value.limit,
|
|
213
|
+
].some((field) => field !== undefined)
|
|
214
|
+
) {
|
|
215
|
+
context.addIssue({
|
|
216
|
+
code: "custom",
|
|
217
|
+
path: ["mode"],
|
|
218
|
+
message: "accessibility filters cannot be combined with a DOM query",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (
|
|
222
|
+
value.mode !== "dom" &&
|
|
223
|
+
[
|
|
224
|
+
value.expectedTargetGeneration,
|
|
225
|
+
value.expectedDocumentGeneration,
|
|
226
|
+
value.expectedFrameId,
|
|
227
|
+
].some((field) => field !== undefined)
|
|
228
|
+
) {
|
|
229
|
+
context.addIssue({
|
|
230
|
+
code: "custom",
|
|
231
|
+
path: ["mode"],
|
|
232
|
+
message: "DOM generation fences require mode=dom",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
const BrowserReadOutput = z
|
|
237
|
+
.object({
|
|
238
|
+
browserSessionId: z.string().uuid(),
|
|
239
|
+
targetId: z.string(),
|
|
240
|
+
observationId: z.string(),
|
|
241
|
+
targetGeneration: z.string(),
|
|
242
|
+
documentGeneration: z.string().nullable(),
|
|
243
|
+
frameId: z.string().nullable(),
|
|
244
|
+
source: z.literal("accessibility"),
|
|
245
|
+
mode: z.enum(["matches", "count", "subtree"]),
|
|
246
|
+
scopeFound: z.boolean(),
|
|
247
|
+
nodes: z.array(BrowserAgentNode).max(BROWSER_READ_MAX_NODES),
|
|
248
|
+
totalMatches: z.number().int().nonnegative(),
|
|
249
|
+
omittedMatches: z.number().int().nonnegative(),
|
|
250
|
+
clippedFieldCount: z.number().int().nonnegative(),
|
|
251
|
+
maxNodeBytes: z.number().int().positive(),
|
|
252
|
+
})
|
|
253
|
+
.strict();
|
|
254
|
+
const BrowserDomAgentReadOutput = BrowserDomReadResponse.safeExtend({
|
|
255
|
+
source: z.literal("dom"),
|
|
256
|
+
});
|
|
257
|
+
const BrowserReadToolOutput = z.discriminatedUnion("source", [
|
|
258
|
+
BrowserReadOutput,
|
|
259
|
+
BrowserDomAgentReadOutput,
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
type BrowserSemanticNodeValue = z.infer<typeof InteractionSemanticNode>;
|
|
263
|
+
type FlatBrowserNode = {
|
|
264
|
+
node: BrowserSemanticNodeValue;
|
|
265
|
+
depth: number;
|
|
266
|
+
index: number;
|
|
267
|
+
withinScope: boolean;
|
|
268
|
+
};
|
|
269
|
+
|
|
77
270
|
const TOOL_PERMISSION = {
|
|
78
271
|
interaction_discover: "sessions:read",
|
|
79
272
|
browser_open: "sessions:control",
|
|
80
273
|
browser_tabs: "sessions:control",
|
|
81
274
|
browser_observe: "sessions:read",
|
|
275
|
+
browser_read: "sessions:read",
|
|
276
|
+
browser_screenshot: "sessions:read",
|
|
82
277
|
browser_act: "sessions:control",
|
|
83
278
|
browser_clipboard: "sessions:read",
|
|
84
279
|
browser_debug: "sessions:read",
|
|
@@ -183,6 +378,8 @@ const BrowserObserveInput = z
|
|
|
183
378
|
.object({
|
|
184
379
|
browserSessionId: z.string().uuid(),
|
|
185
380
|
targetId: z.string().min(1).max(512),
|
|
381
|
+
view: z.enum(["compact", "full"]).optional(),
|
|
382
|
+
includeScreenshot: z.boolean().optional(),
|
|
186
383
|
})
|
|
187
384
|
.strict();
|
|
188
385
|
const BrowserActInput = z
|
|
@@ -192,6 +389,7 @@ const BrowserActInput = z
|
|
|
192
389
|
expectedTargetGeneration: z.string().min(1).max(256).optional(),
|
|
193
390
|
expectedDocumentGeneration: z.string().min(1).max(256).nullable().optional(),
|
|
194
391
|
expectedFrameId: z.string().min(1).max(256).nullable().optional(),
|
|
392
|
+
view: z.enum(["compact", "full", "none"]).optional(),
|
|
195
393
|
action: z.union([BrowserAction, BrowserActionBatch]),
|
|
196
394
|
})
|
|
197
395
|
.strict();
|
|
@@ -375,13 +573,18 @@ const ComputerObserveInput = z
|
|
|
375
573
|
targetId: z.string().min(1).max(512),
|
|
376
574
|
})
|
|
377
575
|
.strict();
|
|
576
|
+
const ComputerObserveOutput = ComputerObservation.extend({
|
|
577
|
+
capturedFrame: z
|
|
578
|
+
.object({ width: z.number().int().positive(), height: z.number().int().positive() })
|
|
579
|
+
.strict(),
|
|
580
|
+
}).strict();
|
|
378
581
|
const ComputerClipboardInput = z.object({ computerSessionId: z.string().uuid() }).strict();
|
|
379
582
|
const ComputerActInput = z
|
|
380
583
|
.object({
|
|
381
584
|
computerSessionId: z.string().uuid(),
|
|
382
585
|
targetId: z.string().min(1).max(512),
|
|
383
586
|
expectedTargetGeneration: z.string().min(1).max(256).optional(),
|
|
384
|
-
expectedObservationId:
|
|
587
|
+
expectedObservationId: ComputerObservation.shape.observationId.nullable().optional(),
|
|
385
588
|
expectedFrameId: z.string().min(1).max(256).nullable().optional(),
|
|
386
589
|
action: ComputerAction,
|
|
387
590
|
})
|
|
@@ -437,7 +640,10 @@ export function createInteractionAttemptToolDefinitions(
|
|
|
437
640
|
codemodePath: options.codemodePath,
|
|
438
641
|
title: options.title,
|
|
439
642
|
description: options.description,
|
|
440
|
-
inputSchema:
|
|
643
|
+
inputSchema:
|
|
644
|
+
options.name === "browser_act"
|
|
645
|
+
? browserActionInputJsonSchema(options.input)
|
|
646
|
+
: jsonSchema(options.input),
|
|
441
647
|
outputSchema: jsonSchema(options.output),
|
|
442
648
|
annotations: {
|
|
443
649
|
title: options.title,
|
|
@@ -510,7 +716,7 @@ export function createInteractionAttemptToolDefinitions(
|
|
|
510
716
|
codemodePath: ["interaction", "browser", "open"],
|
|
511
717
|
title: "Open or reuse browser",
|
|
512
718
|
description:
|
|
513
|
-
"Open a managed BrowserSession on the current agent placement, reuse a relevant compatible live session by default, attach to an explicit workspace BrowserSession, or open an attached Chrome profile by passing placement={kind:'attached_device',deviceId}. This does not infer or attach the user's existing Chrome: for requests about 'my browser', 'my tabs', or current Chrome, call interaction_discover first and select an actual attachedBrowsers device; if none exists, explain that the Chrome extension must be connected instead of silently creating a blank managed browser. Managed Chromium defaults to headed so OAuth and later human interaction use a supported browser; request headless=true only for agent-only work that will not require sign-in or human control. Returns exact session and tab state.",
|
|
719
|
+
"Open a managed BrowserSession on the current agent placement, reuse a relevant compatible live session by default, attach to an explicit workspace BrowserSession, or open an attached Chrome profile by passing placement={kind:'attached_device',deviceId}. This does not infer or attach the user's existing Chrome: for requests about 'my browser', 'my tabs', or current Chrome, call interaction_discover first and select an actual attachedBrowsers device; if none exists, explain that the Chrome extension must be connected instead of silently creating a blank managed browser. BrowserSessions persist across tool calls; shell-launched browser daemons do not survive remote-command cleanup. Never switch to the user's attached Chrome as a fallback for a failed managed browser unless the user requested that profile. A new attached session opens a dedicated tab. Managed Chromium defaults to headed so OAuth and later human interaction use a supported browser; request headless=true only for agent-only work that will not require sign-in or human control. Returns exact session and tab state.",
|
|
514
720
|
input: BrowserOpenInput,
|
|
515
721
|
output: BrowserOpenOutput,
|
|
516
722
|
readOnly: false,
|
|
@@ -524,7 +730,7 @@ export function createInteractionAttemptToolDefinitions(
|
|
|
524
730
|
codemodePath: ["interaction", "browser", "tabs"],
|
|
525
731
|
title: "Manage browser tabs",
|
|
526
732
|
description:
|
|
527
|
-
"List, open, select, or close tabs in one exact BrowserSession. Returns the authoritative complete tab list after the operation.",
|
|
733
|
+
"List, open, logically select, or close tabs in one exact BrowserSession. Selection changes the BrowserSession's default target, not the visible desktop tab. New attached-Chrome tabs open in the background. Use browser_act activate only when foregrounding the owned tab is explicitly intended. Returns the authoritative complete tab list after the operation.",
|
|
528
734
|
input: BrowserTabsInput,
|
|
529
735
|
output: BrowserTargetListResponse,
|
|
530
736
|
readOnly: false,
|
|
@@ -556,17 +762,161 @@ export function createInteractionAttemptToolDefinitions(
|
|
|
556
762
|
codemodePath: ["interaction", "browser", "observe"],
|
|
557
763
|
title: "Observe browser tab",
|
|
558
764
|
description:
|
|
559
|
-
"Read one tab's
|
|
765
|
+
"Read one tab's URL, title, viewport, causal generations, and a bounded accessibility view. Compact view is the default: agentView lists selected refs and reports omitted nodes and clipped fields; it is not a complete page tree. Set view=full for the complete accessibility snapshot. Set includeScreenshot=true to add a current still image as tool image content; browser_screenshot is faster when only pixels are needed.",
|
|
560
766
|
input: BrowserObserveInput,
|
|
561
|
-
output:
|
|
767
|
+
output: BrowserAgentObservation,
|
|
562
768
|
readOnly: true,
|
|
563
769
|
idempotent: true,
|
|
564
|
-
execute: async (value) =>
|
|
565
|
-
await input.transport.observeBrowserTarget(
|
|
770
|
+
execute: async (value) => {
|
|
771
|
+
let observation = await input.transport.observeBrowserTarget(
|
|
566
772
|
input.workspaceId,
|
|
567
773
|
value.browserSessionId,
|
|
568
774
|
value.targetId,
|
|
569
|
-
)
|
|
775
|
+
);
|
|
776
|
+
if (!value.includeScreenshot)
|
|
777
|
+
return projectBrowserObservation(observation, value.view ?? "compact");
|
|
778
|
+
const frame = await input.transport.captureBrowserTarget(
|
|
779
|
+
input.workspaceId,
|
|
780
|
+
value.browserSessionId,
|
|
781
|
+
value.targetId,
|
|
782
|
+
);
|
|
783
|
+
if (
|
|
784
|
+
observation.target.targetGeneration !== frame.targetGeneration ||
|
|
785
|
+
observation.target.documentGeneration !== frame.documentGeneration
|
|
786
|
+
) {
|
|
787
|
+
observation = await input.transport.observeBrowserTarget(
|
|
788
|
+
input.workspaceId,
|
|
789
|
+
value.browserSessionId,
|
|
790
|
+
value.targetId,
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
if (
|
|
794
|
+
observation.target.targetGeneration !== frame.targetGeneration ||
|
|
795
|
+
observation.target.documentGeneration !== frame.documentGeneration
|
|
796
|
+
) {
|
|
797
|
+
throw new Error("browser target changed while its visual observation was captured");
|
|
798
|
+
}
|
|
799
|
+
return new InteractionExecutionResult(
|
|
800
|
+
projectBrowserObservation(observation, value.view ?? "compact"),
|
|
801
|
+
[browserToolImageContent(frame.data, frame.mediaType)],
|
|
802
|
+
);
|
|
803
|
+
},
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
add({
|
|
807
|
+
name: "browser_read",
|
|
808
|
+
codemodePath: ["interaction", "browser", "read"],
|
|
809
|
+
title: "Read focused browser content",
|
|
810
|
+
description:
|
|
811
|
+
"Search, count, or read a subtree from one tab's accessibility snapshot; filter by ref, role, name, accessibility text, state, or action, and scopeRef. AX mode refreshes the full tree internally and cannot return DOM attributes or editable input values. Use mode=dom with a ref, CSS, test-id, or placeholder locator for bounded element text, editable value, or allowlisted attributes, or a CSS selector for count. Content-matching role/label/text locators and CSS attribute/pseudo selectors are rejected for DOM reads to prevent secret-value probing. DOM reads use exact causal fences and only fixed, read-only browser-side projection; callers cannot supply JavaScript. Ref DOM locators refresh accessibility; CSS/test-id/placeholder locators avoid that scan.",
|
|
812
|
+
input: BrowserReadInput,
|
|
813
|
+
output: BrowserReadToolOutput,
|
|
814
|
+
readOnly: true,
|
|
815
|
+
idempotent: true,
|
|
816
|
+
execute: async (value) => {
|
|
817
|
+
if (value.mode === "dom") {
|
|
818
|
+
const state =
|
|
819
|
+
value.expectedTargetGeneration !== undefined &&
|
|
820
|
+
value.expectedDocumentGeneration !== undefined &&
|
|
821
|
+
value.expectedFrameId !== undefined
|
|
822
|
+
? null
|
|
823
|
+
: await input.transport.getBrowserTargetState(
|
|
824
|
+
input.workspaceId,
|
|
825
|
+
value.browserSessionId,
|
|
826
|
+
value.targetId,
|
|
827
|
+
);
|
|
828
|
+
const fences = {
|
|
829
|
+
expectedTargetGeneration: value.expectedTargetGeneration ?? state!.targetGeneration,
|
|
830
|
+
expectedDocumentGeneration:
|
|
831
|
+
value.expectedDocumentGeneration === undefined
|
|
832
|
+
? state!.documentGeneration
|
|
833
|
+
: value.expectedDocumentGeneration,
|
|
834
|
+
expectedFrameId:
|
|
835
|
+
value.expectedFrameId === undefined ? state!.frameId : value.expectedFrameId,
|
|
836
|
+
};
|
|
837
|
+
const documentGeneration = fences.expectedDocumentGeneration;
|
|
838
|
+
const frameId = fences.expectedFrameId;
|
|
839
|
+
if (documentGeneration === null || frameId === null) {
|
|
840
|
+
throw new OpenGeniApiError(409, "browser target has no inspectable document", {
|
|
841
|
+
code: "document_unavailable",
|
|
842
|
+
retryable: true,
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
const inspectedFences = {
|
|
846
|
+
expectedTargetGeneration: fences.expectedTargetGeneration,
|
|
847
|
+
expectedDocumentGeneration: documentGeneration,
|
|
848
|
+
expectedFrameId: frameId,
|
|
849
|
+
};
|
|
850
|
+
const dom = value.dom!;
|
|
851
|
+
const request =
|
|
852
|
+
dom.kind === "element"
|
|
853
|
+
? {
|
|
854
|
+
...inspectedFences,
|
|
855
|
+
kind: "element" as const,
|
|
856
|
+
locator: dom.locator,
|
|
857
|
+
...(dom.attributes ? { attributes: dom.attributes } : {}),
|
|
858
|
+
maxChars: dom.maxChars ?? 4_096,
|
|
859
|
+
}
|
|
860
|
+
: { ...inspectedFences, kind: "count" as const, selector: dom.selector };
|
|
861
|
+
return {
|
|
862
|
+
...(await input.transport.readBrowserDom(
|
|
863
|
+
input.workspaceId,
|
|
864
|
+
value.browserSessionId,
|
|
865
|
+
value.targetId,
|
|
866
|
+
request,
|
|
867
|
+
)),
|
|
868
|
+
source: "dom" as const,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return readBrowserObservation(
|
|
872
|
+
await input.transport.observeBrowserTarget(
|
|
873
|
+
input.workspaceId,
|
|
874
|
+
value.browserSessionId,
|
|
875
|
+
value.targetId,
|
|
876
|
+
),
|
|
877
|
+
value,
|
|
878
|
+
);
|
|
879
|
+
},
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
add({
|
|
883
|
+
name: "browser_screenshot",
|
|
884
|
+
codemodePath: ["interaction", "browser", "screenshot"],
|
|
885
|
+
title: "Capture browser screenshot",
|
|
886
|
+
description:
|
|
887
|
+
"Capture a current browser tab screenshot without reading its accessibility tree. The image is returned as tool image content, with small structured frame metadata. Set fullPage=true to capture the whole scrollable page when supported; large pages may take longer or exceed capture bounds. Set quality lower to reduce JPEG bytes if a large capture exceeds the Code Mode image limit.",
|
|
888
|
+
input: BrowserScreenshotInput,
|
|
889
|
+
output: BrowserScreenshotOutput,
|
|
890
|
+
readOnly: true,
|
|
891
|
+
idempotent: true,
|
|
892
|
+
execute: async (value) => {
|
|
893
|
+
const frame = await input.transport.captureBrowserTarget(
|
|
894
|
+
input.workspaceId,
|
|
895
|
+
value.browserSessionId,
|
|
896
|
+
value.targetId,
|
|
897
|
+
{},
|
|
898
|
+
{ fullPage: value.fullPage ?? false, ...(value.quality ? { quality: value.quality } : {}) },
|
|
899
|
+
);
|
|
900
|
+
return new InteractionExecutionResult(
|
|
901
|
+
{
|
|
902
|
+
kind: "browser_screenshot" as const,
|
|
903
|
+
browserSessionId: frame.browserSessionId,
|
|
904
|
+
targetId: frame.targetId,
|
|
905
|
+
frameId: frame.frameId,
|
|
906
|
+
targetGeneration: frame.targetGeneration,
|
|
907
|
+
documentGeneration: frame.documentGeneration,
|
|
908
|
+
mediaType: frame.mediaType,
|
|
909
|
+
width: frame.width,
|
|
910
|
+
height: frame.height,
|
|
911
|
+
deviceScaleFactor: frame.deviceScaleFactor,
|
|
912
|
+
scrollX: frame.scrollX,
|
|
913
|
+
scrollY: frame.scrollY,
|
|
914
|
+
capturedAt: frame.capturedAt,
|
|
915
|
+
fullPage: value.fullPage ?? false,
|
|
916
|
+
},
|
|
917
|
+
[browserToolImageContent(frame.data, frame.mediaType)],
|
|
918
|
+
);
|
|
919
|
+
},
|
|
570
920
|
});
|
|
571
921
|
|
|
572
922
|
add({
|
|
@@ -574,29 +924,42 @@ export function createInteractionAttemptToolDefinitions(
|
|
|
574
924
|
codemodePath: ["interaction", "browser", "act"],
|
|
575
925
|
title: "Act in browser tab",
|
|
576
926
|
description:
|
|
577
|
-
"Perform one semantic-first browser action or bounded batch,
|
|
927
|
+
"Perform one semantic-first browser action or bounded batch. Use viewport to set page width, height, desktop/mobile layout and touch emulation, then check the measured viewport in the returned observation; emulation does not prove physical mobile-browser behavior. Use history back/forward for tab navigation; keypress shortcuts are page input and may not navigate browser history. The explicit activate action foregrounds the target in the user's desktop browser; use only when that is intended. Permission actions set a managed browser's web permission for this tab's exact current top-level origin. Omit generation fences to fetch fresh generation metadata without scanning accessibility; supplying all three fences skips that state read while preserving controller validation. The default receipt includes a compact accessibility view with explicit omissions; set view=full for the complete observation, or view=none when no post-action tree is needed.",
|
|
578
928
|
input: BrowserActInput,
|
|
579
|
-
output:
|
|
929
|
+
output: BrowserAgentActionReceipt,
|
|
580
930
|
readOnly: false,
|
|
581
931
|
idempotent: true,
|
|
582
932
|
execute: async (value, context) => {
|
|
583
|
-
const current =
|
|
933
|
+
const current =
|
|
934
|
+
value.expectedTargetGeneration !== undefined &&
|
|
935
|
+
value.expectedDocumentGeneration !== undefined &&
|
|
936
|
+
value.expectedFrameId !== undefined
|
|
937
|
+
? null
|
|
938
|
+
: await input.transport.getBrowserTargetState(
|
|
939
|
+
input.workspaceId,
|
|
940
|
+
value.browserSessionId,
|
|
941
|
+
value.targetId,
|
|
942
|
+
);
|
|
943
|
+
const receipt = await input.transport.actInBrowser(
|
|
584
944
|
input.workspaceId,
|
|
585
945
|
value.browserSessionId,
|
|
586
|
-
|
|
946
|
+
{
|
|
947
|
+
operationId: context.operationId,
|
|
948
|
+
targetId: value.targetId,
|
|
949
|
+
expectedTargetGeneration: value.expectedTargetGeneration ?? current!.targetGeneration,
|
|
950
|
+
expectedDocumentGeneration:
|
|
951
|
+
value.expectedDocumentGeneration === undefined
|
|
952
|
+
? current!.documentGeneration
|
|
953
|
+
: value.expectedDocumentGeneration,
|
|
954
|
+
expectedFrameId:
|
|
955
|
+
value.expectedFrameId === undefined ? current!.frameId : value.expectedFrameId,
|
|
956
|
+
...(value.view === "none" ? { observationMode: "none" as const } : {}),
|
|
957
|
+
action: value.action,
|
|
958
|
+
},
|
|
587
959
|
);
|
|
588
|
-
return
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
expectedTargetGeneration: value.expectedTargetGeneration ?? current.target.targetGeneration,
|
|
592
|
-
expectedDocumentGeneration:
|
|
593
|
-
value.expectedDocumentGeneration === undefined
|
|
594
|
-
? current.target.documentGeneration
|
|
595
|
-
: value.expectedDocumentGeneration,
|
|
596
|
-
expectedFrameId:
|
|
597
|
-
value.expectedFrameId === undefined ? current.frameId : value.expectedFrameId,
|
|
598
|
-
action: value.action,
|
|
599
|
-
});
|
|
960
|
+
return receipt.observation && (value.view ?? "compact") === "compact"
|
|
961
|
+
? { ...receipt, observation: projectBrowserObservation(receipt.observation, "compact") }
|
|
962
|
+
: receipt;
|
|
600
963
|
},
|
|
601
964
|
});
|
|
602
965
|
|
|
@@ -931,9 +1294,9 @@ export function createInteractionAttemptToolDefinitions(
|
|
|
931
1294
|
codemodePath: ["interaction", "computer", "observe"],
|
|
932
1295
|
title: "Observe app or window",
|
|
933
1296
|
description:
|
|
934
|
-
"Read one ComputerSession app/window/screen target, causal generation, semantic accessibility tree, focus, frame identity, and a bounded current screenshot without taking control.",
|
|
1297
|
+
"Read one ComputerSession app/window/screen target, causal generation, semantic accessibility tree, focus, frame identity, and a bounded current screenshot without taking control. capturedFrame gives the screenshot's exact pixel dimensions; use those pixels for pointer coordinates, not the target's native bounds or an image-preview size.",
|
|
935
1298
|
input: ComputerObserveInput,
|
|
936
|
-
output:
|
|
1299
|
+
output: ComputerObserveOutput,
|
|
937
1300
|
readOnly: true,
|
|
938
1301
|
idempotent: true,
|
|
939
1302
|
execute: async (value) => {
|
|
@@ -957,13 +1320,20 @@ export function createInteractionAttemptToolDefinitions(
|
|
|
957
1320
|
if (observation.target.targetGeneration !== frame.targetGeneration) {
|
|
958
1321
|
throw new Error("computer target changed while its visual observation was captured");
|
|
959
1322
|
}
|
|
960
|
-
return new InteractionExecutionResult(
|
|
1323
|
+
return new InteractionExecutionResult(
|
|
961
1324
|
{
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1325
|
+
...observation,
|
|
1326
|
+
frameId: frame.frameId,
|
|
1327
|
+
capturedFrame: { width: frame.width, height: frame.height },
|
|
965
1328
|
},
|
|
966
|
-
|
|
1329
|
+
[
|
|
1330
|
+
{
|
|
1331
|
+
type: "image",
|
|
1332
|
+
data: Buffer.from(frame.data).toString("base64"),
|
|
1333
|
+
mimeType: frame.mediaType,
|
|
1334
|
+
},
|
|
1335
|
+
],
|
|
1336
|
+
);
|
|
967
1337
|
},
|
|
968
1338
|
});
|
|
969
1339
|
|
|
@@ -986,31 +1356,38 @@ export function createInteractionAttemptToolDefinitions(
|
|
|
986
1356
|
codemodePath: ["interaction", "computer", "act"],
|
|
987
1357
|
title: "Act in app or window",
|
|
988
1358
|
description:
|
|
989
|
-
"Perform one action in an exact ComputerSession target. Prefer semantic actions from computer_observe: on macOS they can invoke controls and set values without foregrounding the app. Pointer, keyboard, target focus, screen actions, and clipboard paste use the physical graphical seat and may change the user's foreground app; use them only when foreground control is explicitly intended. Omit fences to use a fresh observation automatically. Returns the durable causal receipt.",
|
|
1359
|
+
"Perform one action in an exact ComputerSession target. Prefer semantic actions from computer_observe: on macOS they can invoke controls and set values without foregrounding the app. Pointer, keyboard, target focus, screen actions, and clipboard paste use the physical graphical seat and may change the user's foreground app; use them only when foreground control is explicitly intended. For pointer actions, set action.frameId from the observed frame and use coordinates in the returned screenshot's pixels; do not scale them to target.bounds. A pixel frame fence applies only to pointer actions; semantic actions use the observation fence even if a frame ID is supplied. Supplying the target generation and relevant action fence preserves the exact observation without refreshing it. Omit fences to use a fresh observation automatically. Returns the durable causal receipt.",
|
|
990
1360
|
input: ComputerActInput,
|
|
991
1361
|
output: ComputerActionReceipt,
|
|
992
1362
|
readOnly: false,
|
|
993
1363
|
idempotent: true,
|
|
994
1364
|
execute: async (value, context) => {
|
|
995
|
-
const current =
|
|
996
|
-
|
|
997
|
-
value.
|
|
998
|
-
|
|
999
|
-
|
|
1365
|
+
const current =
|
|
1366
|
+
value.expectedTargetGeneration !== undefined &&
|
|
1367
|
+
(value.action.type === "pointer"
|
|
1368
|
+
? (value.expectedFrameId ?? value.action.frameId) != null
|
|
1369
|
+
: value.expectedObservationId != null)
|
|
1370
|
+
? null
|
|
1371
|
+
: await input.transport.observeComputerTarget(
|
|
1372
|
+
input.workspaceId,
|
|
1373
|
+
value.computerSessionId,
|
|
1374
|
+
value.targetId,
|
|
1375
|
+
);
|
|
1000
1376
|
return await input.transport.actInComputer(input.workspaceId, value.computerSessionId, {
|
|
1001
1377
|
operationId: context.operationId,
|
|
1002
1378
|
targetId: value.targetId,
|
|
1003
|
-
expectedTargetGeneration:
|
|
1379
|
+
expectedTargetGeneration:
|
|
1380
|
+
value.expectedTargetGeneration ?? current!.target.targetGeneration,
|
|
1004
1381
|
expectedObservationId:
|
|
1005
1382
|
value.expectedObservationId === undefined
|
|
1006
|
-
? current
|
|
1383
|
+
? (current?.observationId ?? null)
|
|
1007
1384
|
: value.expectedObservationId,
|
|
1008
1385
|
expectedFrameId:
|
|
1009
|
-
value.
|
|
1010
|
-
? value.
|
|
1386
|
+
value.action.type === "pointer"
|
|
1387
|
+
? value.expectedFrameId === undefined
|
|
1011
1388
|
? value.action.frameId
|
|
1012
|
-
:
|
|
1013
|
-
:
|
|
1389
|
+
: value.expectedFrameId
|
|
1390
|
+
: null,
|
|
1014
1391
|
action: value.action,
|
|
1015
1392
|
});
|
|
1016
1393
|
},
|
|
@@ -1131,7 +1508,8 @@ async function openBrowser(
|
|
|
1131
1508
|
if (value.browserSessionId) {
|
|
1132
1509
|
session = await transport.getBrowserSession(workspaceId, value.browserSessionId);
|
|
1133
1510
|
} else {
|
|
1134
|
-
const listed =
|
|
1511
|
+
const listed =
|
|
1512
|
+
value.mode === "new" ? { sessions: [] } : await transport.listBrowserSessions(workspaceId);
|
|
1135
1513
|
// The agent-facing browser is human-capable by default. A headless session
|
|
1136
1514
|
// is a deliberately narrower execution mode and must never be silently
|
|
1137
1515
|
// reused for an omitted/default headed request: OAuth providers such as
|
|
@@ -1141,7 +1519,18 @@ async function openBrowser(
|
|
|
1141
1519
|
value.mode === "new"
|
|
1142
1520
|
? null
|
|
1143
1521
|
: newestRelevant(
|
|
1144
|
-
listed.sessions.filter(
|
|
1522
|
+
listed.sessions.filter(
|
|
1523
|
+
(candidate) =>
|
|
1524
|
+
candidate.headless === requestedHeadless &&
|
|
1525
|
+
compatibleInteractionPlacement(candidate.placement, value.placement) &&
|
|
1526
|
+
(value.identityId === undefined || candidate.identityId === value.identityId) &&
|
|
1527
|
+
(value.baseRevisionId === undefined ||
|
|
1528
|
+
candidate.baseRevisionId === value.baseRevisionId) &&
|
|
1529
|
+
(value.networkRouteId === undefined ||
|
|
1530
|
+
candidate.networkRouteId === value.networkRouteId) &&
|
|
1531
|
+
(value.linkedComputerSessionId === undefined ||
|
|
1532
|
+
candidate.linkedComputerSessionId === value.linkedComputerSessionId),
|
|
1533
|
+
),
|
|
1145
1534
|
sourceSessionId,
|
|
1146
1535
|
);
|
|
1147
1536
|
if (reusable) {
|
|
@@ -1205,8 +1594,14 @@ async function openComputer(
|
|
|
1205
1594
|
if (value.computerSessionId) {
|
|
1206
1595
|
session = await transport.getComputerSession(workspaceId, value.computerSessionId);
|
|
1207
1596
|
} else {
|
|
1208
|
-
const listed =
|
|
1209
|
-
|
|
1597
|
+
const listed =
|
|
1598
|
+
value.mode === "new" ? { sessions: [] } : await transport.listComputerSessions(workspaceId);
|
|
1599
|
+
const reusable = newestRelevant(
|
|
1600
|
+
listed.sessions.filter((candidate) =>
|
|
1601
|
+
compatibleInteractionPlacement(candidate.placement, value.placement),
|
|
1602
|
+
),
|
|
1603
|
+
sourceSessionId,
|
|
1604
|
+
);
|
|
1210
1605
|
session = reusable
|
|
1211
1606
|
? reusable
|
|
1212
1607
|
: (
|
|
@@ -1227,6 +1622,16 @@ async function openComputer(
|
|
|
1227
1622
|
};
|
|
1228
1623
|
}
|
|
1229
1624
|
|
|
1625
|
+
function compatibleInteractionPlacement(
|
|
1626
|
+
candidate: z.infer<typeof InteractionPlacement>,
|
|
1627
|
+
requested: z.infer<typeof InteractionPlacement> | undefined,
|
|
1628
|
+
): boolean {
|
|
1629
|
+
if (!requested) return candidate.kind !== "attached_device";
|
|
1630
|
+
return Object.entries(requested).every(
|
|
1631
|
+
([key, value]) => (candidate as Record<string, unknown>)[key] === value,
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1230
1635
|
function newestRelevant<
|
|
1231
1636
|
T extends {
|
|
1232
1637
|
lifecycle: string;
|
|
@@ -1270,6 +1675,195 @@ function assertInterventionResumeMatches(
|
|
|
1270
1675
|
}
|
|
1271
1676
|
}
|
|
1272
1677
|
|
|
1678
|
+
function flattenBrowserNodes(
|
|
1679
|
+
roots: readonly BrowserSemanticNodeValue[],
|
|
1680
|
+
scopeRef?: string,
|
|
1681
|
+
): FlatBrowserNode[] {
|
|
1682
|
+
const flattened: FlatBrowserNode[] = [];
|
|
1683
|
+
const stack = [...roots].reverse().map((node) => ({ node, depth: 0, withinScope: !scopeRef }));
|
|
1684
|
+
while (stack.length > 0) {
|
|
1685
|
+
const next = stack.pop()!;
|
|
1686
|
+
const withinScope = next.withinScope || next.node.ref === scopeRef;
|
|
1687
|
+
flattened.push({
|
|
1688
|
+
node: next.node,
|
|
1689
|
+
depth: next.depth,
|
|
1690
|
+
index: flattened.length,
|
|
1691
|
+
withinScope,
|
|
1692
|
+
});
|
|
1693
|
+
for (const child of [...(next.node.children ?? [])].reverse()) {
|
|
1694
|
+
stack.push({ node: child, depth: next.depth + 1, withinScope });
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return flattened;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
function boundedAgentField(value: string, onClip: () => void): string {
|
|
1701
|
+
if (value.length <= BROWSER_AGENT_MAX_FIELD_LENGTH) return value;
|
|
1702
|
+
onClip();
|
|
1703
|
+
return `${value.slice(0, BROWSER_AGENT_MAX_FIELD_LENGTH - 1)}…`;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
function projectAgentNode(flat: FlatBrowserNode): {
|
|
1707
|
+
node: z.infer<typeof BrowserAgentNode>;
|
|
1708
|
+
clippedFieldCount: number;
|
|
1709
|
+
} {
|
|
1710
|
+
let clippedFieldCount = 0;
|
|
1711
|
+
const clip = () => {
|
|
1712
|
+
clippedFieldCount += 1;
|
|
1713
|
+
};
|
|
1714
|
+
const source = flat.node;
|
|
1715
|
+
const nodes = {
|
|
1716
|
+
ref: source.ref,
|
|
1717
|
+
role: source.role,
|
|
1718
|
+
depth: flat.depth,
|
|
1719
|
+
...(source.name !== undefined ? { name: boundedAgentField(source.name, clip) } : {}),
|
|
1720
|
+
...(source.description !== undefined
|
|
1721
|
+
? { description: boundedAgentField(source.description, clip) }
|
|
1722
|
+
: {}),
|
|
1723
|
+
...(source.value !== undefined
|
|
1724
|
+
? {
|
|
1725
|
+
value:
|
|
1726
|
+
typeof source.value === "string" ? boundedAgentField(source.value, clip) : source.value,
|
|
1727
|
+
}
|
|
1728
|
+
: {}),
|
|
1729
|
+
states: source.states.slice(0, 6).map((state) => boundedAgentField(state, clip)),
|
|
1730
|
+
actions: source.actions.slice(0, 6).map((action) => boundedAgentField(action, clip)),
|
|
1731
|
+
};
|
|
1732
|
+
if (source.states.length > 6) clippedFieldCount += source.states.length - 6;
|
|
1733
|
+
if (source.actions.length > 6) clippedFieldCount += source.actions.length - 6;
|
|
1734
|
+
return { node: nodes, clippedFieldCount };
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
function selectAgentNodes(
|
|
1738
|
+
flattened: readonly FlatBrowserNode[],
|
|
1739
|
+
maxNodes: number,
|
|
1740
|
+
maxBytes: number,
|
|
1741
|
+
prioritize = true,
|
|
1742
|
+
): { nodes: z.infer<typeof BrowserAgentNode>[]; clippedFieldCount: number } {
|
|
1743
|
+
const priority = (flat: FlatBrowserNode): number => {
|
|
1744
|
+
const { node } = flat;
|
|
1745
|
+
if (node.actions.length > 0) return 0;
|
|
1746
|
+
if (["heading", "dialog", "alert", "status", "navigation", "main"].includes(node.role))
|
|
1747
|
+
return 1;
|
|
1748
|
+
if (["text", "paragraph", "listitem"].includes(node.role) && node.name) return 2;
|
|
1749
|
+
if (node.name || node.value || node.description) return 3;
|
|
1750
|
+
return 4;
|
|
1751
|
+
};
|
|
1752
|
+
const candidates = prioritize
|
|
1753
|
+
? [...flattened].sort((a, b) => priority(a) - priority(b) || a.index - b.index)
|
|
1754
|
+
: flattened;
|
|
1755
|
+
const selected: Array<{ index: number; node: z.infer<typeof BrowserAgentNode> }> = [];
|
|
1756
|
+
let usedBytes = 0;
|
|
1757
|
+
let clippedFieldCount = 0;
|
|
1758
|
+
for (const flat of candidates) {
|
|
1759
|
+
if (selected.length >= maxNodes) break;
|
|
1760
|
+
const projected = projectAgentNode(flat);
|
|
1761
|
+
const bytes = Buffer.byteLength(JSON.stringify(projected.node), "utf8");
|
|
1762
|
+
if (usedBytes + bytes > maxBytes) continue;
|
|
1763
|
+
selected.push({ index: flat.index, node: projected.node });
|
|
1764
|
+
usedBytes += bytes;
|
|
1765
|
+
clippedFieldCount += projected.clippedFieldCount;
|
|
1766
|
+
}
|
|
1767
|
+
return {
|
|
1768
|
+
nodes: selected.sort((a, b) => a.index - b.index).map((entry) => entry.node),
|
|
1769
|
+
clippedFieldCount,
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
function projectBrowserObservation(
|
|
1774
|
+
observation: z.infer<typeof BrowserObservation>,
|
|
1775
|
+
view: "compact" | "full",
|
|
1776
|
+
): z.infer<typeof BrowserAgentObservation> {
|
|
1777
|
+
if (view === "full") return observation;
|
|
1778
|
+
const semantic = observation.semantic;
|
|
1779
|
+
const roots =
|
|
1780
|
+
semantic?.kind === "snapshot"
|
|
1781
|
+
? semantic.roots
|
|
1782
|
+
: semantic?.kind === "diff"
|
|
1783
|
+
? semantic.changed
|
|
1784
|
+
: [];
|
|
1785
|
+
const flattened = flattenBrowserNodes(roots);
|
|
1786
|
+
const projected = selectAgentNodes(
|
|
1787
|
+
flattened,
|
|
1788
|
+
BROWSER_AGENT_MAX_NODES,
|
|
1789
|
+
BROWSER_AGENT_MAX_NODE_BYTES,
|
|
1790
|
+
);
|
|
1791
|
+
return {
|
|
1792
|
+
...observation,
|
|
1793
|
+
semantic: null,
|
|
1794
|
+
agentView: {
|
|
1795
|
+
kind: "compact",
|
|
1796
|
+
sourceKind: semantic?.kind ?? "none",
|
|
1797
|
+
nodes: projected.nodes,
|
|
1798
|
+
sourceNodeCount: flattened.length,
|
|
1799
|
+
omittedNodeCount: flattened.length - projected.nodes.length,
|
|
1800
|
+
removedRefCount: semantic?.kind === "diff" ? semantic.removedRefs.length : 0,
|
|
1801
|
+
clippedFieldCount: projected.clippedFieldCount,
|
|
1802
|
+
maxNodes: BROWSER_AGENT_MAX_NODES,
|
|
1803
|
+
maxNodeBytes: BROWSER_AGENT_MAX_NODE_BYTES,
|
|
1804
|
+
},
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
function readBrowserObservation(
|
|
1809
|
+
observation: z.infer<typeof BrowserObservation>,
|
|
1810
|
+
input: z.output<typeof BrowserReadInput>,
|
|
1811
|
+
): z.infer<typeof BrowserReadOutput> {
|
|
1812
|
+
if (observation.semantic?.kind !== "snapshot") {
|
|
1813
|
+
throw new Error("browser focused read requires a complete accessibility snapshot");
|
|
1814
|
+
}
|
|
1815
|
+
const mode = input.mode && input.mode !== "dom" ? input.mode : "matches";
|
|
1816
|
+
const scopeRef = mode === "subtree" ? input.ref : input.scopeRef;
|
|
1817
|
+
const flattened = flattenBrowserNodes(observation.semantic.roots, scopeRef);
|
|
1818
|
+
const scopeFound = !scopeRef || flattened.some((entry) => entry.node.ref === scopeRef);
|
|
1819
|
+
const includes = (source: string | undefined, needle: string | undefined): boolean =>
|
|
1820
|
+
needle === undefined || (source ?? "").toLowerCase().includes(needle.toLowerCase());
|
|
1821
|
+
const matches = flattened.filter(({ node, withinScope }) => {
|
|
1822
|
+
if (!withinScope) return false;
|
|
1823
|
+
if (mode !== "subtree" && input.ref && node.ref !== input.ref) return false;
|
|
1824
|
+
if (input.role && node.role.toLowerCase() !== input.role.toLowerCase()) return false;
|
|
1825
|
+
if (!includes(node.name, input.nameContains)) return false;
|
|
1826
|
+
if (
|
|
1827
|
+
input.textContains &&
|
|
1828
|
+
![node.name, node.description, typeof node.value === "string" ? node.value : undefined].some(
|
|
1829
|
+
(candidate) => includes(candidate, input.textContains),
|
|
1830
|
+
)
|
|
1831
|
+
)
|
|
1832
|
+
return false;
|
|
1833
|
+
if (
|
|
1834
|
+
input.state &&
|
|
1835
|
+
!node.states.some((state) => state.toLowerCase() === input.state!.toLowerCase())
|
|
1836
|
+
)
|
|
1837
|
+
return false;
|
|
1838
|
+
if (
|
|
1839
|
+
input.action &&
|
|
1840
|
+
!node.actions.some((action) => action.toLowerCase() === input.action!.toLowerCase())
|
|
1841
|
+
)
|
|
1842
|
+
return false;
|
|
1843
|
+
return true;
|
|
1844
|
+
});
|
|
1845
|
+
const projected =
|
|
1846
|
+
mode === "count"
|
|
1847
|
+
? { nodes: [] as z.infer<typeof BrowserAgentNode>[], clippedFieldCount: 0 }
|
|
1848
|
+
: selectAgentNodes(matches, input.limit ?? 40, BROWSER_AGENT_MAX_NODE_BYTES, false);
|
|
1849
|
+
return {
|
|
1850
|
+
browserSessionId: observation.browserSessionId,
|
|
1851
|
+
targetId: observation.target.id,
|
|
1852
|
+
observationId: observation.observationId,
|
|
1853
|
+
targetGeneration: observation.target.targetGeneration,
|
|
1854
|
+
documentGeneration: observation.target.documentGeneration,
|
|
1855
|
+
frameId: observation.frameId,
|
|
1856
|
+
source: "accessibility",
|
|
1857
|
+
mode,
|
|
1858
|
+
scopeFound,
|
|
1859
|
+
nodes: projected.nodes,
|
|
1860
|
+
totalMatches: matches.length,
|
|
1861
|
+
omittedMatches: matches.length - projected.nodes.length,
|
|
1862
|
+
clippedFieldCount: projected.clippedFieldCount,
|
|
1863
|
+
maxNodeBytes: BROWSER_AGENT_MAX_NODE_BYTES,
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1273
1867
|
async function safeInteractionExecution<TInput extends z.ZodType, TOutput extends z.ZodType>(
|
|
1274
1868
|
inputSchema: TInput,
|
|
1275
1869
|
outputSchema: TOutput,
|
|
@@ -1311,6 +1905,20 @@ async function safeInteractionExecution<TInput extends z.ZodType, TOutput extend
|
|
|
1311
1905
|
}
|
|
1312
1906
|
}
|
|
1313
1907
|
|
|
1908
|
+
function browserToolImageContent(
|
|
1909
|
+
data: Uint8Array,
|
|
1910
|
+
mimeType: string,
|
|
1911
|
+
): AttemptToolResultValue["content"][number] {
|
|
1912
|
+
if (data.byteLength > BROWSER_TOOL_IMAGE_MAX_BYTES) {
|
|
1913
|
+
throw new OpenGeniApiError(
|
|
1914
|
+
413,
|
|
1915
|
+
"Browser screenshot exceeds the Code Mode image limit; capture the viewport or lower JPEG quality.",
|
|
1916
|
+
{ code: "browser_screenshot_too_large", retryable: false },
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
return { type: "image", data: Buffer.from(data).toString("base64"), mimeType };
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1314
1922
|
function interactionErrorResult(
|
|
1315
1923
|
code: string,
|
|
1316
1924
|
message: string,
|