@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.
@@ -11,11 +11,37 @@ const DEFAULT_SETTLE_MS = 500;
11
11
  const DEFAULT_MAX_BATCH_ACTIONS = 20;
12
12
  const DEFAULT_MAX_WAIT_MS = 10_000;
13
13
  const DEFAULT_WAIT_FOR_MS = 10_000;
14
+ /**
15
+ * The wait between the last action and the screenshot that follows it. A host
16
+ * that can tell when the window stopped changing (see
17
+ * {@link ComputerUseHost.settle}) is asked first and ends the wait early; a
18
+ * host without it, one that cannot say, or one that fails keeps the fixed
19
+ * `settleMs` it always had. Cancellation is never swallowed.
20
+ */
21
+ async function settleAfterActions(host, windowId, settleMs, signal) {
22
+ if (settleMs > 0 && typeof host.settle === 'function') {
23
+ try {
24
+ const settled = await host.settle({ budgetMs: settleMs, ...(windowId !== undefined ? { windowId } : {}) }, signal);
25
+ if (settled !== undefined)
26
+ return;
27
+ }
28
+ catch {
29
+ // Fall through to the fixed wait; a cancelled signal makes it reject at once.
30
+ }
31
+ }
32
+ await sleep(settleMs, signal);
33
+ }
14
34
  const MAX_LISTED_WINDOWS = 50;
15
35
  /** The most of a ui_snapshot the model is shown, in characters (about 4 000 tokens). */
16
36
  const MAX_UI_SNAPSHOT_CHARS = 14_000;
17
37
  /** The refs of earlier snapshots the tool still resolves for the host. */
18
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;
19
45
  /**
20
46
  * Why `provider` cannot drive `computer_use`, or undefined when it can.
21
47
  *
@@ -149,6 +175,16 @@ const uiActSchema = z.object({
149
175
  ref: z.string().min(1),
150
176
  action: z.enum(UI_ACTIONS),
151
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(),
152
188
  });
153
189
  /** What a batch may carry: everything but the image-returning actions and another batch. */
