From d10e13136e80386c89e6724e63ba4057b6d50541 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 08:04:11 -0500 Subject: [PATCH 01/19] feat(templates): implement Gemini Computer Use for TypeScript and Python MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement browser control agents using the Gemini 2.5 Computer Use Preview model (gemini-2.5-computer-use-preview-10-2025) with Kernel's Computer Controls API. ## TypeScript Template Changes - Refactored index.ts to use modular architecture - Added loop.ts: Core sampling loop implementing Google's agent pattern - Added session.ts: KernelBrowserSession for browser lifecycle management - Added tools/computer.ts: Maps Gemini actions to Kernel Computer Controls - Added tools/types/gemini.ts: Type definitions for Gemini actions - Updated package.json: @google/genai ^1.0.0 ## Python Template (New) - main.py: Action handler with KernelBrowserSession context manager - loop.py: Async sampling loop matching TypeScript implementation - session.py: Async context manager for browser lifecycle - tools/computer.py: Gemini-to-Kernel action mapping - tools/types.py: Dataclasses and enums for Gemini actions ## Integration Pattern Both templates implement Google's recommended Computer Use agent loop: 1. Send request with computerUse tool configured 2. Receive function_call from model (click_at, type_text_at, navigate, etc.) 3. Execute via Kernel Computer Controls API 4. Capture screenshot + URL, send as function_response 5. Loop until task complete ## Key Features - Coordinate denormalization (Gemini 0-1000 → Kernel pixels) - Screenshot pruning to manage context size - Optional replay recording for debugging - Safety decision handling (auto-acknowledge for automation) Tested: Both templates successfully navigate to Wikipedia and extract the featured article title. --- pkg/create/templates.go | 9 +- .../python/gemini-computer-use/README.md | 59 + .../python/gemini-computer-use/_gitignore | 6 + .../python/gemini-computer-use/loop.py | 303 ++ .../python/gemini-computer-use/main.py | 69 + .../python/gemini-computer-use/pyproject.toml | 13 + .../python/gemini-computer-use/session.py | 149 + .../gemini-computer-use/tools/__init__.py | 23 + .../gemini-computer-use/tools/computer.py | 283 ++ .../python/gemini-computer-use/tools/types.py | 124 + .../typescript/gemini-computer-use/README.md | 83 +- .../typescript/gemini-computer-use/index.ts | 157 +- .../typescript/gemini-computer-use/loop.ts | 296 ++ .../gemini-computer-use/package.json | 5 +- .../gemini-computer-use/pnpm-lock.yaml | 2971 ----------------- .../typescript/gemini-computer-use/session.ts | 222 ++ .../gemini-computer-use/tools/computer.ts | 316 ++ .../gemini-computer-use/tools/types/gemini.ts | 125 + 18 files changed, 2100 insertions(+), 3113 deletions(-) create mode 100644 pkg/templates/python/gemini-computer-use/README.md create mode 100644 pkg/templates/python/gemini-computer-use/_gitignore create mode 100644 pkg/templates/python/gemini-computer-use/loop.py create mode 100644 pkg/templates/python/gemini-computer-use/main.py create mode 100644 pkg/templates/python/gemini-computer-use/pyproject.toml create mode 100644 pkg/templates/python/gemini-computer-use/session.py create mode 100644 pkg/templates/python/gemini-computer-use/tools/__init__.py create mode 100644 pkg/templates/python/gemini-computer-use/tools/computer.py create mode 100644 pkg/templates/python/gemini-computer-use/tools/types.py create mode 100644 pkg/templates/typescript/gemini-computer-use/loop.ts delete mode 100644 pkg/templates/typescript/gemini-computer-use/pnpm-lock.yaml create mode 100644 pkg/templates/typescript/gemini-computer-use/session.ts create mode 100644 pkg/templates/typescript/gemini-computer-use/tools/computer.ts create mode 100644 pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts diff --git a/pkg/create/templates.go b/pkg/create/templates.go index f99c4e6..b4596f1 100644 --- a/pkg/create/templates.go +++ b/pkg/create/templates.go @@ -62,7 +62,7 @@ var Templates = map[string]TemplateInfo{ TemplateGeminiComputerUse: { Name: "Gemini Computer Use", Description: "Implements a Gemini computer use agent", - Languages: []string{LanguageTypeScript}, + Languages: []string{LanguageTypeScript, LanguagePython}, }, TemplateBrowserUse: { Name: "Browser Use", @@ -193,7 +193,7 @@ var Commands = map[string]map[string]DeployConfig{ TemplateGeminiComputerUse: { EntryPoint: "index.ts", NeedsEnvFile: true, - InvokeCommand: "kernel invoke ts-gemini-cua gemini-cua-task", + InvokeCommand: `kernel invoke ts-gemini-cua cua-task --payload '{"query": "Navigate to http://magnitasks.com and click on Tasks in the sidebar"}'`, }, TemplateClaudeAgentSDK: { EntryPoint: "index.ts", @@ -237,6 +237,11 @@ var Commands = map[string]map[string]DeployConfig{ NeedsEnvFile: true, InvokeCommand: `kernel invoke py-claude-agent-sdk agent-task --payload '{"task": "Go to https://news.ycombinator.com and get the top 3 stories"}'`, }, + TemplateGeminiComputerUse: { + EntryPoint: "main.py", + NeedsEnvFile: true, + InvokeCommand: `kernel invoke python-gemini-cua cua-task --payload '{"query": "Navigate to http://magnitasks.com and click on Tasks in the sidebar"}'`, + }, }, } diff --git a/pkg/templates/python/gemini-computer-use/README.md b/pkg/templates/python/gemini-computer-use/README.md new file mode 100644 index 0000000..a7ec879 --- /dev/null +++ b/pkg/templates/python/gemini-computer-use/README.md @@ -0,0 +1,59 @@ +# Kernel Python Sample App - Gemini Computer Use + +This is a Kernel application that implements a prompt loop using Google's Gemini Computer Use model with Kernel's Computer Controls API. + +It follows the [Google Computer Use Preview Reference](https://github.com/google-gemini/computer-use-preview) but uses Kernel's Computer Controls API for browser automation. + +## Setup + +1. Get your API keys: + - **Kernel**: [dashboard.onkernel.com](https://dashboard.onkernel.com) + - **Google AI**: [aistudio.google.com/apikey](https://aistudio.google.com/apikey) + +2. Deploy the app: +```bash +kernel login +cp .env.example .env # Add your GOOGLE_API_KEY +kernel deploy main.py --env-file .env +``` + +## Usage + +```bash +kernel invoke python-gemini-cua cua-task --payload '{"query": "Navigate to https://example.com and describe the page"}' +``` + +## Recording Replays + +> **Note:** Replay recording is only available to Kernel users on paid plans. + +Add `"record_replay": true` to your payload to capture a video of the browser session: + +```bash +kernel invoke python-gemini-cua cua-task --payload '{"query": "Navigate to https://example.com", "record_replay": true}' +``` + +When enabled, the response will include a `replay_url` field with a link to view the recorded session. + +## Gemini Computer Use Actions + +The Gemini model can execute the following browser actions: + +| Action | Description | +|--------|-------------| +| `click_at` | Click at coordinates (x, y) | +| `hover_at` | Move mouse to coordinates (x, y) | +| `type_text_at` | Click and type text at coordinates | +| `scroll_document` | Scroll the page (up/down/left/right) | +| `scroll_at` | Scroll at specific coordinates | +| `navigate` | Navigate to a URL | +| `go_back` | Go back in browser history | +| `go_forward` | Go forward in browser history | +| `key_combination` | Press key combination (e.g., "ctrl+c") | +| `drag_and_drop` | Drag from one point to another | +| `wait_5_seconds` | Wait for 5 seconds | + +## Resources + +- [Google Gemini Computer Use Documentation](https://ai.google.dev/gemini-api/docs/computer-use) +- [Kernel Documentation](https://www.kernel.sh/docs/quickstart) diff --git a/pkg/templates/python/gemini-computer-use/_gitignore b/pkg/templates/python/gemini-computer-use/_gitignore new file mode 100644 index 0000000..deac005 --- /dev/null +++ b/pkg/templates/python/gemini-computer-use/_gitignore @@ -0,0 +1,6 @@ +.venv/ +__pycache__/ +*.pyc +.env +.env.local +uv.lock diff --git a/pkg/templates/python/gemini-computer-use/loop.py b/pkg/templates/python/gemini-computer-use/loop.py new file mode 100644 index 0000000..d8c1edc --- /dev/null +++ b/pkg/templates/python/gemini-computer-use/loop.py @@ -0,0 +1,303 @@ +""" +Gemini Computer Use sampling loop. +Based on Google's computer-use-preview reference implementation. +""" + +import os +from datetime import datetime +from typing import Any, Dict, List, Optional + +from google import genai +from google.genai import types +from google.genai.types import ( + Content, + FunctionResponse, + GenerateContentConfig, + Part, +) +from kernel import Kernel + +from tools import ComputerTool, PREDEFINED_COMPUTER_USE_FUNCTIONS, GeminiAction + + +# System prompt for browser-based computer use +SYSTEM_PROMPT = f"""You are a helpful assistant that can use a web browser. +You are operating a Chrome browser through computer use tools. +The browser is already open and ready for use. + +When you need to navigate to a page, use the navigate action with a full URL. +When you need to interact with elements, use click_at, type_text_at, etc. +After each action, carefully evaluate the screenshot to determine your next step. + +Current date: {datetime.now().strftime("%A, %B %d, %Y")}.""" + +# Maximum number of recent turns to keep screenshots for (to manage context) +MAX_RECENT_TURN_WITH_SCREENSHOTS = 3 + + +async def sampling_loop( + *, + model: str, + query: str, + api_key: str, + kernel: Kernel, + session_id: str, + max_iterations: int = 50, + system_prompt_suffix: str = "", +) -> Dict[str, Any]: + """ + Run the Gemini computer use sampling loop. + + Args: + model: The Gemini model to use + query: The user's query/task + api_key: Google API key + kernel: Kernel client instance + session_id: Browser session ID + max_iterations: Maximum number of loop iterations + system_prompt_suffix: Additional system prompt text + + Returns: + Dict with 'final_response' and 'iterations' + """ + # Initialize the Gemini client + client = genai.Client( + api_key=api_key, + vertexai=os.environ.get("USE_VERTEXAI", "0").lower() in ["true", "1"], + project=os.environ.get("VERTEXAI_PROJECT"), + location=os.environ.get("VERTEXAI_LOCATION"), + ) + + computer_tool = ComputerTool(kernel, session_id) + + # Initialize conversation with user query + contents: List[Content] = [ + Content( + role="user", + parts=[Part(text=query)], + ) + ] + + system_prompt = ( + f"{SYSTEM_PROMPT}\n\n{system_prompt_suffix}" + if system_prompt_suffix + else SYSTEM_PROMPT + ) + + # Generate content config + generate_content_config = GenerateContentConfig( + temperature=1, + top_p=0.95, + top_k=40, + max_output_tokens=8192, + system_instruction=system_prompt, + tools=[ + types.Tool( + computer_use=types.ComputerUse( + environment=types.Environment.ENVIRONMENT_BROWSER, + ), + ), + ], + thinking_config=types.ThinkingConfig(include_thoughts=True), + ) + + iteration = 0 + final_response = "" + + while iteration < max_iterations: + iteration += 1 + print(f"\n=== Iteration {iteration} ===") + + try: + # Generate response from Gemini + response = client.models.generate_content( + model=model, + contents=contents, + config=generate_content_config, + ) + + if not response.candidates: + print("No candidates in response") + break + + candidate = response.candidates[0] + if not candidate.content: + print("No content in candidate") + break + + # Add assistant response to conversation + contents.append(candidate.content) + + # Extract text and function calls + reasoning = _extract_text(candidate.content) + function_calls = _extract_function_calls(candidate.content) + + # Log the response + print(f"Reasoning: {reasoning or '(none)'}") + print(f"Function calls: {len(function_calls)}") + for fc in function_calls: + print(f" - {fc.name}: {fc.args}") + + # Check finish reason + finish_reason = candidate.finish_reason + if ( + finish_reason == types.FinishReason.MALFORMED_FUNCTION_CALL + and not function_calls + ): + print("Malformed function call, retrying...") + continue + + # If no function calls, the model is done + if not function_calls: + print("Agent loop complete") + final_response = reasoning or "" + break + + # Execute function calls and collect results + function_responses: List[Part] = [] + for fc in function_calls: + args = dict(fc.args) if fc.args else {} + + # Handle safety decisions if present + if ( + "safety_decision" in args + and args["safety_decision"].get("decision") == "require_confirmation" + ): + print( + f"Safety confirmation required: {args['safety_decision'].get('explanation')}" + ) + print("Auto-acknowledging safety check") + + # Execute the action + print(f"Executing action: {fc.name}") + result = await computer_tool.execute_action(fc.name, args) + + if result.error: + print(f"Action error: {result.error}") + function_responses.append( + Part( + function_response=FunctionResponse( + name=fc.name, + response={"error": result.error}, + ) + ) + ) + else: + # Build response with screenshot - always include URL (required by Computer Use API) + response_data: Dict[str, Any] = { + "url": result.url or "about:blank", + } + + # Include screenshot for predefined functions + parts = None + if result.screenshot and _is_predefined_function(fc.name): + parts = [ + types.FunctionResponsePart( + inline_data=types.FunctionResponseBlob( + mime_type="image/png", + data=result.screenshot, + ) + ) + ] + + function_responses.append( + Part( + function_response=FunctionResponse( + name=fc.name, + response=response_data, + parts=parts, + ) + ) + ) + + # Add function responses to conversation + contents.append( + Content( + role="user", + parts=function_responses, + ) + ) + + # Manage screenshot history to avoid context overflow + _prune_old_screenshots(contents) + + except Exception as e: + print(f"Error in sampling loop: {e}") + break + + if iteration >= max_iterations: + print("Max iterations reached") + + return { + "final_response": final_response, + "iterations": iteration, + } + + +def _extract_text(content: Content) -> str: + """Extract text from a Gemini content response.""" + if not content.parts: + return "" + + texts: List[str] = [] + for part in content.parts: + if hasattr(part, "text") and part.text: + texts.append(part.text) + return " ".join(texts) + + +def _extract_function_calls(content: Content) -> List[types.FunctionCall]: + """Extract function calls from a Gemini content response.""" + if not content.parts: + return [] + + calls: List[types.FunctionCall] = [] + for part in content.parts: + if hasattr(part, "function_call") and part.function_call: + calls.append(part.function_call) + return calls + + +def _is_predefined_function(name: str) -> bool: + """Check if a function name is a predefined computer use function.""" + return name in [a.value for a in PREDEFINED_COMPUTER_USE_FUNCTIONS] + + +def _prune_old_screenshots(contents: List[Content]) -> None: + """Prune old screenshots from conversation history to manage context size.""" + turns_with_screenshots = 0 + + # Iterate in reverse to find recent turns with screenshots + for content in reversed(contents): + if content.role != "user" or not content.parts: + continue + + # Check if this turn has screenshots from predefined functions + has_screenshot = False + for part in content.parts: + if ( + hasattr(part, "function_response") + and part.function_response + and _is_predefined_function(part.function_response.name or "") + ): + # Check if it has parts (which contain screenshots) + if ( + hasattr(part.function_response, "parts") + and part.function_response.parts + ): + has_screenshot = True + break + + if has_screenshot: + turns_with_screenshots += 1 + + # Remove screenshots from old turns + if turns_with_screenshots > MAX_RECENT_TURN_WITH_SCREENSHOTS: + for part in content.parts: + if ( + hasattr(part, "function_response") + and part.function_response + and _is_predefined_function(part.function_response.name or "") + ): + # Remove the parts array (which contains the screenshot) + part.function_response.parts = None diff --git a/pkg/templates/python/gemini-computer-use/main.py b/pkg/templates/python/gemini-computer-use/main.py new file mode 100644 index 0000000..2c84a20 --- /dev/null +++ b/pkg/templates/python/gemini-computer-use/main.py @@ -0,0 +1,69 @@ +import os +from typing import Optional, TypedDict + +import kernel +from loop import sampling_loop +from session import KernelBrowserSession + + +class QueryInput(TypedDict): + query: str + record_replay: Optional[bool] + + +class QueryOutput(TypedDict): + result: str + replay_url: Optional[str] + + +api_key = os.getenv("GOOGLE_API_KEY") +if not api_key: + raise ValueError("GOOGLE_API_KEY is not set") + +app = kernel.App("python-gemini-cua") + + +@app.action("cua-task") +async def cua_task( + ctx: kernel.KernelContext, + payload: QueryInput, +) -> QueryOutput: + """ + Process a user query using Gemini Computer Use with Kernel's browser automation. + + Args: + ctx: Kernel context containing invocation information + payload: An object containing: + - query: The task/query string to process + - record_replay: Optional boolean to enable video replay recording + + Returns: + A dictionary containing: + - result: The result of the sampling loop as a string + - replay_url: URL to view the replay (if recording was enabled) + """ + if not payload or not payload.get("query"): + raise ValueError("Query is required") + + record_replay = payload.get("record_replay", False) + + async with KernelBrowserSession( + stealth=True, + record_replay=record_replay, + ) as session: + print("Kernel browser live view url:", session.live_view_url) + + result = await sampling_loop( + model="gemini-2.5-computer-use-preview-10-2025", + query=payload["query"], + api_key=str(api_key), + kernel=session.kernel, + session_id=session.session_id, + ) + + final_response = result.get("final_response", "") + + return { + "result": final_response, + "replay_url": session.replay_view_url, + } diff --git a/pkg/templates/python/gemini-computer-use/pyproject.toml b/pkg/templates/python/gemini-computer-use/pyproject.toml new file mode 100644 index 0000000..c689f30 --- /dev/null +++ b/pkg/templates/python/gemini-computer-use/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "python-gemini-cua" +version = "0.1.0" +description = "Gemini Computer Use template for Kernel" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "google-genai>=1.0.0", + "kernel>=0.5.0", +] + +[tool.uv] +dev-dependencies = [] diff --git a/pkg/templates/python/gemini-computer-use/session.py b/pkg/templates/python/gemini-computer-use/session.py new file mode 100644 index 0000000..3227b28 --- /dev/null +++ b/pkg/templates/python/gemini-computer-use/session.py @@ -0,0 +1,149 @@ +""" +Kernel Browser Session Manager. + +Provides an async context manager for managing Kernel browser lifecycle +with optional video replay recording. +""" + +import asyncio +import time +from dataclasses import dataclass, field +from typing import Optional + +from kernel import Kernel + + +@dataclass +class KernelBrowserSession: + """ + Manages Kernel browser lifecycle as an async context manager. + + Creates a browser session on entry and cleans it up on exit. + Optionally records a video replay of the entire session. + Provides session_id to computer tools. + + Usage: + async with KernelBrowserSession(record_replay=True) as session: + # Use session.session_id and session.kernel for operations + pass + # Browser is automatically cleaned up, replay URL available in session.replay_view_url + """ + + stealth: bool = True + timeout_seconds: int = 300 + + # Replay recording options + record_replay: bool = False + replay_grace_period: float = 5.0 # Seconds to wait before stopping replay + + # Set after browser creation + session_id: Optional[str] = field(default=None, init=False) + live_view_url: Optional[str] = field(default=None, init=False) + replay_id: Optional[str] = field(default=None, init=False) + replay_view_url: Optional[str] = field(default=None, init=False) + _kernel: Optional[Kernel] = field(default=None, init=False) + + async def __aenter__(self) -> "KernelBrowserSession": + """Create a Kernel browser session and optionally start recording.""" + self._kernel = Kernel() + + # Create browser with specified settings + browser = self._kernel.browsers.create( + stealth=self.stealth, + timeout_seconds=self.timeout_seconds, + viewport={ + "width": 1024, + "height": 768, + "refresh_rate": 60, + }, + ) + + self.session_id = browser.session_id + self.live_view_url = browser.browser_live_view_url + + print(f"Kernel browser created: {self.session_id}") + print(f"Live view URL: {self.live_view_url}") + + # Start replay recording if enabled + if self.record_replay: + try: + await self._start_replay() + except Exception as e: + print(f"Warning: Failed to start replay recording: {e}") + print("Continuing without replay recording.") + + return self + + async def _start_replay(self) -> None: + """Start recording a replay of the browser session.""" + if not self._kernel or not self.session_id: + return + + print("Starting replay recording...") + replay = self._kernel.browsers.replays.start(self.session_id) + self.replay_id = replay.replay_id + print(f"Replay recording started: {self.replay_id}") + + async def _stop_and_get_replay_url(self) -> None: + """Stop recording and get the replay URL.""" + if not self._kernel or not self.session_id or not self.replay_id: + return + + print("Stopping replay recording...") + self._kernel.browsers.replays.stop( + replay_id=self.replay_id, + id=self.session_id, + ) + print("Replay recording stopped. Processing video...") + + # Wait a moment for processing + await asyncio.sleep(2) + + # Poll for replay to be ready (with timeout) + max_wait = 60 # seconds + start_time = time.time() + replay_ready = False + + while time.time() - start_time < max_wait: + try: + replays = self._kernel.browsers.replays.list(self.session_id) + for replay in replays: + if replay.replay_id == self.replay_id: + self.replay_view_url = replay.replay_view_url + replay_ready = True + break + if replay_ready: + break + except Exception: + pass + await asyncio.sleep(1) + + if not replay_ready: + print("Warning: Replay may still be processing") + elif self.replay_view_url: + print(f"Replay view URL: {self.replay_view_url}") + + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: + """Stop recording and delete the browser session.""" + if self._kernel and self.session_id: + try: + # Stop replay if recording was enabled + if self.record_replay and self.replay_id: + # Wait grace period before stopping to capture final state + if self.replay_grace_period > 0: + print(f"Waiting {self.replay_grace_period}s grace period...") + await asyncio.sleep(self.replay_grace_period) + await self._stop_and_get_replay_url() + finally: + print(f"Destroying browser session: {self.session_id}") + self._kernel.browsers.delete_by_id(self.session_id) + print("Browser session destroyed.") + + self._kernel = None + + @property + def kernel(self) -> Kernel: + """Get the Kernel client instance.""" + if self._kernel is None: + raise RuntimeError("Session not initialized. Use async with context.") + return self._kernel diff --git a/pkg/templates/python/gemini-computer-use/tools/__init__.py b/pkg/templates/python/gemini-computer-use/tools/__init__.py new file mode 100644 index 0000000..1679429 --- /dev/null +++ b/pkg/templates/python/gemini-computer-use/tools/__init__.py @@ -0,0 +1,23 @@ +"""Gemini Computer Use tools package.""" + +from .computer import ComputerTool +from .types import ( + GeminiAction, + PREDEFINED_COMPUTER_USE_FUNCTIONS, + ToolResult, + EnvState, + ScreenSize, + DEFAULT_SCREEN_SIZE, + COORDINATE_SCALE, +) + +__all__ = [ + "ComputerTool", + "GeminiAction", + "PREDEFINED_COMPUTER_USE_FUNCTIONS", + "ToolResult", + "EnvState", + "ScreenSize", + "DEFAULT_SCREEN_SIZE", + "COORDINATE_SCALE", +] diff --git a/pkg/templates/python/gemini-computer-use/tools/computer.py b/pkg/templates/python/gemini-computer-use/tools/computer.py new file mode 100644 index 0000000..ed08712 --- /dev/null +++ b/pkg/templates/python/gemini-computer-use/tools/computer.py @@ -0,0 +1,283 @@ +""" +Gemini Computer Tool - Maps Gemini actions to Kernel Computer Controls API. +Based on Google's computer-use-preview reference implementation. +""" + +import asyncio +import base64 +from typing import Any, Dict + +from kernel import Kernel + +from .types import ( + GeminiAction, + PREDEFINED_COMPUTER_USE_FUNCTIONS, + DEFAULT_SCREEN_SIZE, + COORDINATE_SCALE, + GeminiFunctionArgs, + ToolResult, + ScreenSize, +) + + +TYPING_DELAY_MS = 12 +SCREENSHOT_DELAY_MS = 0.5 + + +class ComputerTool: + """Computer tool that maps Gemini actions to Kernel's Computer Controls API.""" + + def __init__( + self, + kernel: Kernel, + session_id: str, + screen_size: ScreenSize = DEFAULT_SCREEN_SIZE, + ): + self.kernel = kernel + self.session_id = session_id + self.screen_size = screen_size + + def denormalize_x(self, x: int) -> int: + """Denormalize X coordinate from Gemini's 0-1000 scale to actual pixels.""" + return int((x / COORDINATE_SCALE) * self.screen_size.width) + + def denormalize_y(self, y: int) -> int: + """Denormalize Y coordinate from Gemini's 0-1000 scale to actual pixels.""" + return int((y / COORDINATE_SCALE) * self.screen_size.height) + + async def screenshot(self) -> ToolResult: + """Take a screenshot and return it as base64.""" + try: + await asyncio.sleep(SCREENSHOT_DELAY_MS) + response = self.kernel.browsers.computer.capture_screenshot(self.session_id) + screenshot_bytes = response.read() + + # Get current URL + url = "" + try: + state = self.kernel.browsers.computer.get_state(self.session_id) + url = state.url or "" + except Exception: + pass + + return ToolResult( + base64_image=base64.b64encode(screenshot_bytes).decode("utf-8"), + screenshot=screenshot_bytes, + url=url, + ) + except Exception as e: + return ToolResult(error=f"Failed to take screenshot: {e}") + + async def execute_action( + self, action_name: str, args: Dict[str, Any] + ) -> ToolResult: + """Execute a Gemini action and return the result with a screenshot.""" + # Check if this is a known computer use function + if action_name not in [a.value for a in PREDEFINED_COMPUTER_USE_FUNCTIONS]: + return ToolResult(error=f"Unknown action: {action_name}") + + try: + if action_name == GeminiAction.OPEN_WEB_BROWSER: + # Browser is already open in Kernel, just return screenshot + pass + + elif action_name == GeminiAction.CLICK_AT: + if "x" not in args or "y" not in args: + return ToolResult(error="click_at requires x and y coordinates") + x = self.denormalize_x(args["x"]) + y = self.denormalize_y(args["y"]) + self.kernel.browsers.computer.click_mouse( + self.session_id, + x=x, + y=y, + button="left", + click_type="click", + num_clicks=1, + ) + + elif action_name == GeminiAction.HOVER_AT: + if "x" not in args or "y" not in args: + return ToolResult(error="hover_at requires x and y coordinates") + x = self.denormalize_x(args["x"]) + y = self.denormalize_y(args["y"]) + self.kernel.browsers.computer.move_mouse( + self.session_id, x=x, y=y + ) + + elif action_name == GeminiAction.TYPE_TEXT_AT: + if "x" not in args or "y" not in args: + return ToolResult(error="type_text_at requires x and y coordinates") + if "text" not in args: + return ToolResult(error="type_text_at requires text") + + x = self.denormalize_x(args["x"]) + y = self.denormalize_y(args["y"]) + + # Click at the location first + self.kernel.browsers.computer.click_mouse( + self.session_id, + x=x, + y=y, + button="left", + click_type="click", + num_clicks=1, + ) + + # Clear existing text if requested (default: true) + if args.get("clear_before_typing", True): + self.kernel.browsers.computer.press_key( + self.session_id, keys=["ctrl+a"] + ) + await asyncio.sleep(0.05) + + # Type the text + self.kernel.browsers.computer.type_text( + self.session_id, + text=args["text"], + delay=TYPING_DELAY_MS, + ) + + # Press enter if requested + if args.get("press_enter", False): + await asyncio.sleep(0.1) + self.kernel.browsers.computer.press_key( + self.session_id, keys=["Return"] + ) + + elif action_name == GeminiAction.SCROLL_DOCUMENT: + if "direction" not in args: + return ToolResult(error="scroll_document requires direction") + # Scroll at center of viewport + center_x = self.screen_size.width // 2 + center_y = self.screen_size.height // 2 + scroll_delta = 500 + + delta_x, delta_y = 0, 0 + direction = args["direction"] + if direction == "down": + delta_y = scroll_delta + elif direction == "up": + delta_y = -scroll_delta + elif direction == "right": + delta_x = scroll_delta + elif direction == "left": + delta_x = -scroll_delta + + self.kernel.browsers.computer.scroll( + self.session_id, + x=center_x, + y=center_y, + delta_x=delta_x, + delta_y=delta_y, + ) + + elif action_name == GeminiAction.SCROLL_AT: + if "x" not in args or "y" not in args: + return ToolResult(error="scroll_at requires x and y coordinates") + if "direction" not in args: + return ToolResult(error="scroll_at requires direction") + + x = self.denormalize_x(args["x"]) + y = self.denormalize_y(args["y"]) + + # Denormalize magnitude if provided + magnitude = args.get("magnitude", 800) + direction = args["direction"] + if direction in ("up", "down"): + magnitude = self.denormalize_y(magnitude) + else: + magnitude = self.denormalize_x(magnitude) + + delta_x, delta_y = 0, 0 + if direction == "down": + delta_y = magnitude + elif direction == "up": + delta_y = -magnitude + elif direction == "right": + delta_x = magnitude + elif direction == "left": + delta_x = -magnitude + + self.kernel.browsers.computer.scroll( + self.session_id, + x=x, + y=y, + delta_x=delta_x, + delta_y=delta_y, + ) + + elif action_name == GeminiAction.WAIT_5_SECONDS: + await asyncio.sleep(5) + + elif action_name == GeminiAction.GO_BACK: + self.kernel.browsers.computer.press_key( + self.session_id, keys=["alt+Left"] + ) + await asyncio.sleep(1) + + elif action_name == GeminiAction.GO_FORWARD: + self.kernel.browsers.computer.press_key( + self.session_id, keys=["alt+Right"] + ) + await asyncio.sleep(1) + + elif action_name == GeminiAction.SEARCH: + # Focus URL bar (Ctrl+L) - equivalent to clicking search + self.kernel.browsers.computer.press_key( + self.session_id, keys=["ctrl+l"] + ) + + elif action_name == GeminiAction.NAVIGATE: + if "url" not in args: + return ToolResult(error="navigate requires url") + # Focus URL bar and type the URL + self.kernel.browsers.computer.press_key( + self.session_id, keys=["ctrl+l"] + ) + await asyncio.sleep(0.1) + self.kernel.browsers.computer.type_text( + self.session_id, + text=args["url"], + delay=TYPING_DELAY_MS, + ) + await asyncio.sleep(0.1) + self.kernel.browsers.computer.press_key( + self.session_id, keys=["Return"] + ) + await asyncio.sleep(1.5) # Wait for navigation + + elif action_name == GeminiAction.KEY_COMBINATION: + if "keys" not in args: + return ToolResult(error="key_combination requires keys") + # Gemini sends keys as "key1+key2+key3" + self.kernel.browsers.computer.press_key( + self.session_id, keys=[args["keys"]] + ) + + elif action_name == GeminiAction.DRAG_AND_DROP: + required = ["x", "y", "destination_x", "destination_y"] + if not all(k in args for k in required): + return ToolResult( + error="drag_and_drop requires x, y, destination_x, and destination_y" + ) + + start_x = self.denormalize_x(args["x"]) + start_y = self.denormalize_y(args["y"]) + end_x = self.denormalize_x(args["destination_x"]) + end_y = self.denormalize_y(args["destination_y"]) + + self.kernel.browsers.computer.drag_mouse( + self.session_id, + path=[[start_x, start_y], [end_x, end_y]], + button="left", + ) + + else: + return ToolResult(error=f"Unhandled action: {action_name}") + + # Wait a moment for the action to complete, then take a screenshot + await asyncio.sleep(SCREENSHOT_DELAY_MS) + return await self.screenshot() + + except Exception as e: + return ToolResult(error=f"Action failed: {e}") diff --git a/pkg/templates/python/gemini-computer-use/tools/types.py b/pkg/templates/python/gemini-computer-use/tools/types.py new file mode 100644 index 0000000..f12c4d1 --- /dev/null +++ b/pkg/templates/python/gemini-computer-use/tools/types.py @@ -0,0 +1,124 @@ +""" +Type definitions for Gemini Computer Use actions. +Based on Google's computer-use-preview reference implementation. +""" + +from dataclasses import dataclass +from enum import StrEnum +from typing import Literal, Optional, TypedDict + + +class GeminiAction(StrEnum): + """Gemini predefined computer use actions.""" + + OPEN_WEB_BROWSER = "open_web_browser" + CLICK_AT = "click_at" + HOVER_AT = "hover_at" + TYPE_TEXT_AT = "type_text_at" + SCROLL_DOCUMENT = "scroll_document" + SCROLL_AT = "scroll_at" + WAIT_5_SECONDS = "wait_5_seconds" + GO_BACK = "go_back" + GO_FORWARD = "go_forward" + SEARCH = "search" + NAVIGATE = "navigate" + KEY_COMBINATION = "key_combination" + DRAG_AND_DROP = "drag_and_drop" + + +# All predefined Gemini computer use function names +PREDEFINED_COMPUTER_USE_FUNCTIONS = [ + GeminiAction.OPEN_WEB_BROWSER, + GeminiAction.CLICK_AT, + GeminiAction.HOVER_AT, + GeminiAction.TYPE_TEXT_AT, + GeminiAction.SCROLL_DOCUMENT, + GeminiAction.SCROLL_AT, + GeminiAction.WAIT_5_SECONDS, + GeminiAction.GO_BACK, + GeminiAction.GO_FORWARD, + GeminiAction.SEARCH, + GeminiAction.NAVIGATE, + GeminiAction.KEY_COMBINATION, + GeminiAction.DRAG_AND_DROP, +] + + +# Scroll direction type +ScrollDirection = Literal["up", "down", "left", "right"] + + +class SafetyDecision(TypedDict, total=False): + """Safety decision from Gemini.""" + + decision: str + explanation: str + + +class GeminiFunctionArgs(TypedDict, total=False): + """Arguments for Gemini function calls.""" + + # click_at, hover_at, scroll_at + x: int + y: int + + # type_text_at + text: str + press_enter: bool + clear_before_typing: bool + + # scroll_document, scroll_at + direction: ScrollDirection + magnitude: int + + # navigate + url: str + + # key_combination + keys: str + + # drag_and_drop + destination_x: int + destination_y: int + + # Safety decision (may be included in any function call) + safety_decision: SafetyDecision + + +@dataclass +class ToolResult: + """Result from executing a computer action.""" + + # Base64-encoded screenshot image + base64_image: Optional[str] = None + # Screenshot as raw bytes + screenshot: Optional[bytes] = None + # Current URL of the browser + url: Optional[str] = None + # Error message if the action failed + error: Optional[str] = None + + +@dataclass +class EnvState: + """Environment state returned from computer actions.""" + + # Current URL of the browser + url: str + # Screenshot as bytes + screenshot: bytes + + +@dataclass +class ScreenSize: + """Screen dimensions for coordinate denormalization.""" + + width: int + height: int + + +# Default screen size (matching Kernel browser viewport) +DEFAULT_SCREEN_SIZE = ScreenSize(width=1024, height=768) + +# Gemini uses normalized coordinates (0-1000) +COORDINATE_SCALE = 1000 diff --git a/pkg/templates/typescript/gemini-computer-use/README.md b/pkg/templates/typescript/gemini-computer-use/README.md index 0775ccd..7387109 100644 --- a/pkg/templates/typescript/gemini-computer-use/README.md +++ b/pkg/templates/typescript/gemini-computer-use/README.md @@ -1,68 +1,59 @@ # Kernel TypeScript Sample App - Gemini Computer Use -A Kernel application that demonstrates Computer Use Agent (CUA) capabilities using Google's Gemini 2.5 model with Stagehand for browser automation. +This is a Kernel application that implements a prompt loop using Google's Gemini Computer Use model with Kernel's Computer Controls API. -## What It Does - -This app uses [Gemini 2.5's computer use model](https://blog.google/technology/google-deepmind/gemini-computer-use-model/) capabilities to autonomously navigate websites and complete tasks. The agent can interact with web pages just like a human would - clicking, typing, scrolling, and extracting information. +It follows the [Google Computer Use Preview Reference](https://github.com/google-gemini/computer-use-preview) but uses Kernel's Computer Controls API for browser automation. ## Setup -1. **Add your API keys as environment variables:** - - `KERNEL_API_KEY` - Get from [Kernel dashboard](https://dashboard.onkernel.com/sign-in) - - `GOOGLE_API_KEY` - Get from [Google AI Studio](https://aistudio.google.com/apikey) - -## Running Locally - -Execute the script directly with tsx: +1. Get your API keys: + - **Kernel**: [dashboard.onkernel.com](https://dashboard.onkernel.com) + - **Google AI**: [aistudio.google.com/apikey](https://aistudio.google.com/apikey) +2. Deploy the app: ```bash -npx tsx index.ts +kernel login +cp .env.example .env # Add your GOOGLE_API_KEY +kernel deploy index.ts --env-file .env ``` -This runs the agent without a Kernel invocation context and provides the browser live view URL for debugging. +## Usage -## Deploying to Kernel +```bash +kernel invoke ts-gemini-cua cua-task --payload '{"query": "Navigate to https://example.com and describe the page"}' +``` -1. **Copy the example env file, add your API keys, and deploy:** - ```bash - cp .example.env .env - kernel deploy index.ts --env-file .env - ``` +## Recording Replays -2. **Invoke the action:** - ```bash - kernel invoke ts-gemini-cua gemini-cua-task - ``` +> **Note:** Replay recording is only available to Kernel users on paid plans. -The action creates a Kernel-managed browser and associates it with the invocation for tracking and monitoring. +Add `"record_replay": true` to your payload to capture a video of the browser session: -## Alternative Model Providers +```bash +kernel invoke ts-gemini-cua cua-task --payload '{"query": "Navigate to https://example.com", "record_replay": true}' +``` -Stagehand's CUA agent supports multiple model providers. You can switch from Gemini to OpenAI or Anthropic by changing the model configuration in `index.ts` and redeploying your Kernel app: +When enabled, the response will include a `replay_url` field with a link to view the recorded session. -**OpenAI Computer Use:** -```typescript -model: { - modelName: "openai/computer-use-preview", - apiKey: process.env.OPENAI_API_KEY -} -``` +## Gemini Computer Use Actions -**Anthropic Claude Sonnet:** -```typescript -model: { - modelName: "anthropic/claude-sonnet-4-20250514", - apiKey: process.env.ANTHROPIC_API_KEY -} -``` +The Gemini model can execute the following browser actions: -When using alternative providers, make sure to: -1. Add the corresponding API key to your environment variables -2. Update the deploy command to include the new API key (e.g., `--env OPENAI_API_KEY=XXX`) +| Action | Description | +|--------|-------------| +| `click_at` | Click at coordinates (x, y) | +| `hover_at` | Move mouse to coordinates (x, y) | +| `type_text_at` | Click and type text at coordinates | +| `scroll_document` | Scroll the page (up/down/left/right) | +| `scroll_at` | Scroll at specific coordinates | +| `navigate` | Navigate to a URL | +| `go_back` | Go back in browser history | +| `go_forward` | Go forward in browser history | +| `key_combination` | Press key combination (e.g., "ctrl+c") | +| `drag_and_drop` | Drag from one point to another | +| `wait_5_seconds` | Wait for 5 seconds | -## Documentation +## Resources +- [Google Gemini Computer Use Documentation](https://ai.google.dev/gemini-api/docs/computer-use) - [Kernel Documentation](https://www.kernel.sh/docs/quickstart) -- [Kernel Stagehand Guide](https://www.kernel.sh/docs/integrations/stagehand) -- [Gemini 2.5 Computer Use](https://blog.google/technology/google-deepmind/gemini-computer-use-model/) diff --git a/pkg/templates/typescript/gemini-computer-use/index.ts b/pkg/templates/typescript/gemini-computer-use/index.ts index a2e0bdf..54757f9 100644 --- a/pkg/templates/typescript/gemini-computer-use/index.ts +++ b/pkg/templates/typescript/gemini-computer-use/index.ts @@ -1,25 +1,23 @@ -import { Stagehand } from "@browserbasehq/stagehand"; import { Kernel, type KernelContext } from '@onkernel/sdk'; +import { samplingLoop } from './loop'; +import { KernelBrowserSession } from './session'; -const kernel = new Kernel({ - apiKey: process.env.KERNEL_API_KEY -}); +const kernel = new Kernel(); const app = kernel.app('ts-gemini-cua'); -interface CuaTaskInput { - startingUrl?: string; - instruction?: string; +interface QueryInput { + query: string; + record_replay?: boolean; } -interface SearchQueryOutput { - success: boolean; +interface QueryOutput { result: string; - error?: string; + replay_url?: string; } -// API Key for LLM provider -// - GOOGLE_API_KEY: Required for Gemini 2.5 Computer Use Agent +// API Key for Gemini +// - GOOGLE_API_KEY: Required for Gemini Computer Use model // Set via environment variables or `kernel deploy --env-file .env` // See https://www.kernel.sh/docs/launch/deploy#environment-variables const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY; @@ -28,96 +26,73 @@ if (!GOOGLE_API_KEY) { throw new Error('GOOGLE_API_KEY is not set'); } -async function runStagehandTask( - invocationId?: string, - startingUrl: string = "https://www.magnitasks.com/", - instruction: string = "Click the Tasks option in the left-side bar, and move the 5 items in the 'To Do' and 'In Progress' items to the 'Done' section of the Kanban board? You are done successfully when the items are moved." -): Promise { - // Executes a Computer Use Agent (CUA) task using Gemini 2.5 and Stagehand - - const browserOptions = { - stealth: true, - viewport: { - width: 1440, - height: 900, - refresh_rate: 25 - }, - ...(invocationId && { invocation_id: invocationId }) - }; - - const kernelBrowser = await kernel.browsers.create(browserOptions); - - console.log("Kernel browser live view url: ", kernelBrowser.browser_live_view_url); - - const stagehand = new Stagehand({ - env: "LOCAL", - verbose: 1, - domSettleTimeout: 30_000, - localBrowserLaunchOptions: { - cdpUrl: kernelBrowser.cdp_ws_url +app.action( + 'cua-task', + async (ctx: KernelContext, payload?: QueryInput): Promise => { + if (!payload?.query) { + throw new Error('Query is required'); } - }); - await stagehand.init(); - - ///////////////////////////////////// - // Your Stagehand implementation here - ///////////////////////////////////// - try { - const page = stagehand.context.pages()[0]; - - const agent = stagehand.agent({ - cua: true, - model: { - modelName: "google/gemini-2.5-computer-use-preview-10-2025", - apiKey: GOOGLE_API_KEY, - }, - systemPrompt: `You are a helpful assistant that can use a web browser. - You are currently on the following page: ${page.url()}. - Do not ask follow up questions, the user will trust your judgement.`, - }); - // Navigate to the starting website - await page.goto(startingUrl); - - // Execute the instruction - const result = await agent.execute({ - instruction, - maxSteps: 20, + // Create browser session with optional replay recording + const session = new KernelBrowserSession(kernel, { + stealth: true, + recordReplay: payload.record_replay ?? false, }); - console.log("result: ", result); - - return { success: true, result: result.message }; - } catch (error) { - console.error(error); - const errorMessage = error instanceof Error ? error.message : String(error); - return { success: false, result: "", error: errorMessage }; - } finally { - console.log("Deleting browser and closing stagehand..."); - await stagehand.close(); - await kernel.browsers.deleteByID(kernelBrowser.session_id); - } -} + await session.start(); + console.log('Kernel browser live view url:', session.liveViewUrl); -// Register Kernel action handler for remote invocation -// Invoked via: kernel invoke ts-gemini-cua gemini-cua-task -app.action( - 'gemini-cua-task', - async (ctx: KernelContext, payload?: CuaTaskInput): Promise => { - return runStagehandTask( - ctx.invocation_id, - payload?.startingUrl, - payload?.instruction - ); + try { + // Run the Gemini sampling loop + const result = await samplingLoop({ + model: 'gemini-2.5-computer-use-preview-10-2025', + query: payload.query, + apiKey: GOOGLE_API_KEY, + kernel, + sessionId: session.sessionId, + }); + + // Stop session and get replay URL if recording was enabled + const sessionInfo = await session.stop(); + + return { + result: result.finalResponse, + replay_url: sessionInfo.replayViewUrl, + }; + } catch (error) { + console.error('Error in sampling loop:', error); + await session.stop(); + throw error; + } }, ); // Run locally if executed directly (not imported as a module) // Execute via: npx tsx index.ts if (import.meta.url === `file://${process.argv[1]}`) { - runStagehandTask().then(result => { - console.log('Local execution result:', result); - process.exit(result.success ? 0 : 1); + const testQuery = "Navigate to https://www.google.com and describe what you see"; + + console.log('Running local test with query:', testQuery); + + const session = new KernelBrowserSession(kernel, { + stealth: true, + recordReplay: false, + }); + + session.start().then(async () => { + try { + const result = await samplingLoop({ + model: 'gemini-2.5-computer-use-preview-10-2025', + query: testQuery, + apiKey: GOOGLE_API_KEY, + kernel, + sessionId: session.sessionId, + }); + console.log('Result:', result.finalResponse); + } finally { + await session.stop(); + } + process.exit(0); }).catch(error => { console.error('Local execution failed:', error); process.exit(1); diff --git a/pkg/templates/typescript/gemini-computer-use/loop.ts b/pkg/templates/typescript/gemini-computer-use/loop.ts new file mode 100644 index 0000000..2ecac22 --- /dev/null +++ b/pkg/templates/typescript/gemini-computer-use/loop.ts @@ -0,0 +1,296 @@ +/** + * Gemini Computer Use sampling loop. + * Based on Google's computer-use-preview reference implementation. + */ + +import { + GoogleGenAI, + type Content, + type FunctionCall, + type Part, +} from '@google/genai'; +import type { Kernel } from '@onkernel/sdk'; +import { ComputerTool } from './tools/computer'; +import { PREDEFINED_COMPUTER_USE_FUNCTIONS, type GeminiFunctionArgs } from './tools/types/gemini'; + +// System prompt for browser-based computer use +const SYSTEM_PROMPT = `You are a helpful assistant that can use a web browser. +You are operating a Chrome browser through computer use tools. +The browser is already open and ready for use. + +When you need to navigate to a page, use the navigate action with a full URL. +When you need to interact with elements, use click_at, type_text_at, etc. +After each action, carefully evaluate the screenshot to determine your next step. + +Current date: ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}.`; + +// Maximum number of recent turns to keep screenshots for (to manage context) +const MAX_RECENT_TURN_WITH_SCREENSHOTS = 3; + +interface SamplingLoopOptions { + model: string; + query: string; + apiKey: string; + kernel: Kernel; + sessionId: string; + maxIterations?: number; + systemPromptSuffix?: string; +} + +interface SamplingLoopResult { + finalResponse: string; + iterations: number; +} + +/** + * Run the Gemini computer use sampling loop. + */ +export async function samplingLoop({ + model, + query, + apiKey, + kernel, + sessionId, + maxIterations = 50, + systemPromptSuffix = '', +}: SamplingLoopOptions): Promise { + const ai = new GoogleGenAI({ apiKey }); + + const computerTool = new ComputerTool(kernel, sessionId); + + // Initialize conversation with user query + const contents: Content[] = [ + { + role: 'user', + parts: [{ text: query }], + }, + ]; + + const systemPrompt = systemPromptSuffix + ? `${SYSTEM_PROMPT}\n\n${systemPromptSuffix}` + : SYSTEM_PROMPT; + + let iteration = 0; + let finalResponse = ''; + + while (iteration < maxIterations) { + iteration++; + console.log(`\n=== Iteration ${iteration} ===`); + + try { + // Generate response from Gemini + const response = await ai.models.generateContent({ + model, + contents, + config: { + temperature: 1, + topP: 0.95, + topK: 40, + maxOutputTokens: 8192, + systemInstruction: systemPrompt, + tools: [ + { + computerUse: { + environment: 'ENVIRONMENT_BROWSER', + }, + }, + ], + thinkingConfig: { + includeThoughts: true, + }, + }, + }); + + if (!response.candidates || response.candidates.length === 0) { + console.log('No candidates in response'); + break; + } + + const candidate = response.candidates[0]; + if (!candidate.content) { + console.log('No content in candidate'); + break; + } + + // Add assistant response to conversation + contents.push(candidate.content); + + // Extract text and function calls + const reasoning = extractText(candidate.content); + const functionCalls = extractFunctionCalls(candidate.content); + + // Log the response + console.log('Reasoning:', reasoning || '(none)'); + console.log('Function calls:', functionCalls.length); + for (const fc of functionCalls) { + console.log(` - ${fc.name}:`, fc.args); + } + + // Check finish reason + const finishReason = candidate.finishReason; + if (finishReason === 'MALFORMED_FUNCTION_CALL' && !functionCalls.length) { + console.log('Malformed function call, retrying...'); + continue; + } + + // If no function calls, the model is done + if (functionCalls.length === 0) { + console.log('Agent loop complete'); + finalResponse = reasoning || ''; + break; + } + + // Execute function calls and collect results + const functionResponses: Part[] = []; + for (const fc of functionCalls) { + const args = fc.args as GeminiFunctionArgs || {}; + + // Handle safety decisions if present + if (args.safety_decision?.decision === 'require_confirmation') { + console.log('Safety confirmation required:', args.safety_decision.explanation); + // Auto-acknowledge for automated execution + console.log('Auto-acknowledging safety check'); + } + + // Execute the action + console.log(`Executing action: ${fc.name}`); + const result = await computerTool.executeAction(fc.name, args); + + if (result.error) { + console.log(`Action error: ${result.error}`); + functionResponses.push({ + functionResponse: { + name: fc.name, + response: { error: result.error }, + }, + }); + } else { + // Build response with screenshot - always include URL (required by Computer Use API) + const responseData: Record = { + url: result.url || 'about:blank', + }; + + functionResponses.push({ + functionResponse: { + name: fc.name, + response: responseData, + // Include screenshot as inline data + ...(result.base64Image && isPredefinedFunction(fc.name) ? { + parts: [{ + inlineData: { + mimeType: 'image/png', + data: result.base64Image, + }, + }], + } : {}), + }, + }); + } + } + + // Add function responses to conversation + contents.push({ + role: 'user', + parts: functionResponses, + }); + + // Manage screenshot history to avoid context overflow + pruneOldScreenshots(contents); + + } catch (error) { + console.error('Error in sampling loop:', error); + break; + } + } + + if (iteration >= maxIterations) { + console.log('Max iterations reached'); + } + + return { + finalResponse, + iterations: iteration, + }; +} + +/** + * Extract text from a Gemini content response. + */ +function extractText(content: Content): string { + if (!content.parts) return ''; + + const texts: string[] = []; + for (const part of content.parts) { + if ('text' in part && part.text) { + texts.push(part.text); + } + } + return texts.join(' '); +} + +/** + * Extract function calls from a Gemini content response. + */ +function extractFunctionCalls(content: Content): FunctionCall[] { + if (!content.parts) return []; + + const calls: FunctionCall[] = []; + for (const part of content.parts) { + if ('functionCall' in part && part.functionCall) { + calls.push(part.functionCall); + } + } + return calls; +} + +/** + * Check if a function name is a predefined computer use function. + */ +function isPredefinedFunction(name: string): boolean { + return PREDEFINED_COMPUTER_USE_FUNCTIONS.includes(name as typeof PREDEFINED_COMPUTER_USE_FUNCTIONS[number]); +} + +/** + * Prune old screenshots from conversation history to manage context size. + */ +function pruneOldScreenshots(contents: Content[]): void { + let turnsWithScreenshots = 0; + + // Iterate in reverse to find recent turns with screenshots + for (let i = contents.length - 1; i >= 0; i--) { + const content = contents[i]; + if (content.role !== 'user' || !content.parts) continue; + + // Check if this turn has screenshots from predefined functions + let hasScreenshot = false; + for (const part of content.parts) { + if ('functionResponse' in part && + part.functionResponse && + isPredefinedFunction(part.functionResponse.name || '')) { + // Check if it has inline data (screenshot) + const fr = part.functionResponse as { parts?: Array<{ inlineData?: unknown }> }; + if (fr.parts?.some(p => p.inlineData)) { + hasScreenshot = true; + break; + } + } + } + + if (hasScreenshot) { + turnsWithScreenshots++; + + // Remove screenshots from old turns + if (turnsWithScreenshots > MAX_RECENT_TURN_WITH_SCREENSHOTS) { + for (const part of content.parts) { + if ('functionResponse' in part && + part.functionResponse && + isPredefinedFunction(part.functionResponse.name || '')) { + // Remove the parts array (which contains the screenshot) + const fr = part.functionResponse as { parts?: unknown }; + delete fr.parts; + } + } + } + } + } +} diff --git a/pkg/templates/typescript/gemini-computer-use/package.json b/pkg/templates/typescript/gemini-computer-use/package.json index dd74931..8ef6ebf 100644 --- a/pkg/templates/typescript/gemini-computer-use/package.json +++ b/pkg/templates/typescript/gemini-computer-use/package.json @@ -4,9 +4,8 @@ "type": "module", "private": true, "dependencies": { - "@browserbasehq/stagehand": "^3.0.6", - "@onkernel/sdk": "^0.23.0", - "zod": "^4.2.0" + "@google/genai": "^1.0.0", + "@onkernel/sdk": "^0.23.0" }, "devDependencies": { "@types/node": "^22.15.17", diff --git a/pkg/templates/typescript/gemini-computer-use/pnpm-lock.yaml b/pkg/templates/typescript/gemini-computer-use/pnpm-lock.yaml deleted file mode 100644 index b1e3100..0000000 --- a/pkg/templates/typescript/gemini-computer-use/pnpm-lock.yaml +++ /dev/null @@ -1,2971 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@browserbasehq/stagehand': - specifier: ^3.0.6 - version: 3.0.6(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(deepmerge@4.3.1)(dotenv@16.6.1)(hono@4.11.1)(zod@4.2.0) - '@onkernel/sdk': - specifier: ^0.23.0 - version: 0.23.0 - zod: - specifier: ^4.2.0 - version: 4.2.0 - devDependencies: - '@types/node': - specifier: ^22.15.17 - version: 22.19.3 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - -packages: - - '@ai-sdk/anthropic@2.0.56': - resolution: {integrity: sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/azure@2.0.88': - resolution: {integrity: sha512-OMAXXZV7GiFz8qpCpzhaesTfiuiXU92WZWdvtr+K8rjfTNGm9sJWUuSLZ29z5aAeLUSRlwDMUlK4lYr8/1IewQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/cerebras@1.0.33': - resolution: {integrity: sha512-2gSSS/7kunIwMdC4td5oWsUAzoLw84ccGpz6wQbxVnrb1iWnrEnKa5tRBduaP6IXpzLWsu8wME3+dQhZy+gT7w==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/deepseek@1.0.32': - resolution: {integrity: sha512-DDNZSZn6OuExVBJBAWdk3VeyQPH+pYwSykixePhzll9EnT3aakapMYr5gjw3wMl+eZ0tLplythHL1TfIehUZ0g==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/gateway@2.0.21': - resolution: {integrity: sha512-BwV7DU/lAm3Xn6iyyvZdWgVxgLu3SNXzl5y57gMvkW4nGhAOV5269IrJzQwGt03bb107sa6H6uJwWxc77zXoGA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/google-vertex@3.0.91': - resolution: {integrity: sha512-SonFMMdSIlos0fjBFBff7rcZQx+q3WP4CpXdz7+YEIEWItnR/k9f5MqRCXMZilfyzcpz5wFxa7Sqlnapv3oqsA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/google@2.0.46': - resolution: {integrity: sha512-8PK6u4sGE/kXebd7ZkTp+0aya4kNqzoqpS5m7cHY2NfTK6fhPc6GNvE+MZIZIoHQTp5ed86wGBdeBPpFaaUtyg==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/groq@2.0.33': - resolution: {integrity: sha512-FWGl7xNr88NBveao3y9EcVWYUt9ABPrwLFY7pIutSNgaTf32vgvyhREobaMrLU4Scr5G/2tlNqOPZ5wkYMaZig==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/mistral@2.0.26': - resolution: {integrity: sha512-jxDB++4WI1wEx5ONNBI+VbkmYJOYIuS8UQY13/83UGRaiW7oB/WHiH4ETe6KzbKpQPB3XruwTJQjUMsMfKyTXA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/openai-compatible@1.0.29': - resolution: {integrity: sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/openai@2.0.86': - resolution: {integrity: sha512-obsLIOyA93lbQiSt1rvBItoVQp1U2RDPs0bNG0JYhm6Gku8Dg/0Cm8e4NUWT5p5PN10/doKSb3SMSKCixwIAKA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/perplexity@2.0.22': - resolution: {integrity: sha512-zwzcnk08R2J3mZcQPn4Ifl4wYGrvANR7jsBB0hCTUSbb+Rx3ybpikSWiGuXQXxdiRc1I5MWXgj70m+bZaLPvHw==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/provider-utils@3.0.19': - resolution: {integrity: sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/provider@2.0.0': - resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} - engines: {node: '>=18'} - - '@ai-sdk/togetherai@1.0.30': - resolution: {integrity: sha512-9bxQbIXnWSN4bNismrza3NvIo+ui/Y3pj3UN6e9vCszCWFCN45RgISi4oDe10RqmzaJ/X8cfO/Tem+K8MT3wGQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/xai@2.0.41': - resolution: {integrity: sha512-mztE/1svgeBscJBEcy/AqVZSxTXasnN22o58Vsf7rX7f1X9gqadVoDHH4a/caiklADg/HNu2h1u+vTgVYL8XbA==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@anthropic-ai/sdk@0.39.0': - resolution: {integrity: sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==} - - '@browserbasehq/sdk@2.6.0': - resolution: {integrity: sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==} - - '@browserbasehq/stagehand@3.0.6': - resolution: {integrity: sha512-WN/GuJMHXyXNsQTErmeTEstxUe0gZf9DLXlPkfJtVUuqErIJgZPiBstlTrdgx7I8n1ZE+q8gaEU/beJsW30+bg==} - peerDependencies: - deepmerge: ^4.3.1 - dotenv: ^16.4.5 - zod: ^3.25.76 || ^4.1.8 - - '@cfworker/json-schema@4.1.1': - resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} - - '@google/genai@1.33.0': - resolution: {integrity: sha512-ThUjFZ1N0DU88peFjnQkb8K198EWaW2RmmnDShFQ+O+xkIH9itjpRe358x3L/b4X/A7dimkvq63oz49Vbh7Cog==} - engines: {node: '>=20.0.0'} - peerDependencies: - '@modelcontextprotocol/sdk': ^1.24.0 - peerDependenciesMeta: - '@modelcontextprotocol/sdk': - optional: true - - '@hono/node-server@1.19.7': - resolution: {integrity: sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==} - engines: {node: '>=18.14.1'} - peerDependencies: - hono: ^4 - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@langchain/core@0.3.79': - resolution: {integrity: sha512-ZLAs5YMM5N2UXN3kExMglltJrKKoW7hs3KMZFlXUnD7a5DFKBYxPFMeXA4rT+uvTxuJRZPCYX0JKI5BhyAWx4A==} - engines: {node: '>=18'} - - '@langchain/openai@0.4.9': - resolution: {integrity: sha512-NAsaionRHNdqaMjVLPkFCyjUDze+OqRHghA1Cn4fPoAafz+FXcl9c7LlEl9Xo0FH6/8yiCl7Rw2t780C/SBVxQ==} - engines: {node: '>=18'} - peerDependencies: - '@langchain/core': '>=0.3.39 <0.4.0' - - '@modelcontextprotocol/sdk@1.25.0': - resolution: {integrity: sha512-z0Zhn/LmQ3yz91dEfd5QgS7DpSjA4pk+3z2++zKgn5L6iDFM9QapsVoAQSbKLvlrFsZk9+ru6yHHWNq2lCYJKQ==} - engines: {node: '>=18'} - peerDependencies: - '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@cfworker/json-schema': - optional: true - - '@onkernel/sdk@0.23.0': - resolution: {integrity: sha512-P/ez6HU8sO2QvqWATkvC+Wdv+fgto4KfBCHLl2T6EUpoU3LhgOZ/sJP2ZRf/vh5Vh7QR2Vf05RgMaFcIGBGD9Q==} - - '@opentelemetry/api@1.9.0': - resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} - engines: {node: '>=8.0.0'} - - '@pinojs/redact@0.4.0': - resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@puppeteer/browsers@2.3.0': - resolution: {integrity: sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==} - engines: {node: '>=18'} - hasBin: true - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@tootallnate/quickjs-emscripten@0.23.0': - resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - - '@types/node-fetch@2.6.13': - resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} - - '@types/node@18.19.130': - resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - - '@types/node@22.19.3': - resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} - - '@types/retry@0.12.0': - resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} - - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - - '@vercel/oidc@3.0.5': - resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} - engines: {node: '>= 20'} - - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - agentkeepalive@4.6.0: - resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} - engines: {node: '>= 8.0.0'} - - ai@5.0.113: - resolution: {integrity: sha512-26vivpSO/mzZj0k1Si2IpsFspp26ttQICHRySQiMrtWcRd5mnJMX2a8sG28vmZ38C+JUn1cWmfZrsLMxkSMw9g==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - ast-types@0.13.4: - resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} - engines: {node: '>=4'} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - - b4a@1.7.3: - resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} - peerDependencies: - react-native-b4a: '*' - peerDependenciesMeta: - react-native-b4a: - optional: true - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - bare-events@2.8.2: - resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} - peerDependencies: - bare-abort-controller: '*' - peerDependenciesMeta: - bare-abort-controller: - optional: true - - bare-fs@4.5.2: - resolution: {integrity: sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==} - engines: {bare: '>=1.16.0'} - peerDependencies: - bare-buffer: '*' - peerDependenciesMeta: - bare-buffer: - optional: true - - bare-os@3.6.2: - resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} - engines: {bare: '>=1.14.0'} - - bare-path@3.0.0: - resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - - bare-stream@2.7.0: - resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} - peerDependencies: - bare-buffer: '*' - bare-events: '*' - peerDependenciesMeta: - bare-buffer: - optional: true - bare-events: - optional: true - - bare-url@2.3.2: - resolution: {integrity: sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - basic-ftp@5.0.5: - resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} - engines: {node: '>=10.0.0'} - - bignumber.js@9.3.1: - resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - - body-parser@2.2.1: - resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} - engines: {node: '>=18'} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - bufferutil@4.0.9: - resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==} - engines: {node: '>=6.14.2'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chrome-launcher@1.2.1: - resolution: {integrity: sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A==} - engines: {node: '>=12.13.0'} - hasBin: true - - chromium-bidi@0.6.3: - resolution: {integrity: sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==} - peerDependencies: - devtools-protocol: '*' - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - console-table-printer@2.15.0: - resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==} - - content-disposition@1.0.1: - resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - - data-uri-to-buffer@6.0.2: - resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} - engines: {node: '>= 14'} - - dateformat@4.6.3: - resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - degenerator@5.0.1: - resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} - engines: {node: '>= 14'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - devtools-protocol@0.0.1312386: - resolution: {integrity: sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==} - - devtools-protocol@0.0.1464554: - resolution: {integrity: sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==} - - dotenv@16.6.1: - resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} - engines: {node: '>=12'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - - events-universal@1.0.1: - resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} - - eventsource-parser@3.0.6: - resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} - engines: {node: '>=18.0.0'} - - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} - engines: {node: '>=18.0.0'} - - express-rate-limit@7.5.1: - resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - - fast-copy@4.0.1: - resolution: {integrity: sha512-+uUOQlhsaswsizHFmEFAQhB3lSiQ+lisxl50N6ZP0wywlZeWsIESxSi9ftPEps8UGfiBzyYP7x27zA674WUvXw==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - - fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - - fetch-cookie@3.2.0: - resolution: {integrity: sha512-n61pQIxP25C6DRhcJxn7BDzgHP/+S56Urowb5WFxtcRMpU6drqXD90xjyAsVQYsNSNNVbaCcYY1DuHsdkZLuiA==} - - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - form-data-encoder@1.7.2: - resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} - - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} - engines: {node: '>= 6'} - - formdata-node@4.4.1: - resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} - engines: {node: '>= 12.20'} - - formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - gaxios@7.1.3: - resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} - engines: {node: '>=18'} - - gcp-metadata@8.1.2: - resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} - engines: {node: '>=18'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - - get-uri@6.0.5: - resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} - engines: {node: '>= 14'} - - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - hasBin: true - - google-auth-library@10.5.0: - resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} - engines: {node: '>=18'} - - google-logging-utils@1.1.3: - resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} - engines: {node: '>=14'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - gtoken@8.0.0: - resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} - engines: {node: '>=18'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - help-me@5.0.0: - resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - - hono@4.11.1: - resolution: {integrity: sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg==} - engines: {node: '>=16.9.0'} - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - - iconv-lite@0.7.1: - resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} - engines: {node: '>=0.10.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} - engines: {node: '>= 12'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jose@6.1.3: - resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - js-tiktoken@1.0.21: - resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} - - json-bigint@1.0.0: - resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - - jwa@2.0.1: - resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - - jws@4.0.1: - resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - - langsmith@0.3.87: - resolution: {integrity: sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q==} - peerDependencies: - '@opentelemetry/api': '*' - '@opentelemetry/exporter-trace-otlp-proto': '*' - '@opentelemetry/sdk-trace-base': '*' - openai: '*' - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@opentelemetry/exporter-trace-otlp-proto': - optional: true - '@opentelemetry/sdk-trace-base': - optional: true - openai: - optional: true - - lighthouse-logger@2.0.2: - resolution: {integrity: sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - marky@1.3.0: - resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - - netmask@2.0.2: - resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} - engines: {node: '>= 0.4.0'} - - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - deprecated: Use your platform's native DOMException instead - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - node-gyp-build@4.8.4: - resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} - hasBin: true - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - ollama-ai-provider-v2@1.5.5: - resolution: {integrity: sha512-1YwTFdPjhPNHny/DrOHO+s8oVGGIE5Jib61/KnnjPRNWQhVVimrJJdaAX3e6nNRRDXrY5zbb9cfm2+yVvgsrqw==} - engines: {node: '>=18'} - peerDependencies: - zod: ^4.0.16 - - on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - openai@4.104.0: - resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==} - hasBin: true - peerDependencies: - ws: ^8.18.0 - zod: ^3.23.8 - peerDependenciesMeta: - ws: - optional: true - zod: - optional: true - - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - - p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} - - p-retry@4.6.2: - resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} - engines: {node: '>=8'} - - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - - pac-proxy-agent@7.2.0: - resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} - engines: {node: '>= 14'} - - pac-resolver@7.0.1: - resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} - engines: {node: '>= 14'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - patchright-core@1.57.0: - resolution: {integrity: sha512-um/9Wue7IFAa9UDLacjNgDn62ub5GJe1b1qouvYpELIF9rsFVMNhRo/rRXYajupLwp5xKJ0sSjOV6sw8/HarBQ==} - engines: {node: '>=18'} - hasBin: true - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - - pino-abstract-transport@3.0.0: - resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} - - pino-pretty@13.1.3: - resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} - hasBin: true - - pino-std-serializers@7.0.0: - resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - - pino@9.14.0: - resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} - hasBin: true - - pkce-challenge@5.0.1: - resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} - engines: {node: '>=16.20.0'} - - playwright-core@1.57.0: - resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.57.0: - resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==} - engines: {node: '>=18'} - hasBin: true - - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - proxy-agent@6.5.0: - resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} - engines: {node: '>= 14'} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - - puppeteer-core@22.15.0: - resolution: {integrity: sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==} - engines: {node: '>=18'} - - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - - quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - - real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - - rimraf@5.0.10: - resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} - hasBin: true - - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - secure-json-parse@4.1.0: - resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} - - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - - set-cookie-parser@2.7.2: - resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - simple-wcswidth@1.1.2: - resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==} - - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} - - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - - sonic-boom@4.2.0: - resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - - streamx@2.23.0: - resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} - - strip-json-comments@5.0.3: - resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} - engines: {node: '>=14.16'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - tar-fs@3.1.1: - resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} - - tar-stream@3.1.7: - resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - - text-decoder@1.2.3: - resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} - - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - tldts-core@7.0.19: - resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} - - tldts@7.0.19: - resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} - hasBin: true - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - tough-cookie@6.0.0: - resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} - engines: {node: '>=16'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - unbzip2-stream@1.4.3: - resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - urlpattern-polyfill@10.0.0: - resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} - - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} - hasBin: true - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - - web-streams-polyfill@4.0.0-beta.3: - resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} - engines: {node: '>= 14'} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - - zod-to-json-schema@3.25.0: - resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} - peerDependencies: - zod: ^3.25 || ^4 - - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - - zod@4.2.0: - resolution: {integrity: sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw==} - -snapshots: - - '@ai-sdk/anthropic@2.0.56(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/azure@2.0.88(zod@4.2.0)': - dependencies: - '@ai-sdk/openai': 2.0.86(zod@4.2.0) - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/cerebras@1.0.33(zod@4.2.0)': - dependencies: - '@ai-sdk/openai-compatible': 1.0.29(zod@4.2.0) - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/deepseek@1.0.32(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/gateway@2.0.21(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - '@vercel/oidc': 3.0.5 - zod: 4.2.0 - - '@ai-sdk/google-vertex@3.0.91(zod@4.2.0)': - dependencies: - '@ai-sdk/anthropic': 2.0.56(zod@4.2.0) - '@ai-sdk/google': 2.0.46(zod@4.2.0) - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - google-auth-library: 10.5.0 - zod: 4.2.0 - transitivePeerDependencies: - - supports-color - optional: true - - '@ai-sdk/google@2.0.46(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/groq@2.0.33(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/mistral@2.0.26(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/openai-compatible@1.0.29(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/openai@2.0.86(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/perplexity@2.0.22(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/provider-utils@3.0.19(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.0.6 - zod: 4.2.0 - - '@ai-sdk/provider@2.0.0': - dependencies: - json-schema: 0.4.0 - - '@ai-sdk/togetherai@1.0.30(zod@4.2.0)': - dependencies: - '@ai-sdk/openai-compatible': 1.0.29(zod@4.2.0) - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@ai-sdk/xai@2.0.41(zod@4.2.0)': - dependencies: - '@ai-sdk/openai-compatible': 1.0.29(zod@4.2.0) - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - '@anthropic-ai/sdk@0.39.0': - dependencies: - '@types/node': 18.19.130 - '@types/node-fetch': 2.6.13 - abort-controller: 3.0.0 - agentkeepalive: 4.6.0 - form-data-encoder: 1.7.2 - formdata-node: 4.4.1 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - - '@browserbasehq/sdk@2.6.0': - dependencies: - '@types/node': 18.19.130 - '@types/node-fetch': 2.6.13 - abort-controller: 3.0.0 - agentkeepalive: 4.6.0 - form-data-encoder: 1.7.2 - formdata-node: 4.4.1 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - - '@browserbasehq/stagehand@3.0.6(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(deepmerge@4.3.1)(dotenv@16.6.1)(hono@4.11.1)(zod@4.2.0)': - dependencies: - '@ai-sdk/provider': 2.0.0 - '@anthropic-ai/sdk': 0.39.0 - '@browserbasehq/sdk': 2.6.0 - '@google/genai': 1.33.0(@modelcontextprotocol/sdk@1.25.0(@cfworker/json-schema@4.1.1)(hono@4.11.1)(zod@4.2.0))(bufferutil@4.0.9) - '@langchain/openai': 0.4.9(@langchain/core@0.3.79(@opentelemetry/api@1.9.0)(openai@4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0)))(ws@8.18.3(bufferutil@4.0.9)) - '@modelcontextprotocol/sdk': 1.25.0(@cfworker/json-schema@4.1.1)(hono@4.11.1)(zod@4.2.0) - ai: 5.0.113(zod@4.2.0) - deepmerge: 4.3.1 - devtools-protocol: 0.0.1464554 - dotenv: 16.6.1 - fetch-cookie: 3.2.0 - openai: 4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0) - pino: 9.14.0 - pino-pretty: 13.1.3 - playwright: 1.57.0 - uuid: 11.1.0 - ws: 8.18.3(bufferutil@4.0.9) - zod: 4.2.0 - zod-to-json-schema: 3.25.0(zod@4.2.0) - optionalDependencies: - '@ai-sdk/anthropic': 2.0.56(zod@4.2.0) - '@ai-sdk/azure': 2.0.88(zod@4.2.0) - '@ai-sdk/cerebras': 1.0.33(zod@4.2.0) - '@ai-sdk/deepseek': 1.0.32(zod@4.2.0) - '@ai-sdk/google': 2.0.46(zod@4.2.0) - '@ai-sdk/google-vertex': 3.0.91(zod@4.2.0) - '@ai-sdk/groq': 2.0.33(zod@4.2.0) - '@ai-sdk/mistral': 2.0.26(zod@4.2.0) - '@ai-sdk/openai': 2.0.86(zod@4.2.0) - '@ai-sdk/perplexity': 2.0.22(zod@4.2.0) - '@ai-sdk/togetherai': 1.0.30(zod@4.2.0) - '@ai-sdk/xai': 2.0.41(zod@4.2.0) - '@langchain/core': 0.3.79(@opentelemetry/api@1.9.0)(openai@4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0)) - bufferutil: 4.0.9 - chrome-launcher: 1.2.1 - ollama-ai-provider-v2: 1.5.5(zod@4.2.0) - patchright-core: 1.57.0 - playwright-core: 1.57.0 - puppeteer-core: 22.15.0(bufferutil@4.0.9) - transitivePeerDependencies: - - '@cfworker/json-schema' - - '@opentelemetry/api' - - '@opentelemetry/exporter-trace-otlp-proto' - - '@opentelemetry/sdk-trace-base' - - bare-abort-controller - - bare-buffer - - encoding - - hono - - react-native-b4a - - supports-color - - utf-8-validate - - '@cfworker/json-schema@4.1.1': {} - - '@google/genai@1.33.0(@modelcontextprotocol/sdk@1.25.0(@cfworker/json-schema@4.1.1)(hono@4.11.1)(zod@4.2.0))(bufferutil@4.0.9)': - dependencies: - google-auth-library: 10.5.0 - ws: 8.18.3(bufferutil@4.0.9) - optionalDependencies: - '@modelcontextprotocol/sdk': 1.25.0(@cfworker/json-schema@4.1.1)(hono@4.11.1)(zod@4.2.0) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@hono/node-server@1.19.7(hono@4.11.1)': - dependencies: - hono: 4.11.1 - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@langchain/core@0.3.79(@opentelemetry/api@1.9.0)(openai@4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0))': - dependencies: - '@cfworker/json-schema': 4.1.1 - ansi-styles: 5.2.0 - camelcase: 6.3.0 - decamelize: 1.2.0 - js-tiktoken: 1.0.21 - langsmith: 0.3.87(@opentelemetry/api@1.9.0)(openai@4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0)) - mustache: 4.2.0 - p-queue: 6.6.2 - p-retry: 4.6.2 - uuid: 10.0.0 - zod: 3.25.76 - zod-to-json-schema: 3.25.0(zod@3.25.76) - transitivePeerDependencies: - - '@opentelemetry/api' - - '@opentelemetry/exporter-trace-otlp-proto' - - '@opentelemetry/sdk-trace-base' - - openai - - '@langchain/openai@0.4.9(@langchain/core@0.3.79(@opentelemetry/api@1.9.0)(openai@4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0)))(ws@8.18.3(bufferutil@4.0.9))': - dependencies: - '@langchain/core': 0.3.79(@opentelemetry/api@1.9.0)(openai@4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0)) - js-tiktoken: 1.0.21 - openai: 4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@3.25.76) - zod: 3.25.76 - zod-to-json-schema: 3.25.0(zod@3.25.76) - transitivePeerDependencies: - - encoding - - ws - - '@modelcontextprotocol/sdk@1.25.0(@cfworker/json-schema@4.1.1)(hono@4.11.1)(zod@4.2.0)': - dependencies: - '@hono/node-server': 1.19.7(hono@4.11.1) - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) - content-type: 1.0.5 - cors: 2.8.5 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.0.6 - express: 5.2.1 - express-rate-limit: 7.5.1(express@5.2.1) - jose: 6.1.3 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 4.2.0 - zod-to-json-schema: 3.25.0(zod@4.2.0) - optionalDependencies: - '@cfworker/json-schema': 4.1.1 - transitivePeerDependencies: - - hono - - supports-color - - '@onkernel/sdk@0.23.0': {} - - '@opentelemetry/api@1.9.0': {} - - '@pinojs/redact@0.4.0': {} - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@puppeteer/browsers@2.3.0': - dependencies: - debug: 4.4.3 - extract-zip: 2.0.1 - progress: 2.0.3 - proxy-agent: 6.5.0 - semver: 7.7.3 - tar-fs: 3.1.1 - unbzip2-stream: 1.4.3 - yargs: 17.7.2 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a - - supports-color - optional: true - - '@standard-schema/spec@1.1.0': {} - - '@tootallnate/quickjs-emscripten@0.23.0': - optional: true - - '@types/node-fetch@2.6.13': - dependencies: - '@types/node': 22.19.3 - form-data: 4.0.5 - - '@types/node@18.19.130': - dependencies: - undici-types: 5.26.5 - - '@types/node@22.19.3': - dependencies: - undici-types: 6.21.0 - - '@types/retry@0.12.0': {} - - '@types/uuid@10.0.0': {} - - '@types/yauzl@2.10.3': - dependencies: - '@types/node': 22.19.3 - optional: true - - '@vercel/oidc@3.0.5': {} - - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 - - agent-base@7.1.4: {} - - agentkeepalive@4.6.0: - dependencies: - humanize-ms: 1.2.1 - - ai@5.0.113(zod@4.2.0): - dependencies: - '@ai-sdk/gateway': 2.0.21(zod@4.2.0) - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - '@opentelemetry/api': 1.9.0 - zod: 4.2.0 - - ajv-formats@3.0.1(ajv@8.17.1): - optionalDependencies: - ajv: 8.17.1 - - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - ansi-styles@6.2.3: {} - - ast-types@0.13.4: - dependencies: - tslib: 2.8.1 - optional: true - - asynckit@0.4.0: {} - - atomic-sleep@1.0.0: {} - - b4a@1.7.3: - optional: true - - balanced-match@1.0.2: {} - - bare-events@2.8.2: - optional: true - - bare-fs@4.5.2: - dependencies: - bare-events: 2.8.2 - bare-path: 3.0.0 - bare-stream: 2.7.0(bare-events@2.8.2) - bare-url: 2.3.2 - fast-fifo: 1.3.2 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - optional: true - - bare-os@3.6.2: - optional: true - - bare-path@3.0.0: - dependencies: - bare-os: 3.6.2 - optional: true - - bare-stream@2.7.0(bare-events@2.8.2): - dependencies: - streamx: 2.23.0 - optionalDependencies: - bare-events: 2.8.2 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - optional: true - - bare-url@2.3.2: - dependencies: - bare-path: 3.0.0 - optional: true - - base64-js@1.5.1: {} - - basic-ftp@5.0.5: - optional: true - - bignumber.js@9.3.1: {} - - body-parser@2.2.1: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.1 - on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.2 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - buffer-crc32@0.2.13: - optional: true - - buffer-equal-constant-time@1.0.1: {} - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - optional: true - - bufferutil@4.0.9: - dependencies: - node-gyp-build: 4.8.4 - optional: true - - bytes@3.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - camelcase@6.3.0: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chrome-launcher@1.2.1: - dependencies: - '@types/node': 22.19.3 - escape-string-regexp: 4.0.0 - is-wsl: 2.2.0 - lighthouse-logger: 2.0.2 - transitivePeerDependencies: - - supports-color - optional: true - - chromium-bidi@0.6.3(devtools-protocol@0.0.1312386): - dependencies: - devtools-protocol: 0.0.1312386 - mitt: 3.0.1 - urlpattern-polyfill: 10.0.0 - zod: 3.23.8 - optional: true - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - optional: true - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - colorette@2.0.20: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - console-table-printer@2.15.0: - dependencies: - simple-wcswidth: 1.1.2 - - content-disposition@1.0.1: {} - - content-type@1.0.5: {} - - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - - cors@2.8.5: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - data-uri-to-buffer@4.0.1: {} - - data-uri-to-buffer@6.0.2: - optional: true - - dateformat@4.6.3: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decamelize@1.2.0: {} - - deepmerge@4.3.1: {} - - degenerator@5.0.1: - dependencies: - ast-types: 0.13.4 - escodegen: 2.1.0 - esprima: 4.0.1 - optional: true - - delayed-stream@1.0.0: {} - - depd@2.0.0: {} - - devtools-protocol@0.0.1312386: - optional: true - - devtools-protocol@0.0.1464554: {} - - dotenv@16.6.1: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - eastasianwidth@0.2.0: {} - - ecdsa-sig-formatter@1.0.11: - dependencies: - safe-buffer: 5.2.1 - - ee-first@1.1.1: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - encodeurl@2.0.0: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - escalade@3.2.0: - optional: true - - escape-html@1.0.3: {} - - escape-string-regexp@4.0.0: - optional: true - - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - optional: true - - esprima@4.0.1: - optional: true - - estraverse@5.3.0: - optional: true - - esutils@2.0.3: - optional: true - - etag@1.8.1: {} - - event-target-shim@5.0.1: {} - - eventemitter3@4.0.7: {} - - events-universal@1.0.1: - dependencies: - bare-events: 2.8.2 - transitivePeerDependencies: - - bare-abort-controller - optional: true - - eventsource-parser@3.0.6: {} - - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.0.6 - - express-rate-limit@7.5.1(express@5.2.1): - dependencies: - express: 5.2.1 - - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.1 - content-disposition: 1.0.1 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.14.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.2 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - extend@3.0.2: {} - - extract-zip@2.0.1: - dependencies: - debug: 4.4.3 - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color - optional: true - - fast-copy@4.0.1: {} - - fast-deep-equal@3.1.3: {} - - fast-fifo@1.3.2: - optional: true - - fast-safe-stringify@2.1.1: {} - - fast-uri@3.1.0: {} - - fd-slicer@1.1.0: - dependencies: - pend: 1.2.0 - optional: true - - fetch-blob@3.2.0: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - - fetch-cookie@3.2.0: - dependencies: - set-cookie-parser: 2.7.2 - tough-cookie: 6.0.0 - - finalhandler@2.1.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - form-data-encoder@1.7.2: {} - - form-data@4.0.5: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - - formdata-node@4.4.1: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 4.0.0-beta.3 - - formdata-polyfill@4.0.10: - dependencies: - fetch-blob: 3.2.0 - - forwarded@0.2.0: {} - - fresh@2.0.0: {} - - fsevents@2.3.2: - optional: true - - function-bind@1.1.2: {} - - gaxios@7.1.3: - dependencies: - extend: 3.0.2 - https-proxy-agent: 7.0.6 - node-fetch: 3.3.2 - rimraf: 5.0.10 - transitivePeerDependencies: - - supports-color - - gcp-metadata@8.1.2: - dependencies: - gaxios: 7.1.3 - google-logging-utils: 1.1.3 - json-bigint: 1.0.0 - transitivePeerDependencies: - - supports-color - - get-caller-file@2.0.5: - optional: true - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-stream@5.2.0: - dependencies: - pump: 3.0.3 - optional: true - - get-uri@6.0.5: - dependencies: - basic-ftp: 5.0.5 - data-uri-to-buffer: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - optional: true - - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - google-auth-library@10.5.0: - dependencies: - base64-js: 1.5.1 - ecdsa-sig-formatter: 1.0.11 - gaxios: 7.1.3 - gcp-metadata: 8.1.2 - google-logging-utils: 1.1.3 - gtoken: 8.0.0 - jws: 4.0.1 - transitivePeerDependencies: - - supports-color - - google-logging-utils@1.1.3: {} - - gopd@1.2.0: {} - - gtoken@8.0.0: - dependencies: - gaxios: 7.1.3 - jws: 4.0.1 - transitivePeerDependencies: - - supports-color - - has-flag@4.0.0: {} - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - help-me@5.0.0: {} - - hono@4.11.1: {} - - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - optional: true - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - humanize-ms@1.2.1: - dependencies: - ms: 2.1.3 - - iconv-lite@0.7.1: - dependencies: - safer-buffer: 2.1.2 - - ieee754@1.2.1: - optional: true - - inherits@2.0.4: {} - - ip-address@10.1.0: - optional: true - - ipaddr.js@1.9.1: {} - - is-docker@2.2.1: - optional: true - - is-fullwidth-code-point@3.0.0: {} - - is-promise@4.0.0: {} - - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - optional: true - - isexe@2.0.0: {} - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jose@6.1.3: {} - - joycon@3.1.1: {} - - js-tiktoken@1.0.21: - dependencies: - base64-js: 1.5.1 - - json-bigint@1.0.0: - dependencies: - bignumber.js: 9.3.1 - - json-schema-traverse@1.0.0: {} - - json-schema-typed@8.0.2: {} - - json-schema@0.4.0: {} - - jwa@2.0.1: - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - - jws@4.0.1: - dependencies: - jwa: 2.0.1 - safe-buffer: 5.2.1 - - langsmith@0.3.87(@opentelemetry/api@1.9.0)(openai@4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0)): - dependencies: - '@types/uuid': 10.0.0 - chalk: 4.1.2 - console-table-printer: 2.15.0 - p-queue: 6.6.2 - semver: 7.7.3 - uuid: 10.0.0 - optionalDependencies: - '@opentelemetry/api': 1.9.0 - openai: 4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0) - - lighthouse-logger@2.0.2: - dependencies: - debug: 4.4.3 - marky: 1.3.0 - transitivePeerDependencies: - - supports-color - optional: true - - lru-cache@10.4.3: {} - - lru-cache@7.18.3: - optional: true - - marky@1.3.0: - optional: true - - math-intrinsics@1.1.0: {} - - media-typer@1.1.0: {} - - merge-descriptors@2.0.0: {} - - mime-db@1.52.0: {} - - mime-db@1.54.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - - minimist@1.2.8: {} - - minipass@7.1.2: {} - - mitt@3.0.1: - optional: true - - ms@2.1.3: {} - - mustache@4.2.0: {} - - negotiator@1.0.0: {} - - netmask@2.0.2: - optional: true - - node-domexception@1.0.0: {} - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-fetch@3.3.2: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - - node-gyp-build@4.8.4: - optional: true - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - ollama-ai-provider-v2@1.5.5(zod@4.2.0): - dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.2.0) - zod: 4.2.0 - optional: true - - on-exit-leak-free@2.1.2: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - openai@4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@3.25.76): - dependencies: - '@types/node': 18.19.130 - '@types/node-fetch': 2.6.13 - abort-controller: 3.0.0 - agentkeepalive: 4.6.0 - form-data-encoder: 1.7.2 - formdata-node: 4.4.1 - node-fetch: 2.7.0 - optionalDependencies: - ws: 8.18.3(bufferutil@4.0.9) - zod: 3.25.76 - transitivePeerDependencies: - - encoding - - openai@4.104.0(ws@8.18.3(bufferutil@4.0.9))(zod@4.2.0): - dependencies: - '@types/node': 18.19.130 - '@types/node-fetch': 2.6.13 - abort-controller: 3.0.0 - agentkeepalive: 4.6.0 - form-data-encoder: 1.7.2 - formdata-node: 4.4.1 - node-fetch: 2.7.0 - optionalDependencies: - ws: 8.18.3(bufferutil@4.0.9) - zod: 4.2.0 - transitivePeerDependencies: - - encoding - - p-finally@1.0.0: {} - - p-queue@6.6.2: - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - - p-retry@4.6.2: - dependencies: - '@types/retry': 0.12.0 - retry: 0.13.1 - - p-timeout@3.2.0: - dependencies: - p-finally: 1.0.0 - - pac-proxy-agent@7.2.0: - dependencies: - '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.4 - debug: 4.4.3 - get-uri: 6.0.5 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - optional: true - - pac-resolver@7.0.1: - dependencies: - degenerator: 5.0.1 - netmask: 2.0.2 - optional: true - - package-json-from-dist@1.0.1: {} - - parseurl@1.3.3: {} - - patchright-core@1.57.0: - optional: true - - path-key@3.1.1: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - - path-to-regexp@8.3.0: {} - - pend@1.2.0: - optional: true - - pino-abstract-transport@2.0.0: - dependencies: - split2: 4.2.0 - - pino-abstract-transport@3.0.0: - dependencies: - split2: 4.2.0 - - pino-pretty@13.1.3: - dependencies: - colorette: 2.0.20 - dateformat: 4.6.3 - fast-copy: 4.0.1 - fast-safe-stringify: 2.1.1 - help-me: 5.0.0 - joycon: 3.1.1 - minimist: 1.2.8 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 3.0.0 - pump: 3.0.3 - secure-json-parse: 4.1.0 - sonic-boom: 4.2.0 - strip-json-comments: 5.0.3 - - pino-std-serializers@7.0.0: {} - - pino@9.14.0: - dependencies: - '@pinojs/redact': 0.4.0 - atomic-sleep: 1.0.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.0.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.0 - thread-stream: 3.1.0 - - pkce-challenge@5.0.1: {} - - playwright-core@1.57.0: {} - - playwright@1.57.0: - dependencies: - playwright-core: 1.57.0 - optionalDependencies: - fsevents: 2.3.2 - - process-warning@5.0.0: {} - - progress@2.0.3: - optional: true - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - proxy-agent@6.5.0: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 7.18.3 - pac-proxy-agent: 7.2.0 - proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - optional: true - - proxy-from-env@1.1.0: - optional: true - - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - puppeteer-core@22.15.0(bufferutil@4.0.9): - dependencies: - '@puppeteer/browsers': 2.3.0 - chromium-bidi: 0.6.3(devtools-protocol@0.0.1312386) - debug: 4.4.3 - devtools-protocol: 0.0.1312386 - ws: 8.18.3(bufferutil@4.0.9) - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - bufferutil - - react-native-b4a - - supports-color - - utf-8-validate - optional: true - - qs@6.14.0: - dependencies: - side-channel: 1.1.0 - - quick-format-unescaped@4.0.4: {} - - range-parser@1.2.1: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.1 - unpipe: 1.0.0 - - real-require@0.2.0: {} - - require-directory@2.1.1: - optional: true - - require-from-string@2.0.2: {} - - retry@0.13.1: {} - - rimraf@5.0.10: - dependencies: - glob: 10.5.0 - - router@2.2.0: - dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.3.0 - transitivePeerDependencies: - - supports-color - - safe-buffer@5.2.1: {} - - safe-stable-stringify@2.5.0: {} - - safer-buffer@2.1.2: {} - - secure-json-parse@4.1.0: {} - - semver@7.7.3: {} - - send@1.2.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@2.2.1: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1 - transitivePeerDependencies: - - supports-color - - set-cookie-parser@2.7.2: {} - - setprototypeof@1.2.0: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - signal-exit@4.1.0: {} - - simple-wcswidth@1.1.2: {} - - smart-buffer@4.2.0: - optional: true - - socks-proxy-agent@8.0.5: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - socks: 2.8.7 - transitivePeerDependencies: - - supports-color - optional: true - - socks@2.8.7: - dependencies: - ip-address: 10.1.0 - smart-buffer: 4.2.0 - optional: true - - sonic-boom@4.2.0: - dependencies: - atomic-sleep: 1.0.0 - - source-map@0.6.1: - optional: true - - split2@4.2.0: {} - - statuses@2.0.2: {} - - streamx@2.23.0: - dependencies: - events-universal: 1.0.1 - fast-fifo: 1.3.2 - text-decoder: 1.2.3 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - optional: true - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.2: - dependencies: - ansi-regex: 6.2.2 - - strip-json-comments@5.0.3: {} - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - tar-fs@3.1.1: - dependencies: - pump: 3.0.3 - tar-stream: 3.1.7 - optionalDependencies: - bare-fs: 4.5.2 - bare-path: 3.0.0 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a - optional: true - - tar-stream@3.1.7: - dependencies: - b4a: 1.7.3 - fast-fifo: 1.3.2 - streamx: 2.23.0 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - optional: true - - text-decoder@1.2.3: - dependencies: - b4a: 1.7.3 - transitivePeerDependencies: - - react-native-b4a - optional: true - - thread-stream@3.1.0: - dependencies: - real-require: 0.2.0 - - through@2.3.8: - optional: true - - tldts-core@7.0.19: {} - - tldts@7.0.19: - dependencies: - tldts-core: 7.0.19 - - toidentifier@1.0.1: {} - - tough-cookie@6.0.0: - dependencies: - tldts: 7.0.19 - - tr46@0.0.3: {} - - tslib@2.8.1: - optional: true - - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.2 - - typescript@5.9.3: {} - - unbzip2-stream@1.4.3: - dependencies: - buffer: 5.7.1 - through: 2.3.8 - optional: true - - undici-types@5.26.5: {} - - undici-types@6.21.0: {} - - unpipe@1.0.0: {} - - urlpattern-polyfill@10.0.0: - optional: true - - uuid@10.0.0: {} - - uuid@11.1.0: {} - - vary@1.1.2: {} - - web-streams-polyfill@3.3.3: {} - - web-streams-polyfill@4.0.0-beta.3: {} - - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - - wrappy@1.0.2: {} - - ws@8.18.3(bufferutil@4.0.9): - optionalDependencies: - bufferutil: 4.0.9 - - y18n@5.0.8: - optional: true - - yargs-parser@21.1.1: - optional: true - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - optional: true - - yauzl@2.10.0: - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - optional: true - - zod-to-json-schema@3.25.0(zod@3.25.76): - dependencies: - zod: 3.25.76 - - zod-to-json-schema@3.25.0(zod@4.2.0): - dependencies: - zod: 4.2.0 - - zod@3.23.8: - optional: true - - zod@3.25.76: {} - - zod@4.2.0: {} diff --git a/pkg/templates/typescript/gemini-computer-use/session.ts b/pkg/templates/typescript/gemini-computer-use/session.ts new file mode 100644 index 0000000..06e30a6 --- /dev/null +++ b/pkg/templates/typescript/gemini-computer-use/session.ts @@ -0,0 +1,222 @@ +/** + * Kernel Browser Session Manager. + * + * Provides a class for managing Kernel browser lifecycle + * with optional video replay recording. + */ + +import type { Kernel } from '@onkernel/sdk'; + +export interface SessionOptions { + /** Enable stealth mode to avoid bot detection */ + stealth?: boolean; + /** Browser session timeout in seconds */ + timeoutSeconds?: number; + /** Enable replay recording (requires paid plan) */ + recordReplay?: boolean; + /** Grace period in seconds before stopping replay */ + replayGracePeriod?: number; +} + +export interface SessionInfo { + sessionId: string; + liveViewUrl: string; + replayId?: string; + replayViewUrl?: string; +} + +const DEFAULT_OPTIONS: Required = { + stealth: true, + timeoutSeconds: 300, + recordReplay: false, + replayGracePeriod: 5.0, +}; + +/** + * Manages Kernel browser lifecycle with optional replay recording. + * + * Usage: + * ```typescript + * const session = new KernelBrowserSession(kernel, options); + * await session.start(); + * try { + * // Use session.sessionId for computer controls + * } finally { + * await session.stop(); + * } + * ``` + */ +export class KernelBrowserSession { + private kernel: Kernel; + private options: Required; + + // Session state + private _sessionId: string | null = null; + private _liveViewUrl: string | null = null; + private _replayId: string | null = null; + private _replayViewUrl: string | null = null; + + constructor(kernel: Kernel, options: SessionOptions = {}) { + this.kernel = kernel; + this.options = { ...DEFAULT_OPTIONS, ...options }; + } + + get sessionId(): string { + if (!this._sessionId) { + throw new Error('Session not started. Call start() first.'); + } + return this._sessionId; + } + + get liveViewUrl(): string | null { + return this._liveViewUrl; + } + + get replayViewUrl(): string | null { + return this._replayViewUrl; + } + + get info(): SessionInfo { + return { + sessionId: this.sessionId, + liveViewUrl: this._liveViewUrl || '', + replayId: this._replayId || undefined, + replayViewUrl: this._replayViewUrl || undefined, + }; + } + + /** + * Create a Kernel browser session and optionally start recording. + */ + async start(): Promise { + // Create browser with specified settings + const browser = await this.kernel.browsers.create({ + stealth: this.options.stealth, + timeout_seconds: this.options.timeoutSeconds, + viewport: { + width: 1024, + height: 768, + refresh_rate: 60, + }, + }); + + this._sessionId = browser.session_id; + this._liveViewUrl = browser.browser_live_view_url; + + console.log(`Kernel browser created: ${this._sessionId}`); + console.log(`Live view URL: ${this._liveViewUrl}`); + + // Start replay recording if enabled + if (this.options.recordReplay) { + try { + await this.startReplay(); + } catch (error) { + console.warn(`Warning: Failed to start replay recording: ${error}`); + console.warn('Continuing without replay recording.'); + } + } + + return this.info; + } + + /** + * Start recording a replay of the browser session. + */ + private async startReplay(): Promise { + if (!this._sessionId) { + return; + } + + console.log('Starting replay recording...'); + const replay = await this.kernel.browsers.replays.start(this._sessionId); + this._replayId = replay.replay_id; + console.log(`Replay recording started: ${this._replayId}`); + } + + /** + * Stop recording and get the replay URL. + */ + private async stopReplay(): Promise { + if (!this._sessionId || !this._replayId) { + return; + } + + console.log('Stopping replay recording...'); + await this.kernel.browsers.replays.stop(this._replayId, { + id: this._sessionId, + }); + console.log('Replay recording stopped. Processing video...'); + + // Wait a moment for processing + await this.sleep(2000); + + // Poll for replay to be ready (with timeout) + const maxWait = 60000; // 60 seconds + const startTime = Date.now(); + let replayReady = false; + + while (Date.now() - startTime < maxWait) { + try { + const replays = await this.kernel.browsers.replays.list(this._sessionId); + for (const replay of replays) { + if (replay.replay_id === this._replayId) { + this._replayViewUrl = replay.replay_view_url; + replayReady = true; + break; + } + } + if (replayReady) { + break; + } + } catch { + // Ignore errors while polling + } + await this.sleep(1000); + } + + if (!replayReady) { + console.log('Warning: Replay may still be processing'); + } else if (this._replayViewUrl) { + console.log(`Replay view URL: ${this._replayViewUrl}`); + } + } + + /** + * Stop recording, and delete the browser session. + */ + async stop(): Promise { + const info = this.info; + + if (this._sessionId) { + try { + // Stop replay if recording was enabled + if (this.options.recordReplay && this._replayId) { + // Wait grace period before stopping to capture final state + if (this.options.replayGracePeriod > 0) { + console.log(`Waiting ${this.options.replayGracePeriod}s grace period...`); + await this.sleep(this.options.replayGracePeriod * 1000); + } + await this.stopReplay(); + info.replayViewUrl = this._replayViewUrl || undefined; + } + } finally { + // Always clean up the browser session, even if replay stopping fails + console.log(`Destroying browser session: ${this._sessionId}`); + await this.kernel.browsers.deleteByID(this._sessionId); + console.log('Browser session destroyed.'); + } + } + + // Reset state + this._sessionId = null; + this._liveViewUrl = null; + this._replayId = null; + this._replayViewUrl = null; + + return info; + } + + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} diff --git a/pkg/templates/typescript/gemini-computer-use/tools/computer.ts b/pkg/templates/typescript/gemini-computer-use/tools/computer.ts new file mode 100644 index 0000000..6388040 --- /dev/null +++ b/pkg/templates/typescript/gemini-computer-use/tools/computer.ts @@ -0,0 +1,316 @@ +/** + * Gemini Computer Tool - Maps Gemini actions to Kernel Computer Controls API. + * Based on Google's computer-use-preview reference implementation. + */ + +import { Buffer } from 'buffer'; +import type { Kernel } from '@onkernel/sdk'; +import { + GeminiAction, + PREDEFINED_COMPUTER_USE_FUNCTIONS, + DEFAULT_SCREEN_SIZE, + COORDINATE_SCALE, + type GeminiFunctionArgs, + type ToolResult, + type ScreenSize, +} from './types/gemini'; + +const TYPING_DELAY_MS = 12; +const SCREENSHOT_DELAY_MS = 500; + +/** + * Computer tool that maps Gemini actions to Kernel's Computer Controls API. + */ +export class ComputerTool { + private kernel: Kernel; + private sessionId: string; + private screenSize: ScreenSize; + + constructor(kernel: Kernel, sessionId: string, screenSize: ScreenSize = DEFAULT_SCREEN_SIZE) { + this.kernel = kernel; + this.sessionId = sessionId; + this.screenSize = screenSize; + } + + /** + * Denormalize X coordinate from Gemini's 0-1000 scale to actual pixels. + */ + private denormalizeX(x: number): number { + return Math.round((x / COORDINATE_SCALE) * this.screenSize.width); + } + + /** + * Denormalize Y coordinate from Gemini's 0-1000 scale to actual pixels. + */ + private denormalizeY(y: number): number { + return Math.round((y / COORDINATE_SCALE) * this.screenSize.height); + } + + /** + * Take a screenshot and return it as base64. + */ + async screenshot(): Promise { + try { + await this.sleep(SCREENSHOT_DELAY_MS); + const response = await this.kernel.browsers.computer.captureScreenshot(this.sessionId); + const blob = await response.blob(); + const arrayBuffer = await blob.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + + // Get current URL + let url = ''; + try { + const state = await this.kernel.browsers.computer.getState(this.sessionId); + url = state.url || ''; + } catch { + // Ignore URL fetch errors + } + + return { + base64Image: buffer.toString('base64'), + url, + }; + } catch (error) { + return { + error: `Failed to take screenshot: ${error}`, + }; + } + } + + /** + * Execute a Gemini action and return the result with a screenshot. + */ + async executeAction(actionName: string, args: GeminiFunctionArgs): Promise { + // Check if this is a known computer use function + if (!PREDEFINED_COMPUTER_USE_FUNCTIONS.includes(actionName as GeminiAction)) { + return { error: `Unknown action: ${actionName}` }; + } + + try { + switch (actionName) { + case GeminiAction.OPEN_WEB_BROWSER: + // Browser is already open in Kernel, just return screenshot + break; + + case GeminiAction.CLICK_AT: { + if (args.x === undefined || args.y === undefined) { + return { error: 'click_at requires x and y coordinates' }; + } + const x = this.denormalizeX(args.x); + const y = this.denormalizeY(args.y); + await this.kernel.browsers.computer.clickMouse(this.sessionId, { + x, + y, + button: 'left', + click_type: 'click', + num_clicks: 1, + }); + break; + } + + case GeminiAction.HOVER_AT: { + if (args.x === undefined || args.y === undefined) { + return { error: 'hover_at requires x and y coordinates' }; + } + const x = this.denormalizeX(args.x); + const y = this.denormalizeY(args.y); + await this.kernel.browsers.computer.moveMouse(this.sessionId, { x, y }); + break; + } + + case GeminiAction.TYPE_TEXT_AT: { + if (args.x === undefined || args.y === undefined) { + return { error: 'type_text_at requires x and y coordinates' }; + } + if (!args.text) { + return { error: 'type_text_at requires text' }; + } + + const x = this.denormalizeX(args.x); + const y = this.denormalizeY(args.y); + + // Click at the location first + await this.kernel.browsers.computer.clickMouse(this.sessionId, { + x, + y, + button: 'left', + click_type: 'click', + num_clicks: 1, + }); + + // Clear existing text if requested (default: true) + if (args.clear_before_typing !== false) { + await this.kernel.browsers.computer.pressKey(this.sessionId, { + keys: ['ctrl+a'], + }); + await this.sleep(50); + } + + // Type the text + await this.kernel.browsers.computer.typeText(this.sessionId, { + text: args.text, + delay: TYPING_DELAY_MS, + }); + + // Press enter if requested + if (args.press_enter) { + await this.sleep(100); + await this.kernel.browsers.computer.pressKey(this.sessionId, { + keys: ['Return'], + }); + } + break; + } + + case GeminiAction.SCROLL_DOCUMENT: { + if (!args.direction) { + return { error: 'scroll_document requires direction' }; + } + // Scroll at center of viewport + const centerX = Math.round(this.screenSize.width / 2); + const centerY = Math.round(this.screenSize.height / 2); + const scrollDelta = 500; + + let deltaX = 0; + let deltaY = 0; + if (args.direction === 'down') deltaY = scrollDelta; + else if (args.direction === 'up') deltaY = -scrollDelta; + else if (args.direction === 'right') deltaX = scrollDelta; + else if (args.direction === 'left') deltaX = -scrollDelta; + + await this.kernel.browsers.computer.scroll(this.sessionId, { + x: centerX, + y: centerY, + delta_x: deltaX, + delta_y: deltaY, + }); + break; + } + + case GeminiAction.SCROLL_AT: { + if (args.x === undefined || args.y === undefined) { + return { error: 'scroll_at requires x and y coordinates' }; + } + if (!args.direction) { + return { error: 'scroll_at requires direction' }; + } + + const x = this.denormalizeX(args.x); + const y = this.denormalizeY(args.y); + + // Denormalize magnitude if provided + let magnitude = args.magnitude || 800; + if (args.direction === 'up' || args.direction === 'down') { + magnitude = this.denormalizeY(magnitude); + } else { + magnitude = this.denormalizeX(magnitude); + } + + let deltaX = 0; + let deltaY = 0; + if (args.direction === 'down') deltaY = magnitude; + else if (args.direction === 'up') deltaY = -magnitude; + else if (args.direction === 'right') deltaX = magnitude; + else if (args.direction === 'left') deltaX = -magnitude; + + await this.kernel.browsers.computer.scroll(this.sessionId, { + x, + y, + delta_x: deltaX, + delta_y: deltaY, + }); + break; + } + + case GeminiAction.WAIT_5_SECONDS: + await this.sleep(5000); + break; + + case GeminiAction.GO_BACK: + await this.kernel.browsers.computer.pressKey(this.sessionId, { + keys: ['alt+Left'], + }); + await this.sleep(1000); + break; + + case GeminiAction.GO_FORWARD: + await this.kernel.browsers.computer.pressKey(this.sessionId, { + keys: ['alt+Right'], + }); + await this.sleep(1000); + break; + + case GeminiAction.SEARCH: + // Focus URL bar (Ctrl+L) - equivalent to clicking search + await this.kernel.browsers.computer.pressKey(this.sessionId, { + keys: ['ctrl+l'], + }); + break; + + case GeminiAction.NAVIGATE: { + if (!args.url) { + return { error: 'navigate requires url' }; + } + // Focus URL bar and type the URL + await this.kernel.browsers.computer.pressKey(this.sessionId, { + keys: ['ctrl+l'], + }); + await this.sleep(100); + await this.kernel.browsers.computer.typeText(this.sessionId, { + text: args.url, + delay: TYPING_DELAY_MS, + }); + await this.sleep(100); + await this.kernel.browsers.computer.pressKey(this.sessionId, { + keys: ['Return'], + }); + await this.sleep(1500); // Wait for navigation + break; + } + + case GeminiAction.KEY_COMBINATION: { + if (!args.keys) { + return { error: 'key_combination requires keys' }; + } + // Gemini sends keys as "key1+key2+key3" + await this.kernel.browsers.computer.pressKey(this.sessionId, { + keys: [args.keys], + }); + break; + } + + case GeminiAction.DRAG_AND_DROP: { + if (args.x === undefined || args.y === undefined || + args.destination_x === undefined || args.destination_y === undefined) { + return { error: 'drag_and_drop requires x, y, destination_x, and destination_y' }; + } + + const startX = this.denormalizeX(args.x); + const startY = this.denormalizeY(args.y); + const endX = this.denormalizeX(args.destination_x); + const endY = this.denormalizeY(args.destination_y); + + await this.kernel.browsers.computer.dragMouse(this.sessionId, { + path: [[startX, startY], [endX, endY]], + button: 'left', + }); + break; + } + + default: + return { error: `Unhandled action: ${actionName}` }; + } + + // Wait a moment for the action to complete, then take a screenshot + await this.sleep(SCREENSHOT_DELAY_MS); + return await this.screenshot(); + + } catch (error) { + return { error: `Action failed: ${error}` }; + } + } + + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} diff --git a/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts new file mode 100644 index 0000000..b36779e --- /dev/null +++ b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts @@ -0,0 +1,125 @@ +/** + * Type definitions for Gemini Computer Use actions. + * Based on Google's computer-use-preview reference implementation. + */ + +/** + * Gemini predefined computer use actions. + */ +export enum GeminiAction { + OPEN_WEB_BROWSER = 'open_web_browser', + CLICK_AT = 'click_at', + HOVER_AT = 'hover_at', + TYPE_TEXT_AT = 'type_text_at', + SCROLL_DOCUMENT = 'scroll_document', + SCROLL_AT = 'scroll_at', + WAIT_5_SECONDS = 'wait_5_seconds', + GO_BACK = 'go_back', + GO_FORWARD = 'go_forward', + SEARCH = 'search', + NAVIGATE = 'navigate', + KEY_COMBINATION = 'key_combination', + DRAG_AND_DROP = 'drag_and_drop', +} + +/** + * All predefined Gemini computer use function names. + */ +export const PREDEFINED_COMPUTER_USE_FUNCTIONS = [ + GeminiAction.OPEN_WEB_BROWSER, + GeminiAction.CLICK_AT, + GeminiAction.HOVER_AT, + GeminiAction.TYPE_TEXT_AT, + GeminiAction.SCROLL_DOCUMENT, + GeminiAction.SCROLL_AT, + GeminiAction.WAIT_5_SECONDS, + GeminiAction.GO_BACK, + GeminiAction.GO_FORWARD, + GeminiAction.SEARCH, + GeminiAction.NAVIGATE, + GeminiAction.KEY_COMBINATION, + GeminiAction.DRAG_AND_DROP, +] as const; + +/** + * Scroll direction options. + */ +export type ScrollDirection = 'up' | 'down' | 'left' | 'right'; + +/** + * Arguments for Gemini function calls. + */ +export interface GeminiFunctionArgs { + // click_at, hover_at, scroll_at + x?: number; + y?: number; + + // type_text_at + text?: string; + press_enter?: boolean; + clear_before_typing?: boolean; + + // scroll_document, scroll_at + direction?: ScrollDirection; + magnitude?: number; + + // navigate + url?: string; + + // key_combination + keys?: string; + + // drag_and_drop + destination_x?: number; + destination_y?: number; + + // Safety decision (may be included in any function call) + safety_decision?: { + decision: string; + explanation: string; + }; +} + +/** + * Result from executing a computer action. + */ +export interface ToolResult { + /** Base64-encoded screenshot image */ + base64Image?: string; + /** Current URL of the browser */ + url?: string; + /** Error message if the action failed */ + error?: string; +} + +/** + * Environment state returned from computer actions. + */ +export interface EnvState { + /** Current URL of the browser */ + url: string; + /** Screenshot as bytes */ + screenshot: Uint8Array; +} + +/** + * Screen dimensions for coordinate denormalization. + */ +export interface ScreenSize { + width: number; + height: number; +} + +/** + * Default screen size (matching Kernel browser viewport). + */ +export const DEFAULT_SCREEN_SIZE: ScreenSize = { + width: 1024, + height: 768, +}; + +/** + * Gemini uses normalized coordinates (0-1000). + * This constant defines the normalization scale. + */ +export const COORDINATE_SCALE = 1000; From f1f01dca80711db362a8a2ed5fab4d31b2d3e85e Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 08:05:52 -0500 Subject: [PATCH 02/19] refactor(templates): cleanup Gemini Computer Use templates Minor cleanup following Option A from the alignment analysis: ## Removed Unused Code - Python: Removed unused EnvState class from types.py - Python: Removed unused GeminiAction and Optional imports from loop.py - TypeScript: Removed unused EnvState interface from gemini.ts ## Fixed Type Hints - Python: Use GeminiFunctionArgs TypedDict instead of Dict[str, Any] - Python: Export GeminiFunctionArgs from tools package ## Fixed Naming Consistency - Python: Renamed SCREENSHOT_DELAY_MS to SCREENSHOT_DELAY_SECS (was already in seconds) ## Improved Error Messages - Added deployment hint to GOOGLE_API_KEY error - Added payload format hint to query validation error Net result: -15 lines of unused code, better type safety, clearer errors. --- .gitignore | 3 +++ pkg/templates/python/gemini-computer-use/loop.py | 4 ++-- pkg/templates/python/gemini-computer-use/main.py | 7 +++++-- .../python/gemini-computer-use/tools/__init__.py | 4 ++-- .../python/gemini-computer-use/tools/computer.py | 11 +++++------ .../python/gemini-computer-use/tools/types.py | 10 ---------- pkg/templates/typescript/gemini-computer-use/index.ts | 7 +++++-- .../gemini-computer-use/tools/types/gemini.ts | 10 ---------- 8 files changed, 22 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 900b34b..39ae834 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store kernel + +# QA testing directories +qa-* diff --git a/pkg/templates/python/gemini-computer-use/loop.py b/pkg/templates/python/gemini-computer-use/loop.py index d8c1edc..991e8bc 100644 --- a/pkg/templates/python/gemini-computer-use/loop.py +++ b/pkg/templates/python/gemini-computer-use/loop.py @@ -5,7 +5,7 @@ import os from datetime import datetime -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List from google import genai from google.genai import types @@ -17,7 +17,7 @@ ) from kernel import Kernel -from tools import ComputerTool, PREDEFINED_COMPUTER_USE_FUNCTIONS, GeminiAction +from tools import ComputerTool, PREDEFINED_COMPUTER_USE_FUNCTIONS # System prompt for browser-based computer use diff --git a/pkg/templates/python/gemini-computer-use/main.py b/pkg/templates/python/gemini-computer-use/main.py index 2c84a20..c896906 100644 --- a/pkg/templates/python/gemini-computer-use/main.py +++ b/pkg/templates/python/gemini-computer-use/main.py @@ -18,7 +18,10 @@ class QueryOutput(TypedDict): api_key = os.getenv("GOOGLE_API_KEY") if not api_key: - raise ValueError("GOOGLE_API_KEY is not set") + raise ValueError( + "GOOGLE_API_KEY is not set. " + "Set it via environment variable or deploy with: kernel deploy main.py --env-file .env" + ) app = kernel.App("python-gemini-cua") @@ -43,7 +46,7 @@ async def cua_task( - replay_url: URL to view the replay (if recording was enabled) """ if not payload or not payload.get("query"): - raise ValueError("Query is required") + raise ValueError('Query is required. Payload must include: {"query": "your task description"}') record_replay = payload.get("record_replay", False) diff --git a/pkg/templates/python/gemini-computer-use/tools/__init__.py b/pkg/templates/python/gemini-computer-use/tools/__init__.py index 1679429..54a6769 100644 --- a/pkg/templates/python/gemini-computer-use/tools/__init__.py +++ b/pkg/templates/python/gemini-computer-use/tools/__init__.py @@ -3,9 +3,9 @@ from .computer import ComputerTool from .types import ( GeminiAction, + GeminiFunctionArgs, PREDEFINED_COMPUTER_USE_FUNCTIONS, ToolResult, - EnvState, ScreenSize, DEFAULT_SCREEN_SIZE, COORDINATE_SCALE, @@ -14,9 +14,9 @@ __all__ = [ "ComputerTool", "GeminiAction", + "GeminiFunctionArgs", "PREDEFINED_COMPUTER_USE_FUNCTIONS", "ToolResult", - "EnvState", "ScreenSize", "DEFAULT_SCREEN_SIZE", "COORDINATE_SCALE", diff --git a/pkg/templates/python/gemini-computer-use/tools/computer.py b/pkg/templates/python/gemini-computer-use/tools/computer.py index ed08712..6083a3d 100644 --- a/pkg/templates/python/gemini-computer-use/tools/computer.py +++ b/pkg/templates/python/gemini-computer-use/tools/computer.py @@ -5,23 +5,22 @@ import asyncio import base64 -from typing import Any, Dict from kernel import Kernel from .types import ( GeminiAction, + GeminiFunctionArgs, PREDEFINED_COMPUTER_USE_FUNCTIONS, DEFAULT_SCREEN_SIZE, COORDINATE_SCALE, - GeminiFunctionArgs, ToolResult, ScreenSize, ) TYPING_DELAY_MS = 12 -SCREENSHOT_DELAY_MS = 0.5 +SCREENSHOT_DELAY_SECS = 0.5 class ComputerTool: @@ -48,7 +47,7 @@ def denormalize_y(self, y: int) -> int: async def screenshot(self) -> ToolResult: """Take a screenshot and return it as base64.""" try: - await asyncio.sleep(SCREENSHOT_DELAY_MS) + await asyncio.sleep(SCREENSHOT_DELAY_SECS) response = self.kernel.browsers.computer.capture_screenshot(self.session_id) screenshot_bytes = response.read() @@ -69,7 +68,7 @@ async def screenshot(self) -> ToolResult: return ToolResult(error=f"Failed to take screenshot: {e}") async def execute_action( - self, action_name: str, args: Dict[str, Any] + self, action_name: str, args: GeminiFunctionArgs ) -> ToolResult: """Execute a Gemini action and return the result with a screenshot.""" # Check if this is a known computer use function @@ -276,7 +275,7 @@ async def execute_action( return ToolResult(error=f"Unhandled action: {action_name}") # Wait a moment for the action to complete, then take a screenshot - await asyncio.sleep(SCREENSHOT_DELAY_MS) + await asyncio.sleep(SCREENSHOT_DELAY_SECS) return await self.screenshot() except Exception as e: diff --git a/pkg/templates/python/gemini-computer-use/tools/types.py b/pkg/templates/python/gemini-computer-use/tools/types.py index f12c4d1..0a133ae 100644 --- a/pkg/templates/python/gemini-computer-use/tools/types.py +++ b/pkg/templates/python/gemini-computer-use/tools/types.py @@ -99,16 +99,6 @@ class ToolResult: error: Optional[str] = None -@dataclass -class EnvState: - """Environment state returned from computer actions.""" - - # Current URL of the browser - url: str - # Screenshot as bytes - screenshot: bytes - - @dataclass class ScreenSize: """Screen dimensions for coordinate denormalization.""" diff --git a/pkg/templates/typescript/gemini-computer-use/index.ts b/pkg/templates/typescript/gemini-computer-use/index.ts index 54757f9..aa57b40 100644 --- a/pkg/templates/typescript/gemini-computer-use/index.ts +++ b/pkg/templates/typescript/gemini-computer-use/index.ts @@ -23,14 +23,17 @@ interface QueryOutput { const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY; if (!GOOGLE_API_KEY) { - throw new Error('GOOGLE_API_KEY is not set'); + throw new Error( + 'GOOGLE_API_KEY is not set. ' + + 'Set it via environment variable or deploy with: kernel deploy index.ts --env-file .env' + ); } app.action( 'cua-task', async (ctx: KernelContext, payload?: QueryInput): Promise => { if (!payload?.query) { - throw new Error('Query is required'); + throw new Error('Query is required. Payload must include: { "query": "your task description" }'); } // Create browser session with optional replay recording diff --git a/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts index b36779e..3d338f0 100644 --- a/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts +++ b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts @@ -92,16 +92,6 @@ export interface ToolResult { error?: string; } -/** - * Environment state returned from computer actions. - */ -export interface EnvState { - /** Current URL of the browser */ - url: string; - /** Screenshot as bytes */ - screenshot: Uint8Array; -} - /** * Screen dimensions for coordinate denormalization. */ From 370132e38e5f7d2d4d52668efc7e48a6758457de Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 08:08:31 -0500 Subject: [PATCH 03/19] Update qa.md Add in new gemini computer use templates to qa.md flow --- .cursor/commands/qa.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.cursor/commands/qa.md b/.cursor/commands/qa.md index 46fb874..55875b5 100644 --- a/.cursor/commands/qa.md +++ b/.cursor/commands/qa.md @@ -59,6 +59,7 @@ Here are all valid language + template combinations: | typescript | gemini-computer-use | ts-gemini-cua | ts-gemini-cua | Yes | GOOGLE_API_KEY | | typescript | claude-agent-sdk | ts-claude-agent-sdk | ts-claude-agent-sdk | Yes | ANTHROPIC_API_KEY | | python | sample-app | py-sample-app | python-basic | No | - | +| python | gemini-computer-use | py-gemini-cua | python-gemini-cua | Yes | GOOGLE_API_KEY | | python | captcha-solver | py-captcha-solver | python-captcha-solver | No | - | | python | browser-use | py-browser-use | python-bu | Yes | OPENAI_API_KEY | | python | anthropic-computer-use | py-anthropic-cua | python-anthropic-cua | Yes | ANTHROPIC_API_KEY | @@ -89,6 +90,7 @@ Run each of these (they are non-interactive when all flags are provided): ../bin/kernel create -n py-openai-cua -l python -t openai-computer-use ../bin/kernel create -n py-openagi-cua -l python -t openagi-computer-use ../bin/kernel create -n py-claude-agent-sdk -l python -t claude-agent-sdk +../bin/kernel create -n py-gemini-cua -l python -t gemini-computer-use ``` ## Step 5: Deploy Each Template @@ -221,6 +223,15 @@ echo "ANTHROPIC_API_KEY=" > .env cd .. ``` +**py-gemini-cua** (needs GOOGLE_API_KEY): + +```bash +cd py-gemini-cua +echo "GOOGLE_API_KEY=" > .env +../bin/kernel deploy main.py --env-file .env +cd .. +``` + ## Step 6: Provide Invoke Commands Once all deployments are complete, present the human with these invoke commands to test manually: @@ -233,7 +244,7 @@ kernel invoke ts-stagehand teamsize-task --payload '{"company": "Kernel"}' kernel invoke ts-anthropic-cua cua-task --payload '{"query": "Go to http://magnitasks.com, Click the Tasks option in the left-side bar, and move the 5 items in the To Do and In Progress items to the Done section of the Kanban board. You are done successfully when the items are moved.", "record_replay": true}' kernel invoke ts-magnitude mag-url-extract --payload '{"url": "https://en.wikipedia.org/wiki/Special:Random"}' kernel invoke ts-openai-cua cua-task --payload '{"task": "Go to https://news.ycombinator.com and get the top 5 articles"}' -kernel invoke ts-gemini-cua gemini-cua-task --payload '{"startingUrl": "https://www.magnitasks.com/", "instruction": "Click the Tasks option in the left-side bar, and move the 5 items in the To Do and In Progress items to the Done section of the Kanban board? You are done successfully when the items are moved."}' +kernel invoke ts-gemini-cua cua-task --payload '{"query": "Go to http://magnitasks.com, Click the Tasks option in the left-side bar, and move the 5 items in the To Do and In Progress items to the Done section of the Kanban board. You are done successfully when the items are moved.", "record_replay": true}' kernel invoke ts-claude-agent-sdk agent-task --payload '{"task": "Go to https://news.ycombinator.com and get the top 3 stories"}' # Python apps @@ -244,11 +255,12 @@ kernel invoke python-anthropic-cua cua-task --payload '{"query": "Go to http://m kernel invoke python-openai-cua cua-task --payload '{"task": "Go to https://news.ycombinator.com and get the top 5 articles"}' kernel invoke python-openagi-cua openagi-default-task -p '{"instruction": "Navigate to https://agiopen.org and click the What is Computer Use? button"}' kernel invoke py-claude-agent-sdk agent-task --payload '{"task": "Go to https://news.ycombinator.com and get the top 3 stories"}' +kernel invoke python-gemini-cua cua-task --payload '{"query": "Go to http://magnitasks.com, Click the Tasks option in the left-side bar, and move the 5 items in the To Do and In Progress items to the Done section of the Kanban board. You are done successfully when the items are moved.", "record_replay": true}' ``` ## Step 7: Automated Runtime Testing (Optional) -**STOP and ask the human:** "Would you like me to automatically invoke all 15 templates and report back on their runtime status?" +**STOP and ask the human:** "Would you like me to automatically invoke all 16 templates and report back on their runtime status?" If the human agrees, invoke each template use the Kernel CLI and collect results. Present findings in this format: @@ -275,6 +287,7 @@ If the human agrees, invoke each template use the Kernel CLI and collect results | py-openai-cua | python-openai-cua | | | | py-openagi-cua | python-openagi-cua | | | | py-claude-agent-sdk | py-claude-agent-sdk | | | +| py-gemini-cua | python-gemini-cua | | | Status values: - **SUCCESS**: App started and returned a result @@ -287,9 +300,9 @@ Notes should include brief error messages for failures or confirmation of succes - [ ] Built CLI with `make build` - [ ] Created QA directory - [ ] Got KERNEL_API_KEY from human -- [ ] Created all 15 template variations +- [ ] Created all 16 template variations - [ ] Got required API keys from human (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY, OAGI_API_KEY) -- [ ] Deployed all 15 apps +- [ ] Deployed all 16 apps - [ ] Provided invoke commands to human for manual testing - [ ] (Optional) Ran automated runtime testing and reviewed results From 8c645043b43fe1c6c3b5ea7c5a7f09b9cf96d441 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 08:27:11 -0500 Subject: [PATCH 04/19] refactor(templates): remove AI slop from Gemini Computer Use templates Remove excessive JSDoc/docstring comments on private helpers and simple type definitions that don't need explanation. Keep only necessary comments that document non-obvious behavior. --- .../python/gemini-computer-use/loop.py | 4 --- .../python/gemini-computer-use/session.py | 19 ------------ .../gemini-computer-use/tools/computer.py | 6 ---- .../python/gemini-computer-use/tools/types.py | 14 --------- .../typescript/gemini-computer-use/loop.ts | 12 -------- .../typescript/gemini-computer-use/session.ts | 30 ------------------- .../gemini-computer-use/tools/computer.ts | 12 -------- .../gemini-computer-use/tools/types/gemini.ts | 26 +--------------- 8 files changed, 1 insertion(+), 122 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/loop.py b/pkg/templates/python/gemini-computer-use/loop.py index 991e8bc..835bc70 100644 --- a/pkg/templates/python/gemini-computer-use/loop.py +++ b/pkg/templates/python/gemini-computer-use/loop.py @@ -235,7 +235,6 @@ async def sampling_loop( def _extract_text(content: Content) -> str: - """Extract text from a Gemini content response.""" if not content.parts: return "" @@ -247,7 +246,6 @@ def _extract_text(content: Content) -> str: def _extract_function_calls(content: Content) -> List[types.FunctionCall]: - """Extract function calls from a Gemini content response.""" if not content.parts: return [] @@ -259,12 +257,10 @@ def _extract_function_calls(content: Content) -> List[types.FunctionCall]: def _is_predefined_function(name: str) -> bool: - """Check if a function name is a predefined computer use function.""" return name in [a.value for a in PREDEFINED_COMPUTER_USE_FUNCTIONS] def _prune_old_screenshots(contents: List[Content]) -> None: - """Prune old screenshots from conversation history to manage context size.""" turns_with_screenshots = 0 # Iterate in reverse to find recent turns with screenshots diff --git a/pkg/templates/python/gemini-computer-use/session.py b/pkg/templates/python/gemini-computer-use/session.py index 3227b28..d86d3c6 100644 --- a/pkg/templates/python/gemini-computer-use/session.py +++ b/pkg/templates/python/gemini-computer-use/session.py @@ -15,20 +15,6 @@ @dataclass class KernelBrowserSession: - """ - Manages Kernel browser lifecycle as an async context manager. - - Creates a browser session on entry and cleans it up on exit. - Optionally records a video replay of the entire session. - Provides session_id to computer tools. - - Usage: - async with KernelBrowserSession(record_replay=True) as session: - # Use session.session_id and session.kernel for operations - pass - # Browser is automatically cleaned up, replay URL available in session.replay_view_url - """ - stealth: bool = True timeout_seconds: int = 300 @@ -44,7 +30,6 @@ class KernelBrowserSession: _kernel: Optional[Kernel] = field(default=None, init=False) async def __aenter__(self) -> "KernelBrowserSession": - """Create a Kernel browser session and optionally start recording.""" self._kernel = Kernel() # Create browser with specified settings @@ -75,7 +60,6 @@ async def __aenter__(self) -> "KernelBrowserSession": return self async def _start_replay(self) -> None: - """Start recording a replay of the browser session.""" if not self._kernel or not self.session_id: return @@ -85,7 +69,6 @@ async def _start_replay(self) -> None: print(f"Replay recording started: {self.replay_id}") async def _stop_and_get_replay_url(self) -> None: - """Stop recording and get the replay URL.""" if not self._kernel or not self.session_id or not self.replay_id: return @@ -124,7 +107,6 @@ async def _stop_and_get_replay_url(self) -> None: print(f"Replay view URL: {self.replay_view_url}") async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: - """Stop recording and delete the browser session.""" if self._kernel and self.session_id: try: # Stop replay if recording was enabled @@ -143,7 +125,6 @@ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: @property def kernel(self) -> Kernel: - """Get the Kernel client instance.""" if self._kernel is None: raise RuntimeError("Session not initialized. Use async with context.") return self._kernel diff --git a/pkg/templates/python/gemini-computer-use/tools/computer.py b/pkg/templates/python/gemini-computer-use/tools/computer.py index 6083a3d..f951f8a 100644 --- a/pkg/templates/python/gemini-computer-use/tools/computer.py +++ b/pkg/templates/python/gemini-computer-use/tools/computer.py @@ -24,8 +24,6 @@ class ComputerTool: - """Computer tool that maps Gemini actions to Kernel's Computer Controls API.""" - def __init__( self, kernel: Kernel, @@ -37,15 +35,12 @@ def __init__( self.screen_size = screen_size def denormalize_x(self, x: int) -> int: - """Denormalize X coordinate from Gemini's 0-1000 scale to actual pixels.""" return int((x / COORDINATE_SCALE) * self.screen_size.width) def denormalize_y(self, y: int) -> int: - """Denormalize Y coordinate from Gemini's 0-1000 scale to actual pixels.""" return int((y / COORDINATE_SCALE) * self.screen_size.height) async def screenshot(self) -> ToolResult: - """Take a screenshot and return it as base64.""" try: await asyncio.sleep(SCREENSHOT_DELAY_SECS) response = self.kernel.browsers.computer.capture_screenshot(self.session_id) @@ -70,7 +65,6 @@ async def screenshot(self) -> ToolResult: async def execute_action( self, action_name: str, args: GeminiFunctionArgs ) -> ToolResult: - """Execute a Gemini action and return the result with a screenshot.""" # Check if this is a known computer use function if action_name not in [a.value for a in PREDEFINED_COMPUTER_USE_FUNCTIONS]: return ToolResult(error=f"Unknown action: {action_name}") diff --git a/pkg/templates/python/gemini-computer-use/tools/types.py b/pkg/templates/python/gemini-computer-use/tools/types.py index 0a133ae..b19772f 100644 --- a/pkg/templates/python/gemini-computer-use/tools/types.py +++ b/pkg/templates/python/gemini-computer-use/tools/types.py @@ -9,8 +9,6 @@ class GeminiAction(StrEnum): - """Gemini predefined computer use actions.""" - OPEN_WEB_BROWSER = "open_web_browser" CLICK_AT = "click_at" HOVER_AT = "hover_at" @@ -49,15 +47,11 @@ class GeminiAction(StrEnum): class SafetyDecision(TypedDict, total=False): - """Safety decision from Gemini.""" - decision: str explanation: str class GeminiFunctionArgs(TypedDict, total=False): - """Arguments for Gemini function calls.""" - # click_at, hover_at, scroll_at x: int y: int @@ -87,22 +81,14 @@ class GeminiFunctionArgs(TypedDict, total=False): @dataclass class ToolResult: - """Result from executing a computer action.""" - - # Base64-encoded screenshot image base64_image: Optional[str] = None - # Screenshot as raw bytes screenshot: Optional[bytes] = None - # Current URL of the browser url: Optional[str] = None - # Error message if the action failed error: Optional[str] = None @dataclass class ScreenSize: - """Screen dimensions for coordinate denormalization.""" - width: int height: int diff --git a/pkg/templates/typescript/gemini-computer-use/loop.ts b/pkg/templates/typescript/gemini-computer-use/loop.ts index 2ecac22..579dd1f 100644 --- a/pkg/templates/typescript/gemini-computer-use/loop.ts +++ b/pkg/templates/typescript/gemini-computer-use/loop.ts @@ -213,9 +213,6 @@ export async function samplingLoop({ }; } -/** - * Extract text from a Gemini content response. - */ function extractText(content: Content): string { if (!content.parts) return ''; @@ -228,9 +225,6 @@ function extractText(content: Content): string { return texts.join(' '); } -/** - * Extract function calls from a Gemini content response. - */ function extractFunctionCalls(content: Content): FunctionCall[] { if (!content.parts) return []; @@ -243,16 +237,10 @@ function extractFunctionCalls(content: Content): FunctionCall[] { return calls; } -/** - * Check if a function name is a predefined computer use function. - */ function isPredefinedFunction(name: string): boolean { return PREDEFINED_COMPUTER_USE_FUNCTIONS.includes(name as typeof PREDEFINED_COMPUTER_USE_FUNCTIONS[number]); } -/** - * Prune old screenshots from conversation history to manage context size. - */ function pruneOldScreenshots(contents: Content[]): void { let turnsWithScreenshots = 0; diff --git a/pkg/templates/typescript/gemini-computer-use/session.ts b/pkg/templates/typescript/gemini-computer-use/session.ts index 06e30a6..3903e08 100644 --- a/pkg/templates/typescript/gemini-computer-use/session.ts +++ b/pkg/templates/typescript/gemini-computer-use/session.ts @@ -8,13 +8,9 @@ import type { Kernel } from '@onkernel/sdk'; export interface SessionOptions { - /** Enable stealth mode to avoid bot detection */ stealth?: boolean; - /** Browser session timeout in seconds */ timeoutSeconds?: number; - /** Enable replay recording (requires paid plan) */ recordReplay?: boolean; - /** Grace period in seconds before stopping replay */ replayGracePeriod?: number; } @@ -32,20 +28,6 @@ const DEFAULT_OPTIONS: Required = { replayGracePeriod: 5.0, }; -/** - * Manages Kernel browser lifecycle with optional replay recording. - * - * Usage: - * ```typescript - * const session = new KernelBrowserSession(kernel, options); - * await session.start(); - * try { - * // Use session.sessionId for computer controls - * } finally { - * await session.stop(); - * } - * ``` - */ export class KernelBrowserSession { private kernel: Kernel; private options: Required; @@ -85,9 +67,6 @@ export class KernelBrowserSession { }; } - /** - * Create a Kernel browser session and optionally start recording. - */ async start(): Promise { // Create browser with specified settings const browser = await this.kernel.browsers.create({ @@ -119,9 +98,6 @@ export class KernelBrowserSession { return this.info; } - /** - * Start recording a replay of the browser session. - */ private async startReplay(): Promise { if (!this._sessionId) { return; @@ -133,9 +109,6 @@ export class KernelBrowserSession { console.log(`Replay recording started: ${this._replayId}`); } - /** - * Stop recording and get the replay URL. - */ private async stopReplay(): Promise { if (!this._sessionId || !this._replayId) { return; @@ -181,9 +154,6 @@ export class KernelBrowserSession { } } - /** - * Stop recording, and delete the browser session. - */ async stop(): Promise { const info = this.info; diff --git a/pkg/templates/typescript/gemini-computer-use/tools/computer.ts b/pkg/templates/typescript/gemini-computer-use/tools/computer.ts index 6388040..8924498 100644 --- a/pkg/templates/typescript/gemini-computer-use/tools/computer.ts +++ b/pkg/templates/typescript/gemini-computer-use/tools/computer.ts @@ -32,23 +32,14 @@ export class ComputerTool { this.screenSize = screenSize; } - /** - * Denormalize X coordinate from Gemini's 0-1000 scale to actual pixels. - */ private denormalizeX(x: number): number { return Math.round((x / COORDINATE_SCALE) * this.screenSize.width); } - /** - * Denormalize Y coordinate from Gemini's 0-1000 scale to actual pixels. - */ private denormalizeY(y: number): number { return Math.round((y / COORDINATE_SCALE) * this.screenSize.height); } - /** - * Take a screenshot and return it as base64. - */ async screenshot(): Promise { try { await this.sleep(SCREENSHOT_DELAY_MS); @@ -77,9 +68,6 @@ export class ComputerTool { } } - /** - * Execute a Gemini action and return the result with a screenshot. - */ async executeAction(actionName: string, args: GeminiFunctionArgs): Promise { // Check if this is a known computer use function if (!PREDEFINED_COMPUTER_USE_FUNCTIONS.includes(actionName as GeminiAction)) { diff --git a/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts index 3d338f0..6d528df 100644 --- a/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts +++ b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts @@ -3,9 +3,6 @@ * Based on Google's computer-use-preview reference implementation. */ -/** - * Gemini predefined computer use actions. - */ export enum GeminiAction { OPEN_WEB_BROWSER = 'open_web_browser', CLICK_AT = 'click_at', @@ -22,9 +19,6 @@ export enum GeminiAction { DRAG_AND_DROP = 'drag_and_drop', } -/** - * All predefined Gemini computer use function names. - */ export const PREDEFINED_COMPUTER_USE_FUNCTIONS = [ GeminiAction.OPEN_WEB_BROWSER, GeminiAction.CLICK_AT, @@ -41,14 +35,8 @@ export const PREDEFINED_COMPUTER_USE_FUNCTIONS = [ GeminiAction.DRAG_AND_DROP, ] as const; -/** - * Scroll direction options. - */ export type ScrollDirection = 'up' | 'down' | 'left' | 'right'; -/** - * Arguments for Gemini function calls. - */ export interface GeminiFunctionArgs { // click_at, hover_at, scroll_at x?: number; @@ -80,9 +68,6 @@ export interface GeminiFunctionArgs { }; } -/** - * Result from executing a computer action. - */ export interface ToolResult { /** Base64-encoded screenshot image */ base64Image?: string; @@ -92,24 +77,15 @@ export interface ToolResult { error?: string; } -/** - * Screen dimensions for coordinate denormalization. - */ export interface ScreenSize { width: number; height: number; } -/** - * Default screen size (matching Kernel browser viewport). - */ export const DEFAULT_SCREEN_SIZE: ScreenSize = { width: 1024, height: 768, }; -/** - * Gemini uses normalized coordinates (0-1000). - * This constant defines the normalization scale. - */ +// Gemini uses normalized coordinates (0-1000) export const COORDINATE_SCALE = 1000; From 16f2700e9cb0a7e75d07c0d4407c45f302d2c447 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 08:53:18 -0500 Subject: [PATCH 05/19] fix(session): update viewport dimensions and improve session info handling - Changed default viewport dimensions from 1024x768 to 1200x800. - Refactored session info construction in the stop method to avoid errors if the session wasn't started, ensuring safer handling of session data. --- .../typescript/gemini-computer-use/session.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/templates/typescript/gemini-computer-use/session.ts b/pkg/templates/typescript/gemini-computer-use/session.ts index 3903e08..b7fe9b9 100644 --- a/pkg/templates/typescript/gemini-computer-use/session.ts +++ b/pkg/templates/typescript/gemini-computer-use/session.ts @@ -73,9 +73,8 @@ export class KernelBrowserSession { stealth: this.options.stealth, timeout_seconds: this.options.timeoutSeconds, viewport: { - width: 1024, - height: 768, - refresh_rate: 60, + width: 1200, + height: 800, }, }); @@ -155,9 +154,16 @@ export class KernelBrowserSession { } async stop(): Promise { - const info = this.info; + // Build info object directly to avoid throwing if session wasn't started + const currentSessionId = this._sessionId; + const info: SessionInfo = { + sessionId: currentSessionId || '', + liveViewUrl: this._liveViewUrl || '', + replayId: this._replayId || undefined, + replayViewUrl: this._replayViewUrl || undefined, + }; - if (this._sessionId) { + if (currentSessionId) { try { // Stop replay if recording was enabled if (this.options.recordReplay && this._replayId) { @@ -171,8 +177,8 @@ export class KernelBrowserSession { } } finally { // Always clean up the browser session, even if replay stopping fails - console.log(`Destroying browser session: ${this._sessionId}`); - await this.kernel.browsers.deleteByID(this._sessionId); + console.log(`Destroying browser session: ${currentSessionId}`); + await this.kernel.browsers.deleteByID(currentSessionId); console.log('Browser session destroyed.'); } } From 614f005029da4934b3e04575219eb2b96d8dd28d Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 09:06:34 -0500 Subject: [PATCH 06/19] fix(loop): update screenshot handling in sampling loop - Changed the variable used for capturing screenshots from `result.screenshot` to `result.base64_image` for predefined functions, ensuring compatibility with the updated response structure. --- pkg/templates/python/gemini-computer-use/loop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/loop.py b/pkg/templates/python/gemini-computer-use/loop.py index 835bc70..649d0bb 100644 --- a/pkg/templates/python/gemini-computer-use/loop.py +++ b/pkg/templates/python/gemini-computer-use/loop.py @@ -190,12 +190,12 @@ async def sampling_loop( # Include screenshot for predefined functions parts = None - if result.screenshot and _is_predefined_function(fc.name): + if result.base64_image and _is_predefined_function(fc.name): parts = [ types.FunctionResponsePart( inline_data=types.FunctionResponseBlob( mime_type="image/png", - data=result.screenshot, + data=result.base64_image, ) ) ] From 021ba51200cf651f8ee1b21fa599e977f60fb1a2 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 09:07:30 -0500 Subject: [PATCH 07/19] Update python viewport update default viewport size --- pkg/templates/python/gemini-computer-use/session.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/session.py b/pkg/templates/python/gemini-computer-use/session.py index d86d3c6..7b65f30 100644 --- a/pkg/templates/python/gemini-computer-use/session.py +++ b/pkg/templates/python/gemini-computer-use/session.py @@ -37,9 +37,8 @@ async def __aenter__(self) -> "KernelBrowserSession": stealth=self.stealth, timeout_seconds=self.timeout_seconds, viewport={ - "width": 1024, - "height": 768, - "refresh_rate": 60, + "width": 1200, + "height": 800, }, ) From ed94644c0db8097728ba9df9dfbf052ec95cca47 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 09:12:07 -0500 Subject: [PATCH 08/19] fix(templates): sync DEFAULT_SCREEN_SIZE with viewport dimensions Update DEFAULT_SCREEN_SIZE from 1024x768 to 1200x800 to match the actual browser viewport created in session.ts/session.py. Mismatched dimensions caused incorrect coordinate denormalization for Gemini's 0-1000 scale. --- pkg/templates/python/gemini-computer-use/tools/types.py | 2 +- .../typescript/gemini-computer-use/tools/types/gemini.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/tools/types.py b/pkg/templates/python/gemini-computer-use/tools/types.py index b19772f..f11ab18 100644 --- a/pkg/templates/python/gemini-computer-use/tools/types.py +++ b/pkg/templates/python/gemini-computer-use/tools/types.py @@ -94,7 +94,7 @@ class ScreenSize: # Default screen size (matching Kernel browser viewport) -DEFAULT_SCREEN_SIZE = ScreenSize(width=1024, height=768) +DEFAULT_SCREEN_SIZE = ScreenSize(width=1200, height=800) # Gemini uses normalized coordinates (0-1000) COORDINATE_SCALE = 1000 diff --git a/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts index 6d528df..b45f87a 100644 --- a/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts +++ b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts @@ -83,8 +83,8 @@ export interface ScreenSize { } export const DEFAULT_SCREEN_SIZE: ScreenSize = { - width: 1024, - height: 768, + width: 1200, + height: 800, }; // Gemini uses normalized coordinates (0-1000) From fa9acba5cf92b485827c3d5ad9749762c2e07ccd Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 09:14:03 -0500 Subject: [PATCH 09/19] refactor(templates): centralize viewport dimensions in DEFAULT_SCREEN_SIZE Import DEFAULT_SCREEN_SIZE from types into session files so viewport dimensions are defined in one place. Users can now update dimensions by editing only the types file. --- pkg/templates/python/gemini-computer-use/session.py | 5 +++-- pkg/templates/typescript/gemini-computer-use/session.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/session.py b/pkg/templates/python/gemini-computer-use/session.py index 7b65f30..82e856d 100644 --- a/pkg/templates/python/gemini-computer-use/session.py +++ b/pkg/templates/python/gemini-computer-use/session.py @@ -11,6 +11,7 @@ from typing import Optional from kernel import Kernel +from tools import DEFAULT_SCREEN_SIZE @dataclass @@ -37,8 +38,8 @@ async def __aenter__(self) -> "KernelBrowserSession": stealth=self.stealth, timeout_seconds=self.timeout_seconds, viewport={ - "width": 1200, - "height": 800, + "width": DEFAULT_SCREEN_SIZE.width, + "height": DEFAULT_SCREEN_SIZE.height, }, ) diff --git a/pkg/templates/typescript/gemini-computer-use/session.ts b/pkg/templates/typescript/gemini-computer-use/session.ts index b7fe9b9..fd68442 100644 --- a/pkg/templates/typescript/gemini-computer-use/session.ts +++ b/pkg/templates/typescript/gemini-computer-use/session.ts @@ -6,6 +6,7 @@ */ import type { Kernel } from '@onkernel/sdk'; +import { DEFAULT_SCREEN_SIZE } from './tools/types/gemini'; export interface SessionOptions { stealth?: boolean; @@ -73,8 +74,8 @@ export class KernelBrowserSession { stealth: this.options.stealth, timeout_seconds: this.options.timeoutSeconds, viewport: { - width: 1200, - height: 800, + width: DEFAULT_SCREEN_SIZE.width, + height: DEFAULT_SCREEN_SIZE.height, }, }); From a3837e00b5ae738f22cefcbee562d6158ba73434 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 10:21:38 -0500 Subject: [PATCH 10/19] refactor(loop): centralize system prompt generation with current date - Introduced `getSystemPrompt` function in both TypeScript and Python templates to generate the system prompt dynamically, including the current date. - Updated the sampling loop to utilize the new function, improving code readability and maintainability. --- .../python/gemini-computer-use/loop.py | 12 ++++++++---- .../typescript/gemini-computer-use/loop.ts | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/loop.py b/pkg/templates/python/gemini-computer-use/loop.py index 649d0bb..80cc217 100644 --- a/pkg/templates/python/gemini-computer-use/loop.py +++ b/pkg/templates/python/gemini-computer-use/loop.py @@ -21,7 +21,10 @@ # System prompt for browser-based computer use -SYSTEM_PROMPT = f"""You are a helpful assistant that can use a web browser. +def get_system_prompt() -> str: + """Generate system prompt with current date.""" + current_date = datetime.now().strftime("%A, %B %d, %Y") + return f"""You are a helpful assistant that can use a web browser. You are operating a Chrome browser through computer use tools. The browser is already open and ready for use. @@ -29,7 +32,7 @@ When you need to interact with elements, use click_at, type_text_at, etc. After each action, carefully evaluate the screenshot to determine your next step. -Current date: {datetime.now().strftime("%A, %B %d, %Y")}.""" +Current date: {current_date}.""" # Maximum number of recent turns to keep screenshots for (to manage context) MAX_RECENT_TURN_WITH_SCREENSHOTS = 3 @@ -78,10 +81,11 @@ async def sampling_loop( ) ] + base_prompt = get_system_prompt() system_prompt = ( - f"{SYSTEM_PROMPT}\n\n{system_prompt_suffix}" + f"{base_prompt}\n\n{system_prompt_suffix}" if system_prompt_suffix - else SYSTEM_PROMPT + else base_prompt ) # Generate content config diff --git a/pkg/templates/typescript/gemini-computer-use/loop.ts b/pkg/templates/typescript/gemini-computer-use/loop.ts index 579dd1f..d08335f 100644 --- a/pkg/templates/typescript/gemini-computer-use/loop.ts +++ b/pkg/templates/typescript/gemini-computer-use/loop.ts @@ -14,7 +14,15 @@ import { ComputerTool } from './tools/computer'; import { PREDEFINED_COMPUTER_USE_FUNCTIONS, type GeminiFunctionArgs } from './tools/types/gemini'; // System prompt for browser-based computer use -const SYSTEM_PROMPT = `You are a helpful assistant that can use a web browser. +function getSystemPrompt(): string { + const currentDate = new Date().toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }); + + return `You are a helpful assistant that can use a web browser. You are operating a Chrome browser through computer use tools. The browser is already open and ready for use. @@ -22,7 +30,8 @@ When you need to navigate to a page, use the navigate action with a full URL. When you need to interact with elements, use click_at, type_text_at, etc. After each action, carefully evaluate the screenshot to determine your next step. -Current date: ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}.`; +Current date: ${currentDate}.`; +} // Maximum number of recent turns to keep screenshots for (to manage context) const MAX_RECENT_TURN_WITH_SCREENSHOTS = 3; @@ -66,9 +75,10 @@ export async function samplingLoop({ }, ]; + const basePrompt = getSystemPrompt(); const systemPrompt = systemPromptSuffix - ? `${SYSTEM_PROMPT}\n\n${systemPromptSuffix}` - : SYSTEM_PROMPT; + ? `${basePrompt}\n\n${systemPromptSuffix}` + : basePrompt; let iteration = 0; let finalResponse = ''; From 08ff1aa5229d1acd33d6fda34f4fd866f4e82250 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 10:26:08 -0500 Subject: [PATCH 11/19] refactor(tests): remove unused Gemini Computer Use template test case - Deleted the test case for the Gemini Computer Use template for Python, as it is no longer available. - Updated the list of unavailable template-language combinations accordingly. --- cmd/create_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cmd/create_test.go b/cmd/create_test.go index e37b861..f164966 100644 --- a/cmd/create_test.go +++ b/cmd/create_test.go @@ -439,12 +439,6 @@ func TestCreateCommand_InvalidLanguageTemplateCombinations(t *testing.T) { template: create.TemplateMagnitude, errContains: "template not found: python/magnitude", }, - { - name: "gemini-computer-use not available for python", - language: create.LanguagePython, - template: create.TemplateGeminiComputerUse, - errContains: "template not found: python/gemini-computer-use", - }, { name: "invalid language", language: "ruby", @@ -558,7 +552,6 @@ func TestCreateCommand_TemplateNotAvailableForLanguage(t *testing.T) { create.TemplateBrowserUse: {create.LanguageTypeScript}, create.TemplateStagehand: {create.LanguagePython}, create.TemplateMagnitude: {create.LanguagePython}, - create.TemplateGeminiComputerUse: {create.LanguagePython}, } for template, unavailableLanguages := range unavailableCombinations { From 9899b61f868003b0815d32cdac33031dd68315c6 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 10:31:26 -0500 Subject: [PATCH 12/19] fix(docs): update API key link and resource references in README files - Corrected the Google AI API key link in both Python and TypeScript README files. - Updated the Kernel documentation link to point to the Computer Controls section for better clarity. --- pkg/templates/python/gemini-computer-use/README.md | 6 ++---- pkg/templates/typescript/gemini-computer-use/README.md | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/README.md b/pkg/templates/python/gemini-computer-use/README.md index a7ec879..281c3e1 100644 --- a/pkg/templates/python/gemini-computer-use/README.md +++ b/pkg/templates/python/gemini-computer-use/README.md @@ -2,13 +2,11 @@ This is a Kernel application that implements a prompt loop using Google's Gemini Computer Use model with Kernel's Computer Controls API. -It follows the [Google Computer Use Preview Reference](https://github.com/google-gemini/computer-use-preview) but uses Kernel's Computer Controls API for browser automation. - ## Setup 1. Get your API keys: - **Kernel**: [dashboard.onkernel.com](https://dashboard.onkernel.com) - - **Google AI**: [aistudio.google.com/apikey](https://aistudio.google.com/apikey) + - **Google AI**: [aistudio.google.com/api-keys](https://aistudio.google.com/api-keys) 2. Deploy the app: ```bash @@ -56,4 +54,4 @@ The Gemini model can execute the following browser actions: ## Resources - [Google Gemini Computer Use Documentation](https://ai.google.dev/gemini-api/docs/computer-use) -- [Kernel Documentation](https://www.kernel.sh/docs/quickstart) +- [Kernel Computer Controls](https://www.kernel.sh/docs/browsers/computer-controls) diff --git a/pkg/templates/typescript/gemini-computer-use/README.md b/pkg/templates/typescript/gemini-computer-use/README.md index 7387109..f4c41a4 100644 --- a/pkg/templates/typescript/gemini-computer-use/README.md +++ b/pkg/templates/typescript/gemini-computer-use/README.md @@ -2,13 +2,11 @@ This is a Kernel application that implements a prompt loop using Google's Gemini Computer Use model with Kernel's Computer Controls API. -It follows the [Google Computer Use Preview Reference](https://github.com/google-gemini/computer-use-preview) but uses Kernel's Computer Controls API for browser automation. - ## Setup 1. Get your API keys: - **Kernel**: [dashboard.onkernel.com](https://dashboard.onkernel.com) - - **Google AI**: [aistudio.google.com/apikey](https://aistudio.google.com/apikey) + - **Google AI**: [aistudio.google.com/api-keys](https://aistudio.google.com/api-keys) 2. Deploy the app: ```bash @@ -56,4 +54,4 @@ The Gemini model can execute the following browser actions: ## Resources - [Google Gemini Computer Use Documentation](https://ai.google.dev/gemini-api/docs/computer-use) -- [Kernel Documentation](https://www.kernel.sh/docs/quickstart) +- [Kernel Computer Controls](https://www.kernel.sh/docs/browsers/computer-controls) From 25b484a66b708ab6bba65021cd9e08a7218da24f Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 10:33:27 -0500 Subject: [PATCH 13/19] fix(tools): use nullish coalescing for default magnitude in ComputerTool - Updated the magnitude assignment in the ComputerTool class to use nullish coalescing (??) instead of logical OR (||) for better handling of undefined values. --- pkg/templates/typescript/gemini-computer-use/tools/computer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/templates/typescript/gemini-computer-use/tools/computer.ts b/pkg/templates/typescript/gemini-computer-use/tools/computer.ts index 8924498..82e98b6 100644 --- a/pkg/templates/typescript/gemini-computer-use/tools/computer.ts +++ b/pkg/templates/typescript/gemini-computer-use/tools/computer.ts @@ -187,7 +187,7 @@ export class ComputerTool { const y = this.denormalizeY(args.y); // Denormalize magnitude if provided - let magnitude = args.magnitude || 800; + let magnitude = args.magnitude ?? 800; if (args.direction === 'up' || args.direction === 'down') { magnitude = this.denormalizeY(magnitude); } else { From efa55202e63cda3e6a2c648fc3a42da47284e84a Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 11:04:44 -0500 Subject: [PATCH 14/19] docs(templates): update README files for Gemini Computer Use actions - Added descriptions for `open_web_browser` and `search` actions in both Python and TypeScript README files to enhance clarity on browser functionalities. --- pkg/templates/python/gemini-computer-use/README.md | 2 ++ pkg/templates/typescript/gemini-computer-use/README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pkg/templates/python/gemini-computer-use/README.md b/pkg/templates/python/gemini-computer-use/README.md index 281c3e1..6344a2f 100644 --- a/pkg/templates/python/gemini-computer-use/README.md +++ b/pkg/templates/python/gemini-computer-use/README.md @@ -39,11 +39,13 @@ The Gemini model can execute the following browser actions: | Action | Description | |--------|-------------| +| `open_web_browser` | Returns a screenshot (browser is already running) | | `click_at` | Click at coordinates (x, y) | | `hover_at` | Move mouse to coordinates (x, y) | | `type_text_at` | Click and type text at coordinates | | `scroll_document` | Scroll the page (up/down/left/right) | | `scroll_at` | Scroll at specific coordinates | +| `search` | Focus the browser URL bar | | `navigate` | Navigate to a URL | | `go_back` | Go back in browser history | | `go_forward` | Go forward in browser history | diff --git a/pkg/templates/typescript/gemini-computer-use/README.md b/pkg/templates/typescript/gemini-computer-use/README.md index f4c41a4..3fb7c32 100644 --- a/pkg/templates/typescript/gemini-computer-use/README.md +++ b/pkg/templates/typescript/gemini-computer-use/README.md @@ -39,11 +39,13 @@ The Gemini model can execute the following browser actions: | Action | Description | |--------|-------------| +| `open_web_browser` | Returns a screenshot (browser is already running) | | `click_at` | Click at coordinates (x, y) | | `hover_at` | Move mouse to coordinates (x, y) | | `type_text_at` | Click and type text at coordinates | | `scroll_document` | Scroll the page (up/down/left/right) | | `scroll_at` | Scroll at specific coordinates | +| `search` | Focus the browser URL bar | | `navigate` | Navigate to a URL | | `go_back` | Go back in browser history | | `go_forward` | Go forward in browser history | From ae2fd9d3201cc4f9c890154acdf82854b9743624 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Fri, 23 Jan 2026 11:20:26 -0500 Subject: [PATCH 15/19] refactor(tools): remove screenshot attribute from ToolResult and ComputerTool - Eliminated the `screenshot` attribute from the `ToolResult` class and its usage in the `ComputerTool` class, streamlining the data structure and focusing on the `base64_image` representation. --- pkg/templates/python/gemini-computer-use/tools/computer.py | 1 - pkg/templates/python/gemini-computer-use/tools/types.py | 1 - 2 files changed, 2 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/tools/computer.py b/pkg/templates/python/gemini-computer-use/tools/computer.py index f951f8a..de2297d 100644 --- a/pkg/templates/python/gemini-computer-use/tools/computer.py +++ b/pkg/templates/python/gemini-computer-use/tools/computer.py @@ -56,7 +56,6 @@ async def screenshot(self) -> ToolResult: return ToolResult( base64_image=base64.b64encode(screenshot_bytes).decode("utf-8"), - screenshot=screenshot_bytes, url=url, ) except Exception as e: diff --git a/pkg/templates/python/gemini-computer-use/tools/types.py b/pkg/templates/python/gemini-computer-use/tools/types.py index f11ab18..ac9501a 100644 --- a/pkg/templates/python/gemini-computer-use/tools/types.py +++ b/pkg/templates/python/gemini-computer-use/tools/types.py @@ -82,7 +82,6 @@ class GeminiFunctionArgs(TypedDict, total=False): @dataclass class ToolResult: base64_image: Optional[str] = None - screenshot: Optional[bytes] = None url: Optional[str] = None error: Optional[str] = None From 4265d3e07d2a70984a6c306d88de3b2a9c2ca809 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Sun, 25 Jan 2026 09:55:15 -0500 Subject: [PATCH 16/19] Enhance error handling in Gemini computer use templates - Added an `error` field to the `QueryOutput` type in both Python and TypeScript templates to capture and return error messages. - Updated the `sampling_loop` function in both languages to handle exceptions and store error messages, improving feedback during execution. - Adjusted return values to include the new `error` field, ensuring consistent output structure across templates. --- pkg/templates/python/gemini-computer-use/loop.py | 7 +++++-- pkg/templates/python/gemini-computer-use/main.py | 2 ++ pkg/templates/typescript/gemini-computer-use/index.ts | 2 ++ pkg/templates/typescript/gemini-computer-use/loop.ts | 6 +++++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/loop.py b/pkg/templates/python/gemini-computer-use/loop.py index 80cc217..42b922d 100644 --- a/pkg/templates/python/gemini-computer-use/loop.py +++ b/pkg/templates/python/gemini-computer-use/loop.py @@ -61,7 +61,7 @@ async def sampling_loop( system_prompt_suffix: Additional system prompt text Returns: - Dict with 'final_response' and 'iterations' + Dict with 'final_response', 'iterations', and 'error' """ # Initialize the Gemini client client = genai.Client( @@ -107,6 +107,7 @@ async def sampling_loop( iteration = 0 final_response = "" + error = None while iteration < max_iterations: iteration += 1 @@ -226,7 +227,8 @@ async def sampling_loop( _prune_old_screenshots(contents) except Exception as e: - print(f"Error in sampling loop: {e}") + error = str(e) + print(f"Error in sampling loop: {error}") break if iteration >= max_iterations: @@ -235,6 +237,7 @@ async def sampling_loop( return { "final_response": final_response, "iterations": iteration, + "error": error, } diff --git a/pkg/templates/python/gemini-computer-use/main.py b/pkg/templates/python/gemini-computer-use/main.py index c896906..d20d84d 100644 --- a/pkg/templates/python/gemini-computer-use/main.py +++ b/pkg/templates/python/gemini-computer-use/main.py @@ -14,6 +14,7 @@ class QueryInput(TypedDict): class QueryOutput(TypedDict): result: str replay_url: Optional[str] + error: Optional[str] api_key = os.getenv("GOOGLE_API_KEY") @@ -69,4 +70,5 @@ async def cua_task( return { "result": final_response, "replay_url": session.replay_view_url, + "error": result.get("error"), } diff --git a/pkg/templates/typescript/gemini-computer-use/index.ts b/pkg/templates/typescript/gemini-computer-use/index.ts index aa57b40..868b9b4 100644 --- a/pkg/templates/typescript/gemini-computer-use/index.ts +++ b/pkg/templates/typescript/gemini-computer-use/index.ts @@ -14,6 +14,7 @@ interface QueryInput { interface QueryOutput { result: string; replay_url?: string; + error?: string; } // API Key for Gemini @@ -61,6 +62,7 @@ app.action( return { result: result.finalResponse, replay_url: sessionInfo.replayViewUrl, + error: result.error, }; } catch (error) { console.error('Error in sampling loop:', error); diff --git a/pkg/templates/typescript/gemini-computer-use/loop.ts b/pkg/templates/typescript/gemini-computer-use/loop.ts index d08335f..284147b 100644 --- a/pkg/templates/typescript/gemini-computer-use/loop.ts +++ b/pkg/templates/typescript/gemini-computer-use/loop.ts @@ -49,6 +49,7 @@ interface SamplingLoopOptions { interface SamplingLoopResult { finalResponse: string; iterations: number; + error?: string; } /** @@ -82,6 +83,7 @@ export async function samplingLoop({ let iteration = 0; let finalResponse = ''; + let error: string | undefined; while (iteration < maxIterations) { iteration++; @@ -207,7 +209,8 @@ export async function samplingLoop({ // Manage screenshot history to avoid context overflow pruneOldScreenshots(contents); - } catch (error) { + } catch (err) { + error = err instanceof Error ? err.message : String(err); console.error('Error in sampling loop:', error); break; } @@ -220,6 +223,7 @@ export async function samplingLoop({ return { finalResponse, iterations: iteration, + error, }; } From 3dbb329be24c92248fdf083f332d45462e94db6a Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Sun, 25 Jan 2026 10:11:21 -0500 Subject: [PATCH 17/19] Remove vertex config vars from python template setup refactor(loop): simplify Gemini client initialization in sampling loop - Removed environment variable dependencies from the Gemini client initialization in the `sampling_loop` function, streamlining the setup process. - The client is now instantiated solely with the API key, enhancing clarity and reducing complexity in the code. --- pkg/templates/python/gemini-computer-use/loop.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/loop.py b/pkg/templates/python/gemini-computer-use/loop.py index 42b922d..ada2130 100644 --- a/pkg/templates/python/gemini-computer-use/loop.py +++ b/pkg/templates/python/gemini-computer-use/loop.py @@ -3,7 +3,6 @@ Based on Google's computer-use-preview reference implementation. """ -import os from datetime import datetime from typing import Any, Dict, List @@ -64,12 +63,7 @@ async def sampling_loop( Dict with 'final_response', 'iterations', and 'error' """ # Initialize the Gemini client - client = genai.Client( - api_key=api_key, - vertexai=os.environ.get("USE_VERTEXAI", "0").lower() in ["true", "1"], - project=os.environ.get("VERTEXAI_PROJECT"), - location=os.environ.get("VERTEXAI_LOCATION"), - ) + client = genai.Client(api_key=api_key) computer_tool = ComputerTool(kernel, session_id) From c87517057cbfc939186c2c9f565831bc173b9587 Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Sun, 25 Jan 2026 10:35:16 -0500 Subject: [PATCH 18/19] Refactor GeminiAction usage in templates to derive predefined functions dynamically - Updated the `PREDEFINED_COMPUTER_USE_FUNCTIONS` in both Python and TypeScript templates to derive directly from the `GeminiAction` enum, ensuring consistency and reducing maintenance overhead when adding new actions. --- .../python/gemini-computer-use/tools/types.py | 18 ++---------------- .../gemini-computer-use/tools/types/gemini.ts | 17 ++--------------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/pkg/templates/python/gemini-computer-use/tools/types.py b/pkg/templates/python/gemini-computer-use/tools/types.py index ac9501a..8fdd67f 100644 --- a/pkg/templates/python/gemini-computer-use/tools/types.py +++ b/pkg/templates/python/gemini-computer-use/tools/types.py @@ -24,22 +24,8 @@ class GeminiAction(StrEnum): DRAG_AND_DROP = "drag_and_drop" -# All predefined Gemini computer use function names -PREDEFINED_COMPUTER_USE_FUNCTIONS = [ - GeminiAction.OPEN_WEB_BROWSER, - GeminiAction.CLICK_AT, - GeminiAction.HOVER_AT, - GeminiAction.TYPE_TEXT_AT, - GeminiAction.SCROLL_DOCUMENT, - GeminiAction.SCROLL_AT, - GeminiAction.WAIT_5_SECONDS, - GeminiAction.GO_BACK, - GeminiAction.GO_FORWARD, - GeminiAction.SEARCH, - GeminiAction.NAVIGATE, - GeminiAction.KEY_COMBINATION, - GeminiAction.DRAG_AND_DROP, -] +# Derive from enum to prevent drift when adding new actions +PREDEFINED_COMPUTER_USE_FUNCTIONS = list(GeminiAction) # Scroll direction type diff --git a/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts index b45f87a..56764b5 100644 --- a/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts +++ b/pkg/templates/typescript/gemini-computer-use/tools/types/gemini.ts @@ -19,21 +19,8 @@ export enum GeminiAction { DRAG_AND_DROP = 'drag_and_drop', } -export const PREDEFINED_COMPUTER_USE_FUNCTIONS = [ - GeminiAction.OPEN_WEB_BROWSER, - GeminiAction.CLICK_AT, - GeminiAction.HOVER_AT, - GeminiAction.TYPE_TEXT_AT, - GeminiAction.SCROLL_DOCUMENT, - GeminiAction.SCROLL_AT, - GeminiAction.WAIT_5_SECONDS, - GeminiAction.GO_BACK, - GeminiAction.GO_FORWARD, - GeminiAction.SEARCH, - GeminiAction.NAVIGATE, - GeminiAction.KEY_COMBINATION, - GeminiAction.DRAG_AND_DROP, -] as const; +// Derive from enum to prevent drift when adding new actions +export const PREDEFINED_COMPUTER_USE_FUNCTIONS = Object.values(GeminiAction); export type ScrollDirection = 'up' | 'down' | 'left' | 'right'; From 5e859dc928b468b4dec7813bf9e325686cbb83bf Mon Sep 17 00:00:00 2001 From: Daniel Prevoznik Date: Sun, 25 Jan 2026 11:20:09 -0500 Subject: [PATCH 19/19] Enhance error logging in Gemini computer use templates + local execution option for python - Added error logging in the TypeScript template to print error messages when present in the result. - Implemented a local execution block in the Python template to run tests directly, including error handling and logging for better debugging feedback. --- .../python/gemini-computer-use/main.py | 35 +++++++++++++++++++ .../typescript/gemini-computer-use/index.ts | 3 ++ 2 files changed, 38 insertions(+) diff --git a/pkg/templates/python/gemini-computer-use/main.py b/pkg/templates/python/gemini-computer-use/main.py index d20d84d..0c7d461 100644 --- a/pkg/templates/python/gemini-computer-use/main.py +++ b/pkg/templates/python/gemini-computer-use/main.py @@ -72,3 +72,38 @@ async def cua_task( "replay_url": session.replay_view_url, "error": result.get("error"), } + + +# Run locally if executed directly (not imported as a module) +# Execute via: uv run main.py +if __name__ == "__main__": + import asyncio + + async def main(): + test_query = "Navigate to https://www.google.com and describe what you see" + + print(f"Running local test with query: {test_query}") + + async with KernelBrowserSession( + stealth=True, + record_replay=False, + ) as session: + print("Kernel browser live view url:", session.live_view_url) + + try: + result = await sampling_loop( + model="gemini-2.5-computer-use-preview-10-2025", + query=test_query, + api_key=str(api_key), + kernel=session.kernel, + session_id=session.session_id, + ) + + print("Result:", result.get("final_response", "")) + if result.get("error"): + print("Error:", result.get("error")) + except Exception as e: + print(f"Local execution failed: {e}") + raise + + asyncio.run(main()) diff --git a/pkg/templates/typescript/gemini-computer-use/index.ts b/pkg/templates/typescript/gemini-computer-use/index.ts index 868b9b4..1b27b42 100644 --- a/pkg/templates/typescript/gemini-computer-use/index.ts +++ b/pkg/templates/typescript/gemini-computer-use/index.ts @@ -94,6 +94,9 @@ if (import.meta.url === `file://${process.argv[1]}`) { sessionId: session.sessionId, }); console.log('Result:', result.finalResponse); + if (result.error) { + console.error('Error:', result.error); + } } finally { await session.stop(); }