@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.
@@ -91,11 +91,72 @@ const MAX_UI_SNAPSHOT_CHARS = 14_000
91
91
  /** The refs of earlier snapshots the tool still resolves for the host. */
92
92
  const MAX_HELD_UI_REFS = 4_000
93
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
+
94
148
  /**
95
149
  * How `createComputerUseTool` sizes screenshots, paces actions and bounds a
96
150
  * batch. Every field is optional.
97
151
  */
98
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
99
160
  /**
100
161
  * What every screenshot and zoom image is fitted to before the model sees
101
162
  * it. Default {@link STANDARD_SCREENSHOT_LIMITS}, which every current vision
@@ -270,6 +331,16 @@ const uiActSchema = z.object({
270
331
  ref: z.string().min(1),
271
332
  action: z.enum(UI_ACTIONS),
272
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(),
273
344
  })
274
345
 
275
346
  /** What a batch may carry: everything but the image-returning actions and another batch. */
@@ -285,6 +356,7 @@ const batchItemSchema = z.discriminatedUnion('type', [
285
356
  listWindowsSchema,
286
357
  focusWindowSchema,
287
358
  uiActSchema,
359
+ uiDoSchema,
288
360
  ])
289
361
 
290
362
  const withFrame = { screenshot_id: screenshotIdSchema }
