@namzu/sdk 61.2.1 → 61.4.0
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/CHANGELOG.md +12 -0
- package/dist/public-runtime.d.ts +1 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts +80 -0
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +193 -10
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/types/computer-use/index.d.ts +28 -0
- package/dist/types/computer-use/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/public-runtime.ts +4 -0
- package/src/tools/builtins/computer-use.ts +273 -8
- package/src/types/computer-use/index.ts +34 -0
|
@@ -58,17 +58,105 @@ const DEFAULT_SETTLE_MS = 500
|
|
|
58
58
|
const DEFAULT_MAX_BATCH_ACTIONS = 20
|
|
59
59
|
const DEFAULT_MAX_WAIT_MS = 10_000
|
|
60
60
|
const DEFAULT_WAIT_FOR_MS = 10_000
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The wait between the last action and the screenshot that follows it. A host
|
|
64
|
+
* that can tell when the window stopped changing (see
|
|
65
|
+
* {@link ComputerUseHost.settle}) is asked first and ends the wait early; a
|
|
66
|
+
* host without it, one that cannot say, or one that fails keeps the fixed
|
|
67
|
+
* `settleMs` it always had. Cancellation is never swallowed.
|
|
68
|
+
*/
|
|
69
|
+
async function settleAfterActions(
|
|
70
|
+
host: ComputerUseHost,
|
|
71
|
+
windowId: string | undefined,
|
|
72
|
+
settleMs: number,
|
|
73
|
+
signal: AbortSignal | undefined,
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
if (settleMs > 0 && typeof host.settle === 'function') {
|
|
76
|
+
try {
|
|
77
|
+
const settled = await host.settle(
|
|
78
|
+
{ budgetMs: settleMs, ...(windowId !== undefined ? { windowId } : {}) },
|
|
79
|
+
signal,
|
|
80
|
+
)
|
|
81
|
+
if (settled !== undefined) return
|
|
82
|
+
} catch {
|
|
83
|
+
// Fall through to the fixed wait; a cancelled signal makes it reject at once.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
await sleep(settleMs, signal)
|
|
87
|
+
}
|
|
61
88
|
const MAX_LISTED_WINDOWS = 50
|
|
62
89
|
/** The most of a ui_snapshot the model is shown, in characters (about 4 000 tokens). */
|
|
63
90
|
const MAX_UI_SNAPSHOT_CHARS = 14_000
|
|
64
91
|
/** The refs of earlier snapshots the tool still resolves for the host. */
|
|
65
92
|
const MAX_HELD_UI_REFS = 4_000
|
|
66
93
|
|
|
94
|
+
/** One control an advisor may be asked to choose among. */
|
|
95
|
+
export interface ComputerUseAdvisorCandidate {
|
|
96
|
+
/** The ref the model was shown for it. */
|
|
97
|
+
readonly ref: string
|
|
98
|
+
/** The control as the model reads it, such as `Button "Save"`. */
|
|
99
|
+
readonly description: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** What an advisor is asked: which of these controls does this intent name? */
|
|
103
|
+
export interface ComputerUseAdvisorRequest {
|
|
104
|
+
/** What the model wants done, in its own words. */
|
|
105
|
+
readonly intent: string
|
|
106
|
+
readonly candidates: readonly ComputerUseAdvisorCandidate[]
|
|
107
|
+
/**
|
|
108
|
+
* The ref the model itself chose for this intent, when it chose one. Then the
|
|
109
|
+
* advisor is only watching: the answer is not acted on.
|
|
110
|
+
*/
|
|
111
|
+
readonly chosen?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** An advisor's answer: the candidates in order of fit, best first, each with how sure it is (0 to 1). */
|
|
115
|
+
export interface ComputerUseAdvice {
|
|
116
|
+
readonly ranked: readonly {
|
|
117
|
+
readonly ref: string
|
|
118
|
+
readonly confidence: number
|
|
119
|
+
}[]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A fast second opinion on which control an intent names, for hosts that run a
|
|
124
|
+
* small decision model beside the main one. The tool never depends on it: an
|
|
125
|
+
* advisor that fails, is slow or is unsure is the same as none.
|
|
126
|
+
*/
|
|
127
|
+
export interface ComputerUseAdvisor {
|
|
128
|
+
/**
|
|
129
|
+
* Whether the tool may act on the answer. Then the model can name a control
|
|
130
|
+
* by words (`ui_do`) and the tool chooses; when false the advisor is asked
|
|
131
|
+
* about the controls the model chose by ref (`ui_act` with `intent`) and its
|
|
132
|
+
* answer is not used.
|
|
133
|
+
*/
|
|
134
|
+
readonly acts: boolean
|
|
135
|
+
pick(
|
|
136
|
+
request: ComputerUseAdvisorRequest,
|
|
137
|
+
signal?: AbortSignal,
|
|
138
|
+
): Promise<ComputerUseAdvice | undefined>
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The least confidence in the best control for the tool to act on it. */
|
|
142
|
+
const ADVICE_MIN_CONFIDENCE = 0.85
|
|
143
|
+
/** How far the best control must lead the next one. */
|
|
144
|
+
const ADVICE_MIN_MARGIN = 0.4
|
|
145
|
+
/** The most controls an advisor is asked about at once. */
|
|
146
|
+
const MAX_ADVISED_CANDIDATES = 255
|
|
147
|
+
|
|
67
148
|
/**
|
|
68
149
|
* How `createComputerUseTool` sizes screenshots, paces actions and bounds a
|
|
69
150
|
* batch. Every field is optional.
|
|
70
151
|
*/
|
|
71
152
|
export interface ComputerUseToolOptions {
|
|
153
|
+
/**
|
|
154
|
+
* A second opinion on which control an intent names; see
|
|
155
|
+
* {@link ComputerUseAdvisor}. With one that `acts`, the tool offers `ui_do`
|
|
156
|
+
* (name a control by words); with any, `ui_act` accepts an `intent`. Absent,
|
|
157
|
+
* neither exists and the tool is exactly what it was.
|
|
158
|
+
*/
|
|
159
|
+
readonly advisor?: ComputerUseAdvisor
|
|
72
160
|
/**
|
|
73
161
|
* What every screenshot and zoom image is fitted to before the model sees
|
|
74
162
|
* it. Default {@link STANDARD_SCREENSHOT_LIMITS}, which every current vision
|
|
@@ -243,6 +331,16 @@ const uiActSchema = z.object({
|
|
|
243
331
|
ref: z.string().min(1),
|
|
244
332
|
action: z.enum(UI_ACTIONS),
|
|
245
333
|
value: z.string().optional(),
|
|
334
|
+
/** What this is for, in a few words; only read when the tool has an advisor. */
|
|
335
|
+
intent: z.string().min(1).max(200).optional(),
|
|
336
|
+
})
|
|
337
|
+
const uiDoSchema = z.object({
|
|
338
|
+
type: z.literal('ui_do'),
|
|
339
|
+
/** The control, in words: "the Save button", "the name field". */
|
|
340
|
+
target: z.string().min(1).max(200),
|
|
341
|
+
action: z.enum(UI_ACTIONS),
|
|
342
|
+
value: z.string().optional(),
|
|
343
|
+
window_id: z.string().min(1).optional(),
|
|
246
344
|
})
|
|
247
345
|
|
|
248
346
|
/** What a batch may carry: everything but the image-returning actions and another batch. */
|
|
@@ -258,6 +356,7 @@ const batchItemSchema = z.discriminatedUnion('type', [
|
|
|
258
356
|
listWindowsSchema,
|
|
259
357
|
focusWindowSchema,
|
|
260
358
|
uiActSchema,
|
|
359
|
+
uiDoSchema,
|
|
261
360
|
])
|
|
262
361
|
|
|
263
362
|
const withFrame = { screenshot_id: screenshotIdSchema }
|
|
@@ -281,6 +380,7 @@ const actionSchema = z.discriminatedUnion('type', [
|
|
|
281
380
|
uiSnapshotSchema,
|
|
282
381
|
waitForSchema,
|
|
283
382
|
uiActSchema.extend(withFrame),
|
|
383
|
+
uiDoSchema.extend(withFrame),
|
|
284
384
|
z.object({
|
|
285
385
|
type: z.literal('batch'),
|
|
286
386
|
actions: z.array(batchItemSchema).min(1),
|
|
@@ -328,6 +428,7 @@ const ALL_ACTIONS: readonly ToolActionType[] = [
|
|
|
328
428
|
'ui_snapshot',
|
|
329
429
|
'wait_for',
|
|
330
430
|
'ui_act',
|
|
431
|
+
'ui_do',
|
|
331
432
|
'batch',
|
|
332
433
|
]
|
|
333
434
|
|
|
@@ -349,6 +450,7 @@ const DESTRUCTIVE_ACTIONS = new Set<string>([
|
|
|
349
450
|
'key',
|
|
350
451
|
'scroll',
|
|
351
452
|
'ui_act',
|
|
453
|
+
'ui_do',
|
|
352
454
|
])
|
|
353
455
|
|
|
354
456
|
/** Observations that show the model what is on the screen, on their own. */
|
|
@@ -427,6 +529,7 @@ function requiredCapability(type: string): keyof ComputerUseCapabilities | null
|
|
|
427
529
|
case 'ui_snapshot':
|
|
428
530
|
case 'wait_for':
|
|
429
531
|
case 'ui_act':
|
|
532
|
+
case 'ui_do':
|
|
430
533
|
return 'uiTree'
|
|
431
534
|
default:
|
|
432
535
|
return null
|
|
@@ -454,7 +557,11 @@ function windowCaptureAvailable(host: ComputerUseHost, caps: ComputerUseCapabili
|
|
|
454
557
|
)
|
|
455
558
|
}
|
|
456
559
|
|
|
457
|
-
function availableActions(
|
|
560
|
+
function availableActions(
|
|
561
|
+
host: ComputerUseHost,
|
|
562
|
+
caps: ComputerUseCapabilities,
|
|
563
|
+
advisor?: ComputerUseAdvisor,
|
|
564
|
+
): ToolActionType[] {
|
|
458
565
|
const displayScreenshot = hostActionAvailable(caps, 'screenshot')
|
|
459
566
|
const window = windowCaptureAvailable(host, caps)
|
|
460
567
|
const screenshot = displayScreenshot || window
|
|
@@ -489,6 +596,8 @@ function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities):
|
|
|
489
596
|
case 'wait_for':
|
|
490
597
|
case 'ui_act':
|
|
491
598
|
return uiTree
|
|
599
|
+
case 'ui_do':
|
|
600
|
+
return uiTree && advisor?.acts === true
|
|
492
601
|
case 'batch':
|
|
493
602
|
return false
|
|
494
603
|
default:
|
|
@@ -523,8 +632,9 @@ function buildDescription(
|
|
|
523
632
|
host: ComputerUseHost,
|
|
524
633
|
caps: ComputerUseCapabilities,
|
|
525
634
|
settings: ResolvedSettings,
|
|
635
|
+
advisor?: ComputerUseAdvisor,
|
|
526
636
|
): string {
|
|
527
|
-
const available = availableActions(host, caps)
|
|
637
|
+
const available = availableActions(host, caps, advisor)
|
|
528
638
|
const unavailable = unavailableHostActions(host, caps)
|
|
529
639
|
const intro = hostActionAvailable(caps, 'screenshot')
|
|
530
640
|
? `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`
|
|
@@ -570,6 +680,10 @@ function buildDescription(
|
|
|
570
680
|
lines.push(
|
|
571
681
|
'To start a Windows program, a shell command (Start-Process <program>, or cmd.exe /c start <program>) is surest when you have a shell tool. The Start menu searches by display names in the system language, and ENTER on a name it does not find opens a web search in the browser.',
|
|
572
682
|
)
|
|
683
|
+
if (available.includes('ui_do'))
|
|
684
|
+
lines.push(
|
|
685
|
+
'ui_do {target, action, value, window_id} acts on a control named in words ("the Save button") among those the latest ui_snapshot showed: a second model chooses it and the tool says which control it acted on. When it is not sure it acts on nothing and lists the closest controls: then use ui_act on one of their refs. Use ui_act with a ref whenever you already have one.',
|
|
686
|
+
)
|
|
573
687
|
if (available.includes('list_windows'))
|
|
574
688
|
lines.push(
|
|
575
689
|
'list_windows names the open windows with their ids; focus_window brings one to the front.',
|
|
@@ -668,6 +782,7 @@ const ACTION_REQUIREMENTS: Readonly<Record<ToolActionType, string>> = {
|
|
|
668
782
|
'ui_snapshot takes an optional window_id, root (a ref to open) and query (text to find)',
|
|
669
783
|
wait_for: 'wait_for needs until and match; role, window_id and timeout_ms are optional',
|
|
670
784
|
ui_act: 'ui_act needs ref and action, and value for set_value',
|
|
785
|
+
ui_do: 'ui_do needs target (the control, in words) and action, and value for set_value',
|
|
671
786
|
batch: 'batch needs actions',
|
|
672
787
|
}
|
|
673
788
|
|
|
@@ -685,10 +800,11 @@ function hostModelSchema(
|
|
|
685
800
|
host: ComputerUseHost,
|
|
686
801
|
caps: ComputerUseCapabilities,
|
|
687
802
|
settings: ResolvedSettings,
|
|
803
|
+
advisor?: ComputerUseAdvisor,
|
|
688
804
|
): Record<string, unknown> {
|
|
689
805
|
// An unavailable host remains a diagnostic tool. Avoid invalid empty enums
|
|
690
806
|
// on provider wires; every execution is still refused before host access.
|
|
691
|
-
const offered = availableActions(host, caps)
|
|
807
|
+
const offered = availableActions(host, caps, advisor)
|
|
692
808
|
const actions = offered.length > 0 ? offered : ALL_ACTIONS.filter((a) => a !== 'batch')
|
|
693
809
|
const items = actions.filter((action) => !OUTSIDE_BATCH.has(action))
|
|
694
810
|
const buttons = new Set<string>()
|
|
@@ -814,6 +930,30 @@ function hostModelSchema(
|
|
|
814
930
|
type: 'string',
|
|
815
931
|
description: 'The text set_value puts in the control.',
|
|
816
932
|
}
|
|
933
|
+
if (advisor)
|
|
934
|
+
fields.intent = {
|
|
935
|
+
type: 'string',
|
|
936
|
+
description:
|
|
937
|
+
'For ui_act: what this is for, in a few words ("save the file"). Optional; it only helps check the choice.',
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (present.includes('ui_do')) {
|
|
941
|
+
fields.target = {
|
|
942
|
+
type: 'string',
|
|
943
|
+
description:
|
|
944
|
+
'For ui_do: the control in words, such as "the Save button" or "the name field". The tool picks the control from the latest ui_snapshot.',
|
|
945
|
+
}
|
|
946
|
+
if (!present.includes('ui_act')) {
|
|
947
|
+
fields.action = {
|
|
948
|
+
type: 'string',
|
|
949
|
+
enum: [...UI_ACTIONS],
|
|
950
|
+
description: 'What ui_do does to the control.',
|
|
951
|
+
}
|
|
952
|
+
fields.value = {
|
|
953
|
+
type: 'string',
|
|
954
|
+
description: 'The text set_value puts in the control.',
|
|
955
|
+
}
|
|
956
|
+
}
|
|
817
957
|
}
|
|
818
958
|
if (!forItems && present.includes('zoom'))
|
|
819
959
|
fields.region = {
|
|
@@ -936,6 +1076,33 @@ function uiActLabel(
|
|
|
936
1076
|
}
|
|
937
1077
|
}
|
|
938
1078
|
|
|
1079
|
+
/** What a ui_do does, over the words the model named the control with. */
|
|
1080
|
+
function uiDoLabel(input: {
|
|
1081
|
+
readonly target: string
|
|
1082
|
+
readonly action: UiElementAction
|
|
1083
|
+
readonly value?: string
|
|
1084
|
+
}): string {
|
|
1085
|
+
const target = quotedText(input.target)
|
|
1086
|
+
switch (input.action) {
|
|
1087
|
+
case 'invoke':
|
|
1088
|
+
return `Press the control for ${target}`
|
|
1089
|
+
case 'set_value':
|
|
1090
|
+
return `Set the control for ${target} to ${quotedText(input.value ?? '')}`
|
|
1091
|
+
case 'toggle':
|
|
1092
|
+
return `Toggle the control for ${target}`
|
|
1093
|
+
case 'select':
|
|
1094
|
+
return `Select the control for ${target}`
|
|
1095
|
+
case 'expand':
|
|
1096
|
+
return `Expand the control for ${target}`
|
|
1097
|
+
case 'collapse':
|
|
1098
|
+
return `Collapse the control for ${target}`
|
|
1099
|
+
case 'focus':
|
|
1100
|
+
return `Focus the control for ${target}`
|
|
1101
|
+
case 'scroll_into_view':
|
|
1102
|
+
return `Scroll to the control for ${target}`
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
939
1106
|
/**
|
|
940
1107
|
* Human activity text for one action; the raw input remains the model-facing
|
|
941
1108
|
* record. `describe` names a ui_act ref's control when the tool knows it.
|
|
@@ -981,6 +1148,8 @@ function itemLabel(
|
|
|
981
1148
|
return `Wait until ${input.until.replace('_', ' ')} ${quotedText(input.match)}`
|
|
982
1149
|
case 'ui_act':
|
|
983
1150
|
return uiActLabel(input, describe)
|
|
1151
|
+
case 'ui_do':
|
|
1152
|
+
return uiDoLabel(input)
|
|
984
1153
|
case 'batch':
|
|
985
1154
|
return batchLabel(input.actions, describe)
|
|
986
1155
|
}
|
|
@@ -1322,7 +1491,8 @@ export function createComputerUseTool(
|
|
|
1322
1491
|
}
|
|
1323
1492
|
: host.capabilities
|
|
1324
1493
|
const frames = new ScreenshotFrames()
|
|
1325
|
-
const available = new Set<string>(availableActions(host, caps))
|
|
1494
|
+
const available = new Set<string>(availableActions(host, caps, options.advisor))
|
|
1495
|
+
const advisor = options.advisor
|
|
1326
1496
|
|
|
1327
1497
|
// The controls of the latest ui_snapshot, by the ref the model was shown.
|
|
1328
1498
|
// A ref names a control, not one reading of it: the same host ref keeps the
|
|
@@ -1330,6 +1500,8 @@ export function createComputerUseTool(
|
|
|
1330
1500
|
// control. Whether the control is still there is the host's answer.
|
|
1331
1501
|
const uiRefs = new Map<string, UiRef>()
|
|
1332
1502
|
const uiRefByHost = new Map<string, string>()
|
|
1503
|
+
// The refs the latest reading of each window showed, so an advisor is asked about what is there now.
|
|
1504
|
+
const shownRefs = new Map<string, Set<string>>()
|
|
1333
1505
|
let uiSnapshots = 0
|
|
1334
1506
|
let uiRefCount = 0
|
|
1335
1507
|
let uiSnapshotWindowId: string | undefined
|
|
@@ -1344,6 +1516,30 @@ export function createComputerUseTool(
|
|
|
1344
1516
|
}
|
|
1345
1517
|
const label = (input: BatchItem | ActionInput): string => itemLabel(input, describeUiRef)
|
|
1346
1518
|
|
|
1519
|
+
/** The controls of a window's latest reading that offer an action (any, when none is named), as an advisor is shown them. */
|
|
1520
|
+
const advisedCandidates = (
|
|
1521
|
+
windowId: string | undefined,
|
|
1522
|
+
action?: UiElementAction,
|
|
1523
|
+
): ComputerUseAdvisorCandidate[] => {
|
|
1524
|
+
const shown = shownRefs.get(windowId ?? '')
|
|
1525
|
+
const out: ComputerUseAdvisorCandidate[] = []
|
|
1526
|
+
if (!shown) return out
|
|
1527
|
+
for (const ref of shown) {
|
|
1528
|
+
const entry = uiRefs.get(ref)
|
|
1529
|
+
if (!entry) continue
|
|
1530
|
+
const offered = entry.element.actions ?? []
|
|
1531
|
+
if (offered.length === 0) continue
|
|
1532
|
+
if (action !== undefined && !offered.includes(action)) continue
|
|
1533
|
+
const text = uiElementText(entry.element)
|
|
1534
|
+
out.push({
|
|
1535
|
+
ref,
|
|
1536
|
+
description: text.length > 80 ? `${text.slice(0, 79)}…` : text,
|
|
1537
|
+
})
|
|
1538
|
+
if (out.length >= MAX_ADVISED_CANDIDATES) break
|
|
1539
|
+
}
|
|
1540
|
+
return out
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1347
1543
|
const refusal = (type: string): string | null => {
|
|
1348
1544
|
if (available.has(type)) return null
|
|
1349
1545
|
const required = requiredCapability(type)
|
|
@@ -1678,6 +1874,14 @@ export function createComputerUseTool(
|
|
|
1678
1874
|
if (item.action === 'set_value' && item.value === undefined)
|
|
1679
1875
|
throw new StepFailure('set_value needs value, the text to put in the control')
|
|
1680
1876
|
return step(async () => {
|
|
1877
|
+
// A second opinion on the model's own choice: asked beside the action, never waited for.
|
|
1878
|
+
if (advisor && item.intent !== undefined) {
|
|
1879
|
+
const candidates = advisedCandidates(entry.windowId)
|
|
1880
|
+
if (candidates.length >= 2)
|
|
1881
|
+
void advisor
|
|
1882
|
+
.pick({ intent: item.intent, candidates, chosen: item.ref })
|
|
1883
|
+
.catch(() => undefined)
|
|
1884
|
+
}
|
|
1681
1885
|
let outcome: UiActResult
|
|
1682
1886
|
try {
|
|
1683
1887
|
outcome = await (host.uiAct as NonNullable<ComputerUseHost['uiAct']>)(
|
|
@@ -1701,6 +1905,62 @@ export function createComputerUseTool(
|
|
|
1701
1905
|
return deliveryOutcome(outcome)
|
|
1702
1906
|
})
|
|
1703
1907
|
}
|
|
1908
|
+
case 'ui_do': {
|
|
1909
|
+
if (!advisor?.acts)
|
|
1910
|
+
throw new StepFailure('ui_do is not available here; use ui_act with a ref')
|
|
1911
|
+
if (item.action === 'set_value' && item.value === undefined)
|
|
1912
|
+
throw new StepFailure('set_value needs value, the text to put in the control')
|
|
1913
|
+
if (uiSnapshots === 0)
|
|
1914
|
+
throw new StepFailure(
|
|
1915
|
+
'no ui_snapshot has been taken yet; take one, because ui_do chooses among the controls it showed',
|
|
1916
|
+
)
|
|
1917
|
+
const windowId = item.window_id ?? uiSnapshotWindowId
|
|
1918
|
+
const candidates = advisedCandidates(windowId, item.action)
|
|
1919
|
+
if (candidates.length === 0)
|
|
1920
|
+
throw new StepFailure(
|
|
1921
|
+
`no control in the latest ui_snapshot${windowId ? ` of window ${windowId}` : ''} offers ${item.action}; take a ui_snapshot and act with ui_act on a ref`,
|
|
1922
|
+
)
|
|
1923
|
+
const target = item.target
|
|
1924
|
+
return step(async (signal) => {
|
|
1925
|
+
let advice: ComputerUseAdvice | undefined
|
|
1926
|
+
try {
|
|
1927
|
+
advice = await advisor.pick({ intent: target, candidates }, signal)
|
|
1928
|
+
} catch {
|
|
1929
|
+
if (signal?.aborted) throw new StepFailure('cancelled')
|
|
1930
|
+
}
|
|
1931
|
+
const [best, next] = advice?.ranked ?? []
|
|
1932
|
+
const sure =
|
|
1933
|
+
best !== undefined &&
|
|
1934
|
+
best.confidence >= ADVICE_MIN_CONFIDENCE &&
|
|
1935
|
+
best.confidence - (next?.confidence ?? 0) >= ADVICE_MIN_MARGIN &&
|
|
1936
|
+
uiRefs.has(best.ref)
|
|
1937
|
+
if (!best || !sure) {
|
|
1938
|
+
const close = (advice?.ranked ?? [])
|
|
1939
|
+
.filter((entry) => uiRefs.has(entry.ref))
|
|
1940
|
+
.slice(0, 3)
|
|
1941
|
+
.map((entry) => describeUiRef(entry.ref))
|
|
1942
|
+
throw deliveryFailure({
|
|
1943
|
+
delivery: 'not_delivered',
|
|
1944
|
+
retrySafety: 'safe',
|
|
1945
|
+
detail: `ui_do was not sure which control is ${quotedText(target)}, so it acted on nothing.${close.length > 0 ? ` Closest: ${close.join('; ')}.` : ''} Use ui_act on one of the refs, or ui_snapshot {query: ...} to find it.`,
|
|
1946
|
+
})
|
|
1947
|
+
}
|
|
1948
|
+
const chosen = describeUiRef(best.ref) ?? best.ref
|
|
1949
|
+
const ran = await plan(
|
|
1950
|
+
{
|
|
1951
|
+
type: 'ui_act',
|
|
1952
|
+
ref: best.ref,
|
|
1953
|
+
action: item.action,
|
|
1954
|
+
...(item.value !== undefined ? { value: item.value } : {}),
|
|
1955
|
+
},
|
|
1956
|
+
undefined,
|
|
1957
|
+
).run(signal)
|
|
1958
|
+
const prefix = `chose ${chosen} for ${quotedText(target)} (${Math.round(best.confidence * 100)}% sure)`
|
|
1959
|
+
return typeof ran === 'string'
|
|
1960
|
+
? `${prefix}: ${ran}`
|
|
1961
|
+
: { ...ran, note: `${prefix}: ${ran.note}` }
|
|
1962
|
+
})
|
|
1963
|
+
}
|
|
1704
1964
|
}
|
|
1705
1965
|
}
|
|
1706
1966
|
|
|
@@ -1795,6 +2055,11 @@ export function createComputerUseTool(
|
|
|
1795
2055
|
}
|
|
1796
2056
|
}
|
|
1797
2057
|
uiSnapshotWindowId = snapshot.windowId ?? requestedWindowId
|
|
2058
|
+
{
|
|
2059
|
+
const key = uiSnapshotWindowId ?? ''
|
|
2060
|
+
const now = new Set(refs.keys())
|
|
2061
|
+
shownRefs.set(key, snapshot.region ? new Set([...(shownRefs.get(key) ?? []), ...now]) : now)
|
|
2062
|
+
}
|
|
1798
2063
|
if (uiSnapshotWindowId !== undefined) treeUsable.set(uiSnapshotWindowId, refs.size > 0)
|
|
1799
2064
|
uiSnapshotFrame = frames.latest()
|
|
1800
2065
|
const window =
|
|
@@ -2320,7 +2585,7 @@ export function createComputerUseTool(
|
|
|
2320
2585
|
if (step.item.type === 'focus_window' && windowCaptureAvailable(host, caps))
|
|
2321
2586
|
postWindowId = step.item.window_id
|
|
2322
2587
|
else if (
|
|
2323
|
-
step.item.type === 'ui_act' &&
|
|
2588
|
+
(step.item.type === 'ui_act' || step.item.type === 'ui_do') &&
|
|
2324
2589
|
uiSnapshotWindowId &&
|
|
2325
2590
|
!explicitDisplayFrame &&
|
|
2326
2591
|
windowCaptureAvailable(host, caps)
|
|
@@ -2377,7 +2642,7 @@ export function createComputerUseTool(
|
|
|
2377
2642
|
let shotError: string | undefined
|
|
2378
2643
|
if (looks) {
|
|
2379
2644
|
try {
|
|
2380
|
-
await
|
|
2645
|
+
await settleAfterActions(host, postWindowId, settings.settleMs, signal)
|
|
2381
2646
|
shot = await capture(postWindowId)
|
|
2382
2647
|
} catch (error) {
|
|
2383
2648
|
shotError = signal?.aborted ? 'cancelled' : errorText(error)
|
|
@@ -2477,9 +2742,9 @@ export function createComputerUseTool(
|
|
|
2477
2742
|
|
|
2478
2743
|
const tool = defineTool({
|
|
2479
2744
|
name: COMPUTER_USE_TOOL_NAME,
|
|
2480
|
-
description: buildDescription(host, caps, settings),
|
|
2745
|
+
description: buildDescription(host, caps, settings, options.advisor),
|
|
2481
2746
|
inputSchema: actionSchema,
|
|
2482
|
-
modelInputSchema: hostModelSchema(host, caps, settings),
|
|
2747
|
+
modelInputSchema: hostModelSchema(host, caps, settings, options.advisor),
|
|
2483
2748
|
validationErrorHint: `Action requirements: ${ALL_ACTIONS.map((action) => actionRequirement(action, host, caps)).join('; ')}. A batch is {"type":"batch","actions":[...]} with at most ${settings.maxBatchActions} actions and no screenshot, zoom, ui_snapshot or batch inside.`,
|
|
2484
2749
|
category: 'custom',
|
|
2485
2750
|
permissions: [],
|
|
@@ -568,6 +568,40 @@ export interface ComputerUseHost {
|
|
|
568
568
|
signal?: AbortSignal,
|
|
569
569
|
): Promise<UiActResult>
|
|
570
570
|
|
|
571
|
+
/**
|
|
572
|
+
* Wait until the window the last action went to has stopped changing,
|
|
573
|
+
* instead of the tool sleeping a fixed {@link ComputerUseSettleRequest.budgetMs}
|
|
574
|
+
* before it takes the screenshot it returns. A host that is told by the
|
|
575
|
+
* window itself (events, not polling) answers as soon as a change was
|
|
576
|
+
* seen and the window has been quiet for a moment, and after the whole
|
|
577
|
+
* budget when nothing changed at all, so it is never slower than the fixed
|
|
578
|
+
* wait.
|
|
579
|
+
*
|
|
580
|
+
* Resolve `undefined` (or reject) when the host cannot say; the tool then
|
|
581
|
+
* sleeps the fixed time as it always did. A host that omits this method
|
|
582
|
+
* keeps the fixed wait.
|
|
583
|
+
*/
|
|
584
|
+
settle?(
|
|
585
|
+
request: ComputerUseSettleRequest,
|
|
586
|
+
signal?: AbortSignal,
|
|
587
|
+
): Promise<ComputerUseSettleResult | undefined>
|
|
588
|
+
|
|
571
589
|
initialize?(): Promise<void>
|
|
572
590
|
dispose?(): Promise<void>
|
|
573
591
|
}
|
|
592
|
+
|
|
593
|
+
/** What the tool asks of {@link ComputerUseHost.settle}. */
|
|
594
|
+
export interface ComputerUseSettleRequest {
|
|
595
|
+
/** The window the batch acted in, when it acted in one; absent for display input. */
|
|
596
|
+
readonly windowId?: string
|
|
597
|
+
/** The most the tool is willing to wait, in milliseconds (its `settleMs`). */
|
|
598
|
+
readonly budgetMs: number
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** What {@link ComputerUseHost.settle} reports. */
|
|
602
|
+
export interface ComputerUseSettleResult {
|
|
603
|
+
/** How long the host waited, in milliseconds. */
|
|
604
|
+
readonly waitedMs: number
|
|
605
|
+
/** Whether the window changed at all while it waited. */
|
|
606
|
+
readonly changed: boolean
|
|
607
|
+
}
|