154
190
  const batchItemSchema = z.discriminatedUnion('type', [
@@ -163,6 +199,7 @@ const batchItemSchema = z.discriminatedUnion('type', [
163
199
  listWindowsSchema,
164
200
  focusWindowSchema,
165
201
  uiActSchema,
202
+ uiDoSchema,
166
203
  ]);
167
204
  const withFrame = { screenshot_id: screenshotIdSchema };
168
205
  const actionSchema = z.discriminatedUnion('type', [
@@ -184,6 +221,7 @@ const actionSchema = z.discriminatedUnion('type', [
184
221
  uiSnapshotSchema,
185
222
  waitForSchema,
186
223
  uiActSchema.extend(withFrame),
224
+ uiDoSchema.extend(withFrame),
187
225
  z.object({
188
226
  type: z.literal('batch'),
189
227
  actions: z.array(batchItemSchema).min(1),
@@ -216,6 +254,7 @@ const ALL_ACTIONS = [
216
254
  'ui_snapshot',
217
255
  'wait_for',
218
256
  'ui_act',
257
+ 'ui_do',
219
258
  'batch',
220
259
  ];
221
260
  /** Observations: they change nothing on the desktop. */
@@ -235,6 +274,7 @@ const DESTRUCTIVE_ACTIONS = new Set([
235
274
  'key',
236
275
  'scroll',
237
276
  'ui_act',
277
+ 'ui_do',
238
278
  ]);
239
279
  /** Observations that show the model what is on the screen, on their own. */
240
280
  const SCREEN_OBSERVATIONS = new Set([
@@ -300,6 +340,7 @@ function requiredCapability(type) {
300
340
  case 'ui_snapshot':
301
341
  case 'wait_for':
302
342
  case 'ui_act':
343
+ case 'ui_do':
303
344
  return 'uiTree';
304
345
  default:
305
346
  return null;
@@ -317,7 +358,7 @@ function windowCaptureAvailable(host, caps) {
317
358
  typeof host.captureWindow === 'function' &&
318
359
  typeof host.executeWindow === 'function');
319
360
  }
320
- function availableActions(host, caps) {
361
+ function availableActions(host, caps, advisor) {
321
362
  const displayScreenshot = hostActionAvailable(caps, 'screenshot');
322
363
  const window = windowCaptureAvailable(host, caps);
323
364
  const screenshot = displayScreenshot || window;
@@ -350,6 +391,8 @@ function availableActions(host, caps) {
350
391
  case 'wait_for':
351
392
  case 'ui_act':
352
393
  return uiTree;
394
+ case 'ui_do':
395
+ return uiTree && advisor?.acts === true;
353
396
  case 'batch':
354
397
  return false;
355
398
  default:
@@ -383,8 +426,8 @@ function unavailableHostActions(host, caps) {
383
426
  // ---------------------------------------------------------------------------
384
427
  // Model-facing description and schema
385
428
  // ---------------------------------------------------------------------------
386
- function buildDescription(host, caps, settings) {
387
- const available = availableActions(host, caps);
429
+ function buildDescription(host, caps, settings, advisor) {
430
+ const available = availableActions(host, caps, advisor);
388
431
  const unavailable = unavailableHostActions(host, caps);
389
432
  const intro = hostActionAvailable(caps, 'screenshot')
390
433
  ? `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`
@@ -413,6 +456,8 @@ function buildDescription(host, caps, settings) {
413
456
  available.includes('key') &&
414
457
  available.includes('type_text'))
415
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.');
416
461
  if (available.includes('list_windows'))
417
462
  lines.push('list_windows names the open windows with their ids; focus_window brings one to the front.');
418
463
  if (windowCaptureAvailable(host, caps))
@@ -487,6 +532,7 @@ const ACTION_REQUIREMENTS = {
487
532
  ui_snapshot: 'ui_snapshot takes an optional window_id, root (a ref to open) and query (text to find)',
488
533
  wait_for: 'wait_for needs until and match; role, window_id and timeout_ms are optional',
489
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',
490
536
  batch: 'batch needs actions',
491
537
  };
492
538
  function actionRequirement(action, host, caps) {
@@ -494,10 +540,10 @@ function actionRequirement(action, host, caps) {
494
540
  ? `screenshot ${hostActionAvailable(caps, 'screenshot') ? 'takes an optional' : 'requires'} window_id to capture pixels for that window`
495
541
  : ACTION_REQUIREMENTS[action];
496
542
  }
497
- function hostModelSchema(host, caps, settings) {
543
+ function hostModelSchema(host, caps, settings, advisor) {
498
544
  // An unavailable host remains a diagnostic tool. Avoid invalid empty enums
499
545
  // on provider wires; every execution is still refused before host access.
500
- const offered = availableActions(host, caps);
546
+ const offered = availableActions(host, caps, advisor);
501
547
  const actions = offered.length > 0 ? offered : ALL_ACTIONS.filter((a) => a !== 'batch');
502
548
  const items = actions.filter((action) => !OUTSIDE_BATCH.has(action));
503
549
  const buttons = new Set();
@@ -613,6 +659,28 @@ function hostModelSchema(host, caps, settings) {
613
659
  type: 'string',
614
660
  description: 'The text set_value puts in the control.',
615
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
+ }
616
684
  }
617
685
  if (!forItems && present.includes('zoom'))
618
686
  fields.region = {
@@ -719,6 +787,28 @@ function uiActLabel(input, describe) {
719
787
  return `Scroll to ${target}`;
720
788
  }
721
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
+ }
722
812
  /**
723
813
  * Human activity text for one action; the raw input remains the model-facing
724
814
  * record. `describe` names a ui_act ref's control when the tool knows it.
@@ -759,6 +849,8 @@ function itemLabel(input, describe) {
759
849
  return `Wait until ${input.until.replace('_', ' ')} ${quotedText(input.match)}`;
760
850
  case 'ui_act':
761
851
  return uiActLabel(input, describe);
852
+ case 'ui_do':
853
+ return uiDoLabel(input);
762
854
  case 'batch':
763
855
  return batchLabel(input.actions, describe);
764
856
  }
@@ -973,13 +1065,16 @@ export function createComputerUseTool(host, options = {}) {
973
1065
  }
974
1066
  : host.capabilities;
975
1067
  const frames = new ScreenshotFrames();
976
- const available = new Set(availableActions(host, caps));
1068
+ const available = new Set(availableActions(host, caps, options.advisor));
1069
+ const advisor = options.advisor;
977
1070
  // The controls of the latest ui_snapshot, by the ref the model was shown.
978
1071
  // A ref names a control, not one reading of it: the same host ref keeps the
979
1072
  // same number in every snapshot, and a number is never reused for another
980
1073
  // control. Whether the control is still there is the host's answer.
981
1074
  const uiRefs = new Map();
982
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();
983
1078
  let uiSnapshots = 0;
984
1079
  let uiRefCount = 0;
985
1080
  let uiSnapshotWindowId;
@@ -993,6 +1088,31 @@ export function createComputerUseTool(host, options = {}) {
993
1088
  return entry ? `${uiElementText(entry.element)} (${ref})` : undefined;
994
1089
  };
995
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
+ };
996
1116
  const refusal = (type) => {
997
1117
  if (available.has(type))
998
1118
  return null;
@@ -1242,6 +1362,14 @@ export function createComputerUseTool(host, options = {}) {
1242
1362
  if (item.action === 'set_value' && item.value === undefined)
1243
1363
  throw new StepFailure('set_value needs value, the text to put in the control');
1244
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
+ }
1245
1373
  let outcome;
1246
1374
  try {
1247
1375
  outcome = await host.uiAct(entry.hostRef, item.action, item.value, callSignal);
@@ -1260,6 +1388,56 @@ export function createComputerUseTool(host, options = {}) {
1260
1388
  return deliveryOutcome(outcome);
1261
1389
  });
1262
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
+ }
1263
1441
  }
1264
1442
  };
1265
1443
  /** Read one window's controls, number the ones the model can act on, and show them. */
@@ -1344,6 +1522,11 @@ export function createComputerUseTool(host, options = {}) {
1344
1522
  }
1345
1523
  }
1346
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
+ }
1347
1530
  if (uiSnapshotWindowId !== undefined)
1348
1531
  treeUsable.set(uiSnapshotWindowId, refs.size > 0);
1349
1532
  uiSnapshotFrame = frames.latest();
@@ -1815,7 +1998,7 @@ export function createComputerUseTool(host, options = {}) {
1815
1998
  const outcome = typeof ran === 'string' ? { note: ran } : ran;
1816
1999
  if (step.item.type === 'focus_window' && windowCaptureAvailable(host, caps))
1817
2000
  postWindowId = step.item.window_id;
1818
- else if (step.item.type === 'ui_act' &&
2001
+ else if ((step.item.type === 'ui_act' || step.item.type === 'ui_do') &&
1819
2002
  uiSnapshotWindowId &&
1820
2003
  !explicitDisplayFrame &&
1821
2004
  windowCaptureAvailable(host, caps))
@@ -1869,7 +2052,7 @@ export function createComputerUseTool(host, options = {}) {
1869
2052
  let shotError;
1870
2053
  if (looks) {
1871
2054
  try {
1872
- await sleep(settings.settleMs, signal);
2055
+ await settleAfterActions(host, postWindowId, settings.settleMs, signal);
1873
2056
  shot = await capture(postWindowId);
1874
2057
  }
1875
2058
  catch (error) {
@@ -1964,9 +2147,9 @@ export function createComputerUseTool(host, options = {}) {
1964
2147
  const capturesScreen = (input) => observes && capturesScreenInput(input, settings.screenshotAfterActions);
1965
2148
  const tool = defineTool({
1966
2149
  name: COMPUTER_USE_TOOL_NAME,
1967
- description: buildDescription(host, caps, settings),
2150
+ description: buildDescription(host, caps, settings, options.advisor),
1968
2151
  inputSchema: actionSchema,
1969
- modelInputSchema: hostModelSchema(host, caps, settings),
2152
+ modelInputSchema: hostModelSchema(host, caps, settings, options.advisor),
1970
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.`,
1971
2154
  category: 'custom',
1972
2155
  permissions: [],