Skip to content

feat(tool): browser search bing engine & app updater #253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/agent-tars/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agent-tars-app",
"version": "1.0.0-alpha.2",
"version": "1.0.0-alpha.3",
"description": "A multimodal AI agent that revolutionizes GUI interaction",
"main": "./dist/main/index.js",
"author": "ByteDance",
Expand Down
25 changes: 25 additions & 0 deletions apps/agent-tars/src/main/customTools/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,31 @@ export async function search(toolCall: ToolCall): Promise<MCPToolResult> {
count: args.count,
query: args.query,
});
} else if (
currentSearchConfig.provider === SearchProvider.DUCKDUCKGO_SEARCH
) {
const client = new SearchClient({
provider: SearchProviderEnum.DuckduckgoSearch,
providerConfig: {},
});
results = await client.search({
query: args.query,
count: args.count,
});
// } else if (currentSearchConfig.provider === SearchProvider.BROWSER_SEARCH) {
// const client = new SearchClient({
// provider: SearchProviderEnum.BrowserSearch,
// providerConfig: {
// browserOptions: {
// headless: true,
// },
// defaultEngine: 'bing',
// },
// });
// results = await client.search({
// query: args.query,
// count: args.count || 10,
// });
} else {
// Only for Bing Search, because Tavily is not supported in the bundle of this packages
// Error info: trvily is not defined
Expand Down
20 changes: 20 additions & 0 deletions apps/agent-tars/src/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import { app, shell, BrowserWindow, ipcMain } from 'electron';
import { join } from 'path';
import { registerIpcMain } from '@ui-tars/electron-ipc/main';
import { updateElectronApp, UpdateSourceType } from 'update-electron-app';
import { electronApp, optimizer, is } from '@electron-toolkit/utils';
import { ipcRoutes } from './ipcRoutes';
import icon from '../../resources/icon.png?asset';

class AppUpdater {
constructor() {
// autoUpdater.logger = logger;
// autoUpdater.checkForUpdatesAndNotify();
if (process.env.CI !== 'e2e') {
updateElectronApp({
updateSource: {
type: UpdateSourceType.ElectronPublicUpdateService,
repo: 'bytedance/UI-TARS-desktop',
host: 'https://update.electronjs.org',
},
updateInterval: '20 minutes',
});
}
}
}

function createWindow(): void {
// Create the browser window.
const mainWindow = new BrowserWindow({
Expand Down Expand Up @@ -90,6 +108,8 @@ app.whenReady().then(async () => {
optimizer.watchWindowShortcuts(window);
});

new AppUpdater();

await initializeApp();

// IPC test
Expand Down
2 changes: 2 additions & 0 deletions apps/agent-tars/src/main/ipcRoutes/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ export const actionRoute = t.router({
});
}
} else {
console.log('executeCustomTool_toolCall', toolCall);
const result = await executeCustomTool(toolCall);
console.log('executeCustomTool_result', result);
if (result) {
results.push(...result);
}
Expand Down
1 change: 1 addition & 0 deletions apps/agent-tars/src/main/ipcRoutes/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const llmRoute = t.router({
const messages = input.messages.map((msg) => new Message(msg));
const llm = createLLM(currentLLMConfigRef.current);
console.log('current llm config', currentLLMConfigRef.current);
console.log('current search config', SettingStore.get('search'));
console.log('input.tools', input.tools);
const response = await llm.askTool({
messages,
Expand Down
2 changes: 1 addition & 1 deletion apps/agent-tars/src/main/store/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const DEFAULT_FILESYSTEM_SETTINGS: FileSystemSettings = {
};

const DEFAULT_SEARCH_SETTINGS: SearchSettings = {
provider: SearchProvider.DUCKDUCKGO_SEARCH,
provider: SearchProvider.TAVILY,
apiKey: '',
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ export function SearchSettingsTab({
}}
startContent={getSearchProviderLogo(settings.provider)}
>
<SelectItem
key={SearchProvider.TAVILY}
startContent={getSearchProviderLogo(SearchProvider.TAVILY)}
>
Tavily Search
</SelectItem>
{/* <SelectItem
key={SearchProvider.BROWSER_SEARCH}
startContent={getSearchProviderLogo(SearchProvider.BROWSER_SEARCH)}
>
Browser Search
</SelectItem> */}
<SelectItem
key={SearchProvider.DUCKDUCKGO_SEARCH}
startContent={getSearchProviderLogo(SearchProvider.DUCKDUCKGO_SEARCH)}
Expand All @@ -36,15 +48,11 @@ export function SearchSettingsTab({
>
Bing Search
</SelectItem>
<SelectItem
key={SearchProvider.TAVILY}
startContent={getSearchProviderLogo(SearchProvider.TAVILY)}
>
Tavily Search
</SelectItem>
</Select>

{settings.provider !== SearchProvider.DUCKDUCKGO_SEARCH && (
{[SearchProvider.TAVILY, SearchProvider.BING_SEARCH].includes(
settings.provider,
) && (
<Input
type="password"
label="API Key"
Expand All @@ -65,6 +73,22 @@ export function SearchSettingsTab({
<p className="text-sm text-default-500">Advanced Settings (Optional)</p>
) : null}

{/* {settings.provider === SearchProvider.BROWSER_SEARCH && (
<Select
label="Default Search Engine"
placeholder="Select your default search engine"
value={settings.defaultEngine || 'bing'}
onChange={(e) =>
setSettings({
...settings,
defaultEngine: e.target.value as SearchSettings['defaultEngine'],
})
}
>
<SelectItem key="bing">Bing</SelectItem>
</Select>
)} */}

{settings.provider === SearchProvider.BING_SEARCH && (
<Input
label="Custom Endpoint"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export function getSearchProviderLogo(provider: SearchProvider) {
return <TbSearch size={18} />;
case SearchProvider.DUCKDUCKGO_SEARCH:
return <SiDuckduckgo size={18} />;
// case SearchProvider.BROWSER_SEARCH:
// return <TbBrowser size={18} />;
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const DEFAULT_FILESYSTEM_SETTINGS: FileSystemSettings = {
};

const DEFAULT_SEARCH_SETTINGS: SearchSettings = {
provider: SearchProvider.DUCKDUCKGO_SEARCH,
provider: SearchProvider.TAVILY,
apiKey: '',
};

Expand Down Expand Up @@ -87,7 +87,9 @@ export function useAppSettings() {
}
console.log('searchSettings.provider', searchSettings.provider);
if (
searchSettings.provider !== SearchProvider.DUCKDUCKGO_SEARCH &&
[SearchProvider.BING_SEARCH, SearchProvider.TAVILY].includes(
searchSettings.provider,
) &&
!searchSettings.apiKey
) {
return 'API Key is required';
Expand Down
2 changes: 0 additions & 2 deletions apps/ui-tars/src/renderer/src/pages/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ export default function Settings() {
settings.presetSource.type === 'remote' &&
settings.presetSource.autoUpdate;

console.log('settings', settings);

const handleSubmit = async (values) => {
updateSetting(values);
console.log('values', values);
Expand Down
4 changes: 4 additions & 0 deletions packages/agent-infra/browser/src/base-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,18 @@ export abstract class BaseBrowser implements BrowserInterface {
pageFunction,
pageFunctionParams,
beforePageLoad,
afterPageLoad,
beforeSendResult,
waitForOptions,
} = options;
const page = await this.browser!.newPage();
try {
await beforePageLoad?.(page);
await page.goto(url, {
waitUntil: 'networkidle2',
...waitForOptions,
});
await afterPageLoad?.(page);
const _window = await page.evaluateHandle(() => window);
const result = await page.evaluate(
pageFunction,
Expand Down
2 changes: 2 additions & 0 deletions packages/agent-infra/browser/src/local-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export class LocalBrowser extends BaseBrowser {
},
args: [
'--no-sandbox',
'--mute-audio',
'--disable-gpu',
'--disable-http2',
'--disable-blink-features=AutomationControlled',
'--disable-infobars',
Expand Down
15 changes: 14 additions & 1 deletion packages/agent-infra/browser/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
import { Page } from 'puppeteer-core';
import { Page, WaitForOptions } from 'puppeteer-core';

/**
* Options for launching a browser instance
Expand Down Expand Up @@ -64,6 +64,11 @@ export interface EvaluateOnNewPageOptions<T extends any[], R> {
*/
url: string;

/**
* Options for waiting for the page to load
*/
waitForOptions?: WaitForOptions;

/**
* Function to be evaluated in the page context
* This function runs in the context of the browser page, not Node.js
Expand All @@ -87,6 +92,14 @@ export interface EvaluateOnNewPageOptions<T extends any[], R> {
*/
beforePageLoad?: (page: Page) => void | Promise<void>;

/**
* Optional function to execute after page navigation
* Useful for setting up page configuration after loading the URL
* @param {Page} page - Puppeteer page instance
* @returns {void | Promise<void>}
*/
afterPageLoad?: (page: Page) => void | Promise<void>;

/**
* Optional function to process the result before returning
* Can be used to transform or validate the result from page evaluation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export async function bingSearch() {
browserOptions: {
headless: false,
},
defaultEngine: 'bing',
});

try {
Expand Down
16 changes: 13 additions & 3 deletions packages/agent-infra/search/browser-search/src/browser-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class BrowserSearch {
visitedUrls,
excludeDomains,
truncate: options.truncate,
needVisitedUrls: options.needVisitedUrls,
engine,
}),
),
Expand Down Expand Up @@ -102,6 +103,7 @@ export class BrowserSearch {
options: {
query: string;
count?: number;
needVisitedUrls?: boolean;
excludeDomains: string[];
queue: PromiseQueue;
visitedUrls: Set<string>;
Expand All @@ -119,11 +121,17 @@ export class BrowserSearch {

let links = await browser.evaluateOnNewPage({
url,
waitForOptions: {
waitUntil: 'networkidle0',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里还有个 Breaking Change,之前 Browser Search 是采用默认值 networkidle2,现在是 networkidle0,Browser Search 这种场景 networkidle2 应该够用了

Refs:

  • 如果你需要尽快捕获页面(比如只需要 DOM 加载完成,不关心后续的异步请求),用 networkidle2。
  • 如果你需要确保页面所有资源(包括图片、脚本等)都加载完成,用 networkidle0。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

试了下 networkidle2 拿不到数据,失败概率比较高

},
pageFunction: searchEngine.extractSearchResults,
pageFunctionParams: [],
beforePageLoad: async (page) => {
await interceptRequest(page);
},
afterPageLoad: async (page) => {
await page.waitForSelector('.b_pag');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是不是会导致 local browser search 只能在 bing 下面工作?baidu 现在现在不 work 了

},
});

this.logger.info('Fetched links:', links);
Expand All @@ -143,9 +151,11 @@ export class BrowserSearch {

// Visit each link and extract content
const results = await Promise.allSettled(
links.map((item) =>
options.queue.add(() => this.visitLink(this.browser, item)),
),
options.needVisitedUrls
? links.map((item) =>
options.queue.add(() => this.visitLink(this.browser, item)),
)
: links,
);

return results
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { GoogleSearchEngine } from './google-engine';
import { BingSearchEngine } from './bing-engine';
import { BaiduSearchEngine } from './baidu-engine';
import type { SearchEngine, SearchEngineAdapter } from '../types';

/**
* Factory function to get the appropriate search engine adapter instance.
*
* @param engine - The search engine identifier ('google', 'bing', or 'baidu')
* @returns An instance of the requested search engine adapter
*/
export function getSearchEngine(engine: SearchEngine): SearchEngineAdapter {
switch (engine) {
case 'google':
return new GoogleSearchEngine();
case 'bing':
return new BingSearchEngine();
case 'baidu':
return new BaiduSearchEngine();
default:
return new GoogleSearchEngine();
}
}
24 changes: 1 addition & 23 deletions packages/agent-infra/search/browser-search/src/engines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,8 @@
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
import { GoogleSearchEngine } from './google-engine';
import { BingSearchEngine } from './bing-engine';
import { BaiduSearchEngine } from './baidu-engine';
import type { SearchEngine, SearchEngineAdapter } from '../types';

export * from './google-engine';
export * from './bing-engine';
export * from './baidu-engine';

/**
* Factory function to get the appropriate search engine adapter instance.
*
* @param engine - The search engine identifier ('google', 'bing', or 'baidu')
* @returns An instance of the requested search engine adapter
*/
export function getSearchEngine(engine: SearchEngine): SearchEngineAdapter {
switch (engine) {
case 'google':
return new GoogleSearchEngine();
case 'bing':
return new BingSearchEngine();
case 'baidu':
return new BaiduSearchEngine();
default:
return new GoogleSearchEngine();
}
}
export { getSearchEngine } from './get-search-engine';
5 changes: 5 additions & 0 deletions packages/agent-infra/search/browser-search/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export interface BrowserSearchOptions {
* Search engine to use (default: 'google')
*/
engine?: SearchEngine;
/**
* need visited urls
* @default false
*/
needVisitedUrls?: boolean;
}

export interface BrowserSearchConfig {
Expand Down
Loading