@@ -308,6 +380,7 @@ const actionSchema = z.discriminatedUnion('type', [
308
380
  uiSnapshotSchema,
309
381
  waitForSchema,
310
382
  uiActSchema.extend(withFrame),
383
+ uiDoSchema.extend(withFrame),
311
384
  z.object({
312
385
  type: z.literal('batch'),
313
386
  actions: z.array(batchItemSchema).min(1),
@@ -355,6 +428,7 @@ const ALL_ACTIONS: readonly ToolActionType[] = [
355
428
  'ui_snapshot',
356
429
  'wait_for',
357
430
  'ui_act',
431
+ 'ui_do',
358
432
  'batch',
359
433
  ]
360
434
 
@@ -376,6 +450,7 @@ const DESTRUCTIVE_ACTIONS = new Set<string>([
376
450
  'key',
377
451
  'scroll',
378
452
  'ui_act',
453
+ 'ui_do',
379
454
  ])
380
455
 
381
456
  /** Observations that show the model what is on the screen, on their own. */
@@ -454,6 +529,7 @@ function requiredCapability(type: string): keyof ComputerUseCapabilities | null
454
529
  case 'ui_snapshot':
455
530
  case 'wait_for':
456
531
  case 'ui_act':
532
+ case 'ui_do':
457
533
  return 'uiTree'
458
534
  default:
459
535
  return null
@@ -481,7 +557,11 @@ function windowCaptureAvailable(host: ComputerUseHost, caps: ComputerUseCapabili
481
557
  )
482
558
  }
483
559
 
484
- function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities): ToolActionType[] {
560
+ function availableActions(
561
+ host: ComputerUseHost,
562
+ caps: ComputerUseCapabilities,
563
+ advisor?: ComputerUseAdvisor,
564
+ ): ToolActionType[] {
485
565
  const displayScreenshot = hostActionAvailable(caps, 'screenshot')
486
566
  const window = windowCaptureAvailable(host, caps)
487
567
  const screenshot = displayScreenshot || window
@@ -516,6 +596,8 @@ function availableActions(host: ComputerUseHost, caps: ComputerUseCapabilities):
516
596
  case 'wait_for':
517
597
  case 'ui_act':
518
598
  return uiTree
599
+ case 'ui_do':
600
+ return uiTree && advisor?.acts === true
519
601
  case 'batch':
520
602
  return false
521
603
  default:
@@ -550,8 +632,9 @@ function buildDescription(
550
632
  host: ComputerUseHost,
551
633
  caps: ComputerUseCapabilities,
552
634
  settings: ResolvedSettings,
635
+ advisor?: ComputerUseAdvisor,
553
636
  ): string {
554
- const available = availableActions(host, caps)
637
+ const available = availableActions(host, caps, advisor)
555
638
  const unavailable = unavailableHostActions(host, caps)
556
639
  const intro = hostActionAvailable(caps, 'screenshot')
557
640
  ? `Controls the user's desktop on a ${caps.displayServer} host: screenshots, mouse and keyboard, for GUI tasks.`
@@ -597,6 +680,10 @@ function buildDescription(
597
680
  lines.push(
598
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.',
599
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
+ )
600
687
  if (available.includes('list_windows'))
601
688
  lines.push(
602
689
  'list_windows names the open windows with their ids; focus_window brings one to the front.',
@@ -695,6 +782,7 @@ const ACTION_REQUIREMENTS: Readonly<Record<ToolActionType, string>> = {
695
782
  'ui_snapshot takes an optional window_id, root (a ref to open) and query (text to find)',
696
783
  wait_for: 'wait_for needs until and match; role, window_id and timeout_ms are optional',
697
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',
698
786
  batch: 'batch needs actions',
699
787
  }
700
788
 
@@ -712,10 +800,11 @@ function hostModelSchema(
712
800
  host: ComputerUseHost,
713
801
  caps: ComputerUseCapabilities,
714
802
  settings: ResolvedSettings,
803
+ advisor?: ComputerUseAdvisor,
715
804
  ): Record<string, unknown> {
716
805
  // An unavailable host remains a diagnostic tool. Avoid invalid empty enums
717
806
  // on provider wires; every execution is still refused before host access.
718
- const offered = availableActions(host, caps)
807
+ const offered = availableActions(host, caps, advisor)
719
808
  const actions = offered.length > 0 ? offered : ALL_ACTIONS.filter((a) => a !== 'batch')
720
809
  const items = actions.filter((action) => !OUTSIDE_BATCH.has(action))
721
810
  const buttons = new Set<string>()
@@ -841,6 +930,30 @@ function hostModelSchema(
841
930
  type: 'string',
842
931
  description: 'The text set_value puts in the control.',
843
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
+ }
844
957
  }
845
958
  if (!forItems && present.includes('zoom'))
846
959
  fields.region = {
@@ -963,6 +1076,33 @@ function uiActLabel(
963
1076
  }
964
1077
  }
965
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
+
966
1106
  /**
967
1107
  * Human activity text for one action; the raw input remains the model-facing
968
1108
  * record. `describe` names a ui_act ref's control when the tool knows it.
@@ -1008,6 +1148,8 @@ function itemLabel(
1008
1148
  return `Wait until ${input.until.replace('_', ' ')} ${quotedText(input.match)}`
1009
1149
  case 'ui_act':
1010
1150
  return uiActLabel(input, describe)
1151
+ case 'ui_do':
1152
+ return uiDoLabel(input)
1011
1153
  case 'batch':
1012
1154
  return batchLabel(input.actions, describe)
1013
1155
  }
@@ -1349,7 +1491,8 @@ export function createComputerUseTool(
1349
1491
  }
1350
1492
  : host.capabilities
1351
1493
  const frames = new ScreenshotFrames()
1352
- const available = new Set<string>(availableActions(host, caps))
1494
+ const available = new Set<string>(availableActions(host, caps, options.advisor))
1495
+ const advisor = options.advisor
1353
1496
 
1354
1497
  // The controls of the latest ui_snapshot, by the ref the model was shown.
1355
1498
  // A ref names a control, not one reading of it: the same host ref keeps the
@@ -1357,6 +1500,8 @@ export function createComputerUseTool(
1357
1500
  // control. Whether the control is still there is the host's answer.
1358
1501
  const uiRefs = new Map<string, UiRef>()
1359
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>>()
1360
1505
  let uiSnapshots = 0
1361
1506
  let uiRefCount = 0
1362
1507
  let uiSnapshotWindowId: string | undefined
@@ -1371,6 +1516,30 @@ export function createComputerUseTool(
1371
1516
  }
1372
1517
  const label = (input: BatchItem | ActionInput): string => itemLabel(input, describeUiRef)
1373
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
+
1374
1543
  const refusal = (type: string): string | null => {
1375
1544
  if (available.has(type)) return null
1376
1545
  const required = requiredCapability(type)
@@ -1705,6 +1874,14 @@ export function createComputerUseTool(
1705
1874
  if (item.action === 'set_value' && item.value === undefined)
1706
1875
  throw new StepFailure('set_value needs value, the text to put in the control')
1707
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
+ }
1708
1885
  let outcome: UiActResult
1709
1886
  try {
1710
1887
  outcome = await (host.uiAct as NonNullable<ComputerUseHost['uiAct']>)(
@@ -1728,6 +1905,62 @@ export function createComputerUseTool(
1728
1905
  return deliveryOutcome(outcome)
1729
1906
  })
1730
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
+ }
1731
1964
  }
1732
1965
  }
1733
1966
 
@@ -1822,6 +2055,11 @@ export function createComputerUseTool(
1822
2055
  }
1823
2056
  }
1824
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
+ }
1825
2063
  if (uiSnapshotWindowId !== undefined) treeUsable.set(uiSnapshotWindowId, refs.size > 0)
1826
2064
  uiSnapshotFrame = frames.latest()
1827
2065
  const window =
@@ -2347,7 +2585,7 @@ export function createComputerUseTool(
2347
2585
  if (step.item.type === 'focus_window' && windowCaptureAvailable(host, caps))
2348
2586
  postWindowId = step.item.window_id
2349
2587
  else if (
2350
- step.item.type === 'ui_act' &&
2588
+ (step.item.type === 'ui_act' || step.item.type === 'ui_do') &&
2351
2589
  uiSnapshotWindowId &&
2352
2590
  !explicitDisplayFrame &&
2353
2591
  windowCaptureAvailable(host, caps)
@@ -2504,9 +2742,9 @@ export function createComputerUseTool(
2504
2742
 
2505
2743
  const tool = defineTool({
2506
2744
  name: COMPUTER_USE_TOOL_NAME,
2507
- description: buildDescription(host, caps, settings),
2745
+ description: buildDescription(host, caps, settings, options.advisor),
2508
2746
  inputSchema: actionSchema,
2509
- modelInputSchema: hostModelSchema(host, caps, settings),
2747
+ modelInputSchema: hostModelSchema(host, caps, settings, options.advisor),
2510
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.`,
2511
2749
  category: 'custom',
2512
2750
  permissions: [],