From 3266d81b63cd65cb3fc9bb314dbe81cace8d3ced Mon Sep 17 00:00:00 2001
From: Vincent Yung
Date: Wed, 12 Nov 2025 11:45:48 -0800
Subject: [PATCH 001/126] feat: Rebrand to GoDaddy ANS and add multi-provider
support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Rebrand from Atlas to GoDaddy ANS Chat Sidebar
- Add support for Anthropic Claude and OpenAI providers
- Implement browser automation tools for Anthropic (navigate, click, type, screenshot)
- Add custom base URL and model name configuration
- Implement per-tab isolated chat history
- Add automatic page context extraction
- Fix auto-scroll during streaming responses
- Update header to show correct provider/model
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
REVERT_BROWSER_TOOLS.md | 139 ++++++++
anthropic-browser-tools.ts | 201 ++++++++++++
anthropic-service.ts | 68 ++++
background.ts | 9 +-
.../src/renderer/components/Settings.tsx | 26 ++
.../src/renderer/services/gemini-service.ts | 3 +-
electron-browser/src/renderer/types.ts | 2 +
manifest.json | 6 +-
settings.html | 2 +-
settings.tsx | 90 +++++-
sidepanel.html | 2 +-
sidepanel.tsx | 299 ++++++++++++++----
types.ts | 5 +-
13 files changed, 769 insertions(+), 83 deletions(-)
create mode 100644 REVERT_BROWSER_TOOLS.md
create mode 100644 anthropic-browser-tools.ts
create mode 100644 anthropic-service.ts
diff --git a/REVERT_BROWSER_TOOLS.md b/REVERT_BROWSER_TOOLS.md
new file mode 100644
index 0000000..ec0d993
--- /dev/null
+++ b/REVERT_BROWSER_TOOLS.md
@@ -0,0 +1,139 @@
+# How to Revert Anthropic Browser Tools
+
+If you want to remove the Anthropic Browser Tools feature and go back to the simple chat-only version, follow these steps:
+
+## Option 1: Quick Revert (Just disable the feature)
+
+**In the UI:**
+1. Open the Atlas sidebar
+2. Click the **โ** button to disable Browser Tools mode (it will turn to **โ**)
+3. Anthropic will work in simple chat mode (no navigation/browser control)
+
+This is the **easiest way** - no code changes needed!
+
+---
+
+## Option 2: Full Revert (Remove the code)
+
+If you want to completely remove the Anthropic Browser Tools code:
+
+### Files to Delete:
+```bash
+rm anthropic-browser-tools.ts
+```
+
+### Files to Modify:
+
+**1. `sidepanel.tsx` (Line 9)**
+
+Remove this import:
+```typescript
+import { streamAnthropicWithBrowserTools } from './anthropic-browser-tools';
+```
+
+**2. `sidepanel.tsx` (Lines 1259-1297)**
+
+Replace this block:
+```typescript
+ // Route to provider-specific browser tools
+ if (settings.provider === 'google') {
+ await streamWithGeminiComputerUse(newMessages);
+ } else if (settings.provider === 'anthropic') {
+ const assistantMessage: Message = {
+ id: (Date.now() + 1).toString(),
+ role: 'assistant',
+ content: '',
+ };
+ setMessages(prev => [...prev, assistantMessage]);
+
+ const modelToUse = settings.model === 'custom' && settings.customModelName
+ ? settings.customModelName
+ : settings.model;
+
+ await streamAnthropicWithBrowserTools(
+ newMessages,
+ settings.apiKey,
+ modelToUse,
+ settings.customBaseUrl,
+ (text: string) => {
+ setMessages(prev => {
+ const updated = [...prev];
+ const lastMsg = updated[updated.length - 1];
+ if (lastMsg && lastMsg.role === 'assistant') {
+ lastMsg.content += text;
+ }
+ return updated;
+ });
+ },
+ () => {
+ // On complete
+ },
+ executeTool,
+ abortControllerRef.current.signal
+ );
+ } else {
+ throw new Error(`Browser Tools not supported for ${settings.provider}`);
+ }
+```
+
+With this simpler version:
+```typescript
+ // Safety check: Ensure we have Google API key
+ if (settings.provider !== 'google' || !settings.apiKey) {
+ setBrowserToolsEnabled(false);
+ setMessages(prev => {
+ const updated = [...prev];
+ const lastMsg = updated[updated.length - 1];
+ if (lastMsg && lastMsg.role === 'assistant') {
+ lastMsg.content = 'โ ๏ธ **Browser Tools requires Google Gemini**\n\nBrowser Tools only works with Google Gemini.\n\nPlease:\n1. Open Settings (โ๏ธ)\n2. Select "Google" as provider\n3. Add your Google API key\n4. Try again';
+ }
+ return updated;
+ });
+ setIsLoading(false);
+ return;
+ }
+
+ await streamWithGeminiComputerUse(newMessages);
+```
+
+### Rebuild:
+```bash
+npm run build
+```
+
+### Reload extension:
+Go to `chrome://extensions/` and click the refresh icon on the Atlas extension.
+
+---
+
+## What Changed (Summary)
+
+### New Files Added:
+- โ
`anthropic-browser-tools.ts` - Anthropic-specific browser automation
+
+### Modified Files:
+- โ
`sidepanel.tsx` - Added routing for Anthropic browser tools in Browser Tools mode
+
+### What Stayed the Same:
+- โ
Simple chat mode (without Browser Tools button) works exactly as before
+- โ
Google Gemini Computer Use - unchanged
+- โ
All settings and configuration - unchanged
+- โ
Composio MCP integration - unchanged
+
+---
+
+## Testing the Revert
+
+After reverting, test that:
+1. โ
Anthropic simple chat still works (without Browser Tools)
+2. โ
Google Gemini Computer Use still works with Browser Tools
+3. โ
No errors in console
+
+---
+
+## Contact
+
+If you have issues reverting, you can:
+1. Check the git history: `git log`
+2. Revert to a previous commit: `git revert `
+3. Or restore from backup (if you made one before changes)
diff --git a/anthropic-browser-tools.ts b/anthropic-browser-tools.ts
new file mode 100644
index 0000000..712dccf
--- /dev/null
+++ b/anthropic-browser-tools.ts
@@ -0,0 +1,201 @@
+import type { Message } from './types';
+
+// Browser tool definitions for Anthropic API
+const BROWSER_TOOLS = [
+ {
+ name: 'navigate',
+ description: 'Navigate to a specific URL',
+ input_schema: {
+ type: 'object',
+ properties: {
+ url: {
+ type: 'string',
+ description: 'The URL to navigate to (must include http:// or https://)',
+ },
+ },
+ required: ['url'],
+ },
+ },
+ {
+ name: 'click',
+ description: 'Click at specific coordinates on the page',
+ input_schema: {
+ type: 'object',
+ properties: {
+ x: { type: 'number', description: 'X coordinate' },
+ y: { type: 'number', description: 'Y coordinate' },
+ },
+ required: ['x', 'y'],
+ },
+ },
+ {
+ name: 'type',
+ description: 'Type text into a focused input field',
+ input_schema: {
+ type: 'object',
+ properties: {
+ text: { type: 'string', description: 'Text to type' },
+ selector: { type: 'string', description: 'CSS selector for the input (optional)' },
+ },
+ required: ['text'],
+ },
+ },
+ {
+ name: 'scroll',
+ description: 'Scroll the page',
+ input_schema: {
+ type: 'object',
+ properties: {
+ direction: {
+ type: 'string',
+ enum: ['up', 'down'],
+ description: 'Scroll direction',
+ },
+ amount: { type: 'number', description: 'Pixels to scroll (default: 500)' },
+ },
+ required: ['direction'],
+ },
+ },
+ {
+ name: 'screenshot',
+ description: 'Take a screenshot of the current page',
+ input_schema: {
+ type: 'object',
+ properties: {},
+ },
+ },
+ {
+ name: 'getPageContext',
+ description: 'Get information about the current page (URL, title, content)',
+ input_schema: {
+ type: 'object',
+ properties: {},
+ },
+ },
+];
+
+export async function streamAnthropicWithBrowserTools(
+ messages: Message[],
+ apiKey: string,
+ model: string,
+ customBaseUrl: string | undefined,
+ onTextChunk: (text: string) => void,
+ onComplete: () => void,
+ executeTool: (toolName: string, params: any) => Promise,
+ signal?: AbortSignal
+): Promise {
+ const baseUrl = customBaseUrl || 'https://api.anthropic.com';
+ let conversationMessages = [...messages];
+ let fullResponseText = '';
+
+ const MAX_TURNS = 10; // Prevent infinite loops
+ let turnCount = 0;
+
+ while (turnCount < MAX_TURNS) {
+ turnCount++;
+
+ console.log('๐ง Anthropic Browser Tools - Turn', turnCount);
+ console.log('๐ค Sending request with tools:', BROWSER_TOOLS.map(t => t.name));
+
+ const requestBody = {
+ model,
+ max_tokens: 4096,
+ tools: BROWSER_TOOLS,
+ messages: conversationMessages.map(m => ({
+ role: m.role,
+ content: m.content,
+ })),
+ system: 'You are a helpful AI assistant with browser automation capabilities. You can navigate to websites, click elements, type text, scroll pages, and take screenshots. When the user asks you to visit a website or interact with a page, USE THE AVAILABLE TOOLS to perform the action.',
+ };
+
+ console.log('๐ค Request body:', JSON.stringify(requestBody, null, 2));
+
+ const response = await fetch(`${baseUrl}/v1/messages`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-api-key': apiKey,
+ 'anthropic-version': '2023-06-01',
+ },
+ body: JSON.stringify(requestBody),
+ signal,
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ console.error('โ API Error:', error);
+ throw new Error(error.error?.message || 'Anthropic API request failed');
+ }
+
+ const data = await response.json();
+ console.log('๐ฅ Response:', JSON.stringify(data, null, 2));
+
+ // Check if response includes text
+ const textContent = data.content?.find((c: any) => c.type === 'text');
+ if (textContent?.text) {
+ fullResponseText += textContent.text;
+ onTextChunk(textContent.text);
+ }
+
+ // Check for tool use
+ const toolUses = data.content?.filter((c: any) => c.type === 'tool_use') || [];
+
+ if (toolUses.length === 0) {
+ // No more tools to execute, we're done
+ break;
+ }
+
+ // Execute tools and collect results
+ const toolResults: any[] = [];
+
+ for (const toolUse of toolUses) {
+ console.log(`๐ง Executing tool: ${toolUse.name}`, toolUse.input);
+ onTextChunk(`\n[Executing: ${toolUse.name}]\n`);
+ onTextChunk(`\n${JSON.stringify(toolUse.input, null, 2)}\n`);
+
+ try {
+ console.log('๐ง Calling executeTool with:', toolUse.name, toolUse.input);
+ const result = await executeTool(toolUse.name, toolUse.input);
+ console.log('โ
Tool result:', result);
+
+ toolResults.push({
+ type: 'tool_result',
+ tool_use_id: toolUse.id,
+ content: JSON.stringify(result),
+ });
+
+ // Small delay between actions
+ await new Promise(resolve => setTimeout(resolve, 500));
+ } catch (error: any) {
+ console.error('โ Tool execution error:', error);
+ toolResults.push({
+ type: 'tool_result',
+ tool_use_id: toolUse.id,
+ content: JSON.stringify({ error: error.message }),
+ is_error: true,
+ });
+ }
+ }
+
+ // Add assistant message with tool uses
+ conversationMessages.push({
+ id: Date.now().toString(),
+ role: 'assistant',
+ content: JSON.stringify(data.content),
+ });
+
+ // Add user message with tool results
+ conversationMessages.push({
+ id: (Date.now() + 1).toString(),
+ role: 'user',
+ content: JSON.stringify(toolResults),
+ });
+
+ // If the response has a stop_reason of 'end_turn', we're done
+ if (data.stop_reason === 'end_turn') {
+ break;
+ }
+ }
+
+ onComplete();
+}
diff --git a/anthropic-service.ts b/anthropic-service.ts
new file mode 100644
index 0000000..e707ad6
--- /dev/null
+++ b/anthropic-service.ts
@@ -0,0 +1,68 @@
+import type { Message } from './types';
+
+export async function streamAnthropic(
+ messages: Message[],
+ apiKey: string,
+ model: string,
+ customBaseUrl: string | undefined,
+ onChunk: (text: string) => void,
+ signal?: AbortSignal
+): Promise {
+ const baseUrl = customBaseUrl || 'https://api.anthropic.com';
+
+ const response = await fetch(`${baseUrl}/v1/messages`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-api-key': apiKey,
+ 'anthropic-version': '2023-06-01',
+ },
+ body: JSON.stringify({
+ model,
+ max_tokens: 4096,
+ messages: messages.map(m => ({
+ role: m.role,
+ content: m.content,
+ })),
+ stream: true,
+ }),
+ signal,
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error?.message || 'Anthropic API request failed');
+ }
+
+ const reader = response.body!.getReader();
+ const decoder = new TextDecoder();
+ let buffer = '';
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const chunk = decoder.decode(value, { stream: true });
+ buffer += chunk;
+
+ const lines = buffer.split('\n');
+ buffer = lines.pop() || '';
+
+ for (const line of lines) {
+ if (!line.trim() || !line.startsWith('data: ')) continue;
+
+ const data = line.slice(6); // Remove 'data: ' prefix
+ if (data === '[DONE]') continue;
+
+ try {
+ const json = JSON.parse(data);
+
+ if (json.type === 'content_block_delta' && json.delta?.type === 'text_delta') {
+ onChunk(json.delta.text);
+ }
+ } catch (e) {
+ // Skip invalid JSON
+ }
+ }
+ }
+}
diff --git a/background.ts b/background.ts
index 0cf9ead..87bb73b 100644
--- a/background.ts
+++ b/background.ts
@@ -13,17 +13,12 @@ const memory: BrowserMemory = {
sessionData: {}
};
+// Set side panel to open automatically on extension icon click
+// The side panel will be per-tab by default when using tabId
chrome.sidePanel
.setPanelBehavior({ openPanelOnActionClick: true })
.catch((error: Error) => console.error(error));
-// Listen for extension icon clicks
-chrome.action.onClicked.addListener((tab) => {
- if (tab.id) {
- chrome.sidePanel.open({ tabId: tab.id });
- }
-});
-
// Track page visits for memory
chrome.webNavigation.onCompleted.addListener((details) => {
if (details.frameId === 0) { // Main frame only
diff --git a/electron-browser/src/renderer/components/Settings.tsx b/electron-browser/src/renderer/components/Settings.tsx
index 82d918c..4cf6127 100644
--- a/electron-browser/src/renderer/components/Settings.tsx
+++ b/electron-browser/src/renderer/components/Settings.tsx
@@ -12,6 +12,7 @@ const Settings = ({ settings, onSave, onClose }: SettingsProps) => {
const [googleApiKey, setGoogleApiKey] = useState(settings?.googleApiKey || '');
const [composioApiKey, setComposioApiKey] = useState(settings?.composioApiKey || '');
const [model, setModel] = useState(settings?.model || 'gemini-2.0-flash-exp');
+ const [customBaseUrl, setCustomBaseUrl] = useState(settings?.customBaseUrl || '');
const handleSave = async () => {
// Initialize MCP if Composio key is provided
@@ -29,6 +30,7 @@ const Settings = ({ settings, onSave, onClose }: SettingsProps) => {
googleApiKey,
composioApiKey,
model,
+ customBaseUrl,
});
};
@@ -112,6 +114,30 @@ const Settings = ({ settings, onSave, onClose }: SettingsProps) => {
+ {/* Custom Base URL */}
+
+
+
setCustomBaseUrl(e.target.value)}
+ placeholder="e.g., https://your-custom-endpoint.com"
+ style={{
+ padding: '10px 12px',
+ backgroundColor: '#f5f5f5',
+ color: '#1a1a1a',
+ border: '1px solid #e5e5e5',
+ borderRadius: '6px',
+ fontSize: '14px',
+ }}
+ />
+
+ Leave empty to use default Google AI endpoint. Enter a custom Gemini-compatible API endpoint to use your own provider.
+
+
+
{/* Composio API Key */}