stuntdouble 0.1.3 → 0.2.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.
Files changed (46) hide show
  1. package/README.md +31 -6
  2. package/dist/chunk-BFI6P5DO.js +278 -0
  3. package/dist/chunk-BFI6P5DO.js.map +1 -0
  4. package/dist/chunk-DFXASIGV.js +431 -0
  5. package/dist/chunk-DFXASIGV.js.map +1 -0
  6. package/dist/{chunk-7IC2G3Z3.js → chunk-F3R3QL7V.js} +19 -252
  7. package/dist/chunk-F3R3QL7V.js.map +1 -0
  8. package/dist/{mcp-MOXTYXZU.js → chunk-FMZTT2MC.js} +72 -1160
  9. package/dist/chunk-FMZTT2MC.js.map +1 -0
  10. package/dist/chunk-MSV5OLSY.js +117 -0
  11. package/dist/chunk-MSV5OLSY.js.map +1 -0
  12. package/dist/chunk-P3CI4FTE.js +469 -0
  13. package/dist/chunk-P3CI4FTE.js.map +1 -0
  14. package/dist/{chunk-JXTUCMO2.js → chunk-QDA5FTUH.js} +5 -3
  15. package/dist/{chunk-JXTUCMO2.js.map → chunk-QDA5FTUH.js.map} +1 -1
  16. package/dist/chunk-XHSDRB3X.js +996 -0
  17. package/dist/chunk-XHSDRB3X.js.map +1 -0
  18. package/dist/cli.js +179 -22
  19. package/dist/cli.js.map +1 -1
  20. package/dist/cloud-DLLNKYND.js +34 -0
  21. package/dist/credentials-QHESDY4K.js +27 -0
  22. package/dist/doctor-ZAYOOCPT.js +13 -0
  23. package/dist/doctor-ZAYOOCPT.js.map +1 -0
  24. package/dist/files-UBPF7PKW.js +45 -0
  25. package/dist/files-UBPF7PKW.js.map +1 -0
  26. package/dist/mcp-HFIAQ6HP.js +133 -0
  27. package/dist/mcp-HFIAQ6HP.js.map +1 -0
  28. package/dist/open-browser-CTOGH5HQ.js +17 -0
  29. package/dist/open-browser-CTOGH5HQ.js.map +1 -0
  30. package/dist/run-MPIAR3LQ.js +20 -0
  31. package/dist/run-MPIAR3LQ.js.map +1 -0
  32. package/dist/runner-E45EJ76B.js +17 -0
  33. package/dist/runner-E45EJ76B.js.map +1 -0
  34. package/dist/studio-FBC66BER.js +360 -0
  35. package/dist/studio-FBC66BER.js.map +1 -0
  36. package/dist/studio.js +1621 -0
  37. package/dist/ui-server-C7CFJWRD.js +12 -0
  38. package/dist/ui-server-C7CFJWRD.js.map +1 -0
  39. package/dist/ui.html +100 -56
  40. package/package.json +2 -2
  41. package/dist/chunk-7IC2G3Z3.js.map +0 -1
  42. package/dist/doctor-7PW2QD3M.js +0 -12
  43. package/dist/mcp-MOXTYXZU.js.map +0 -1
  44. package/dist/ui-server-ZUJ5WJ7Q.js +0 -9
  45. /package/dist/{doctor-7PW2QD3M.js.map → cloud-DLLNKYND.js.map} +0 -0
  46. /package/dist/{ui-server-ZUJ5WJ7Q.js.map → credentials-QHESDY4K.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../browser-toolset/src/safety.ts","../../browser-toolset/src/keys.ts","../../browser-toolset/src/page-script.ts","../../browser-toolset/src/executor.ts","../../browser-toolset/src/direct-tools.ts"],"sourcesContent":["// A promise about an agent's sessions, enforced rather than asked for: \"they\n// never pay or sign up for real: no payments go through and no real\n// registration details are ever entered\". Benchmark sessions on third-party\n// sites (such as the Stunt Double Index, https://index.stuntdouble.io) make\n// that promise. A prompt line is a request the model under test may ignore,\n// and ignoring it is exactly the behaviour a benchmark may be measuring, so\n// the tools refuse instead.\n//\n// Everything here is off unless a caller opts in (`BrowserSafetyOptions`):\n// agents testing their own product fill forms with test data on purpose, and\n// must keep doing so.\n//\n// No runtime imports, so it runs under `node --test` and inside the page script.\n\nexport type BrowserSafetyOptions = {\n /**\n * Refuse typing into a password or payment card field (including a card\n * field inside a payment provider's iframe), and refuse any typed or filled\n * value that is a Luhn-valid card number, whatever field it is headed for.\n */\n refuseSensitiveInput: boolean;\n /**\n * Refuse a click, or Enter in a field, that would submit a form which pays,\n * subscribes or creates an account. A link that only navigates (a \"Sign up\"\n * link to the sign-up page) is always allowed: reaching the form is the task.\n */\n blockSubmit?: boolean;\n};\n\n/**\n * Every guard on: what benchmark sessions on sites that are not yours should\n * run with. Named for the Stunt Double Index (https://index.stuntdouble.io),\n * where every session runs with it.\n */\nexport const INDEX_SESSION_SAFETY: BrowserSafetyOptions = {\n refuseSensitiveInput: true,\n blockSubmit: true,\n};\n\n/** The text an agent gets back when a guard refuses: a result, not an error. */\nexport const SAFETY_REFUSAL_SUFFIX =\n 'This session is not allowed to pay, subscribe, create an account or enter passwords or card details. Do not try another way round: report what you observed and what the next step would be, then carry on or call done.';\n\n/**\n * Payment providers' own hosts, as suffix-matched domains (an entry covers\n * itself and its subdomains). Stripe is listed by host rather than as\n * `stripe.com` so its marketing site and docs stay reachable, which matters\n * when stripe.com is itself the site under test.\n */\nexport const PAYMENT_PROVIDER_DOMAINS: readonly string[] = [\n 'js.stripe.com',\n 'api.stripe.com',\n 'checkout.stripe.com',\n 'm.stripe.network',\n 'hooks.stripe.com',\n 'paypal.com',\n 'paypalobjects.com',\n 'adyen.com',\n 'adyenpayments.com',\n 'braintreegateway.com',\n 'braintree-api.com',\n 'pay.google.com',\n 'payments.google.com',\n 'applepay.cdn-apple.com',\n 'klarna.com',\n 'klarnaservices.com',\n 'afterpay.com',\n 'checkout.com',\n 'squareup.com',\n 'squarecdn.com',\n 'paddle.com',\n 'lemonsqueezy.com',\n];\n\n/** Whether `host` is `domain` or one of its subdomains. */\nexport function hostMatchesDomain(host: string, domain: string): boolean {\n const h = host.toLowerCase().replace(/\\.$/, '');\n const d = domain.toLowerCase().replace(/^\\*\\./, '');\n return h === d || h.endsWith(`.${d}`);\n}\n\nexport function isPaymentProviderHost(\n host: string,\n domains: readonly string[] = PAYMENT_PROVIDER_DOMAINS\n): boolean {\n return domains.some((d) => hostMatchesDomain(host, d));\n}\n\n/** The last two labels of a host: crude, but every provider above is a plain `.com`/`.network`. */\nfunction baseDomain(host: string): string {\n return host.toLowerCase().replace(/\\.$/, '').split('.').slice(-2).join('.');\n}\n\n/**\n * The payment hosts to block for a session visiting `targetHost`. A provider's\n * own site is exempt, or visiting paypal.com would block the page under test.\n */\nexport function paymentBlocklistFor(targetHost: string): string[] {\n const target = baseDomain(targetHost);\n return PAYMENT_PROVIDER_DOMAINS.filter((d) => baseDomain(d) !== target);\n}\n\n/** The Luhn checksum every payment card number carries. */\nexport function luhnValid(digits: string): boolean {\n let sum = 0;\n let double = false;\n for (let i = digits.length - 1; i >= 0; i--) {\n let n = digits.charCodeAt(i) - 48;\n if (n < 0 || n > 9) return false;\n if (double) {\n n *= 2;\n if (n > 9) n -= 9;\n }\n sum += n;\n double = !double;\n }\n return sum > 0 && sum % 10 === 0;\n}\n\n/**\n * Whether a value contains something that reads as a card number: 13 to 19\n * digits, optionally grouped by spaces or dashes, passing Luhn. The Luhn check\n * is what keeps phone numbers, order ids and postcodes from tripping it.\n */\nexport function containsCardNumber(value: string): boolean {\n const runs = value.match(/\\d(?:[ -]?\\d){12,18}/g) ?? [];\n return runs.some((run) => {\n const digits = run.replace(/[ -]/g, '');\n return digits.length >= 13 && digits.length <= 19 && luhnValid(digits);\n });\n}\n\n/**\n * Field names, ids and labels that mark a payment card field. Kept loose on\n * separators (`card_number`, `cardNumber`, `card-number`) and tight on words,\n * so \"gift card\" and \"expires in\" do not trip it.\n */\nexport const CARD_FIELD_PATTERN =\n /card.?(?:number|num|no\\b)|cc.?(?:num|number|no\\b|exp|csc|cvv|cvc)|credit.?card|debit.?card|\\bcvc\\b|\\bcvv2?\\b|\\bcsc\\b|security.?code|card.?(?:verification|code)|expir(?:y|ation)|exp.?(?:date|month|year)|\\bmm.?\\/?.?yy\\b|card.?holder|name.?on.?card|\\bpan\\b/i;\n\n/**\n * Accessible names of buttons that take money. Anchored at the start of the\n * label and checked on any non-link button, since checkouts built without a\n * `<form>` still pay on a plain button; the lookahead keeps billing toggles\n * (\"Pay monthly\") and provider choices (\"Pay with PayPal\") clickable.\n */\nexport const PAYMENT_ACTION_PATTERN =\n /^(?:pay(?:\\s+now)?\\b(?!\\s+(?:monthly|annually|yearly|per|by|with|later|in|over|as))|purchase\\b|complete (?:my |your |the )?(?:purchase|order|payment|booking)\\b|place (?:my |your |the )?order\\b|confirm (?:and|&) pay\\b|confirm (?:order|purchase|payment|booking)\\b|submit (?:order|payment)\\b|book and pay\\b)/i;\n\n/**\n * Accessible names of controls that commit money or an identity. Checked only\n * on controls that submit a form, never on links, so the \"Sign up\" link that\n * leads to the sign-up page still works.\n */\nexport const COMMIT_ACTION_PATTERN =\n /\\b(?:pay|purchase|place (?:my |your |the )?order|complete (?:my |your |the )?(?:order|purchase|booking)|subscribe|buy now|sign ?up|create (?:an |my |your )?account|register|join now|start (?:my |your )?(?:free )?trial|confirm (?:order|booking)|submit order)\\b/i;\n\n/**\n * The narrower set for a button outside any form on a page showing a password\n * or card field. \"Sign up\" and \"Subscribe\" are left out on purpose: outside a\n * form they usually open the sign-up form, which is what the task asks for.\n */\nexport const FORMLESS_COMMIT_PATTERN =\n /^(?:create (?:an |my |your )?account|complete (?:my |your )?(?:registration|sign ?up)|start (?:my |your )?(?:free )?trial)\\b/i;\n\n/** What the page script reports about an element the agent is about to act on. */\nexport type ElementFacts = {\n /** Lower-case tag name, e.g. \"input\", \"button\", \"a\", \"iframe\". */\n tag: string;\n type: string;\n autocomplete: string;\n name: string;\n id: string;\n ariaLabel: string;\n placeholder: string;\n /** The accessible name as `read_page` would print it. */\n label: string;\n role: string;\n /** A link with a real destination (not `#` or `javascript:`). */\n isNavigatingLink: boolean;\n /** A control that submits its form when clicked. */\n isSubmit: boolean;\n /** A button-like control that is not a link. */\n isButton: boolean;\n /** For an iframe, its `src`; the field is inside it when focus is. */\n iframeSrc: string;\n iframeTitle: string;\n inForm: boolean;\n /**\n * The enclosing form holds a password field, a card field or a payment\n * provider's iframe; outside any form, the page shows one.\n */\n formSensitive: boolean;\n /** The label of the form's first submit control, for Enter-to-submit. */\n formSubmitLabel: string;\n};\n\nfunction hostOf(url: string): string {\n try {\n return new URL(url).hostname;\n } catch {\n return '';\n }\n}\n\n/** Why typing into this element is refused, or null. */\nexport function sensitiveFieldReason(f: ElementFacts): string | null {\n if (f.type === 'password' || /(?:current|new)-password/.test(f.autocomplete)) {\n return 'That is a password field.';\n }\n if (/\\bcc-/.test(f.autocomplete)) return 'That is a payment card field.';\n if (f.tag === 'iframe') {\n if (isPaymentProviderHost(hostOf(f.iframeSrc)))\n return 'That is a payment provider’s card field.';\n if (CARD_FIELD_PATTERN.test(`${f.iframeTitle} ${f.name}`))\n return 'That is a payment card field.';\n }\n const text = [f.name, f.id, f.ariaLabel, f.placeholder, f.label].join(' ');\n if (f.tag !== 'a' && f.tag !== 'button' && CARD_FIELD_PATTERN.test(text)) {\n return 'That is a payment card field.';\n }\n return null;\n}\n\n/** Why clicking this element is refused, or null. */\nexport function submitReason(f: ElementFacts): string | null {\n if (f.isNavigatingLink) return null;\n if (f.isSubmit && f.formSensitive) {\n return 'That would submit a form holding a password or payment card field.';\n }\n if (f.isSubmit && COMMIT_ACTION_PATTERN.test(f.label)) {\n return `That would submit \"${f.label.slice(0, 60)}\".`;\n }\n // A single-page sign-up with no <form>: the button is the submit.\n if (f.isButton && !f.inForm && f.formSensitive && FORMLESS_COMMIT_PATTERN.test(f.label.trim())) {\n return `That would press \"${f.label.slice(0, 60)}\".`;\n }\n if (f.isButton && PAYMENT_ACTION_PATTERN.test(f.label.trim())) {\n return `That would press \"${f.label.slice(0, 60)}\".`;\n }\n return null;\n}\n\n/** Why pressing Enter in this (focused) element is refused, or null. */\nexport function enterSubmitReason(f: ElementFacts): string | null {\n if (!f.inForm || f.tag === 'textarea') return null;\n if (f.formSensitive) return 'Enter would submit a form holding a password or payment card field.';\n if (COMMIT_ACTION_PATTERN.test(f.formSubmitLabel)) {\n return `Enter would submit \"${f.formSubmitLabel.slice(0, 60)}\".`;\n }\n return null;\n}\n\nexport function refusalText(reason: string): string {\n return `Refused: ${reason} ${SAFETY_REFUSAL_SUFFIX}`;\n}\n","// Claude's browser toolset spells keys the xdotool way (`ctrl+a`, `Return`,\n// `Page_Down`, space-separated sequences); `DriverPage.keyPress` takes\n// Playwright's (`Control+A`, `Enter`, `PageDown`). Pure, so it is unit tested.\n\nconst ALIASES: Record<string, string> = {\n ctrl: 'Control',\n control: 'Control',\n cmd: 'Meta',\n command: 'Meta',\n meta: 'Meta',\n super: 'Meta',\n win: 'Meta',\n alt: 'Alt',\n option: 'Alt',\n shift: 'Shift',\n enter: 'Enter',\n return: 'Enter',\n kp_enter: 'Enter',\n esc: 'Escape',\n escape: 'Escape',\n tab: 'Tab',\n space: ' ',\n backspace: 'Backspace',\n delete: 'Delete',\n del: 'Delete',\n insert: 'Insert',\n home: 'Home',\n end: 'End',\n page_up: 'PageUp',\n pageup: 'PageUp',\n prior: 'PageUp',\n page_down: 'PageDown',\n pagedown: 'PageDown',\n next: 'PageDown',\n up: 'ArrowUp',\n down: 'ArrowDown',\n left: 'ArrowLeft',\n right: 'ArrowRight',\n arrowup: 'ArrowUp',\n arrowdown: 'ArrowDown',\n arrowleft: 'ArrowLeft',\n arrowright: 'ArrowRight',\n capslock: 'CapsLock',\n caps_lock: 'CapsLock',\n};\n\nfunction normaliseKey(raw: string): string {\n const key = raw.trim();\n const alias = ALIASES[key.toLowerCase()];\n if (alias) return alias;\n if (/^f([1-9]|1[0-2])$/i.test(key)) return key.toUpperCase();\n // A single character is typed as itself; Playwright wants it upper-cased\n // inside a chord (`Control+A`) and as-is on its own.\n return key;\n}\n\n/** One chord (`ctrl+shift+t`) in Playwright's spelling (`Control+Shift+T`). */\nexport function toPlaywrightChord(chord: string): string {\n const parts = chord.split('+').filter((p) => p.length > 0);\n // A literal plus (`ctrl++` or `+`) survives the split as an empty tail.\n if (chord.endsWith('+') && chord.length > 1 && chord[chord.length - 2] === '+') parts.push('+');\n if (parts.length === 0) return chord === '+' ? '+' : chord;\n const keys = parts.map(normaliseKey);\n if (keys.length > 1) {\n const last = keys[keys.length - 1];\n if (last.length === 1) keys[keys.length - 1] = last.toUpperCase();\n }\n return keys.join('+');\n}\n\n/**\n * `text` as the toolset sends it: one key, one chord, or a space-separated\n * sequence (`Backspace Backspace`). A lone space is the space key.\n */\nexport function toPlaywrightKeySequence(text: string): string[] {\n if (text === ' ') return [' '];\n return text\n .split(/\\s+/)\n .filter((k) => k.length > 0)\n .map(toPlaywrightChord);\n}\n","// The in-page half of the browser toolset executor: one script, evaluated in\n// the page, that reads the accessibility-ish tree Claude's `read_page` and\n// `find` expect and resolves the `ref_N` handles those return.\n//\n// A `BrowserDriver` need not expose raw CDP, so `Accessibility.getFullAXTree`\n// is out of reach; this walks the DOM (including open shadow roots) instead and\n// tags each element it reports with a `data-sd-ref` attribute. A ref therefore survives\n// until the element leaves the DOM, which is the lifetime the toolset docs\n// promise (\"valid until the tab navigates or the DOM changes materially\").\n//\n// It is a string expression, never a function, because drivers serialise\n// functions with `toString()` and a bundler's helpers (esbuild's `__name`, for\n// one) do not exist in the page. `buildPageCall` is the only\n// way in, and it JSON-encodes its arguments, so nothing a model supplies is\n// ever spliced into the script as code.\n\nimport { CARD_FIELD_PATTERN, type ElementFacts, PAYMENT_PROVIDER_DOMAINS } from './safety.ts';\n\nexport type PageOp =\n | { op: 'read'; filter: 'visible' | 'interactive' | 'all'; depth: number; ref?: string }\n | { op: 'find'; query: string }\n | { op: 'text' }\n | { op: 'resolve'; ref: string; scroll: boolean }\n | { op: 'form'; ref: string; value: string | number | boolean }\n | { op: 'viewport' }\n | { op: 'inspect'; target: InspectTarget };\n\n/** An element to describe: a ref, whatever is at a point, or whatever has focus. */\nexport type InspectTarget =\n { type: 'ref'; ref: string } | { type: 'coordinate'; x: number; y: number } | { type: 'focused' };\n\nexport type ReadResult = { ok: true; text: string; truncated: boolean } | PageError;\nexport type ResolveResult = { ok: true; x: number; y: number; inViewport: boolean } | PageError;\nexport type FormResult = { ok: true; description: string } | PageError;\nexport type ViewportResult = { ok: true; width: number; height: number };\n/** `facts` is null when nothing is there (an empty point, nothing focused). */\nexport type InspectResult = { ok: true; facts: ElementFacts | null } | PageError;\nexport type PageError = { ok: false; error: string };\n\n/** Max characters `read_page` / `find` / `get_page_text` return (toolset limit). */\nexport const MAX_PAGE_TEXT_CHARS = 50_000;\n\n// The safety guards' vocabulary, spliced in as JSON: constants of ours, never\n// anything a model supplied.\nconst CARD_PATTERN_SOURCE = JSON.stringify(CARD_FIELD_PATTERN.source);\nconst PAYMENT_DOMAINS_JSON = JSON.stringify(PAYMENT_PROVIDER_DOMAINS);\n\nconst SCRIPT = String.raw`\n(function (args) {\n var REF_ATTR = 'data-sd-ref';\n var MAX = ${MAX_PAGE_TEXT_CHARS};\n var w = window;\n if (typeof w.__sdRefSeq !== 'number') w.__sdRefSeq = 0;\n\n function refOf(el) {\n var existing = el.getAttribute(REF_ATTR);\n if (existing) return existing;\n w.__sdRefSeq += 1;\n var ref = 'ref_' + w.__sdRefSeq;\n el.setAttribute(REF_ATTR, ref);\n return ref;\n }\n\n function findRef(ref, root) {\n root = root || document;\n var hit = root.querySelector('[' + REF_ATTR + '=\"' + ref + '\"]');\n if (hit) return hit;\n var all = root.querySelectorAll('*');\n for (var i = 0; i < all.length; i++) {\n if (all[i].shadowRoot) {\n var inner = findRef(ref, all[i].shadowRoot);\n if (inner) return inner;\n }\n }\n return null;\n }\n\n function isHidden(el) {\n if (el.getAttribute('aria-hidden') === 'true') return true;\n var style = w.getComputedStyle(el);\n if (style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse') return true;\n if (parseFloat(style.opacity) === 0) return true;\n var rect = el.getBoundingClientRect();\n return rect.width === 0 && rect.height === 0;\n }\n\n function inViewport(el) {\n var r = el.getBoundingClientRect();\n return r.bottom > 0 && r.right > 0 && r.top < w.innerHeight && r.left < w.innerWidth;\n }\n\n var INPUT_ROLES = {\n button: 'button', submit: 'button', reset: 'button', image: 'button',\n checkbox: 'checkbox', radio: 'radio', range: 'slider', search: 'searchbox',\n email: 'textbox', tel: 'textbox', url: 'textbox', text: 'textbox',\n password: 'textbox', number: 'spinbutton', date: 'textbox', file: 'button'\n };\n var TAG_ROLES = {\n A: 'link', BUTTON: 'button', SELECT: 'combobox', TEXTAREA: 'textbox',\n H1: 'heading', H2: 'heading', H3: 'heading', H4: 'heading', H5: 'heading', H6: 'heading',\n IMG: 'img', NAV: 'navigation', MAIN: 'main', HEADER: 'banner', FOOTER: 'contentinfo',\n FORM: 'form', DIALOG: 'dialog', TABLE: 'table', UL: 'list', OL: 'list', LI: 'listitem',\n SUMMARY: 'button', LABEL: 'label', OPTION: 'option', IFRAME: 'iframe', VIDEO: 'video'\n };\n var INTERACTIVE_ROLES = {\n button: 1, link: 1, checkbox: 1, radio: 1, textbox: 1, searchbox: 1, combobox: 1,\n slider: 1, spinbutton: 1, switch: 1, tab: 1, menuitem: 1, menuitemcheckbox: 1,\n menuitemradio: 1, option: 1, treeitem: 1\n };\n\n function roleOf(el) {\n var explicit = el.getAttribute('role');\n if (explicit) return explicit.split(' ')[0];\n if (el.tagName === 'INPUT') {\n var t = (el.getAttribute('type') || 'text').toLowerCase();\n if (t === 'hidden') return null;\n return INPUT_ROLES[t] || 'textbox';\n }\n if (el.tagName === 'A' && !el.hasAttribute('href')) return null;\n if (el.tagName === 'IMG' && !el.getAttribute('alt')) return null;\n if (el.isContentEditable && el.parentElement && !el.parentElement.isContentEditable) return 'textbox';\n return TAG_ROLES[el.tagName] || null;\n }\n\n function isInteractive(el, role) {\n if (role && INTERACTIVE_ROLES[role]) return true;\n if (el.hasAttribute('onclick')) return true;\n var tab = el.getAttribute('tabindex');\n return tab !== null && Number(tab) >= 0 && !!role;\n }\n\n function clean(s) {\n return (s || '').replace(/\\s+/g, ' ').trim();\n }\n\n // Roles whose own text is their name. Containers (banner, list, table, form)\n // take a name only from an explicit label: their text is their children's,\n // which the tree already lists, and repeating it drowns \\`find\\`.\n var TEXT_NAMED = { heading: 1, option: 1, label: 1, text: 1, generic: 1, listitem: 0 };\n\n function nameOf(el, role) {\n var label = el.getAttribute('aria-label');\n if (label) return clean(label);\n var by = el.getAttribute('aria-labelledby');\n if (by) {\n var parts = by.split(/\\s+/).map(function (id) {\n var n = document.getElementById(id);\n return n ? n.textContent : '';\n });\n var joined = clean(parts.join(' '));\n if (joined) return joined;\n }\n if (el.id) {\n var forLabel = document.querySelector('label[for=\"' + CSS.escape(el.id) + '\"]');\n if (forLabel) return clean(forLabel.textContent);\n }\n var alt = el.getAttribute('alt') || el.getAttribute('title');\n if (alt) return clean(alt);\n if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {\n return clean(el.getAttribute('placeholder') || '');\n }\n if (role && !INTERACTIVE_ROLES[role] && !TEXT_NAMED[role]) return '';\n return clean(el.innerText || el.textContent).slice(0, 100);\n }\n\n function valueOf(el) {\n if (el.tagName === 'INPUT') {\n var t = (el.getAttribute('type') || 'text').toLowerCase();\n if (t === 'checkbox' || t === 'radio') return el.checked ? 'checked' : 'unchecked';\n if (t === 'password') return el.value ? '••••' : '';\n return el.value ? clean(el.value).slice(0, 80) : '';\n }\n if (el.tagName === 'TEXTAREA') return el.value ? clean(el.value).slice(0, 80) : '';\n if (el.tagName === 'SELECT') {\n var opt = el.options[el.selectedIndex];\n return opt ? clean(opt.text) : '';\n }\n return '';\n }\n\n function hasOwnText(el) {\n for (var c = el.firstChild; c; c = c.nextSibling) {\n if (c.nodeType === 3 && c.nodeValue && c.nodeValue.trim()) return true;\n }\n return false;\n }\n\n function children(el) {\n var out = Array.prototype.slice.call(el.children);\n if (el.shadowRoot) out = out.concat(Array.prototype.slice.call(el.shadowRoot.children));\n return out;\n }\n\n function describe(el, role) {\n var name = nameOf(el, role);\n var line = role + (name ? ' \"' + name.replace(/\"/g, \"'\") + '\"' : '');\n var value = valueOf(el);\n if (value) line += ' value=\"' + value.replace(/\"/g, \"'\") + '\"';\n if (el.tagName === 'A' && el.getAttribute('href')) {\n var href = el.getAttribute('href');\n if (href.indexOf('javascript:') !== 0) line += ' href=\"' + href.slice(0, 200) + '\"';\n }\n var description = clean(el.getAttribute('aria-description'));\n if (description) line += ' description=\"' + description.slice(0, 200).replace(/\"/g, \"'\") + '\"';\n if (el.disabled) line += ' disabled';\n // Semantics WAO (@stunt-double/wao) inferred rather than the page's authors\n // wrote: \\`data-wao=\"role name\"\\` lists what it set.\n var inferred = clean(el.getAttribute('data-wao'));\n if (inferred) line += ' inferred=\"' + inferred.replace(/\"/g, \"'\") + '\"';\n return line + ' [' + refOf(el) + ']';\n }\n\n function read(filter, depth, rootRef) {\n var root = document.body;\n if (rootRef) {\n root = findRef(rootRef);\n if (!root) return { ok: false, error: rootRef + ' is stale or not found on the current page. Re-read the page to get fresh references.' };\n }\n var lines = [];\n var size = 0;\n var truncated = false;\n function walk(el, level, reported) {\n if (truncated || !el || el.nodeType !== 1) return;\n if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE' || el.tagName === 'NOSCRIPT' || el.tagName === 'TEMPLATE') return;\n if (isHidden(el)) return;\n var role = roleOf(el);\n var interactive = isInteractive(el, role);\n var include;\n if (filter === 'interactive') include = interactive;\n else include = !!role || interactive || (hasOwnText(el) && clean(el.innerText).length > 0);\n if (include && filter !== 'all' && !inViewport(el)) include = false;\n var next = reported;\n if (include) {\n var line = ' '.repeat(Math.min(reported, 20)) + describe(el, role || (interactive ? 'generic' : 'text'));\n if (size + line.length + 1 > ${MAX_PAGE_TEXT_CHARS}) { truncated = true; return; }\n lines.push(line);\n size += line.length + 1;\n next = reported + 1;\n if (!interactive && !role) return;\n }\n // Depth counts levels of the reported tree, not DOM nesting: a real\n // page nests its buttons far deeper than 15 elements.\n if (next >= depth) return;\n var kids = children(el);\n for (var i = 0; i < kids.length; i++) walk(kids[i], level + 1, next);\n }\n walk(root, 0, 0);\n var text = lines.join('\\n');\n if (!text) text = filter === 'all' ? 'The page has no readable elements.' : 'No elements in the viewport match. Scroll, or read with filter \"all\".';\n if (truncated) text += '\\n[truncated: the page tree exceeded ' + MAX + ' characters; pass a ref or a smaller depth]';\n return { ok: true, text: text, truncated: truncated };\n }\n\n function find(query) {\n var terms = clean(query).toLowerCase().split(' ').filter(function (t) { return t.length > 1; });\n if (!terms.length) return { ok: false, error: 'find needs a query.' };\n var scored = [];\n var all = document.body.querySelectorAll('*');\n for (var i = 0; i < all.length; i++) {\n var el = all[i];\n if (isHidden(el)) continue;\n var role = roleOf(el);\n var interactive = isInteractive(el, role);\n if (!role && !interactive) continue;\n var hay = (role + ' ' + nameOf(el, role || 'generic') + ' ' + (el.getAttribute('aria-description') || '') + ' ' + (el.getAttribute('placeholder') || '') + ' ' + (el.getAttribute('name') || '')).toLowerCase();\n var score = 0;\n for (var j = 0; j < terms.length; j++) if (hay.indexOf(terms[j]) !== -1) score += 1;\n if (score > 0) scored.push({ el: el, role: role || 'generic', score: score + (interactive ? 0.5 : 0) + (inViewport(el) ? 0.25 : 0) });\n }\n scored.sort(function (a, b) { return b.score - a.score; });\n var top = scored.slice(0, 20);\n if (!top.length) return { ok: true, text: 'No elements match \"' + clean(query) + '\".', truncated: false };\n return { ok: true, text: top.map(function (s) { return describe(s.el, s.role); }).join('\\n'), truncated: false };\n }\n\n function pageText() {\n var main = document.querySelector('main, [role=\"main\"], article');\n var text = clean((main && main.innerText.length > 200 ? main.innerText : document.body.innerText) || '');\n var truncated = text.length > MAX;\n return { ok: true, text: truncated ? text.slice(0, MAX) + '\\n[truncated]' : text || 'The page has no text.', truncated: truncated };\n }\n\n function resolve(ref, scroll) {\n var el = findRef(ref);\n if (!el || !el.isConnected) return { ok: false, error: ref + ' is stale or not found on the current page. Re-read the page to get fresh references.' };\n // Instant, not the page's own scroll-behavior: smooth, which is still\n // moving when the box is measured, so the click would land short.\n if (scroll && !inViewport(el)) el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });\n var r = el.getBoundingClientRect();\n return { ok: true, x: Math.round(r.left + r.width / 2), y: Math.round(r.top + r.height / 2), inViewport: inViewport(el) };\n }\n\n function form(ref, value) {\n var el = findRef(ref);\n if (!el) return { ok: false, error: ref + ' is stale or not found on the current page. Re-read the page to get fresh references.' };\n var fire = function (type) { el.dispatchEvent(new Event(type, { bubbles: true })); };\n if (el.tagName === 'SELECT') {\n var want = String(value).toLowerCase();\n for (var i = 0; i < el.options.length; i++) {\n var o = el.options[i];\n if (o.value.toLowerCase() === want || clean(o.text).toLowerCase() === want) {\n el.selectedIndex = i; fire('input'); fire('change');\n return { ok: true, description: 'Selected \"' + clean(o.text) + '\"' };\n }\n }\n return { ok: false, error: 'No option in ' + ref + ' matches that value. Read the page to see its options.' };\n }\n if (el.tagName === 'INPUT' && (el.type === 'checkbox' || el.type === 'radio')) {\n var on = value === true || String(value).toLowerCase() === 'true' || String(value).toLowerCase() === 'checked';\n if (el.checked !== on) el.click();\n return { ok: true, description: (el.checked ? 'Checked ' : 'Unchecked ') + ref };\n }\n if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {\n var proto = el.tagName === 'INPUT' ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;\n var setter = Object.getOwnPropertyDescriptor(proto, 'value').set;\n el.focus();\n setter.call(el, String(value));\n fire('input'); fire('change');\n // The value can be a password or a code, and results are recorded, so only its length.\n return { ok: true, description: 'Set ' + ref + ' (' + String(value).length + ' characters)' };\n }\n if (el.isContentEditable) {\n el.focus(); el.textContent = String(value); fire('input');\n return { ok: true, description: 'Set ' + ref + ' text' };\n }\n return { ok: false, error: ref + ' is not a form field.' };\n }\n\n // --- inspect: the facts the safety guards decide on ----------------------\n\n var CARD_RE = new RegExp(${CARD_PATTERN_SOURCE}, 'i');\n var PAYMENT_DOMAINS = ${PAYMENT_DOMAINS_JSON};\n var ACTIONABLE = 'a[href],button,input,select,textarea,summary,iframe,[role=\"button\"],[role=\"link\"],[role=\"checkbox\"],[role=\"textbox\"],[contenteditable=\"true\"]';\n\n function isPaymentFrame(src) {\n var host = '';\n try { host = new URL(src, location.href).hostname.toLowerCase(); } catch (e) { return false; }\n for (var i = 0; i < PAYMENT_DOMAINS.length; i++) {\n var d = PAYMENT_DOMAINS[i];\n if (host === d || host.slice(-(d.length + 1)) === '.' + d) return true;\n }\n return false;\n }\n\n function isCardField(el) {\n if (el.tagName === 'IFRAME') {\n return isPaymentFrame(el.getAttribute('src') || '') || CARD_RE.test((el.getAttribute('title') || '') + ' ' + (el.getAttribute('name') || ''));\n }\n if (/(^|\\s)cc-/.test((el.getAttribute('autocomplete') || '').toLowerCase())) return true;\n var text = [el.getAttribute('name'), el.id, el.getAttribute('aria-label'), el.getAttribute('placeholder')].join(' ');\n return CARD_RE.test(text);\n }\n\n // Across the whole page only visible fields count: a login modal parked\n // in the DOM must not make every button on the page look like a submit.\n function holdsSensitive(root, visibleOnly) {\n if (!root) return false;\n var fields = root.querySelectorAll('input, iframe');\n for (var i = 0; i < fields.length; i++) {\n var f = fields[i];\n var t = (f.getAttribute('type') || '').toLowerCase();\n if (t === 'hidden' || t === 'submit' || t === 'button') continue;\n if (t !== 'password' && !isCardField(f)) continue;\n if (visibleOnly && isHidden(f)) continue;\n return true;\n }\n return false;\n }\n\n function deepActive() {\n var el = document.activeElement;\n for (var i = 0; el && i < 10; i++) {\n if (el.shadowRoot && el.shadowRoot.activeElement) { el = el.shadowRoot.activeElement; continue; }\n if (el.tagName === 'IFRAME') {\n var inner = null;\n try { inner = el.contentDocument && el.contentDocument.activeElement; } catch (e) { inner = null; }\n if (inner && inner !== el.contentDocument.body) { el = inner; continue; }\n }\n break;\n }\n return el && el !== document.body && el !== document.documentElement ? el : null;\n }\n\n function atPoint(x, y) {\n var el = document.elementFromPoint(x, y);\n for (var i = 0; el && el.shadowRoot && i < 10; i++) {\n var inner = el.shadowRoot.elementFromPoint(x, y);\n if (!inner || inner === el) break;\n el = inner;\n }\n return el;\n }\n\n function submitLabel(form) {\n var controls = form.querySelectorAll('button, input[type=\"submit\" i], input[type=\"image\" i]');\n for (var i = 0; i < controls.length; i++) {\n var c = controls[i];\n var t = (c.getAttribute('type') || 'submit').toLowerCase();\n if (t === 'submit' || t === 'image') return labelOf(c);\n }\n return '';\n }\n\n function labelOf(el) {\n var name = nameOf(el, roleOf(el) || 'generic');\n if (!name && el.tagName === 'INPUT') name = clean(el.value || '');\n return name;\n }\n\n function factsOf(raw) {\n if (!raw || raw.nodeType !== 1) return null;\n var el = raw.closest ? raw.closest(ACTIONABLE) || raw : raw;\n var tag = el.tagName.toLowerCase();\n var type = (el.getAttribute('type') || '').toLowerCase();\n if (tag === 'button' && !type) type = 'submit';\n var role = roleOf(el) || '';\n var form = el.form || (el.closest ? el.closest('form') : null);\n var href = tag === 'a' ? el.getAttribute('href') || '' : '';\n var navigating = tag === 'a' && !!href && href.charAt(0) !== '#' && href.toLowerCase().indexOf('javascript:') !== 0 && el.getAttribute('role') !== 'button';\n var isSubmit = !!form && ((tag === 'button' && type === 'submit') || (tag === 'input' && (type === 'submit' || type === 'image')));\n var isButton = tag === 'button' || role === 'button' || (tag === 'input' && (type === 'submit' || type === 'button' || type === 'image'));\n return {\n tag: tag,\n type: type,\n autocomplete: (el.getAttribute('autocomplete') || '').toLowerCase(),\n name: el.getAttribute('name') || '',\n id: el.id || '',\n ariaLabel: el.getAttribute('aria-label') || '',\n placeholder: el.getAttribute('placeholder') || '',\n label: labelOf(el).slice(0, 200),\n role: role,\n isNavigatingLink: navigating,\n isSubmit: isSubmit,\n isButton: isButton,\n iframeSrc: tag === 'iframe' ? (el.getAttribute('src') || '').slice(0, 500) : '',\n iframeTitle: tag === 'iframe' ? el.getAttribute('title') || '' : '',\n inForm: !!form,\n // Without a form, the page stands in for one: a single-page sign-up\n // with a password box and a \"Create account\" button is still a sign-up.\n formSensitive: form ? holdsSensitive(form, false) : holdsSensitive(document, true),\n formSubmitLabel: form ? submitLabel(form).slice(0, 200) : ''\n };\n }\n\n function inspect(target) {\n if (target.type === 'focused') return { ok: true, facts: factsOf(deepActive()) };\n if (target.type === 'coordinate') return { ok: true, facts: factsOf(atPoint(target.x, target.y)) };\n var el = findRef(target.ref);\n if (!el) return { ok: false, error: target.ref + ' is stale or not found on the current page. Re-read the page to get fresh references.' };\n return { ok: true, facts: factsOf(el) };\n }\n\n switch (args.op) {\n case 'read': return read(args.filter, args.depth, args.ref);\n case 'find': return find(args.query);\n case 'text': return pageText();\n case 'resolve': return resolve(args.ref, args.scroll);\n case 'form': return form(args.ref, args.value);\n case 'viewport': return { ok: true, width: w.innerWidth, height: w.innerHeight };\n case 'inspect': return inspect(args.target);\n }\n return { ok: false, error: 'unknown op' };\n})`;\n\n/** The expression to `evaluate` for one operation. Arguments are JSON, never code. */\nexport function buildPageCall(op: PageOp): string {\n return `${SCRIPT}(${JSON.stringify(op)})`;\n}\n","// Client-side executor for Anthropic's browser use toolset\n// (`browser_toolset_20260801`): Claude emits `tool_use` blocks\n// with `toolset_name: 'browser'` naming one of the toolset's members, and this\n// runs each against the `BrowserDriver` and builds the `tool_result` the API\n// expects back. Spec: https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-tool\n//\n// Contract points the API enforces or relies on, all handled here:\n// - every result echoes `toolset_name: 'browser'`;\n// - a turn's calls run sequentially, and after the first failure every later\n// call is answered with exactly `HALT_TEXT` rather than run;\n// - tab-management results are exactly one `browser_state` block; other\n// results may carry at most one, never on an error;\n// - navigation refuses anything but http(s), and the caller's network policy.\n//\n// No runtime imports beyond this package, so it runs under `node --test`, and\n// `@anthropic-ai/sdk` is needed for its types only.\n\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { BrowserDriver, DriverPage } from './driver.ts';\nimport { toPlaywrightKeySequence } from './keys.ts';\nimport {\n buildPageCall,\n type FormResult,\n type InspectResult,\n type InspectTarget,\n type PageOp,\n type ReadResult,\n type ResolveResult,\n} from './page-script.ts';\nimport {\n type BrowserSafetyOptions,\n containsCardNumber,\n type ElementFacts,\n enterSubmitReason,\n refusalText,\n sensitiveFieldReason,\n submitReason,\n} from './safety.ts';\n\ntype ToolResult = Anthropic.Messages.ToolResultBlockParam;\ntype ToolUse = Pick<Anthropic.Messages.ToolUseBlock, 'id' | 'name' | 'input' | 'toolset_name'>;\ntype ResultContent = Exclude<ToolResult['content'], string | undefined>;\ntype BrowserState = Anthropic.Messages.BrowserStateBlockParam;\n\n/** The tool entry for `tools`. Optional members (scripting, uploads, console,\n * network) stay disabled: the agent browses like a person, not a developer. */\nexport const BROWSER_TOOLSET: Anthropic.Messages.BrowserToolset20260801 = {\n type: 'browser_toolset_20260801',\n};\n\nexport const BROWSER_TOOLSET_NAME = 'browser';\n\n/** Exact text the API expects for calls skipped after an earlier failure. */\nexport const HALT_TEXT = 'Not executed: an earlier action in this turn failed.';\n\nconst DISABLED_MEMBERS = new Set([\n 'javascript_exec',\n 'file_upload',\n 'read_console',\n 'read_network',\n]);\nconst TAB_MEMBERS = new Set(['new_tab', 'list_tabs', 'switch_tab', 'close_tab']);\nconst MAX_WAIT_SECONDS = 30;\nconst SCROLL_NOTCH_PX = 100;\nconst FIELD_LIMIT = 4_096;\n\ntype Target = { type: 'ref'; ref: string } | { type: 'coordinate'; x: number; y: number };\n\nexport class ToolsetError extends Error {}\n\nexport type BrowserToolsetOptions = {\n /**\n * The caller's network policy: return an error message to refuse `url`, or\n * null to allow it. Scheme checks happen before this is called.\n */\n checkNavigation?: (url: string) => string | null;\n /**\n * Allow loopback, private and link-local hosts. Off by default: a browser\n * running inside your own infrastructure (a job runner, a container) can\n * otherwise be steered at the host's own services or the cloud metadata\n * endpoint by anything on a page that talks the model into navigating there.\n * Turn it on when reaching private hosts is the point (testing an intranet\n * or a local dev server); `checkNavigation` still applies.\n */\n allowPrivateHosts?: boolean;\n navigationTimeoutMs?: number;\n screenshotTimeoutMs?: number;\n /**\n * Guards that refuse paying, signing up and entering secrets (`safety.ts`).\n * Off by default: agents testing their own product fill forms with test data\n * on purpose.\n * A refusal is an ordinary result telling the model why, not an error, so\n * the rest of its turn still runs.\n */\n safety?: BrowserSafetyOptions;\n};\n\n/** One executed call, for the caller's transcript and evidence. */\nexport type ToolsetCallRecord = {\n toolUseId: string;\n name: string;\n input: Record<string, unknown>;\n text: string;\n screenshotBase64?: string;\n isError: boolean;\n};\n\nexport type ToolsetTurnResult = {\n /** In call order, one per browser `tool_use`, ready for the next user turn. */\n results: ToolResult[];\n records: ToolsetCallRecord[];\n};\n\n/**\n * Whether a URL's host is loopback, private or link-local, by name or literal\n * address. Names that resolve to a private address (DNS rebinding) are not\n * caught here; the in-browser domain policy is the layer for those.\n */\nexport function isPrivateHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, '');\n if (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.internal'))\n return true;\n if (host === 'metadata.google.internal') return true;\n const v4 = host.match(/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/);\n if (v4) {\n const [a, b] = [Number(v4[1]), Number(v4[2])];\n return (\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 100 && b >= 64 && b <= 127)\n );\n }\n if (host.includes(':')) {\n return (\n host === '::1' ||\n host === '::' ||\n /^f[cd]/.test(host) ||\n /^fe[89ab]/.test(host) ||\n host.startsWith('::ffff:')\n );\n }\n return false;\n}\n\n/**\n * The input as it may be recorded: typed text and form values can be a\n * password or a code, so the transcript and events get their length, never the\n * value. The model still receives the real input; only records are redacted.\n */\nexport function redactInput(name: string, input: Record<string, unknown>): Record<string, unknown> {\n if (name === 'type' && typeof input.text === 'string') {\n return { ...input, text: `[${input.text.length} characters]` };\n }\n if (name === 'form_input' && 'value' in input) {\n const v = input.value;\n return { ...input, value: typeof v === 'boolean' ? v : `[${String(v).length} characters]` };\n }\n return input;\n}\n\n/** Strip what the toolset forbids in `browser_state` fields and cap the length. */\nexport function sanitiseStateField(value: string): string {\n // eslint-disable-next-line no-control-regex\n return value.replace(/[\\u0000-\\u001f\\u007f\\u2028\\u2029]+/g, ' ').slice(0, FIELD_LIMIT);\n}\n\nexport function isBrowserToolsetCall(block: {\n type: string;\n toolset_name?: string | null;\n}): boolean {\n return block.type === 'tool_use' && block.toolset_name === BROWSER_TOOLSET_NAME;\n}\n\nfunction parseTarget(raw: unknown, allow: 'any' | 'ref' | 'coordinate'): Target {\n const t = (raw ?? {}) as Record<string, unknown>;\n if (t.type === 'ref' && typeof t.ref === 'string' && allow !== 'coordinate') {\n return { type: 'ref', ref: t.ref };\n }\n if (\n t.type === 'coordinate' &&\n typeof t.x === 'number' &&\n typeof t.y === 'number' &&\n allow !== 'ref'\n ) {\n return { type: 'coordinate', x: Math.round(t.x), y: Math.round(t.y) };\n }\n const want =\n allow === 'ref'\n ? 'a ref target'\n : allow === 'coordinate'\n ? 'a coordinate target'\n : 'a ref or coordinate target';\n throw new ToolsetError(`Error: expected ${want}.`);\n}\n\nfunction clampNumber(value: unknown, min: number, max: number, fallback: number): number {\n const n = typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n return Math.min(max, Math.max(min, n));\n}\n\nexport class BrowserToolsetExecutor {\n /** Tab ids by the driver's stable page id (page objects are not stable). */\n private readonly tabIds = new Map<string, string>();\n private tabSeq = 0;\n\n // Plain fields rather than parameter properties: `node --test` runs this\n // file with type stripping only, which rejects parameter properties.\n private readonly driver: BrowserDriver;\n private readonly options: BrowserToolsetOptions;\n\n constructor(driver: BrowserDriver, options: BrowserToolsetOptions = {}) {\n this.driver = driver;\n this.options = options;\n }\n\n /** Turns run one at a time: two batches interleaving on one page would be nonsense. */\n private queue: Promise<unknown> = Promise.resolve();\n\n /**\n * Run every browser call in a turn, in order, halting after the first\n * failure. An aborted `signal` halts the rest the same way, so a caller that\n * gave up on a stuck turn does not have its remaining calls land on the\n * connection it rebuilt.\n */\n runTurn(\n calls: readonly ToolUse[],\n opts: { signal?: AbortSignal } = {}\n ): Promise<ToolsetTurnResult> {\n const run = this.queue.then(() => this.runTurnNow(calls, opts.signal));\n this.queue = run.catch(() => {});\n return run;\n }\n\n private async runTurnNow(\n calls: readonly ToolUse[],\n signal?: AbortSignal\n ): Promise<ToolsetTurnResult> {\n const results: ToolResult[] = [];\n const records: ToolsetCallRecord[] = [];\n let failed = false;\n for (const call of calls) {\n const input = redactInput(call.name, (call.input ?? {}) as Record<string, unknown>);\n if (failed || signal?.aborted) {\n results.push(this.errorResult(call.id, HALT_TEXT));\n records.push({\n toolUseId: call.id,\n name: call.name,\n input,\n text: HALT_TEXT,\n isError: true,\n });\n continue;\n }\n try {\n const content = await this.execute(\n call.name,\n (call.input ?? {}) as Record<string, unknown>\n );\n results.push({\n type: 'tool_result',\n tool_use_id: call.id,\n toolset_name: BROWSER_TOOLSET_NAME,\n content,\n });\n const text = content\n .map((b) =>\n b.type === 'text' ? b.text : b.type === 'browser_state' ? describeState(b) : ''\n )\n .filter(Boolean)\n .join('\\n');\n const image = content.find((b) => b.type === 'image');\n records.push({\n toolUseId: call.id,\n name: call.name,\n input,\n text,\n screenshotBase64:\n image && image.type === 'image' && image.source.type === 'base64'\n ? image.source.data\n : undefined,\n isError: false,\n });\n } catch (err) {\n failed = true;\n const message =\n err instanceof ToolsetError\n ? err.message\n : `Error: ${err instanceof Error ? err.message : String(err)}`;\n results.push(this.errorResult(call.id, message));\n records.push({ toolUseId: call.id, name: call.name, input, text: message, isError: true });\n }\n }\n return { results, records };\n }\n\n private errorResult(toolUseId: string, text: string): ToolResult {\n return {\n type: 'tool_result',\n tool_use_id: toolUseId,\n toolset_name: BROWSER_TOOLSET_NAME,\n is_error: true,\n content: text,\n };\n }\n\n /** Execute one member. Throws `ToolsetError` (or anything) to fail the call. */\n async execute(name: string, input: Record<string, unknown>): Promise<ResultContent> {\n if (DISABLED_MEMBERS.has(name)) {\n throw new ToolsetError(`Error: ${name} is not enabled in this environment.`);\n }\n if (TAB_MEMBERS.has(name)) return [await this.tabMember(name, input)];\n\n const page = await this.pageFor(input.tab_id);\n switch (name) {\n case 'navigate':\n return this.navigate(page, input);\n case 'screenshot':\n return [await this.screenshot(page)];\n case 'zoom':\n return [await this.zoom(page, input)];\n case 'left_click':\n case 'right_click':\n case 'middle_click':\n case 'double_click':\n case 'triple_click': {\n if (this.options.safety?.blockSubmit && name !== 'right_click') {\n const target = parseTarget(input.target, 'any');\n const facts = await this.inspect(page, target);\n const reason = facts && submitReason(facts);\n if (reason) return [text(refusalText(reason))];\n }\n return [text(await this.click(page, name, input))];\n }\n case 'hover': {\n const { x, y } = await this.point(page, parseTarget(input.target, 'any'));\n await page.hover(x, y);\n return [text(`Hovered at (${x}, ${y})`)];\n }\n case 'mouse_move': {\n const { x, y } = await this.point(page, parseTarget(input.target, 'coordinate'));\n await page.hover(x, y);\n return [text(`Moved the mouse to (${x}, ${y})`)];\n }\n case 'left_click_drag': {\n const from = await this.point(page, parseTarget(input.from, 'coordinate'));\n const to = await this.point(page, parseTarget(input.target, 'coordinate'));\n await page.dragAndDrop(from.x, from.y, to.x, to.y);\n return [text(`Dragged from (${from.x}, ${from.y}) to (${to.x}, ${to.y})`)];\n }\n case 'left_mouse_down':\n case 'left_mouse_up':\n case 'hold_key':\n throw new ToolsetError(\n `Error: ${name} is not supported by this browser. Use left_click, left_click_drag or key instead.`\n );\n case 'scroll':\n return [text(await this.scroll(page, input))];\n case 'scroll_to': {\n const target = parseTarget(input.target, 'ref') as Extract<Target, { type: 'ref' }>;\n const r = await this.pageCall<ResolveResult>(page, {\n op: 'resolve',\n ref: target.ref,\n scroll: true,\n });\n if (!r.ok) throw new ToolsetError(`Error: ${r.error}`);\n return [text(`Scrolled ${target.ref} into view`)];\n }\n case 'type': {\n const value = String(input.text ?? '');\n const refusal = await this.typeRefusal(page, value);\n if (refusal) return [text(refusal)];\n await page.type(value);\n return [text(`Typed ${value.length} character${value.length === 1 ? '' : 's'}`)];\n }\n case 'key': {\n const keys = toPlaywrightKeySequence(String(input.text ?? ''));\n if (keys.length === 0) throw new ToolsetError('Error: key needs text.');\n const repeat = Math.round(clampNumber(input.repeat, 1, 100, 1));\n const refusal = await this.keyRefusal(page, keys);\n if (refusal) return [text(refusal)];\n for (let i = 0; i < repeat; i++) for (const k of keys) await page.keyPress(k);\n return [text(`Pressed ${keys.join(' ')}${repeat > 1 ? ` x${repeat}` : ''}`)];\n }\n case 'wait': {\n const seconds = clampNumber(input.duration, 0, MAX_WAIT_SECONDS, 1);\n await page.waitForTimeout(seconds * 1000);\n return [text(`Waited ${seconds}s`)];\n }\n case 'read_page': {\n const filter =\n input.filter === 'interactive' || input.filter === 'all' ? input.filter : 'visible';\n const depth = Math.round(clampNumber(input.depth, 1, 100, 15));\n const ref = typeof input.ref === 'string' ? input.ref : undefined;\n const r = await this.pageCall<ReadResult>(page, { op: 'read', filter, depth, ref });\n if (!r.ok) throw new ToolsetError(`Error: ${r.error}`);\n return [text(r.text)];\n }\n case 'find': {\n const r = await this.pageCall<ReadResult>(page, {\n op: 'find',\n query: String(input.query ?? ''),\n });\n if (!r.ok) throw new ToolsetError(`Error: ${r.error}`);\n return [text(r.text)];\n }\n case 'get_page_text': {\n const r = await this.pageCall<ReadResult>(page, { op: 'text' });\n if (!r.ok) throw new ToolsetError(`Error: ${r.error}`);\n return [text(r.text)];\n }\n case 'form_input': {\n const target = parseTarget(input.target, 'ref') as Extract<Target, { type: 'ref' }>;\n const value = input.value;\n if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {\n throw new ToolsetError('Error: form_input needs a string, number or boolean value.');\n }\n if (this.options.safety?.refuseSensitiveInput && typeof value !== 'boolean') {\n const refusal =\n (containsCardNumber(String(value)) && refusalText('That value is a card number.')) ||\n (await this.fieldRefusal(page, target));\n if (refusal) return [text(refusal)];\n }\n const r = await this.pageCall<FormResult>(page, { op: 'form', ref: target.ref, value });\n if (!r.ok) throw new ToolsetError(`Error: ${r.error}`);\n return [text(r.description)];\n }\n default:\n throw new ToolsetError(\n `Error: ${name} is not a browser toolset member this executor knows.`\n );\n }\n }\n\n // --- members -------------------------------------------------------------\n\n private async navigate(page: DriverPage, input: Record<string, unknown>): Promise<ResultContent> {\n const raw = String(input.url ?? '').trim();\n // History moves, which Claude reaches for as `navigate(\"back\")`.\n if (raw === 'back' || raw === 'forward') {\n await (raw === 'back' ? page.goBack() : page.goForward());\n return [text(`Went ${raw} to ${await page.url()}`), await this.browserState()];\n }\n let url: URL;\n try {\n // A scheme is `scheme://` or one of the schemes that take no slashes;\n // `example.com:8080` is a host and port, not a scheme called example.com.\n const hasScheme =\n /^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw) ||\n /^(javascript|data|file|about|blob|mailto|vbscript|chrome|view-source):/i.test(raw);\n url = new URL(hasScheme ? raw : `https://${raw}`);\n } catch {\n throw new ToolsetError(`Error: \"${raw.slice(0, 200)}\" is not a valid URL.`);\n }\n // A bare word (\"Pricing\") is not a site; without this it becomes\n // https://pricing/ and fails somewhere far less clear.\n if (\n /^https?:$/.test(url.protocol) &&\n !url.hostname.includes('.') &&\n url.hostname !== 'localhost'\n ) {\n throw new ToolsetError(\n `Error: \"${raw.slice(0, 200)}\" is not a URL. Pass a full address, or click the link with left_click.`\n );\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new ToolsetError('Error: Navigation refused. Only http and https URLs are allowed.');\n }\n if (!this.options.allowPrivateHosts && isPrivateHost(url.hostname)) {\n throw new ToolsetError(\n 'Error: Navigation refused. Local and private network addresses are not reachable from this browser.'\n );\n }\n const refusal = this.options.checkNavigation?.(url.href);\n if (refusal) throw new ToolsetError(`Error: Navigation refused. ${refusal}`);\n await page.goto(url.href, {\n waitUntil: 'domcontentloaded',\n ...(this.options.navigationTimeoutMs ? { timeoutMs: this.options.navigationTimeoutMs } : {}),\n });\n return [text(`Navigated to ${await page.url()}`), await this.browserState()];\n }\n\n private async screenshot(page: DriverPage): Promise<Anthropic.Messages.ImageBlockParam> {\n const bytes = await page.screenshot({\n type: 'png',\n ...(this.options.screenshotTimeoutMs ? { timeout: this.options.screenshotTimeoutMs } : {}),\n });\n return image(bytes.toString('base64'));\n }\n\n private async zoom(\n page: DriverPage,\n input: Record<string, unknown>\n ): Promise<Anthropic.Messages.ImageBlockParam> {\n const region = input.region;\n if (\n !Array.isArray(region) ||\n region.length !== 4 ||\n !region.every((n) => typeof n === 'number')\n ) {\n throw new ToolsetError('Error: zoom needs region [x0, y0, x1, y1].');\n }\n const [x0, y0, x1, y1] = region as number[];\n const width = Math.abs(x1 - x0);\n const height = Math.abs(y1 - y0);\n if (width < 1 || height < 1) throw new ToolsetError('Error: zoom region is empty.');\n // Captured at the page's own resolution: without an image library in this\n // package there is no upscale, so a small region reads as small as it is.\n const bytes = await page.screenshot({\n type: 'png',\n clip: { x: Math.min(x0, x1), y: Math.min(y0, y1), width, height },\n ...(this.options.screenshotTimeoutMs ? { timeout: this.options.screenshotTimeoutMs } : {}),\n });\n return image(bytes.toString('base64'));\n }\n\n private async click(\n page: DriverPage,\n name: string,\n input: Record<string, unknown>\n ): Promise<string> {\n if (Array.isArray(input.modifiers) ? input.modifiers.length > 0 : input.modifiers) {\n throw new ToolsetError(\n 'Error: modifier clicks are not supported by this browser. Click without modifiers, or use key.'\n );\n }\n const target = parseTarget(input.target, 'any');\n const { x, y } = await this.point(page, target);\n const button = name === 'right_click' ? 'right' : name === 'middle_click' ? 'middle' : 'left';\n const clickCount = name === 'double_click' ? 2 : name === 'triple_click' ? 3 : 1;\n await page.click(x, y, { button, clickCount });\n const where = target.type === 'ref' ? `${target.ref} at (${x}, ${y})` : `(${x}, ${y})`;\n return `${name.replace('_', ' ')} on ${where}`;\n }\n\n private async scroll(page: DriverPage, input: Record<string, unknown>): Promise<string> {\n const direction = input.scroll_direction;\n if (\n direction !== 'up' &&\n direction !== 'down' &&\n direction !== 'left' &&\n direction !== 'right'\n ) {\n throw new ToolsetError('Error: scroll_direction must be up, down, left or right.');\n }\n const amount = Math.round(clampNumber(input.scroll_amount, 1, 10, 3));\n const { x, y } = await this.point(page, parseTarget(input.target, 'any'));\n const delta = amount * SCROLL_NOTCH_PX;\n const dx = direction === 'left' ? -delta : direction === 'right' ? delta : 0;\n const dy = direction === 'up' ? -delta : direction === 'down' ? delta : 0;\n await page.scroll(x, y, dx, dy);\n return `Scrolled ${direction} ${amount} at (${x}, ${y})`;\n }\n\n private async tabMember(name: string, input: Record<string, unknown>): Promise<BrowserState> {\n const ctx = this.driver.context;\n if (name === 'list_tabs') return this.browserState();\n if (name === 'new_tab') {\n // Number the tabs already open first, so ids follow the order tabs were\n // opened in rather than the order Claude happened to mention them.\n for (const existing of await ctx.pages()) this.tabId(existing);\n const page = await ctx.newPage();\n return this.browserState([{ type: 'tab_opened', tab_id: this.tabId(page) }]);\n }\n const page = await this.pageFor(input.tab_id, true);\n if (name === 'switch_tab') {\n await ctx.setActivePage(page);\n return this.browserState();\n }\n // close_tab: keep a tab active afterwards if there is one to fall back to.\n const wasActive = (await ctx.activePage())?.id === page.id;\n await page.close();\n if (wasActive) {\n const remaining = await ctx.pages();\n if (remaining.length > 0) await ctx.setActivePage(remaining[remaining.length - 1]);\n }\n return this.browserState();\n }\n\n // --- helpers -------------------------------------------------------------\n\n private tabId(page: DriverPage): string {\n let id = this.tabIds.get(page.id);\n if (!id) {\n this.tabSeq += 1;\n id = `tab-${this.tabSeq}`;\n this.tabIds.set(page.id, id);\n }\n return id;\n }\n\n private async pageFor(tabId: unknown, required = false): Promise<DriverPage> {\n const ctx = this.driver.context;\n if (typeof tabId === 'string' && tabId.length > 0) {\n for (const page of await ctx.pages()) if (this.tabId(page) === tabId) return page;\n throw new ToolsetError(\n `Error: no tab with tab_id ${tabId}. Call list_tabs for the current tabs.`\n );\n }\n if (required) throw new ToolsetError('Error: tab_id is required.');\n const page = await ctx.activePage();\n if (!page) throw new ToolsetError('Error: the browser has no open tab. Call new_tab first.');\n return page;\n }\n\n private async point(page: DriverPage, target: Target): Promise<{ x: number; y: number }> {\n if (target.type === 'coordinate') return { x: target.x, y: target.y };\n let r = await this.pageCall<ResolveResult>(page, {\n op: 'resolve',\n ref: target.ref,\n scroll: true,\n });\n if (r.ok && !r.inViewport) {\n // A page with its own scroll handling (sticky headers, scroll-snap) can\n // still be settling; look once more before giving up.\n await page.waitForTimeout(250);\n r = await this.pageCall<ResolveResult>(page, {\n op: 'resolve',\n ref: target.ref,\n scroll: false,\n });\n }\n if (!r.ok) throw new ToolsetError(`Error: ${r.error}`);\n if (!r.inViewport) {\n throw new ToolsetError(\n `Error: ${target.ref} could not be scrolled into view. Try coordinates from a screenshot.`\n );\n }\n return { x: r.x, y: r.y };\n }\n\n // --- safety guards ------------------------------------------------------\n\n /** What the guards need to know about an element; null when nothing is there. */\n private async inspect(page: DriverPage, target: InspectTarget): Promise<ElementFacts | null> {\n const r = await this.pageCall<InspectResult>(page, { op: 'inspect', target });\n // A stale ref is refused by the action itself, with the usual message.\n return r.ok ? r.facts : null;\n }\n\n private async fieldRefusal(page: DriverPage, target: InspectTarget): Promise<string | null> {\n const facts = await this.inspect(page, target);\n const reason = facts && sensitiveFieldReason(facts);\n return reason ? refusalText(reason) : null;\n }\n\n /** Typing goes to whatever has focus, so that is what is checked. */\n private async typeRefusal(page: DriverPage, value: string): Promise<string | null> {\n const safety = this.options.safety;\n if (!safety) return null;\n if (safety.refuseSensitiveInput) {\n if (containsCardNumber(value)) return refusalText('That value is a card number.');\n const refusal = await this.fieldRefusal(page, { type: 'focused' });\n if (refusal) return refusal;\n }\n // A newline types as Enter, which submits the field's form.\n if (safety.blockSubmit && /[\\r\\n]/.test(value)) {\n const facts = await this.inspect(page, { type: 'focused' });\n const reason = facts && enterSubmitReason(facts);\n if (reason) return refusalText(reason);\n }\n return null;\n }\n\n /** Enter submits the focused field's form; Enter or Space presses a focused button. */\n private async keyRefusal(page: DriverPage, keys: readonly string[]): Promise<string | null> {\n if (!this.options.safety?.blockSubmit) return null;\n const pressesEnter = keys.some((k) => /(^|\\+)(Enter|NumpadEnter)$/.test(k));\n const pressesSpace = keys.some((k) => /(^|\\+)(Space| )$/.test(k));\n if (!pressesEnter && !pressesSpace) return null;\n const facts = await this.inspect(page, { type: 'focused' });\n if (!facts) return null;\n const reason =\n (facts.isButton && submitReason(facts)) || (pressesEnter && enterSubmitReason(facts));\n return reason ? refusalText(reason) : null;\n }\n\n private pageCall<R>(page: DriverPage, op: PageOp): Promise<R> {\n return page.evaluate<R>(buildPageCall(op));\n }\n\n private async browserState(stateChanges?: BrowserState['state_changes']): Promise<BrowserState> {\n const ctx = this.driver.context;\n const [pages, active] = await Promise.all([ctx.pages(), ctx.activePage()]);\n const tabs = await Promise.all(\n pages.slice(0, 100).map(async (page) => ({\n tab_id: this.tabId(page),\n title: sanitiseStateField(await page.title().catch(() => '')),\n url: sanitiseStateField(await Promise.resolve(page.url()).catch(() => '')),\n ...(active && page.id === active.id ? { active: true } : {}),\n }))\n );\n if (tabs.length > 0 && !tabs.some((t) => t.active)) tabs[tabs.length - 1].active = true;\n return {\n type: 'browser_state',\n tabs,\n ...(stateChanges && stateChanges.length > 0 ? { state_changes: stateChanges } : {}),\n };\n }\n}\n\nfunction text(value: string): Anthropic.Messages.TextBlockParam {\n return { type: 'text', text: value };\n}\n\nfunction image(data: string): Anthropic.Messages.ImageBlockParam {\n return { type: 'image', source: { type: 'base64', media_type: 'image/png', data } };\n}\n\nfunction describeState(state: BrowserState): string {\n const active = state.tabs.find((t) => t.active);\n return active ? `Tab ${active.tab_id}: ${active.url}` : `${state.tabs.length} tabs`;\n}\n","// Provider-neutral browser tools, run directly against the page with no model\n// call inside any of them.\n//\n// Instruction-style tools (`act(\"click the buy button\")`, `extract(...)`) hand\n// each step to a second model to resolve, so every step the agent takes pays\n// for a hidden model call as well, and is slowed by it. These tools read the\n// page as an accessibility tree with `ref_N` handles and act on those refs (or\n// on screenshot coordinates), through the same executor that serves Anthropic's\n// browser use toolset. The agent's own model does all of the reasoning.\n//\n// They are plain function tools rather than Anthropic's toolset type, because\n// other providers have no equivalent: any model with function calling can be\n// offered exactly these, so agents on different models stay comparable.\n//\n// No runtime imports beyond the executor, so it runs under `node --test`.\n\nimport type { BrowserDriver, DriverPage } from './driver.ts';\nimport { BrowserToolsetExecutor, type BrowserToolsetOptions, ToolsetError } from './executor.ts';\nimport type { BrowserSafetyOptions } from './safety.ts';\n\nexport type DirectToolName =\n | 'navigate'\n | 'read_page'\n | 'find'\n | 'get_page_text'\n | 'click'\n | 'type'\n | 'form_input'\n | 'key'\n | 'scroll'\n | 'screenshot'\n | 'done';\n\nexport type DirectToolResult = {\n text: string;\n screenshotBase64?: string;\n error?: string;\n};\n\ntype JsonSchema = {\n type: 'object';\n properties: Record<string, unknown>;\n required?: string[];\n};\n\nexport type DirectToolSpec = {\n name: DirectToolName;\n description: string;\n parameters: JsonSchema;\n};\n\n/**\n * Page reads are re-sent on every later turn, and not every provider caches\n * prompts, so a full 50k-character tree would be paid for again on each step.\n * 20k is enough for most pages' interactive tree; the note tells the model how\n * to narrow a read when it is not.\n */\nexport const MAX_READ_CHARS = 20_000;\n\n/** One tool call, end to end. Past this the page is treated as stuck. */\nexport const TOOL_TIMEOUT_MS = 45_000;\nconst NAVIGATION_TIMEOUT_MS = 30_000;\nconst SCREENSHOT_TIMEOUT_MS = 10_000;\n/** Settling after an input: long enough for a click's navigation to begin. */\nconst SETTLE_MS = 400;\nconst SETTLE_LOAD_TIMEOUT_MS = 5_000;\n\nconst TARGET_PROPERTIES = {\n ref: {\n type: 'string',\n description: 'An element ref from read_page or find, e.g. \"ref_12\". Preferred.',\n },\n x: {\n type: 'number',\n description: 'Viewport x coordinate in screenshot pixels, when there is no ref.',\n },\n y: {\n type: 'number',\n description: 'Viewport y coordinate in screenshot pixels, when there is no ref.',\n },\n};\n\n/**\n * The tools, in a provider-neutral shape; each agent loop adapts them into its\n * SDK's format. Flat parameters rather than a\n * nested target object, because every provider's function calling handles\n * those reliably.\n */\nexport const DIRECT_TOOL_SPECS: DirectToolSpec[] = [\n {\n name: 'navigate',\n description:\n 'Go to a URL, or \"back\" / \"forward\" in history. Returns the page it landed on and a screenshot.',\n parameters: {\n type: 'object',\n properties: { url: { type: 'string', description: 'A full URL, \"back\" or \"forward\".' } },\n required: ['url'],\n },\n },\n {\n name: 'read_page',\n description:\n 'Read the page as an accessibility tree. Each element worth acting on carries a ref (ref_N) to pass to click, type, form_input or scroll. The filter \"visible\" (default) covers what is on screen, \"interactive\" only controls, \"all\" the whole page. Pass a ref to read just that part of the page.',\n parameters: {\n type: 'object',\n properties: {\n filter: { type: 'string', enum: ['visible', 'interactive', 'all'] },\n ref: { type: 'string', description: 'Read only this element and its descendants.' },\n },\n },\n },\n {\n name: 'find',\n description:\n 'Find elements matching a description or text (e.g. \"search box\", \"Add to cart\"). Returns up to 20 matches with refs.',\n parameters: {\n type: 'object',\n properties: { query: { type: 'string', description: 'What to look for.' } },\n required: ['query'],\n },\n },\n {\n name: 'get_page_text',\n description: \"The page's visible text as plain text, for reading content such as a policy.\",\n parameters: { type: 'object', properties: {} },\n },\n {\n name: 'click',\n description:\n 'Click an element by ref, or at a coordinate from a screenshot. Returns the resulting page and a screenshot.',\n parameters: { type: 'object', properties: TARGET_PROPERTIES },\n },\n {\n name: 'type',\n description:\n 'Type text. With a ref (or coordinate), clicks that field first; without one, types into whatever has focus. Does not press Enter: use key for that.',\n parameters: {\n type: 'object',\n properties: {\n text: { type: 'string', description: 'The text to type.' },\n ...TARGET_PROPERTIES,\n },\n required: ['text'],\n },\n },\n {\n name: 'form_input',\n description:\n 'Set a form control directly by ref: choose a select option (by its label or value), tick or untick a checkbox or radio (\"true\" / \"false\"), or fill an input.',\n parameters: {\n type: 'object',\n properties: {\n ref: { type: 'string', description: 'The control ref from read_page or find.' },\n // A string on every provider: a union type is not something every\n // provider's function declarations take reliably, and the page script reads\n // \"true\" / \"false\" as a checked state.\n value: { type: 'string', description: 'The option, \"true\" / \"false\", or the text.' },\n },\n required: ['ref', 'value'],\n },\n },\n {\n name: 'key',\n description:\n 'Press a key or chord, e.g. \"Enter\", \"Escape\", \"Tab\", \"ctrl+a\". Space-separate several to press them in turn.',\n parameters: {\n type: 'object',\n properties: { text: { type: 'string', description: 'The key or keys.' } },\n required: ['text'],\n },\n },\n {\n name: 'scroll',\n description:\n 'Scroll the page (or the element at a ref or coordinate) up, down, left or right. amount is in wheel notches of about 100px, default 5.',\n parameters: {\n type: 'object',\n properties: {\n direction: { type: 'string', enum: ['up', 'down', 'left', 'right'] },\n amount: { type: 'number', description: 'Notches, 1 to 10.' },\n ...TARGET_PROPERTIES,\n },\n required: ['direction'],\n },\n },\n {\n name: 'screenshot',\n description: 'Look at the current page.',\n parameters: { type: 'object', properties: {} },\n },\n {\n name: 'done',\n description:\n 'Signal that the task is complete. Provide a concise summary of what was accomplished.',\n parameters: {\n type: 'object',\n properties: { message: { type: 'string', description: 'Summary.' } },\n required: ['message'],\n },\n },\n];\n\nexport const DIRECT_TOOL_NAMES: readonly DirectToolName[] = DIRECT_TOOL_SPECS.map((s) => s.name);\n\nexport function clampRead(text: string): string {\n if (text.length <= MAX_READ_CHARS) return text;\n return `${text.slice(0, MAX_READ_CHARS)}\\n[truncated at ${MAX_READ_CHARS} characters: read_page with a ref or the \"interactive\" filter, or find, reads less]`;\n}\n\n/**\n * The executor's target shape from our flat parameters. A ref wins over a\n * coordinate; neither is an error the model can correct.\n */\nexport function targetOf(\n input: Record<string, unknown>\n): { type: 'ref'; ref: string } | { type: 'coordinate'; x: number; y: number } | null {\n if (typeof input.ref === 'string' && input.ref.trim()) {\n return { type: 'ref', ref: input.ref.trim() };\n }\n const x = Number(input.x);\n const y = Number(input.y);\n if (input.x !== undefined && input.y !== undefined && Number.isFinite(x) && Number.isFinite(y)) {\n return { type: 'coordinate', x, y };\n }\n return null;\n}\n\nfunction blocksToText(blocks: readonly unknown[]): string {\n return blocks\n .map((b) => {\n const block = b as { type?: string; text?: string };\n return block.type === 'text' && typeof block.text === 'string' ? block.text : '';\n })\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction withTimeout<T>(work: Promise<T>, ms: number, what: string): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new ToolsetError(`Error: ${what} did not finish within ${ms / 1000}s.`)),\n ms\n );\n });\n return Promise.race([work, timeout]).finally(() => clearTimeout(timer));\n}\n\nexport type DirectBrowserToolsOptions = {\n viewport: { width: number; height: number };\n /** Overridable for tests. */\n settleMs?: number;\n toolTimeoutMs?: number;\n /**\n * The executor's pay / sign-up / secrets guards. Turn them on (for example\n * with `INDEX_SESSION_SAFETY`) whenever the agent is browsing sites that are\n * not yours and must never pay or register for real.\n */\n safety?: BrowserSafetyOptions;\n /** Passed to the executor: see `BrowserToolsetOptions`. */\n allowPrivateHosts?: BrowserToolsetOptions['allowPrivateHosts'];\n checkNavigation?: BrowserToolsetOptions['checkNavigation'];\n};\n\n/**\n * Runs `DIRECT_TOOL_SPECS` calls against a driver. One per session: the\n * executor inside it holds the tab ids for the session's life.\n */\nexport class DirectBrowserTools {\n private readonly driver: BrowserDriver;\n private readonly executor: BrowserToolsetExecutor;\n private readonly viewport: { width: number; height: number };\n private readonly settleMs: number;\n private readonly toolTimeoutMs: number;\n\n constructor(driver: BrowserDriver, options: DirectBrowserToolsOptions) {\n this.driver = driver;\n this.executor = new BrowserToolsetExecutor(driver, {\n navigationTimeoutMs: NAVIGATION_TIMEOUT_MS,\n screenshotTimeoutMs: SCREENSHOT_TIMEOUT_MS,\n ...(options.safety ? { safety: options.safety } : {}),\n ...(options.allowPrivateHosts ? { allowPrivateHosts: true } : {}),\n ...(options.checkNavigation ? { checkNavigation: options.checkNavigation } : {}),\n });\n this.viewport = options.viewport;\n this.settleMs = options.settleMs ?? SETTLE_MS;\n this.toolTimeoutMs = options.toolTimeoutMs ?? TOOL_TIMEOUT_MS;\n }\n\n /**\n * Run one call. Never throws: a failure comes back as `error` with text the\n * model can act on, so a harness loop can always build its tool result.\n */\n async run(name: string, input: Record<string, unknown>): Promise<DirectToolResult> {\n try {\n return await withTimeout(this.execute(name, input), this.toolTimeoutMs, name);\n } catch (err) {\n const text =\n err instanceof ToolsetError\n ? err.message\n : `Error: ${err instanceof Error ? err.message : String(err)}`;\n return { text, error: text };\n }\n }\n\n private async execute(name: string, input: Record<string, unknown>): Promise<DirectToolResult> {\n switch (name) {\n case 'navigate': {\n const out = await this.executor.execute('navigate', { url: input.url });\n return this.afterInput(blocksToText(out.filter((b) => b.type === 'text')));\n }\n case 'read_page': {\n const out = await this.executor.execute('read_page', {\n filter: input.filter,\n ...(typeof input.ref === 'string' && input.ref ? { ref: input.ref } : {}),\n });\n return { text: clampRead(blocksToText(out)) };\n }\n case 'find': {\n const out = await this.executor.execute('find', { query: input.query });\n return { text: clampRead(blocksToText(out)) };\n }\n case 'get_page_text': {\n const out = await this.executor.execute('get_page_text', {});\n return { text: clampRead(blocksToText(out)) };\n }\n case 'click': {\n const target = targetOf(input);\n if (!target) throw new ToolsetError('Error: click needs a ref, or both x and y.');\n const out = await this.executor.execute('left_click', { target });\n return this.afterInput(blocksToText(out));\n }\n case 'type': {\n const value = String(input.text ?? '');\n const target = targetOf(input);\n const said: string[] = [];\n if (target) {\n said.push(blocksToText(await this.executor.execute('left_click', { target })));\n }\n said.push(blocksToText(await this.executor.execute('type', { text: value })));\n return this.afterInput(said.join('\\n'));\n }\n case 'form_input': {\n if (typeof input.ref !== 'string' || !input.ref) {\n throw new ToolsetError('Error: form_input needs a ref from read_page or find.');\n }\n const out = await this.executor.execute('form_input', {\n target: { type: 'ref', ref: input.ref },\n value:\n typeof input.value === 'boolean' || typeof input.value === 'number'\n ? input.value\n : String(input.value ?? ''),\n });\n return this.afterInput(blocksToText(out));\n }\n case 'key': {\n const out = await this.executor.execute('key', { text: input.text });\n return this.afterInput(blocksToText(out));\n }\n case 'scroll': {\n const target = targetOf(input) ?? {\n type: 'coordinate' as const,\n x: Math.round(this.viewport.width / 2),\n y: Math.round(this.viewport.height / 2),\n };\n const out = await this.executor.execute('scroll', {\n scroll_direction: input.direction,\n scroll_amount: input.amount ?? 5,\n target,\n });\n return this.afterInput(blocksToText(out));\n }\n case 'screenshot': {\n const page = await this.activePage();\n return {\n text: `Screenshot of ${await page.url()}`,\n screenshotBase64: await this.capture(page),\n };\n }\n case 'done':\n // A signal tool; every loop ends the session before it would get here.\n return { text: 'Done tool invoked' };\n default:\n throw new ToolsetError(\n `Error: ${name} is not an available tool. Use one of: ${DIRECT_TOOL_NAMES.join(', ')}.`\n );\n }\n }\n\n /**\n * After anything that can change the page: let it settle, then report where\n * the browser is with a screenshot. The agent sees the effect of its action\n * without spending a turn asking.\n */\n private async afterInput(said: string): Promise<DirectToolResult> {\n const page = await this.activePage();\n if (this.settleMs > 0) {\n await page.waitForTimeout(this.settleMs);\n await page.waitForLoadState('domcontentloaded', SETTLE_LOAD_TIMEOUT_MS).catch(() => {});\n }\n const [url, title] = await Promise.all([\n Promise.resolve(page.url()).catch(() => ''),\n page.title().catch(() => ''),\n ]);\n const where = title ? `Now on ${url} (${title.slice(0, 120)})` : `Now on ${url}`;\n return {\n text: said ? `${said}\\n${where}` : where,\n screenshotBase64: await this.capture(page),\n };\n }\n\n private async capture(page: DriverPage): Promise<string> {\n const bytes = await page.screenshot({ type: 'png', timeout: SCREENSHOT_TIMEOUT_MS });\n return Buffer.from(bytes).toString('base64');\n }\n\n private async activePage(): Promise<DriverPage> {\n const page = await this.driver.context.activePage();\n if (!page) throw new ToolsetError('Error: the browser has no open page.');\n return page;\n }\n}\n\n/**\n * Said to an agent that has used every step it was given, on one extra model\n * call offered only `done` (and made to call it). Without it a stepped-out run\n * ends on a bare \"Max steps reached\", which a grader scores near zero even when\n * the transcript holds the answer. The step budget is unchanged: this call can\n * only answer, never act. Shared so that loops on different models stay\n * comparable.\n */\nexport const STEP_BUDGET_FINAL_NOTE =\n 'You have used every step you were given. Do not call any other tool: call `done` now, and in its message give your answer from what you have already found. Say plainly which parts you could not find or finish.';\n\n/** The output of a run whose forced final answer could not be had either. */\nexport const STEP_BUDGET_FALLBACK_OUTPUT = 'Max steps reached';\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCAwLM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAgG7B,mBAAmB;AAAA,0BACtB,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsIvC,SAAS,cAAc,IAAoB;AAChD,SAAO,GAAG,MAAM,IAAI,KAAK,UAAU,EAAE,CAAC;AACxC;;;ACjaO,IAAM,uBAAuB;AAG7B,IAAM,YAAY;AAEzB,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,IAAM,cAAc,oBAAI,IAAI,CAAC,WAAW,aAAa,cAAc,WAAW,CAAC;AAC/E,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,cAAc;AAIb,IAAM,eAAN,cAA2B,MAAM;AAAC;AAkDlC,SAAS,cAAc,UAA2B;AACvD,QAAM,OAAO,SAAS,YAAY,EAAE,QAAQ,YAAY,EAAE;AAC1D,MAAI,SAAS,eAAe,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,WAAW;AAClF,WAAO;AACT,MAAI,SAAS,2BAA4B,QAAO;AAChD,QAAM,KAAK,KAAK,MAAM,8CAA8C;AACpE,MAAI,IAAI;AACN,UAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC;AAC5C,WACE,MAAM,KACN,MAAM,MACN,MAAM,OACL,MAAM,OAAO,MAAM,OACnB,MAAM,OAAO,KAAK,MAAM,KAAK,MAC7B,MAAM,OAAO,MAAM,OACnB,MAAM,OAAO,KAAK,MAAM,KAAK;AAAA,EAElC;AACA,MAAI,KAAK,SAAS,GAAG,GAAG;AACtB,WACE,SAAS,SACT,SAAS,QACT,SAAS,KAAK,IAAI,KAClB,YAAY,KAAK,IAAI,KACrB,KAAK,WAAW,SAAS;AAAA,EAE7B;AACA,SAAO;AACT;AAOO,SAAS,YAAY,MAAc,OAAyD;AACjG,MAAI,SAAS,UAAU,OAAO,MAAM,SAAS,UAAU;AACrD,WAAO,EAAE,GAAG,OAAO,MAAM,IAAI,MAAM,KAAK,MAAM,eAAe;AAAA,EAC/D;AACA,MAAI,SAAS,gBAAgB,WAAW,OAAO;AAC7C,UAAM,IAAI,MAAM;AAChB,WAAO,EAAE,GAAG,OAAO,OAAO,OAAO,MAAM,YAAY,IAAI,IAAI,OAAO,CAAC,EAAE,MAAM,eAAe;AAAA,EAC5F;AACA,SAAO;AACT;AAGO,SAAS,mBAAmB,OAAuB;AAExD,SAAO,MAAM,QAAQ,uCAAuC,GAAG,EAAE,MAAM,GAAG,WAAW;AACvF;AASA,SAAS,YAAY,KAAc,OAA6C;AAC9E,QAAM,IAAK,OAAO,CAAC;AACnB,MAAI,EAAE,SAAS,SAAS,OAAO,EAAE,QAAQ,YAAY,UAAU,cAAc;AAC3E,WAAO,EAAE,MAAM,OAAO,KAAK,EAAE,IAAI;AAAA,EACnC;AACA,MACE,EAAE,SAAS,gBACX,OAAO,EAAE,MAAM,YACf,OAAO,EAAE,MAAM,YACf,UAAU,OACV;AACA,WAAO,EAAE,MAAM,cAAc,GAAG,KAAK,MAAM,EAAE,CAAC,GAAG,GAAG,KAAK,MAAM,EAAE,CAAC,EAAE;AAAA,EACtE;AACA,QAAM,OACJ,UAAU,QACN,iBACA,UAAU,eACR,wBACA;AACR,QAAM,IAAI,aAAa,mBAAmB,IAAI,GAAG;AACnD;AAEA,SAAS,YAAY,OAAgB,KAAa,KAAa,UAA0B;AACvF,QAAM,IAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ;AACxE,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,CAAC,CAAC;AACvC;AAEO,IAAM,yBAAN,MAA6B;AAAA;AAAA,EAEjB,SAAS,oBAAI,IAAoB;AAAA,EAC1C,SAAS;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA,EAEjB,YAAY,QAAuB,UAAiC,CAAC,GAAG;AACtE,SAAK,SAAS;AACd,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EAGQ,QAA0B,QAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlD,QACE,OACA,OAAiC,CAAC,GACN;AAC5B,UAAM,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,WAAW,OAAO,KAAK,MAAM,CAAC;AACrE,SAAK,QAAQ,IAAI,MAAM,MAAM;AAAA,IAAC,CAAC;AAC/B,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,WACZ,OACA,QAC4B;AAC5B,UAAM,UAAwB,CAAC;AAC/B,UAAM,UAA+B,CAAC;AACtC,QAAI,SAAS;AACb,eAAW,QAAQ,OAAO;AACxB,YAAM,QAAQ,YAAY,KAAK,MAAO,KAAK,SAAS,CAAC,CAA6B;AAClF,UAAI,UAAU,QAAQ,SAAS;AAC7B,gBAAQ,KAAK,KAAK,YAAY,KAAK,IAAI,SAAS,CAAC;AACjD,gBAAQ,KAAK;AAAA,UACX,WAAW,KAAK;AAAA,UAChB,MAAM,KAAK;AAAA,UACX;AAAA,UACA,MAAM;AAAA,UACN,SAAS;AAAA,QACX,CAAC;AACD;AAAA,MACF;AACA,UAAI;AACF,cAAM,UAAU,MAAM,KAAK;AAAA,UACzB,KAAK;AAAA,UACJ,KAAK,SAAS,CAAC;AAAA,QAClB;AACA,gBAAQ,KAAK;AAAA,UACX,MAAM;AAAA,UACN,aAAa,KAAK;AAAA,UAClB,cAAc;AAAA,UACd;AAAA,QACF,CAAC;AACD,cAAMC,QAAO,QACV;AAAA,UAAI,CAAC,MACJ,EAAE,SAAS,SAAS,EAAE,OAAO,EAAE,SAAS,kBAAkB,cAAc,CAAC,IAAI;AAAA,QAC/E,EACC,OAAO,OAAO,EACd,KAAK,IAAI;AACZ,cAAMC,SAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO;AACpD,gBAAQ,KAAK;AAAA,UACX,WAAW,KAAK;AAAA,UAChB,MAAM,KAAK;AAAA,UACX;AAAA,UACA,MAAAD;AAAA,UACA,kBACEC,UAASA,OAAM,SAAS,WAAWA,OAAM,OAAO,SAAS,WACrDA,OAAM,OAAO,OACb;AAAA,UACN,SAAS;AAAA,QACX,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,iBAAS;AACT,cAAM,UACJ,eAAe,eACX,IAAI,UACJ,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAChE,gBAAQ,KAAK,KAAK,YAAY,KAAK,IAAI,OAAO,CAAC;AAC/C,gBAAQ,KAAK,EAAE,WAAW,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,MAC3F;AAAA,IACF;AACA,WAAO,EAAE,SAAS,QAAQ;AAAA,EAC5B;AAAA,EAEQ,YAAY,WAAmBD,OAA0B;AAC/D,WAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,cAAc;AAAA,MACd,UAAU;AAAA,MACV,SAASA;AAAA,IACX;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,QAAQ,MAAc,OAAwD;AAClF,QAAI,iBAAiB,IAAI,IAAI,GAAG;AAC9B,YAAM,IAAI,aAAa,UAAU,IAAI,sCAAsC;AAAA,IAC7E;AACA,QAAI,YAAY,IAAI,IAAI,EAAG,QAAO,CAAC,MAAM,KAAK,UAAU,MAAM,KAAK,CAAC;AAEpE,UAAM,OAAO,MAAM,KAAK,QAAQ,MAAM,MAAM;AAC5C,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,eAAO,KAAK,SAAS,MAAM,KAAK;AAAA,MAClC,KAAK;AACH,eAAO,CAAC,MAAM,KAAK,WAAW,IAAI,CAAC;AAAA,MACrC,KAAK;AACH,eAAO,CAAC,MAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAAA,MACtC,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,gBAAgB;AACnB,YAAI,KAAK,QAAQ,QAAQ,eAAe,SAAS,eAAe;AAC9D,gBAAM,SAAS,YAAY,MAAM,QAAQ,KAAK;AAC9C,gBAAM,QAAQ,MAAM,KAAK,QAAQ,MAAM,MAAM;AAC7C,gBAAM,SAAS,SAAS,aAAa,KAAK;AAC1C,cAAI,OAAQ,QAAO,CAAC,KAAK,YAAY,MAAM,CAAC,CAAC;AAAA,QAC/C;AACA,eAAO,CAAC,KAAK,MAAM,KAAK,MAAM,MAAM,MAAM,KAAK,CAAC,CAAC;AAAA,MACnD;AAAA,MACA,KAAK,SAAS;AACZ,cAAM,EAAE,GAAG,EAAE,IAAI,MAAM,KAAK,MAAM,MAAM,YAAY,MAAM,QAAQ,KAAK,CAAC;AACxE,cAAM,KAAK,MAAM,GAAG,CAAC;AACrB,eAAO,CAAC,KAAK,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC;AAAA,MACzC;AAAA,MACA,KAAK,cAAc;AACjB,cAAM,EAAE,GAAG,EAAE,IAAI,MAAM,KAAK,MAAM,MAAM,YAAY,MAAM,QAAQ,YAAY,CAAC;AAC/E,cAAM,KAAK,MAAM,GAAG,CAAC;AACrB,eAAO,CAAC,KAAK,uBAAuB,CAAC,KAAK,CAAC,GAAG,CAAC;AAAA,MACjD;AAAA,MACA,KAAK,mBAAmB;AACtB,cAAM,OAAO,MAAM,KAAK,MAAM,MAAM,YAAY,MAAM,MAAM,YAAY,CAAC;AACzE,cAAM,KAAK,MAAM,KAAK,MAAM,MAAM,YAAY,MAAM,QAAQ,YAAY,CAAC;AACzE,cAAM,KAAK,YAAY,KAAK,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,CAAC;AACjD,eAAO,CAAC,KAAK,iBAAiB,KAAK,CAAC,KAAK,KAAK,CAAC,SAAS,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC;AAAA,MAC3E;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,cAAM,IAAI;AAAA,UACR,UAAU,IAAI;AAAA,QAChB;AAAA,MACF,KAAK;AACH,eAAO,CAAC,KAAK,MAAM,KAAK,OAAO,MAAM,KAAK,CAAC,CAAC;AAAA,MAC9C,KAAK,aAAa;AAChB,cAAM,SAAS,YAAY,MAAM,QAAQ,KAAK;AAC9C,cAAM,IAAI,MAAM,KAAK,SAAwB,MAAM;AAAA,UACjD,IAAI;AAAA,UACJ,KAAK,OAAO;AAAA,UACZ,QAAQ;AAAA,QACV,CAAC;AACD,YAAI,CAAC,EAAE,GAAI,OAAM,IAAI,aAAa,UAAU,EAAE,KAAK,EAAE;AACrD,eAAO,CAAC,KAAK,YAAY,OAAO,GAAG,YAAY,CAAC;AAAA,MAClD;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,QAAQ,OAAO,MAAM,QAAQ,EAAE;AACrC,cAAM,UAAU,MAAM,KAAK,YAAY,MAAM,KAAK;AAClD,YAAI,QAAS,QAAO,CAAC,KAAK,OAAO,CAAC;AAClC,cAAM,KAAK,KAAK,KAAK;AACrB,eAAO,CAAC,KAAK,SAAS,MAAM,MAAM,aAAa,MAAM,WAAW,IAAI,KAAK,GAAG,EAAE,CAAC;AAAA,MACjF;AAAA,MACA,KAAK,OAAO;AACV,cAAM,OAAO,wBAAwB,OAAO,MAAM,QAAQ,EAAE,CAAC;AAC7D,YAAI,KAAK,WAAW,EAAG,OAAM,IAAI,aAAa,wBAAwB;AACtE,cAAM,SAAS,KAAK,MAAM,YAAY,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC;AAC9D,cAAM,UAAU,MAAM,KAAK,WAAW,MAAM,IAAI;AAChD,YAAI,QAAS,QAAO,CAAC,KAAK,OAAO,CAAC;AAClC,iBAAS,IAAI,GAAG,IAAI,QAAQ,IAAK,YAAW,KAAK,KAAM,OAAM,KAAK,SAAS,CAAC;AAC5E,eAAO,CAAC,KAAK,WAAW,KAAK,KAAK,GAAG,CAAC,GAAG,SAAS,IAAI,KAAK,MAAM,KAAK,EAAE,EAAE,CAAC;AAAA,MAC7E;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,UAAU,YAAY,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAClE,cAAM,KAAK,eAAe,UAAU,GAAI;AACxC,eAAO,CAAC,KAAK,UAAU,OAAO,GAAG,CAAC;AAAA,MACpC;AAAA,MACA,KAAK,aAAa;AAChB,cAAM,SACJ,MAAM,WAAW,iBAAiB,MAAM,WAAW,QAAQ,MAAM,SAAS;AAC5E,cAAM,QAAQ,KAAK,MAAM,YAAY,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;AAC7D,cAAM,MAAM,OAAO,MAAM,QAAQ,WAAW,MAAM,MAAM;AACxD,cAAM,IAAI,MAAM,KAAK,SAAqB,MAAM,EAAE,IAAI,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAClF,YAAI,CAAC,EAAE,GAAI,OAAM,IAAI,aAAa,UAAU,EAAE,KAAK,EAAE;AACrD,eAAO,CAAC,KAAK,EAAE,IAAI,CAAC;AAAA,MACtB;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,IAAI,MAAM,KAAK,SAAqB,MAAM;AAAA,UAC9C,IAAI;AAAA,UACJ,OAAO,OAAO,MAAM,SAAS,EAAE;AAAA,QACjC,CAAC;AACD,YAAI,CAAC,EAAE,GAAI,OAAM,IAAI,aAAa,UAAU,EAAE,KAAK,EAAE;AACrD,eAAO,CAAC,KAAK,EAAE,IAAI,CAAC;AAAA,MACtB;AAAA,MACA,KAAK,iBAAiB;AACpB,cAAM,IAAI,MAAM,KAAK,SAAqB,MAAM,EAAE,IAAI,OAAO,CAAC;AAC9D,YAAI,CAAC,EAAE,GAAI,OAAM,IAAI,aAAa,UAAU,EAAE,KAAK,EAAE;AACrD,eAAO,CAAC,KAAK,EAAE,IAAI,CAAC;AAAA,MACtB;AAAA,MACA,KAAK,cAAc;AACjB,cAAM,SAAS,YAAY,MAAM,QAAQ,KAAK;AAC9C,cAAM,QAAQ,MAAM;AACpB,YAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW;AACxF,gBAAM,IAAI,aAAa,4DAA4D;AAAA,QACrF;AACA,YAAI,KAAK,QAAQ,QAAQ,wBAAwB,OAAO,UAAU,WAAW;AAC3E,gBAAM,UACH,mBAAmB,OAAO,KAAK,CAAC,KAAK,YAAY,8BAA8B,KAC/E,MAAM,KAAK,aAAa,MAAM,MAAM;AACvC,cAAI,QAAS,QAAO,CAAC,KAAK,OAAO,CAAC;AAAA,QACpC;AACA,cAAM,IAAI,MAAM,KAAK,SAAqB,MAAM,EAAE,IAAI,QAAQ,KAAK,OAAO,KAAK,MAAM,CAAC;AACtF,YAAI,CAAC,EAAE,GAAI,OAAM,IAAI,aAAa,UAAU,EAAE,KAAK,EAAE;AACrD,eAAO,CAAC,KAAK,EAAE,WAAW,CAAC;AAAA,MAC7B;AAAA,MACA;AACE,cAAM,IAAI;AAAA,UACR,UAAU,IAAI;AAAA,QAChB;AAAA,IACJ;AAAA,EACF;AAAA;AAAA,EAIA,MAAc,SAAS,MAAkB,OAAwD;AAC/F,UAAM,MAAM,OAAO,MAAM,OAAO,EAAE,EAAE,KAAK;AAEzC,QAAI,QAAQ,UAAU,QAAQ,WAAW;AACvC,aAAO,QAAQ,SAAS,KAAK,OAAO,IAAI,KAAK,UAAU;AACvD,aAAO,CAAC,KAAK,QAAQ,GAAG,OAAO,MAAM,KAAK,IAAI,CAAC,EAAE,GAAG,MAAM,KAAK,aAAa,CAAC;AAAA,IAC/E;AACA,QAAI;AACJ,QAAI;AAGF,YAAM,YACJ,2BAA2B,KAAK,GAAG,KACnC,0EAA0E,KAAK,GAAG;AACpF,YAAM,IAAI,IAAI,YAAY,MAAM,WAAW,GAAG,EAAE;AAAA,IAClD,QAAQ;AACN,YAAM,IAAI,aAAa,WAAW,IAAI,MAAM,GAAG,GAAG,CAAC,uBAAuB;AAAA,IAC5E;AAGA,QACE,YAAY,KAAK,IAAI,QAAQ,KAC7B,CAAC,IAAI,SAAS,SAAS,GAAG,KAC1B,IAAI,aAAa,aACjB;AACA,YAAM,IAAI;AAAA,QACR,WAAW,IAAI,MAAM,GAAG,GAAG,CAAC;AAAA,MAC9B;AAAA,IACF;AACA,QAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAAU;AACzD,YAAM,IAAI,aAAa,kEAAkE;AAAA,IAC3F;AACA,QAAI,CAAC,KAAK,QAAQ,qBAAqB,cAAc,IAAI,QAAQ,GAAG;AAClE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,UAAU,KAAK,QAAQ,kBAAkB,IAAI,IAAI;AACvD,QAAI,QAAS,OAAM,IAAI,aAAa,8BAA8B,OAAO,EAAE;AAC3E,UAAM,KAAK,KAAK,IAAI,MAAM;AAAA,MACxB,WAAW;AAAA,MACX,GAAI,KAAK,QAAQ,sBAAsB,EAAE,WAAW,KAAK,QAAQ,oBAAoB,IAAI,CAAC;AAAA,IAC5F,CAAC;AACD,WAAO,CAAC,KAAK,gBAAgB,MAAM,KAAK,IAAI,CAAC,EAAE,GAAG,MAAM,KAAK,aAAa,CAAC;AAAA,EAC7E;AAAA,EAEA,MAAc,WAAW,MAA+D;AACtF,UAAM,QAAQ,MAAM,KAAK,WAAW;AAAA,MAClC,MAAM;AAAA,MACN,GAAI,KAAK,QAAQ,sBAAsB,EAAE,SAAS,KAAK,QAAQ,oBAAoB,IAAI,CAAC;AAAA,IAC1F,CAAC;AACD,WAAO,MAAM,MAAM,SAAS,QAAQ,CAAC;AAAA,EACvC;AAAA,EAEA,MAAc,KACZ,MACA,OAC6C;AAC7C,UAAM,SAAS,MAAM;AACrB,QACE,CAAC,MAAM,QAAQ,MAAM,KACrB,OAAO,WAAW,KAClB,CAAC,OAAO,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ,GAC1C;AACA,YAAM,IAAI,aAAa,4CAA4C;AAAA,IACrE;AACA,UAAM,CAAC,IAAI,IAAI,IAAI,EAAE,IAAI;AACzB,UAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;AAC9B,UAAM,SAAS,KAAK,IAAI,KAAK,EAAE;AAC/B,QAAI,QAAQ,KAAK,SAAS,EAAG,OAAM,IAAI,aAAa,8BAA8B;AAGlF,UAAM,QAAQ,MAAM,KAAK,WAAW;AAAA,MAClC,MAAM;AAAA,MACN,MAAM,EAAE,GAAG,KAAK,IAAI,IAAI,EAAE,GAAG,GAAG,KAAK,IAAI,IAAI,EAAE,GAAG,OAAO,OAAO;AAAA,MAChE,GAAI,KAAK,QAAQ,sBAAsB,EAAE,SAAS,KAAK,QAAQ,oBAAoB,IAAI,CAAC;AAAA,IAC1F,CAAC;AACD,WAAO,MAAM,MAAM,SAAS,QAAQ,CAAC;AAAA,EACvC;AAAA,EAEA,MAAc,MACZ,MACA,MACA,OACiB;AACjB,QAAI,MAAM,QAAQ,MAAM,SAAS,IAAI,MAAM,UAAU,SAAS,IAAI,MAAM,WAAW;AACjF,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,YAAY,MAAM,QAAQ,KAAK;AAC9C,UAAM,EAAE,GAAG,EAAE,IAAI,MAAM,KAAK,MAAM,MAAM,MAAM;AAC9C,UAAM,SAAS,SAAS,gBAAgB,UAAU,SAAS,iBAAiB,WAAW;AACvF,UAAM,aAAa,SAAS,iBAAiB,IAAI,SAAS,iBAAiB,IAAI;AAC/E,UAAM,KAAK,MAAM,GAAG,GAAG,EAAE,QAAQ,WAAW,CAAC;AAC7C,UAAM,QAAQ,OAAO,SAAS,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC;AACnF,WAAO,GAAG,KAAK,QAAQ,KAAK,GAAG,CAAC,OAAO,KAAK;AAAA,EAC9C;AAAA,EAEA,MAAc,OAAO,MAAkB,OAAiD;AACtF,UAAM,YAAY,MAAM;AACxB,QACE,cAAc,QACd,cAAc,UACd,cAAc,UACd,cAAc,SACd;AACA,YAAM,IAAI,aAAa,0DAA0D;AAAA,IACnF;AACA,UAAM,SAAS,KAAK,MAAM,YAAY,MAAM,eAAe,GAAG,IAAI,CAAC,CAAC;AACpE,UAAM,EAAE,GAAG,EAAE,IAAI,MAAM,KAAK,MAAM,MAAM,YAAY,MAAM,QAAQ,KAAK,CAAC;AACxE,UAAM,QAAQ,SAAS;AACvB,UAAM,KAAK,cAAc,SAAS,CAAC,QAAQ,cAAc,UAAU,QAAQ;AAC3E,UAAM,KAAK,cAAc,OAAO,CAAC,QAAQ,cAAc,SAAS,QAAQ;AACxE,UAAM,KAAK,OAAO,GAAG,GAAG,IAAI,EAAE;AAC9B,WAAO,YAAY,SAAS,IAAI,MAAM,QAAQ,CAAC,KAAK,CAAC;AAAA,EACvD;AAAA,EAEA,MAAc,UAAU,MAAc,OAAuD;AAC3F,UAAM,MAAM,KAAK,OAAO;AACxB,QAAI,SAAS,YAAa,QAAO,KAAK,aAAa;AACnD,QAAI,SAAS,WAAW;AAGtB,iBAAW,YAAY,MAAM,IAAI,MAAM,EAAG,MAAK,MAAM,QAAQ;AAC7D,YAAME,QAAO,MAAM,IAAI,QAAQ;AAC/B,aAAO,KAAK,aAAa,CAAC,EAAE,MAAM,cAAc,QAAQ,KAAK,MAAMA,KAAI,EAAE,CAAC,CAAC;AAAA,IAC7E;AACA,UAAM,OAAO,MAAM,KAAK,QAAQ,MAAM,QAAQ,IAAI;AAClD,QAAI,SAAS,cAAc;AACzB,YAAM,IAAI,cAAc,IAAI;AAC5B,aAAO,KAAK,aAAa;AAAA,IAC3B;AAEA,UAAM,aAAa,MAAM,IAAI,WAAW,IAAI,OAAO,KAAK;AACxD,UAAM,KAAK,MAAM;AACjB,QAAI,WAAW;AACb,YAAM,YAAY,MAAM,IAAI,MAAM;AAClC,UAAI,UAAU,SAAS,EAAG,OAAM,IAAI,cAAc,UAAU,UAAU,SAAS,CAAC,CAAC;AAAA,IACnF;AACA,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA,EAIQ,MAAM,MAA0B;AACtC,QAAI,KAAK,KAAK,OAAO,IAAI,KAAK,EAAE;AAChC,QAAI,CAAC,IAAI;AACP,WAAK,UAAU;AACf,WAAK,OAAO,KAAK,MAAM;AACvB,WAAK,OAAO,IAAI,KAAK,IAAI,EAAE;AAAA,IAC7B;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,QAAQ,OAAgB,WAAW,OAA4B;AAC3E,UAAM,MAAM,KAAK,OAAO;AACxB,QAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,iBAAWA,SAAQ,MAAM,IAAI,MAAM,EAAG,KAAI,KAAK,MAAMA,KAAI,MAAM,MAAO,QAAOA;AAC7E,YAAM,IAAI;AAAA,QACR,6BAA6B,KAAK;AAAA,MACpC;AAAA,IACF;AACA,QAAI,SAAU,OAAM,IAAI,aAAa,4BAA4B;AACjE,UAAM,OAAO,MAAM,IAAI,WAAW;AAClC,QAAI,CAAC,KAAM,OAAM,IAAI,aAAa,yDAAyD;AAC3F,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,MAAM,MAAkB,QAAmD;AACvF,QAAI,OAAO,SAAS,aAAc,QAAO,EAAE,GAAG,OAAO,GAAG,GAAG,OAAO,EAAE;AACpE,QAAI,IAAI,MAAM,KAAK,SAAwB,MAAM;AAAA,MAC/C,IAAI;AAAA,MACJ,KAAK,OAAO;AAAA,MACZ,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,EAAE,MAAM,CAAC,EAAE,YAAY;AAGzB,YAAM,KAAK,eAAe,GAAG;AAC7B,UAAI,MAAM,KAAK,SAAwB,MAAM;AAAA,QAC3C,IAAI;AAAA,QACJ,KAAK,OAAO;AAAA,QACZ,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,QAAI,CAAC,EAAE,GAAI,OAAM,IAAI,aAAa,UAAU,EAAE,KAAK,EAAE;AACrD,QAAI,CAAC,EAAE,YAAY;AACjB,YAAM,IAAI;AAAA,QACR,UAAU,OAAO,GAAG;AAAA,MACtB;AAAA,IACF;AACA,WAAO,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE;AAAA,EAC1B;AAAA;AAAA;AAAA,EAKA,MAAc,QAAQ,MAAkB,QAAqD;AAC3F,UAAM,IAAI,MAAM,KAAK,SAAwB,MAAM,EAAE,IAAI,WAAW,OAAO,CAAC;AAE5E,WAAO,EAAE,KAAK,EAAE,QAAQ;AAAA,EAC1B;AAAA,EAEA,MAAc,aAAa,MAAkB,QAA+C;AAC1F,UAAM,QAAQ,MAAM,KAAK,QAAQ,MAAM,MAAM;AAC7C,UAAM,SAAS,SAAS,qBAAqB,KAAK;AAClD,WAAO,SAAS,YAAY,MAAM,IAAI;AAAA,EACxC;AAAA;AAAA,EAGA,MAAc,YAAY,MAAkB,OAAuC;AACjF,UAAM,SAAS,KAAK,QAAQ;AAC5B,QAAI,CAAC,OAAQ,QAAO;AACpB,QAAI,OAAO,sBAAsB;AAC/B,UAAI,mBAAmB,KAAK,EAAG,QAAO,YAAY,8BAA8B;AAChF,YAAM,UAAU,MAAM,KAAK,aAAa,MAAM,EAAE,MAAM,UAAU,CAAC;AACjE,UAAI,QAAS,QAAO;AAAA,IACtB;AAEA,QAAI,OAAO,eAAe,SAAS,KAAK,KAAK,GAAG;AAC9C,YAAM,QAAQ,MAAM,KAAK,QAAQ,MAAM,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAM,SAAS,SAAS,kBAAkB,KAAK;AAC/C,UAAI,OAAQ,QAAO,YAAY,MAAM;AAAA,IACvC;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAc,WAAW,MAAkB,MAAiD;AAC1F,QAAI,CAAC,KAAK,QAAQ,QAAQ,YAAa,QAAO;AAC9C,UAAM,eAAe,KAAK,KAAK,CAAC,MAAM,6BAA6B,KAAK,CAAC,CAAC;AAC1E,UAAM,eAAe,KAAK,KAAK,CAAC,MAAM,mBAAmB,KAAK,CAAC,CAAC;AAChE,QAAI,CAAC,gBAAgB,CAAC,aAAc,QAAO;AAC3C,UAAM,QAAQ,MAAM,KAAK,QAAQ,MAAM,EAAE,MAAM,UAAU,CAAC;AAC1D,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,SACH,MAAM,YAAY,aAAa,KAAK,KAAO,gBAAgB,kBAAkB,KAAK;AACrF,WAAO,SAAS,YAAY,MAAM,IAAI;AAAA,EACxC;AAAA,EAEQ,SAAY,MAAkB,IAAwB;AAC5D,WAAO,KAAK,SAAY,cAAc,EAAE,CAAC;AAAA,EAC3C;AAAA,EAEA,MAAc,aAAa,cAAqE;AAC9F,UAAM,MAAM,KAAK,OAAO;AACxB,UAAM,CAAC,OAAO,MAAM,IAAI,MAAM,QAAQ,IAAI,CAAC,IAAI,MAAM,GAAG,IAAI,WAAW,CAAC,CAAC;AACzE,UAAM,OAAO,MAAM,QAAQ;AAAA,MACzB,MAAM,MAAM,GAAG,GAAG,EAAE,IAAI,OAAO,UAAU;AAAA,QACvC,QAAQ,KAAK,MAAM,IAAI;AAAA,QACvB,OAAO,mBAAmB,MAAM,KAAK,MAAM,EAAE,MAAM,MAAM,EAAE,CAAC;AAAA,QAC5D,KAAK,mBAAmB,MAAM,QAAQ,QAAQ,KAAK,IAAI,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;AAAA,QACzE,GAAI,UAAU,KAAK,OAAO,OAAO,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAAA,MAC5D,EAAE;AAAA,IACJ;AACA,QAAI,KAAK,SAAS,KAAK,CAAC,KAAK,KAAK,CAAC,MAAM,EAAE,MAAM,EAAG,MAAK,KAAK,SAAS,CAAC,EAAE,SAAS;AACnF,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA,GAAI,gBAAgB,aAAa,SAAS,IAAI,EAAE,eAAe,aAAa,IAAI,CAAC;AAAA,IACnF;AAAA,EACF;AACF;AAEA,SAAS,KAAK,OAAkD;AAC9D,SAAO,EAAE,MAAM,QAAQ,MAAM,MAAM;AACrC;AAEA,SAAS,MAAM,MAAkD;AAC/D,SAAO,EAAE,MAAM,SAAS,QAAQ,EAAE,MAAM,UAAU,YAAY,aAAa,KAAK,EAAE;AACpF;AAEA,SAAS,cAAc,OAA6B;AAClD,QAAM,SAAS,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,MAAM;AAC9C,SAAO,SAAS,OAAO,OAAO,MAAM,KAAK,OAAO,GAAG,KAAK,GAAG,MAAM,KAAK,MAAM;AAC9E;;;AClpBO,IAAM,iBAAiB;AAGvB,IAAM,kBAAkB;AAC/B,IAAM,wBAAwB;AAC9B,IAAM,wBAAwB;AAE9B,IAAM,YAAY;AAClB,IAAM,yBAAyB;AAE/B,IAAM,oBAAoB;AAAA,EACxB,KAAK;AAAA,IACH,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,GAAG;AAAA,IACD,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,GAAG;AAAA,IACD,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AACF;AAQO,IAAM,oBAAsC;AAAA,EACjD;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY,EAAE,KAAK,EAAE,MAAM,UAAU,aAAa,mCAAmC,EAAE;AAAA,MACvF,UAAU,CAAC,KAAK;AAAA,IAClB;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,MAAM,CAAC,WAAW,eAAe,KAAK,EAAE;AAAA,QAClE,KAAK,EAAE,MAAM,UAAU,aAAa,8CAA8C;AAAA,MACpF;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,aAAa,oBAAoB,EAAE;AAAA,MAC1E,UAAU,CAAC,OAAO;AAAA,IACpB;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,EAAE,MAAM,UAAU,YAAY,CAAC,EAAE;AAAA,EAC/C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY,EAAE,MAAM,UAAU,YAAY,kBAAkB;AAAA,EAC9D;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,QACV,MAAM,EAAE,MAAM,UAAU,aAAa,oBAAoB;AAAA,QACzD,GAAG;AAAA,MACL;AAAA,MACA,UAAU,CAAC,MAAM;AAAA,IACnB;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,QACV,KAAK,EAAE,MAAM,UAAU,aAAa,0CAA0C;AAAA;AAAA;AAAA;AAAA,QAI9E,OAAO,EAAE,MAAM,UAAU,aAAa,6CAA6C;AAAA,MACrF;AAAA,MACA,UAAU,CAAC,OAAO,OAAO;AAAA,IAC3B;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY,EAAE,MAAM,EAAE,MAAM,UAAU,aAAa,mBAAmB,EAAE;AAAA,MACxE,UAAU,CAAC,MAAM;AAAA,IACnB;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,QACV,WAAW,EAAE,MAAM,UAAU,MAAM,CAAC,MAAM,QAAQ,QAAQ,OAAO,EAAE;AAAA,QACnE,QAAQ,EAAE,MAAM,UAAU,aAAa,oBAAoB;AAAA,QAC3D,GAAG;AAAA,MACL;AAAA,MACA,UAAU,CAAC,WAAW;AAAA,IACxB;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,EAAE,MAAM,UAAU,YAAY,CAAC,EAAE;AAAA,EAC/C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,aAAa,WAAW,EAAE;AAAA,MACnE,UAAU,CAAC,SAAS;AAAA,IACtB;AAAA,EACF;AACF;AAEO,IAAM,oBAA+C,kBAAkB,IAAI,CAAC,MAAM,EAAE,IAAI;AAExF,SAAS,UAAUC,OAAsB;AAC9C,MAAIA,MAAK,UAAU,eAAgB,QAAOA;AAC1C,SAAO,GAAGA,MAAK,MAAM,GAAG,cAAc,CAAC;AAAA,gBAAmB,cAAc;AAC1E;AAMO,SAAS,SACd,OACoF;AACpF,MAAI,OAAO,MAAM,QAAQ,YAAY,MAAM,IAAI,KAAK,GAAG;AACrD,WAAO,EAAE,MAAM,OAAO,KAAK,MAAM,IAAI,KAAK,EAAE;AAAA,EAC9C;AACA,QAAM,IAAI,OAAO,MAAM,CAAC;AACxB,QAAM,IAAI,OAAO,MAAM,CAAC;AACxB,MAAI,MAAM,MAAM,UAAa,MAAM,MAAM,UAAa,OAAO,SAAS,CAAC,KAAK,OAAO,SAAS,CAAC,GAAG;AAC9F,WAAO,EAAE,MAAM,cAAc,GAAG,EAAE;AAAA,EACpC;AACA,SAAO;AACT;AAEA,SAAS,aAAa,QAAoC;AACxD,SAAO,OACJ,IAAI,CAAC,MAAM;AACV,UAAM,QAAQ;AACd,WAAO,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAAA,EAChF,CAAC,EACA,OAAO,OAAO,EACd,KAAK,IAAI;AACd;AAEA,SAAS,YAAe,MAAkB,IAAY,MAA0B;AAC9E,MAAI;AACJ,QAAM,UAAU,IAAI,QAAe,CAAC,GAAG,WAAW;AAChD,YAAQ;AAAA,MACN,MAAM,OAAO,IAAI,aAAa,UAAU,IAAI,0BAA0B,KAAK,GAAI,IAAI,CAAC;AAAA,MACpF;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO,QAAQ,KAAK,CAAC,MAAM,OAAO,CAAC,EAAE,QAAQ,MAAM,aAAa,KAAK,CAAC;AACxE;AAsBO,IAAM,qBAAN,MAAyB;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAAuB,SAAoC;AACrE,SAAK,SAAS;AACd,SAAK,WAAW,IAAI,uBAAuB,QAAQ;AAAA,MACjD,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,MACnD,GAAI,QAAQ,oBAAoB,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAAA,MAC/D,GAAI,QAAQ,kBAAkB,EAAE,iBAAiB,QAAQ,gBAAgB,IAAI,CAAC;AAAA,IAChF,CAAC;AACD,SAAK,WAAW,QAAQ;AACxB,SAAK,WAAW,QAAQ,YAAY;AACpC,SAAK,gBAAgB,QAAQ,iBAAiB;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,IAAI,MAAc,OAA2D;AACjF,QAAI;AACF,aAAO,MAAM,YAAY,KAAK,QAAQ,MAAM,KAAK,GAAG,KAAK,eAAe,IAAI;AAAA,IAC9E,SAAS,KAAK;AACZ,YAAMA,QACJ,eAAe,eACX,IAAI,UACJ,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAChE,aAAO,EAAE,MAAAA,OAAM,OAAOA,MAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,MAAc,OAA2D;AAC7F,YAAQ,MAAM;AAAA,MACZ,KAAK,YAAY;AACf,cAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC;AACtE,eAAO,KAAK,WAAW,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,CAAC,CAAC;AAAA,MAC3E;AAAA,MACA,KAAK,aAAa;AAChB,cAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,aAAa;AAAA,UACnD,QAAQ,MAAM;AAAA,UACd,GAAI,OAAO,MAAM,QAAQ,YAAY,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,QACzE,CAAC;AACD,eAAO,EAAE,MAAM,UAAU,aAAa,GAAG,CAAC,EAAE;AAAA,MAC9C;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,QAAQ,EAAE,OAAO,MAAM,MAAM,CAAC;AACtE,eAAO,EAAE,MAAM,UAAU,aAAa,GAAG,CAAC,EAAE;AAAA,MAC9C;AAAA,MACA,KAAK,iBAAiB;AACpB,cAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,iBAAiB,CAAC,CAAC;AAC3D,eAAO,EAAE,MAAM,UAAU,aAAa,GAAG,CAAC,EAAE;AAAA,MAC9C;AAAA,MACA,KAAK,SAAS;AACZ,cAAM,SAAS,SAAS,KAAK;AAC7B,YAAI,CAAC,OAAQ,OAAM,IAAI,aAAa,4CAA4C;AAChF,cAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,cAAc,EAAE,OAAO,CAAC;AAChE,eAAO,KAAK,WAAW,aAAa,GAAG,CAAC;AAAA,MAC1C;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,QAAQ,OAAO,MAAM,QAAQ,EAAE;AACrC,cAAM,SAAS,SAAS,KAAK;AAC7B,cAAM,OAAiB,CAAC;AACxB,YAAI,QAAQ;AACV,eAAK,KAAK,aAAa,MAAM,KAAK,SAAS,QAAQ,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,QAC/E;AACA,aAAK,KAAK,aAAa,MAAM,KAAK,SAAS,QAAQ,QAAQ,EAAE,MAAM,MAAM,CAAC,CAAC,CAAC;AAC5E,eAAO,KAAK,WAAW,KAAK,KAAK,IAAI,CAAC;AAAA,MACxC;AAAA,MACA,KAAK,cAAc;AACjB,YAAI,OAAO,MAAM,QAAQ,YAAY,CAAC,MAAM,KAAK;AAC/C,gBAAM,IAAI,aAAa,uDAAuD;AAAA,QAChF;AACA,cAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,cAAc;AAAA,UACpD,QAAQ,EAAE,MAAM,OAAO,KAAK,MAAM,IAAI;AAAA,UACtC,OACE,OAAO,MAAM,UAAU,aAAa,OAAO,MAAM,UAAU,WACvD,MAAM,QACN,OAAO,MAAM,SAAS,EAAE;AAAA,QAChC,CAAC;AACD,eAAO,KAAK,WAAW,aAAa,GAAG,CAAC;AAAA,MAC1C;AAAA,MACA,KAAK,OAAO;AACV,cAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,OAAO,EAAE,MAAM,MAAM,KAAK,CAAC;AACnE,eAAO,KAAK,WAAW,aAAa,GAAG,CAAC;AAAA,MAC1C;AAAA,MACA,KAAK,UAAU;AACb,cAAM,SAAS,SAAS,KAAK,KAAK;AAAA,UAChC,MAAM;AAAA,UACN,GAAG,KAAK,MAAM,KAAK,SAAS,QAAQ,CAAC;AAAA,UACrC,GAAG,KAAK,MAAM,KAAK,SAAS,SAAS,CAAC;AAAA,QACxC;AACA,cAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,UAAU;AAAA,UAChD,kBAAkB,MAAM;AAAA,UACxB,eAAe,MAAM,UAAU;AAAA,UAC/B;AAAA,QACF,CAAC;AACD,eAAO,KAAK,WAAW,aAAa,GAAG,CAAC;AAAA,MAC1C;AAAA,MACA,KAAK,cAAc;AACjB,cAAM,OAAO,MAAM,KAAK,WAAW;AACnC,eAAO;AAAA,UACL,MAAM,iBAAiB,MAAM,KAAK,IAAI,CAAC;AAAA,UACvC,kBAAkB,MAAM,KAAK,QAAQ,IAAI;AAAA,QAC3C;AAAA,MACF;AAAA,MACA,KAAK;AAEH,eAAO,EAAE,MAAM,oBAAoB;AAAA,MACrC;AACE,cAAM,IAAI;AAAA,UACR,UAAU,IAAI,0CAA0C,kBAAkB,KAAK,IAAI,CAAC;AAAA,QACtF;AAAA,IACJ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,WAAW,MAAyC;AAChE,UAAM,OAAO,MAAM,KAAK,WAAW;AACnC,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,KAAK,eAAe,KAAK,QAAQ;AACvC,YAAM,KAAK,iBAAiB,oBAAoB,sBAAsB,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACxF;AACA,UAAM,CAAC,KAAK,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,MACrC,QAAQ,QAAQ,KAAK,IAAI,CAAC,EAAE,MAAM,MAAM,EAAE;AAAA,MAC1C,KAAK,MAAM,EAAE,MAAM,MAAM,EAAE;AAAA,IAC7B,CAAC;AACD,UAAM,QAAQ,QAAQ,UAAU,GAAG,KAAK,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,UAAU,GAAG;AAC9E,WAAO;AAAA,MACL,MAAM,OAAO,GAAG,IAAI;AAAA,EAAK,KAAK,KAAK;AAAA,MACnC,kBAAkB,MAAM,KAAK,QAAQ,IAAI;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,MAAmC;AACvD,UAAM,QAAQ,MAAM,KAAK,WAAW,EAAE,MAAM,OAAO,SAAS,sBAAsB,CAAC;AACnF,WAAO,OAAO,KAAK,KAAK,EAAE,SAAS,QAAQ;AAAA,EAC7C;AAAA,EAEA,MAAc,aAAkC;AAC9C,UAAM,OAAO,MAAM,KAAK,OAAO,QAAQ,WAAW;AAClD,QAAI,CAAC,KAAM,OAAM,IAAI,aAAa,sCAAsC;AACxE,WAAO;AAAA,EACT;AACF;","names":["text","text","text","image","page","text"]}
@@ -0,0 +1,117 @@
1
+ // src/credentials.ts
2
+ import { randomUUID } from "crypto";
3
+ import { chmod, mkdir, readFile, rename, rm, writeFile } from "fs/promises";
4
+ import { homedir } from "os";
5
+ import { join } from "path";
6
+ import { z } from "zod";
7
+ var PROVIDERS = ["anthropic", "openai"];
8
+ var DEFAULT_MODELS = {
9
+ anthropic: "claude-opus-5-5",
10
+ openai: "gpt-5"
11
+ };
12
+ var DEFAULT_BASE_URLS = {
13
+ anthropic: "https://api.anthropic.com",
14
+ openai: "https://api.openai.com/v1"
15
+ };
16
+ var KEY_ENV = {
17
+ anthropic: "ANTHROPIC_API_KEY",
18
+ openai: "OPENAI_API_KEY"
19
+ };
20
+ var ConnectionSchema = z.object({
21
+ provider: z.enum(PROVIDERS),
22
+ model: z.string().min(1).max(200),
23
+ base_url: z.url({ protocol: /^https?$/ }).nullish(),
24
+ api_key: z.string().max(500).nullish()
25
+ });
26
+ var CredentialsSchema = z.object({
27
+ connection: ConnectionSchema.optional(),
28
+ cloud: z.object({
29
+ server: z.string(),
30
+ client: z.record(z.string(), z.unknown()).optional(),
31
+ tokens: z.record(z.string(), z.unknown()).optional(),
32
+ workspace: z.object({ id: z.string(), slug: z.string(), name: z.string() }).nullish()
33
+ }).optional()
34
+ }).loose();
35
+ var stuntdoubleHome = () => process.env.STUNTDOUBLE_HOME || join(homedir(), ".stuntdouble");
36
+ var credentialsFile = () => join(stuntdoubleHome(), "credentials.json");
37
+ async function readCredentials() {
38
+ const raw = await readFile(credentialsFile(), "utf8").catch(() => null);
39
+ if (!raw) return {};
40
+ try {
41
+ const parsed = CredentialsSchema.safeParse(JSON.parse(raw));
42
+ return parsed.success ? parsed.data : {};
43
+ } catch {
44
+ return {};
45
+ }
46
+ }
47
+ async function writeCredentials(credentials) {
48
+ const home = stuntdoubleHome();
49
+ await mkdir(home, { recursive: true, mode: 448 });
50
+ const file = credentialsFile();
51
+ const tmp = `${file}.${randomUUID()}.tmp`;
52
+ await writeFile(tmp, `${JSON.stringify(credentials, null, 2)}
53
+ `, { mode: 384 });
54
+ await rename(tmp, file);
55
+ await chmod(file, 384);
56
+ }
57
+ async function updateCredentials(change) {
58
+ const next = change(await readCredentials());
59
+ await writeCredentials(next);
60
+ return next;
61
+ }
62
+ async function forgetCredentials() {
63
+ await rm(credentialsFile(), { force: true });
64
+ }
65
+ async function resolveConnection() {
66
+ const stored = (await readCredentials()).connection;
67
+ const provider = stored?.provider ?? (process.env.ANTHROPIC_API_KEY ? "anthropic" : null);
68
+ if (!provider) return null;
69
+ const envKey = provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env[KEY_ENV.openai];
70
+ const apiKey = stored?.api_key || envKey || null;
71
+ return {
72
+ provider,
73
+ model: stored?.model ?? DEFAULT_MODELS[provider],
74
+ baseUrl: (stored?.base_url || DEFAULT_BASE_URLS[provider]).replace(/\/+$/, ""),
75
+ apiKey,
76
+ keySource: stored?.api_key ? "stored" : envKey ? "env" : "none"
77
+ };
78
+ }
79
+ async function connectionStatus() {
80
+ const c = await resolveConnection();
81
+ if (!c) return { configured: false };
82
+ return {
83
+ configured: true,
84
+ provider: c.provider,
85
+ model: c.model,
86
+ base_url: c.baseUrl,
87
+ key: c.keySource,
88
+ key_env: KEY_ENV[c.provider]
89
+ };
90
+ }
91
+ async function saveConnection(input) {
92
+ await updateCredentials((current) => {
93
+ const keep = current.connection?.provider === input.provider ? current.connection.api_key : null;
94
+ const connection = ConnectionSchema.parse({
95
+ provider: input.provider,
96
+ model: input.model.trim(),
97
+ base_url: input.base_url?.trim() || null,
98
+ api_key: input.clear_key ? null : input.api_key?.trim() || keep || null
99
+ });
100
+ return { ...current, connection };
101
+ });
102
+ }
103
+
104
+ export {
105
+ PROVIDERS,
106
+ DEFAULT_MODELS,
107
+ DEFAULT_BASE_URLS,
108
+ stuntdoubleHome,
109
+ readCredentials,
110
+ writeCredentials,
111
+ updateCredentials,
112
+ forgetCredentials,
113
+ resolveConnection,
114
+ connectionStatus,
115
+ saveConnection
116
+ };
117
+ //# sourceMappingURL=chunk-MSV5OLSY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/credentials.ts"],"sourcesContent":["// The Studio's secrets: the person's own model connection and their Stunt\n// Double sign-in, in `~/.stuntdouble/credentials.json` (mode 0600), never in\n// the project's `.stuntdouble/`, never in a log and never sent to the page.\n//\n// Policy line: a standalone run uses only a connection the person configures,\n// an Anthropic API key or an OpenAI-compatible endpoint. Nothing here reads a\n// Claude subscription, `~/.claude` or Claude Code's credentials. Only the\n// Studio (studio.ts, cli.ts) imports this module; the MCP server never does\n// (test/no-model.test.ts).\n\nimport { randomUUID } from 'node:crypto';\nimport { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { z } from 'zod';\n\nexport const PROVIDERS = ['anthropic', 'openai'] as const;\nexport type Provider = (typeof PROVIDERS)[number];\n\nexport const DEFAULT_MODELS: Record<Provider, string> = {\n anthropic: 'claude-opus-5-5',\n openai: 'gpt-5',\n};\nexport const DEFAULT_BASE_URLS: Record<Provider, string> = {\n anthropic: 'https://api.anthropic.com',\n openai: 'https://api.openai.com/v1',\n};\n/** The environment variable each provider's key may come from instead. */\nconst KEY_ENV: Record<Provider, string> = {\n anthropic: 'ANTHROPIC_API_KEY',\n openai: 'OPENAI_API_KEY',\n};\n\nconst ConnectionSchema = z.object({\n provider: z.enum(PROVIDERS),\n model: z.string().min(1).max(200),\n base_url: z.url({ protocol: /^https?$/ }).nullish(),\n api_key: z.string().max(500).nullish(),\n});\nexport type StoredConnection = z.infer<typeof ConnectionSchema>;\n\nconst CredentialsSchema = z\n .object({\n connection: ConnectionSchema.optional(),\n cloud: z\n .object({\n server: z.string(),\n client: z.record(z.string(), z.unknown()).optional(),\n tokens: z.record(z.string(), z.unknown()).optional(),\n workspace: z.object({ id: z.string(), slug: z.string(), name: z.string() }).nullish(),\n })\n .optional(),\n })\n .loose();\nexport type Credentials = z.infer<typeof CredentialsSchema>;\n\nexport const stuntdoubleHome = (): string =>\n process.env.STUNTDOUBLE_HOME || join(homedir(), '.stuntdouble');\nconst credentialsFile = () => join(stuntdoubleHome(), 'credentials.json');\n\nexport async function readCredentials(): Promise<Credentials> {\n const raw = await readFile(credentialsFile(), 'utf8').catch(() => null);\n if (!raw) return {};\n try {\n const parsed = CredentialsSchema.safeParse(JSON.parse(raw));\n return parsed.success ? parsed.data : {};\n } catch {\n return {};\n }\n}\n\n/** Written whole, 0600 from the start, in a 0700 folder. */\nexport async function writeCredentials(credentials: Credentials): Promise<void> {\n const home = stuntdoubleHome();\n await mkdir(home, { recursive: true, mode: 0o700 });\n const file = credentialsFile();\n const tmp = `${file}.${randomUUID()}.tmp`;\n await writeFile(tmp, `${JSON.stringify(credentials, null, 2)}\\n`, { mode: 0o600 });\n await rename(tmp, file);\n await chmod(file, 0o600);\n}\n\nexport async function updateCredentials(\n change: (current: Credentials) => Credentials\n): Promise<Credentials> {\n const next = change(await readCredentials());\n await writeCredentials(next);\n return next;\n}\n\nexport async function forgetCredentials(): Promise<void> {\n await rm(credentialsFile(), { force: true });\n}\n\n/** A connection ready to call, key included. Never leaves this process. */\nexport type Connection = {\n provider: Provider;\n model: string;\n baseUrl: string;\n apiKey: string | null;\n keySource: 'stored' | 'env' | 'none';\n};\n\n/**\n * The connection a run uses: the stored one, its key from the file or else the\n * provider's environment variable. With nothing stored, an `ANTHROPIC_API_KEY`\n * in the environment is enough on its own.\n */\nexport async function resolveConnection(): Promise<Connection | null> {\n const stored = (await readCredentials()).connection;\n const provider: Provider | null =\n stored?.provider ?? (process.env.ANTHROPIC_API_KEY ? 'anthropic' : null);\n if (!provider) return null;\n const envKey =\n provider === 'anthropic' ? process.env.ANTHROPIC_API_KEY : process.env[KEY_ENV.openai];\n const apiKey = stored?.api_key || envKey || null;\n return {\n provider,\n model: stored?.model ?? DEFAULT_MODELS[provider],\n baseUrl: (stored?.base_url || DEFAULT_BASE_URLS[provider]).replace(/\\/+$/, ''),\n apiKey,\n keySource: stored?.api_key ? 'stored' : envKey ? 'env' : 'none',\n };\n}\n\n/** What the page may know: whether a key is set and where from, never the key. */\nexport async function connectionStatus() {\n const c = await resolveConnection();\n if (!c) return { configured: false as const };\n return {\n configured: true as const,\n provider: c.provider,\n model: c.model,\n base_url: c.baseUrl,\n key: c.keySource,\n key_env: KEY_ENV[c.provider],\n };\n}\n\n/** Save the connection. An empty `api_key` keeps the stored one; `clear_key` drops it. */\nexport async function saveConnection(input: {\n provider: Provider;\n model: string;\n base_url?: string | null;\n api_key?: string | null;\n clear_key?: boolean;\n}): Promise<void> {\n await updateCredentials((current) => {\n const keep =\n current.connection?.provider === input.provider ? current.connection.api_key : null;\n const connection = ConnectionSchema.parse({\n provider: input.provider,\n model: input.model.trim(),\n base_url: input.base_url?.trim() || null,\n api_key: input.clear_key ? null : input.api_key?.trim() || keep || null,\n });\n return { ...current, connection };\n });\n}\n"],"mappings":";AAUA,SAAS,kBAAkB;AAC3B,SAAS,OAAO,OAAO,UAAU,QAAQ,IAAI,iBAAiB;AAC9D,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,SAAS;AAEX,IAAM,YAAY,CAAC,aAAa,QAAQ;AAGxC,IAAM,iBAA2C;AAAA,EACtD,WAAW;AAAA,EACX,QAAQ;AACV;AACO,IAAM,oBAA8C;AAAA,EACzD,WAAW;AAAA,EACX,QAAQ;AACV;AAEA,IAAM,UAAoC;AAAA,EACxC,WAAW;AAAA,EACX,QAAQ;AACV;AAEA,IAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,UAAU,EAAE,KAAK,SAAS;AAAA,EAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAChC,UAAU,EAAE,IAAI,EAAE,UAAU,WAAW,CAAC,EAAE,QAAQ;AAAA,EAClD,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ;AACvC,CAAC;AAGD,IAAM,oBAAoB,EACvB,OAAO;AAAA,EACN,YAAY,iBAAiB,SAAS;AAAA,EACtC,OAAO,EACJ,OAAO;AAAA,IACN,QAAQ,EAAE,OAAO;AAAA,IACjB,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,IACnD,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,IACnD,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ;AAAA,EACtF,CAAC,EACA,SAAS;AACd,CAAC,EACA,MAAM;AAGF,IAAM,kBAAkB,MAC7B,QAAQ,IAAI,oBAAoB,KAAK,QAAQ,GAAG,cAAc;AAChE,IAAM,kBAAkB,MAAM,KAAK,gBAAgB,GAAG,kBAAkB;AAExE,eAAsB,kBAAwC;AAC5D,QAAM,MAAM,MAAM,SAAS,gBAAgB,GAAG,MAAM,EAAE,MAAM,MAAM,IAAI;AACtE,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,MAAI;AACF,UAAM,SAAS,kBAAkB,UAAU,KAAK,MAAM,GAAG,CAAC;AAC1D,WAAO,OAAO,UAAU,OAAO,OAAO,CAAC;AAAA,EACzC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGA,eAAsB,iBAAiB,aAAyC;AAC9E,QAAM,OAAO,gBAAgB;AAC7B,QAAM,MAAM,MAAM,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAClD,QAAM,OAAO,gBAAgB;AAC7B,QAAM,MAAM,GAAG,IAAI,IAAI,WAAW,CAAC;AACnC,QAAM,UAAU,KAAK,GAAG,KAAK,UAAU,aAAa,MAAM,CAAC,CAAC;AAAA,GAAM,EAAE,MAAM,IAAM,CAAC;AACjF,QAAM,OAAO,KAAK,IAAI;AACtB,QAAM,MAAM,MAAM,GAAK;AACzB;AAEA,eAAsB,kBACpB,QACsB;AACtB,QAAM,OAAO,OAAO,MAAM,gBAAgB,CAAC;AAC3C,QAAM,iBAAiB,IAAI;AAC3B,SAAO;AACT;AAEA,eAAsB,oBAAmC;AACvD,QAAM,GAAG,gBAAgB,GAAG,EAAE,OAAO,KAAK,CAAC;AAC7C;AAgBA,eAAsB,oBAAgD;AACpE,QAAM,UAAU,MAAM,gBAAgB,GAAG;AACzC,QAAM,WACJ,QAAQ,aAAa,QAAQ,IAAI,oBAAoB,cAAc;AACrE,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,SACJ,aAAa,cAAc,QAAQ,IAAI,oBAAoB,QAAQ,IAAI,QAAQ,MAAM;AACvF,QAAM,SAAS,QAAQ,WAAW,UAAU;AAC5C,SAAO;AAAA,IACL;AAAA,IACA,OAAO,QAAQ,SAAS,eAAe,QAAQ;AAAA,IAC/C,UAAU,QAAQ,YAAY,kBAAkB,QAAQ,GAAG,QAAQ,QAAQ,EAAE;AAAA,IAC7E;AAAA,IACA,WAAW,QAAQ,UAAU,WAAW,SAAS,QAAQ;AAAA,EAC3D;AACF;AAGA,eAAsB,mBAAmB;AACvC,QAAM,IAAI,MAAM,kBAAkB;AAClC,MAAI,CAAC,EAAG,QAAO,EAAE,YAAY,MAAe;AAC5C,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,UAAU,EAAE;AAAA,IACZ,OAAO,EAAE;AAAA,IACT,UAAU,EAAE;AAAA,IACZ,KAAK,EAAE;AAAA,IACP,SAAS,QAAQ,EAAE,QAAQ;AAAA,EAC7B;AACF;AAGA,eAAsB,eAAe,OAMnB;AAChB,QAAM,kBAAkB,CAAC,YAAY;AACnC,UAAM,OACJ,QAAQ,YAAY,aAAa,MAAM,WAAW,QAAQ,WAAW,UAAU;AACjF,UAAM,aAAa,iBAAiB,MAAM;AAAA,MACxC,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM,MAAM,KAAK;AAAA,MACxB,UAAU,MAAM,UAAU,KAAK,KAAK;AAAA,MACpC,SAAS,MAAM,YAAY,OAAO,MAAM,SAAS,KAAK,KAAK,QAAQ;AAAA,IACrE,CAAC;AACD,WAAO,EAAE,GAAG,SAAS,WAAW;AAAA,EAClC,CAAC;AACH;","names":[]}