@namzu/sdk 61.3.0 → 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 +6 -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 +172 -9
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/package.json +1 -1
- package/src/public-runtime.ts +4 -0
- package/src/tools/builtins/computer-use.ts +245 -7
|
@@ -36,6 +36,12 @@ const MAX_LISTED_WINDOWS = 50;
|
|
|
36
36
|
const MAX_UI_SNAPSHOT_CHARS = 14_000;
|
|
37
37
|
/** The refs of earlier snapshots the tool still resolves for the host. */
|
|
38
38
|
const MAX_HELD_UI_REFS = 4_000;
|
|
39
|
+
/** The least confidence in the best control for the tool to act on it. */
|
|
40
|
+
const ADVICE_MIN_CONFIDENCE = 0.85;
|
|
41
|
+
/** How far the best control must lead the next one. */
|
|
42
|
+
const ADVICE_MIN_MARGIN = 0.4;
|
|
43
|
+
/** The most controls an advisor is asked about at once. */
|
|
44
|
+
const MAX_ADVISED_CANDIDATES = 255;
|
|
39
45
|
/**
|
|
40
46
|
* Why `provider` cannot drive `computer_use`, or undefined when it can.
|
|
41
47
|
*
|
|
@@ -169,6 +175,16 @@ const uiActSchema = z.object({
|
|
|
169
175
|
ref: z.string().min(1),
|
|
170
176
|
action: z.enum(UI_ACTIONS),
|
|
171
177
|
value: z.string().optional(),
|
|
178
|
+
/** What this is for, in a few words; only read when the tool has an advisor. */
|
|
179
|
+
intent: z.string().min(1).max(200).optional(),
|
|
180
|
+
});
|
|
181
|
+
const uiDoSchema = z.object({
|
|
182
|
+
type: z.literal('ui_do'),
|
|
183
|
+
/** The control, in words: "the Save button", "the name field". */
|
|
184
|
+
target: z.string().min(1).max(200),
|
|
185
|
+
action: z.enum(UI_ACTIONS),
|
|
186
|
+
value: z.string().optional(),
|
|
187
|
+
window_id: z.string().min(1).optional(),
|
|
172
188
|
});
|
|
173
189
|
/** What a batch may carry: everything but the image-returning actions and another batch. */
|
|
174
190
|
const batchItemSchema = z.discriminatedUnion('type', [
|
|
@@ -183,6 +199,7 @@ const batchItemSchema = z.discriminatedUnion('type', [
|
|
|
183
199
|
listWindowsSchema,
|
|
184
200
|
focusWindowSchema,
|
|
185
201
|
uiActSchema,
|
|
202
|
+
uiDoSchema,
|
|
186
203
|
]);
|
|
187
204
|
const withFrame = { screenshot_id: screenshotIdSchema };
|
|
188
205
|
const actionSchema = z.discriminatedUnion('type', [
|
|
@@ -204,6 +221,7 @@ const actionSchema = z.discriminatedUnion('type', [
|
|
|
204
221
|
uiSnapshotSchema,
|
|
205
222
|
waitForSchema,
|
|
206
223
|
uiActSchema.extend(withFrame),
|
|
224
|
+
uiDoSchema.extend(withFrame),
|
|
207
225
|
z.object({
|
|
208
226
|
type: z.literal('batch'),
|
|
209
227
|
actions: z.array(batchItemSchema).min(1),
|
|
@@ -236,6 +254,7 @@ const ALL_ACTIONS = [
|
|
|
236
254
|
'ui_snapshot',
|
|
237
255
|
'wait_for',
|
|
238
256
|
'ui_act',
|
|
257
|
+
'ui_do',
|
|
239
258
|
'batch',
|
|
240
259
|
];
|
|
241
260
|
/** Observations: they change nothing on the desktop. */
|
|
@@ -255,6 +274,7 @@ const DESTRUCTIVE_ACTIONS = new Set([
|
|
|
255
274
|
'key',
|
|
256
275
|
'scroll',
|
|
257
276
|
'ui_act',
|
|
277
|
+
'ui_do',
|
|
258
278
|
]);
|
|
259
279
|
/** Observations that show the model what is on the screen, on their own. */
|
|
260
280
|
const SCREEN_OBSERVATIONS = new Set([
|
|
@@ -320,6 +340,7 @@ function requiredCapability(type) {
|
|
|
320
340
|
case 'ui_snapshot':
|
|
321
341
|
case 'wait_for':
|
|
322
342
|
case 'ui_act':
|
|
343
|
+
case 'ui_do':
|
|
323
344
|
return 'uiTree';
|
|
324
345
|
default:
|
|
325
346
|
return null;
|
|
@@ -337,7 +358,7 @@ function windowCaptureAvailable(host, caps) {
|
|
|
337
358
|
typeof host.captureWindow === 'function' &&
|
|
338
359
|
typeof host.executeWindow === 'function');
|
|
339
360
|
}
|
|
340
|
-
function availableActions(host, caps) {
|
|
361
|
+
function availableActions(host, caps, advisor) {
|
|
341
362
|
const displayScreenshot = hostActionAvailable(caps, 'screenshot');
|
|
342
363
|
const window = windowCaptureAvailable(host, caps);
|
|
343
364
|
const screenshot = displayScreenshot || window;
|
|
@@ -370,6 +391,8 @@ function availableActions(host, caps) {
|
|
|
370
391
|
case 'wait_for':
|
|
371
392
|
case 'ui_act':
|
|
372
393
|
return uiTree;
|
|
394
|
+
case 'ui_do':
|
|
395
|
+
return uiTree && advisor?.acts === true;
|
|
373
396
|
case 'batch':
|
|
374
397
|
return false;
|
|
375
398
|
default:
|
|
@@ -403,8 +426,8 @@ function unavailableHostActions(host, caps) {
|
|
|
403
426
|
// ---------------------------------------------------------------------------
|
|
404
427
|
// Model-facing description and schema
|
|
405
428
|
// ---------------------------------------------------------------------------
|
|
406
|
-
function buildDescription(host, caps, settings) {
|
|
407
|
-
const available = availableActions(host, caps);
|
|
429
|
+
function buildDescription(host, caps, settings, advisor) {
|
|
430
|
+
const available = availableActions(host, caps, advisor);
|
|
408
431
|
const unavailable = unavailableHostActions(host, caps);
|
|
409
432
|
const intro = hostActionAvailable(caps, 'screenshot')
|
|
410
433
|
? `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`
|
|
@@ -433,6 +456,8 @@ function buildDescription(host, caps, settings) {
|
|
|
433
456
|
available.includes('key') &&
|
|
434
457
|
available.includes('type_text'))
|
|
435
458
|
lines.push('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.');
|
|
459
|
+
if (available.includes('ui_do'))
|
|
460
|
+
lines.push('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.');
|
|
436
461
|
if (available.includes('list_windows'))
|
|
437
462
|
lines.push('list_windows names the open windows with their ids; focus_window brings one to the front.');
|
|
438
463
|
if (windowCaptureAvailable(host, caps))
|
|
@@ -507,6 +532,7 @@ const ACTION_REQUIREMENTS = {
|
|
|
507
532
|
ui_snapshot: 'ui_snapshot takes an optional window_id, root (a ref to open) and query (text to find)',
|
|
508
533
|
wait_for: 'wait_for needs until and match; role, window_id and timeout_ms are optional',
|
|
509
534
|
ui_act: 'ui_act needs ref and action, and value for set_value',
|
|
535
|
+
ui_do: 'ui_do needs target (the control, in words) and action, and value for set_value',
|
|
510
536
|
batch: 'batch needs actions',
|
|
511
537
|
};
|
|
512
538
|
function actionRequirement(action, host, caps) {
|
|
@@ -514,10 +540,10 @@ function actionRequirement(action, host, caps) {
|
|
|
514
540
|
? `screenshot ${hostActionAvailable(caps, 'screenshot') ? 'takes an optional' : 'requires'} window_id to capture pixels for that window`
|
|
515
541
|
: ACTION_REQUIREMENTS[action];
|
|
516
542
|
}
|
|
517
|
-
function hostModelSchema(host, caps, settings) {
|
|
543
|
+
function hostModelSchema(host, caps, settings, advisor) {
|
|
518
544
|
// An unavailable host remains a diagnostic tool. Avoid invalid empty enums
|
|
519
545
|
// on provider wires; every execution is still refused before host access.
|
|
520
|
-
const offered = availableActions(host, caps);
|
|
546
|
+
const offered = availableActions(host, caps, advisor);
|
|
521
547
|
const actions = offered.length > 0 ? offered : ALL_ACTIONS.filter((a) => a !== 'batch');
|
|
522
548
|
const items = actions.filter((action) => !OUTSIDE_BATCH.has(action));
|
|
523
549
|
const buttons = new Set();
|
|
@@ -633,6 +659,28 @@ function hostModelSchema(host, caps, settings) {
|
|
|
633
659
|
type: 'string',
|
|
634
660
|
description: 'The text set_value puts in the control.',
|
|
635
661
|
};
|
|
662
|
+
if (advisor)
|
|
663
|
+
fields.intent = {
|
|
664
|
+
type: 'string',
|
|
665
|
+
description: 'For ui_act: what this is for, in a few words ("save the file"). Optional; it only helps check the choice.',
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
if (present.includes('ui_do')) {
|
|
669
|
+
fields.target = {
|
|
670
|
+
type: 'string',
|
|
671
|
+
description: '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.',
|
|
672
|
+
};
|
|
673
|
+
if (!present.includes('ui_act')) {
|
|
674
|
+
fields.action = {
|
|
675
|
+
type: 'string',
|
|
676
|
+
enum: [...UI_ACTIONS],
|
|
677
|
+
description: 'What ui_do does to the control.',
|
|
678
|
+
};
|
|
679
|
+
fields.value = {
|
|
680
|
+
type: 'string',
|
|
681
|
+
description: 'The text set_value puts in the control.',
|
|
682
|
+
};
|
|
683
|
+
}
|
|
636
684
|
}
|
|
637
685
|
if (!forItems && present.includes('zoom'))
|
|
638
686
|
fields.region = {
|
|
@@ -739,6 +787,28 @@ function uiActLabel(input, describe) {
|
|
|
739
787
|
return `Scroll to ${target}`;
|
|
740
788
|
}
|
|
741
789
|
}
|
|
790
|
+
/** What a ui_do does, over the words the model named the control with. */
|
|
791
|
+
function uiDoLabel(input) {
|
|
792
|
+
const target = quotedText(input.target);
|
|
793
|
+
switch (input.action) {
|
|
794
|
+
case 'invoke':
|
|
795
|
+
return `Press the control for ${target}`;
|
|
796
|
+
case 'set_value':
|
|
797
|
+
return `Set the control for ${target} to ${quotedText(input.value ?? '')}`;
|
|
798
|
+
case 'toggle':
|
|
799
|
+
return `Toggle the control for ${target}`;
|
|
800
|
+
case 'select':
|
|
801
|
+
return `Select the control for ${target}`;
|
|
802
|
+
case 'expand':
|
|
803
|
+
return `Expand the control for ${target}`;
|
|
804
|
+
case 'collapse':
|
|
805
|
+
return `Collapse the control for ${target}`;
|
|
806
|
+
case 'focus':
|
|
807
|
+
return `Focus the control for ${target}`;
|
|
808
|
+
case 'scroll_into_view':
|
|
809
|
+
return `Scroll to the control for ${target}`;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
742
812
|
/**
|
|
743
813
|
* Human activity text for one action; the raw input remains the model-facing
|
|
744
814
|
* record. `describe` names a ui_act ref's control when the tool knows it.
|
|
@@ -779,6 +849,8 @@ function itemLabel(input, describe) {
|
|
|
779
849
|
return `Wait until ${input.until.replace('_', ' ')} ${quotedText(input.match)}`;
|
|
780
850
|
case 'ui_act':
|
|
781
851
|
return uiActLabel(input, describe);
|
|
852
|
+
case 'ui_do':
|
|
853
|
+
return uiDoLabel(input);
|
|
782
854
|
case 'batch':
|
|
783
855
|
return batchLabel(input.actions, describe);
|
|
784
856
|
}
|
|
@@ -993,13 +1065,16 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
993
1065
|
}
|
|
994
1066
|
: host.capabilities;
|
|
995
1067
|
const frames = new ScreenshotFrames();
|
|
996
|
-
const available = new Set(availableActions(host, caps));
|
|
1068
|
+
const available = new Set(availableActions(host, caps, options.advisor));
|
|
1069
|
+
const advisor = options.advisor;
|
|
997
1070
|
// The controls of the latest ui_snapshot, by the ref the model was shown.
|
|
998
1071
|
// A ref names a control, not one reading of it: the same host ref keeps the
|
|
999
1072
|
// same number in every snapshot, and a number is never reused for another
|
|
1000
1073
|
// control. Whether the control is still there is the host's answer.
|
|
1001
1074
|
const uiRefs = new Map();
|
|
1002
1075
|
const uiRefByHost = new Map();
|
|
1076
|
+
// The refs the latest reading of each window showed, so an advisor is asked about what is there now.
|
|
1077
|
+
const shownRefs = new Map();
|
|
1003
1078
|
let uiSnapshots = 0;
|
|
1004
1079
|
let uiRefCount = 0;
|
|
1005
1080
|
let uiSnapshotWindowId;
|
|
@@ -1013,6 +1088,31 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1013
1088
|
return entry ? `${uiElementText(entry.element)} (${ref})` : undefined;
|
|
1014
1089
|
};
|
|
1015
1090
|
const label = (input) => itemLabel(input, describeUiRef);
|
|
1091
|
+
/** The controls of a window's latest reading that offer an action (any, when none is named), as an advisor is shown them. */
|
|
1092
|
+
const advisedCandidates = (windowId, action) => {
|
|
1093
|
+
const shown = shownRefs.get(windowId ?? '');
|
|
1094
|
+
const out = [];
|
|
1095
|
+
if (!shown)
|
|
1096
|
+
return out;
|
|
1097
|
+
for (const ref of shown) {
|
|
1098
|
+
const entry = uiRefs.get(ref);
|
|
1099
|
+
if (!entry)
|
|
1100
|
+
continue;
|
|
1101
|
+
const offered = entry.element.actions ?? [];
|
|
1102
|
+
if (offered.length === 0)
|
|
1103
|
+
continue;
|
|
1104
|
+
if (action !== undefined && !offered.includes(action))
|
|
1105
|
+
continue;
|
|
1106
|
+
const text = uiElementText(entry.element);
|
|
1107
|
+
out.push({
|
|
1108
|
+
ref,
|
|
1109
|
+
description: text.length > 80 ? `${text.slice(0, 79)}…` : text,
|
|
1110
|
+
});
|
|
1111
|
+
if (out.length >= MAX_ADVISED_CANDIDATES)
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
return out;
|
|
1115
|
+
};
|
|
1016
1116
|
const refusal = (type) => {
|
|
1017
1117
|
if (available.has(type))
|
|
1018
1118
|
return null;
|
|
@@ -1262,6 +1362,14 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1262
1362
|
if (item.action === 'set_value' && item.value === undefined)
|
|
1263
1363
|
throw new StepFailure('set_value needs value, the text to put in the control');
|
|
1264
1364
|
return step(async () => {
|
|
1365
|
+
// A second opinion on the model's own choice: asked beside the action, never waited for.
|
|
1366
|
+
if (advisor && item.intent !== undefined) {
|
|
1367
|
+
const candidates = advisedCandidates(entry.windowId);
|
|
1368
|
+
if (candidates.length >= 2)
|
|
1369
|
+
void advisor
|
|
1370
|
+
.pick({ intent: item.intent, candidates, chosen: item.ref })
|
|
1371
|
+
.catch(() => undefined);
|
|
1372
|
+
}
|
|
1265
1373
|
let outcome;
|
|
1266
1374
|
try {
|
|
1267
1375
|
outcome = await host.uiAct(entry.hostRef, item.action, item.value, callSignal);
|
|
@@ -1280,6 +1388,56 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1280
1388
|
return deliveryOutcome(outcome);
|
|
1281
1389
|
});
|
|
1282
1390
|
}
|
|
1391
|
+
case 'ui_do': {
|
|
1392
|
+
if (!advisor?.acts)
|
|
1393
|
+
throw new StepFailure('ui_do is not available here; use ui_act with a ref');
|
|
1394
|
+
if (item.action === 'set_value' && item.value === undefined)
|
|
1395
|
+
throw new StepFailure('set_value needs value, the text to put in the control');
|
|
1396
|
+
if (uiSnapshots === 0)
|
|
1397
|
+
throw new StepFailure('no ui_snapshot has been taken yet; take one, because ui_do chooses among the controls it showed');
|
|
1398
|
+
const windowId = item.window_id ?? uiSnapshotWindowId;
|
|
1399
|
+
const candidates = advisedCandidates(windowId, item.action);
|
|
1400
|
+
if (candidates.length === 0)
|
|
1401
|
+
throw new StepFailure(`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`);
|
|
1402
|
+
const target = item.target;
|
|
1403
|
+
return step(async (signal) => {
|
|
1404
|
+
let advice;
|
|
1405
|
+
try {
|
|
1406
|
+
advice = await advisor.pick({ intent: target, candidates }, signal);
|
|
1407
|
+
}
|
|
1408
|
+
catch {
|
|
1409
|
+
if (signal?.aborted)
|
|
1410
|
+
throw new StepFailure('cancelled');
|
|
1411
|
+
}
|
|
1412
|
+
const [best, next] = advice?.ranked ?? [];
|
|
1413
|
+
const sure = best !== undefined &&
|
|
1414
|
+
best.confidence >= ADVICE_MIN_CONFIDENCE &&
|
|
1415
|
+
best.confidence - (next?.confidence ?? 0) >= ADVICE_MIN_MARGIN &&
|
|
1416
|
+
uiRefs.has(best.ref);
|
|
1417
|
+
if (!best || !sure) {
|
|
1418
|
+
const close = (advice?.ranked ?? [])
|
|
1419
|
+
.filter((entry) => uiRefs.has(entry.ref))
|
|
1420
|
+
.slice(0, 3)
|
|
1421
|
+
.map((entry) => describeUiRef(entry.ref));
|
|
1422
|
+
throw deliveryFailure({
|
|
1423
|
+
delivery: 'not_delivered',
|
|
1424
|
+
retrySafety: 'safe',
|
|
1425
|
+
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.`,
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
const chosen = describeUiRef(best.ref) ?? best.ref;
|
|
1429
|
+
const ran = await plan({
|
|
1430
|
+
type: 'ui_act',
|
|
1431
|
+
ref: best.ref,
|
|
1432
|
+
action: item.action,
|
|
1433
|
+
...(item.value !== undefined ? { value: item.value } : {}),
|
|
1434
|
+
}, undefined).run(signal);
|
|
1435
|
+
const prefix = `chose ${chosen} for ${quotedText(target)} (${Math.round(best.confidence * 100)}% sure)`;
|
|
1436
|
+
return typeof ran === 'string'
|
|
1437
|
+
? `${prefix}: ${ran}`
|
|
1438
|
+
: { ...ran, note: `${prefix}: ${ran.note}` };
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1283
1441
|
}
|
|
1284
1442
|
};
|
|
1285
1443
|
/** Read one window's controls, number the ones the model can act on, and show them. */
|
|
@@ -1364,6 +1522,11 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1364
1522
|
}
|
|
1365
1523
|
}
|
|
1366
1524
|
uiSnapshotWindowId = snapshot.windowId ?? requestedWindowId;
|
|
1525
|
+
{
|
|
1526
|
+
const key = uiSnapshotWindowId ?? '';
|
|
1527
|
+
const now = new Set(refs.keys());
|
|
1528
|
+
shownRefs.set(key, snapshot.region ? new Set([...(shownRefs.get(key) ?? []), ...now]) : now);
|
|
1529
|
+
}
|
|
1367
1530
|
if (uiSnapshotWindowId !== undefined)
|
|
1368
1531
|
treeUsable.set(uiSnapshotWindowId, refs.size > 0);
|
|
1369
1532
|
uiSnapshotFrame = frames.latest();
|
|
@@ -1835,7 +1998,7 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1835
1998
|
const outcome = typeof ran === 'string' ? { note: ran } : ran;
|
|
1836
1999
|
if (step.item.type === 'focus_window' && windowCaptureAvailable(host, caps))
|
|
1837
2000
|
postWindowId = step.item.window_id;
|
|
1838
|
-
else if (step.item.type === 'ui_act' &&
|
|
2001
|
+
else if ((step.item.type === 'ui_act' || step.item.type === 'ui_do') &&
|
|
1839
2002
|
uiSnapshotWindowId &&
|
|
1840
2003
|
!explicitDisplayFrame &&
|
|
1841
2004
|
windowCaptureAvailable(host, caps))
|
|
@@ -1984,9 +2147,9 @@ export function createComputerUseTool(host, options = {}) {
|
|
|
1984
2147
|
const capturesScreen = (input) => observes && capturesScreenInput(input, settings.screenshotAfterActions);
|
|
1985
2148
|
const tool = defineTool({
|
|
1986
2149
|
name: COMPUTER_USE_TOOL_NAME,
|
|
1987
|
-
description: buildDescription(host, caps, settings),
|
|
2150
|
+
description: buildDescription(host, caps, settings, options.advisor),
|
|
1988
2151
|
inputSchema: actionSchema,
|
|
1989
|
-
modelInputSchema: hostModelSchema(host, caps, settings),
|
|
2152
|
+
modelInputSchema: hostModelSchema(host, caps, settings, options.advisor),
|
|
1990
2153
|
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.`,
|
|
1991
2154
|
category: 'custom',
|
|
1992
2155
|
permissions: [],
|