UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

203 lines • 10.4 kB
"use strict"; /** * agentCoreBrowser — AWS Bedrock AgentCore **Browser** as a {@link BrowserRunner}. * * A managed Chrome in AWS's account that an agent can drive, a person can watch * live, and — the part this adapter exists for — a person can TAKE OVER * mid-session and hand back. * * ── Two channels, and only one of them is here ─────────────────────────────── * A browser session has two doors, and confusing them wastes a day: * * 1. **The automation stream** — a CDP WebSocket. Navigate, find an element, * fill a form: everything page-shaped happens here, driven by Playwright * or another CDP client. This adapter hands you * {@link BrowserSession.automationEndpoint} and stays out of the way. It * does not depend on Playwright and does not drive the page for you. * 2. **`InvokeBrowser`** — the data-plane operation this adapter calls. It is * OS-LEVEL input ABOVE the page: mouse, keyboard, screenshots. Its action * union, read off the SDK rather than remembered, is exactly * `mouseClick | mouseMove | mouseDrag | mouseScroll | keyType | keyPress | * keyShortcut | screenshot`. **There is no navigate action**, and a reader * expecting one is looking at the wrong door. * * ── The takeover ───────────────────────────────────────────────────────────── * `UpdateBrowserStream` with `automationStreamUpdate.streamStatus` set to * `DISABLED` stops the automation channel and leaves the live-view user in * control; `ENABLED` gives it back. That is `handControlTo('person' | 'agent')` * here, and it composes with this library's check-in: the agent pauses, a * person finishes the login, the agent resumes on the page they left — and both * handovers are ordinary events in the trace. * * ── How this talks to the SDK (the 9.4.0 law) ──────────────────────────────── * Through `client.send(new SomeCommand(input))`, never a method on the client — * a bare `@aws-sdk/client-*` Client carries `send` and `destroy` and nothing * else. Every command name and request shape below was read off a real install * of `@aws-sdk/client-bedrock-agentcore` **3.1118.0**, including the action * union above and `ScreenshotResult`'s `{ status, error?, data? }`. * * ── One documented contradiction, left as AWS wrote it ─────────────────────── * The devguide says a session defaults to 15 minutes; `StartBrowserSession`'s * API reference says 3600 seconds. This adapter sends nothing unless you pass * `sessionTimeoutSeconds`, so the default is whatever the service applies — * rather than this file picking a side in somebody else's disagreement. * * Pattern: Adapter (GoF) + lazy peer-dep load — the SDK is required only when * `start()` first runs, or never, if you inject `_client` / `_sdk`. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.agentCoreBrowser = exports.AWS_SYSTEM_BROWSER = void 0; const lazyRequire_js_1 = require("../../lib/lazyRequire.js"); /** AWS's managed browser. A custom `CreateBrowser` resource id goes here instead. */ exports.AWS_SYSTEM_BROWSER = 'aws.browser.v1'; const BUTTONS = { left: 'LEFT', middle: 'MIDDLE', right: 'RIGHT' }; function createBrowserClient(options) { let mod; if (options._sdk) { mod = options._sdk; } else { try { mod = (0, lazyRequire_js_1.lazyRequire)('@aws-sdk/client-bedrock-agentcore'); } catch { throw new Error('agentCoreBrowser requires the `@aws-sdk/client-bedrock-agentcore` peer dependency.\n' + ' Install: npm install @aws-sdk/client-bedrock-agentcore\n' + ' Or pass `_client` for a pre-built or mock client.'); } } if (!mod.BedrockAgentCoreClient) { throw new Error('agentCoreBrowser: `@aws-sdk/client-bedrock-agentcore` is installed but ' + '`BedrockAgentCoreClient` was not found. Update the SDK.'); } const sdk = new mod.BedrockAgentCoreClient({ ...(options.region && { region: options.region }) }); const send = async (Ctor, name, input) => { if (!Ctor) { throw new Error(`agentCoreBrowser: \`@aws-sdk/client-bedrock-agentcore\` is missing ${name}. ` + 'Upgrade the SDK, or pass `_client` with your own mapping.'); } return sdk.send(new Ctor(input)); }; return { async startBrowserSession(input) { return (await send(mod.StartBrowserSessionCommand, 'StartBrowserSessionCommand', input)); }, async invokeBrowser(input) { return (await send(mod.InvokeBrowserCommand, 'InvokeBrowserCommand', input)); }, async updateBrowserStream(input) { await send(mod.UpdateBrowserStreamCommand, 'UpdateBrowserStreamCommand', input); }, async stopBrowserSession(input) { await send(mod.StopBrowserSessionCommand, 'StopBrowserSessionCommand', input); }, }; } /** * Build a {@link BrowserRunner} backed by AgentCore Browser. * * @example A session, a person taking over, and the agent resuming * const browser = agentCoreBrowser({ region: 'us-east-1' }); * const session = await browser.start({ key: toolSessionKey(ctx, 'run') }); * * // Page work goes over CDP, not through this adapter: * // const page = await chromium.connectOverCDP(session.automationEndpoint!) * * await session.handControlTo?.('person'); // they finish the login * await session.handControlTo?.('agent'); // and the agent carries on * await session.stop(); */ function agentCoreBrowser(options = {}) { const browserIdentifier = options.browserIdentifier ?? exports.AWS_SYSTEM_BROWSER; let client; const getClient = () => { client ??= options._client ?? createBrowserClient(options); return client; }; return { id: options.id ?? 'agentcore-browser', async start(req) { const c = getClient(); const started = await c.startBrowserSession({ browserIdentifier, name: req.key, ...(options.sessionTimeoutSeconds !== undefined && { sessionTimeoutSeconds: options.sessionTimeoutSeconds, }), ...(options.viewport !== undefined && { viewPort: options.viewport }), }); const sessionId = started.sessionId; if (!sessionId) { throw new Error('agentCoreBrowser: StartBrowserSession returned no sessionId, so there is no session ' + 'to drive. Check the browser identifier and the execution role.'); } const act = async (action) => { const answer = await c.invokeBrowser({ browserIdentifier, sessionId, action }); return answer.result; }; const automationEndpoint = started.streams?.automationStream?.streamEndpoint; const liveViewEndpoint = started.streams?.liveViewStream?.streamEndpoint; const session = { id: sessionId, ...(automationEndpoint !== undefined && { automationEndpoint }), ...(liveViewEndpoint !== undefined && { liveViewEndpoint }), async click({ x, y, button, clicks }) { await act({ mouseClick: { x, y, ...(button !== undefined && { button: BUTTONS[button] }), ...(clicks !== undefined && { clickCount: clicks }), }, }); }, async type(text) { await act({ keyType: { text } }); }, async press({ key, times }) { await act({ keyPress: { key, ...(times !== undefined && { presses: times }) } }); }, async screenshot() { const result = await act({ screenshot: { format: 'PNG' } }); const shot = result?.screenshot; if (!shot?.data) { // The result member carries its own status and error, so a failed // screenshot says what the service said rather than answering with // an empty image that reads as a blank page. throw new Error(`agentCoreBrowser: the screenshot did not produce image data` + `${shot?.status ? ` (status ${shot.status})` : ''}` + `${shot?.error ? `: ${shot.error}` : '.'}`); } return { data: shot.data, format: 'png' }; }, async handControlTo(driver) { // The person takes over by DISABLING automation: the live-view user // is already connected, and stopping the automation stream is what // lets their input through. await c.updateBrowserStream({ browserIdentifier, sessionId, streamUpdate: { automationStreamUpdate: { streamStatus: driver === 'person' ? 'DISABLED' : 'ENABLED', }, }, }); }, async stop() { try { await c.stopBrowserSession({ browserIdentifier, sessionId }); } catch { // A managed session reaped by its own idle timeout is the ordinary // case, and stopping one that is already gone is a no-op — the port // says so, and a throw here would fail a teardown that succeeded. } }, }; return session; }, }; } exports.agentCoreBrowser = agentCoreBrowser; //# sourceMappingURL=agentcore.js.map