diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/agents/openclaw-supported-providers.ts b/packages/browseros-agent/apps/agent/entrypoints/app/agents/openclaw-supported-providers.ts index f174ac25e..ebe5b732c 100644 --- a/packages/browseros-agent/apps/agent/entrypoints/app/agents/openclaw-supported-providers.ts +++ b/packages/browseros-agent/apps/agent/entrypoints/app/agents/openclaw-supported-providers.ts @@ -6,6 +6,7 @@ const OPENCLAW_SUPPORTED_PROVIDER_TYPES: ProviderType[] = [ 'openai-compatible', 'anthropic', 'moonshot', + 'zai', ] export function isOpenClawSupportedProviderType( diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/NewProviderDialog.tsx b/packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/NewProviderDialog.tsx index c8f050cf7..25673590e 100644 --- a/packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/NewProviderDialog.tsx +++ b/packages/browseros-agent/apps/agent/entrypoints/app/ai-settings/NewProviderDialog.tsx @@ -87,6 +87,7 @@ const providerTypeEnum = z.enum([ 'chatgpt-pro', 'github-copilot', 'qwen-code', + 'zai', ]) /** diff --git a/packages/browseros-agent/apps/agent/lib/browseros/helpers.ts b/packages/browseros-agent/apps/agent/lib/browseros/helpers.ts index 88a0723db..6d24281a7 100644 --- a/packages/browseros-agent/apps/agent/lib/browseros/helpers.ts +++ b/packages/browseros-agent/apps/agent/lib/browseros/helpers.ts @@ -21,6 +21,11 @@ export class McpPortError extends Error { * @public */ export async function getAgentServerUrl(): Promise { + // In development, always use the configured server port directly + if (env.VITE_BROWSEROS_SERVER_PORT) { + return `http://127.0.0.1:${env.VITE_BROWSEROS_SERVER_PORT}` + } + const supportsUnifiedPort = await Capabilities.supports( Feature.UNIFIED_PORT_SUPPORT, ) diff --git a/packages/browseros-agent/apps/agent/lib/llm-providers/models-dev-data.json b/packages/browseros-agent/apps/agent/lib/llm-providers/models-dev-data.json index df8a5083a..3921bd3e5 100644 --- a/packages/browseros-agent/apps/agent/lib/llm-providers/models-dev-data.json +++ b/packages/browseros-agent/apps/agent/lib/llm-providers/models-dev-data.json @@ -5402,5 +5402,45 @@ "outputCost": 0 } ] + }, + "zai": { + "name": "z.ai", + "api": "https://api.z.ai/api/coding/paas/v4", + "doc": "https://docs.z.ai/guides/llm/glm-4.6", + "models": [ + { + "id": "glm-4.6", + "name": "GLM-4.6", + "contextWindow": 200000, + "maxOutput": 131072, + "supportsImages": true, + "supportsReasoning": true, + "supportsToolCall": true, + "inputCost": 0.6, + "outputCost": 2.2 + }, + { + "id": "glm-4.5", + "name": "GLM-4.5", + "contextWindow": 128000, + "maxOutput": 96000, + "supportsImages": true, + "supportsReasoning": true, + "supportsToolCall": true, + "inputCost": 0.6, + "outputCost": 2.2 + }, + { + "id": "glm-4.5-air", + "name": "GLM-4.5-Air", + "contextWindow": 128000, + "maxOutput": 96000, + "supportsImages": true, + "supportsReasoning": true, + "supportsToolCall": true, + "inputCost": 0.2, + "outputCost": 1.1 + } + ] } } diff --git a/packages/browseros-agent/apps/agent/lib/llm-providers/providerIcons.tsx b/packages/browseros-agent/apps/agent/lib/llm-providers/providerIcons.tsx index 8fd9f8892..ea502d18a 100644 --- a/packages/browseros-agent/apps/agent/lib/llm-providers/providerIcons.tsx +++ b/packages/browseros-agent/apps/agent/lib/llm-providers/providerIcons.tsx @@ -9,6 +9,7 @@ import { OpenAI, OpenRouter, Qwen, + ZAI, } from '@lobehub/icons' import { Bot, Github } from 'lucide-react' import type { FC, SVGProps } from 'react' @@ -36,6 +37,7 @@ const providerIconMap: Record = { 'chatgpt-pro': OpenAI, 'github-copilot': Github, 'qwen-code': Qwen, + zai: ZAI, } interface ProviderIconProps { diff --git a/packages/browseros-agent/apps/agent/lib/llm-providers/providerTemplates.ts b/packages/browseros-agent/apps/agent/lib/llm-providers/providerTemplates.ts index 4d8799b45..0f1584cca 100644 --- a/packages/browseros-agent/apps/agent/lib/llm-providers/providerTemplates.ts +++ b/packages/browseros-agent/apps/agent/lib/llm-providers/providerTemplates.ts @@ -140,6 +140,16 @@ export const providerTemplates: ProviderTemplate[] = [ setupGuideUrl: 'https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html', }), + { + id: 'zai', + name: 'z.ai', + defaultBaseUrl: 'https://api.z.ai/api/coding/paas/v4', + defaultModelId: 'glm-4.6', + supportsImages: true, + contextWindow: 200000, + apiKeyUrl: 'https://z.ai/manage-apikey/apikey-list', + setupGuideUrl: 'https://docs.z.ai/guides/llm/glm-4.6', + }, ] /** @@ -161,6 +171,7 @@ export const providerTypeOptions: { value: ProviderType; label: string }[] = [ { value: 'lmstudio', label: 'LM Studio' }, { value: 'bedrock', label: 'AWS Bedrock' }, { value: 'browseros', label: 'BrowserOS' }, + { value: 'zai', label: 'z.ai' }, ] /** @@ -192,6 +203,7 @@ export const DEFAULT_BASE_URLS: Record = { lmstudio: 'http://localhost:1234/v1', bedrock: '', browseros: '', + zai: 'https://api.z.ai/api/coding/paas/v4', } /** diff --git a/packages/browseros-agent/apps/agent/lib/llm-providers/types.ts b/packages/browseros-agent/apps/agent/lib/llm-providers/types.ts index df537f2fb..695f5fc75 100644 --- a/packages/browseros-agent/apps/agent/lib/llm-providers/types.ts +++ b/packages/browseros-agent/apps/agent/lib/llm-providers/types.ts @@ -17,6 +17,7 @@ export type ProviderType = | 'chatgpt-pro' | 'github-copilot' | 'qwen-code' + | 'zai' /** * LLM Provider configuration diff --git a/packages/browseros-agent/apps/agent/web-ext.config.ts b/packages/browseros-agent/apps/agent/web-ext.config.ts index 785654301..4e569d0cb 100644 --- a/packages/browseros-agent/apps/agent/web-ext.config.ts +++ b/packages/browseros-agent/apps/agent/web-ext.config.ts @@ -7,7 +7,6 @@ const chromiumArgs = [ '--use-mock-keychain', '--show-component-extension-options', '--disable-browseros-server', - '--disable-browseros-extensions', '--browseros-dock-icon=dev', ] diff --git a/packages/browseros-agent/apps/server/src/agent/provider-factory.ts b/packages/browseros-agent/apps/server/src/agent/provider-factory.ts index 263c09ed3..eeca91010 100644 --- a/packages/browseros-agent/apps/server/src/agent/provider-factory.ts +++ b/packages/browseros-agent/apps/server/src/agent/provider-factory.ts @@ -168,6 +168,17 @@ function createMoonshotFactory( }) } +function createZaiFactory( + config: ResolvedAgentConfig, +): (modelId: string) => unknown { + if (!config.apiKey) throw new Error('z.ai provider requires apiKey') + return createOpenAICompatible({ + name: 'zai', + baseURL: config.baseUrl || EXTERNAL_URLS.ZAI_API, + apiKey: config.apiKey, + }) +} + function createQwenCodeFactory( config: ResolvedAgentConfig, ): (modelId: string) => unknown { @@ -218,6 +229,7 @@ const PROVIDER_FACTORIES: Record = { [LLM_PROVIDERS.CHATGPT_PRO]: createChatGPTProFactory, [LLM_PROVIDERS.GITHUB_COPILOT]: createGitHubCopilotFactory, [LLM_PROVIDERS.QWEN_CODE]: createQwenCodeFactory, + [LLM_PROVIDERS.ZAI]: createZaiFactory, } export function createLanguageModel( diff --git a/packages/browseros-agent/apps/server/src/api/routes/provider.ts b/packages/browseros-agent/apps/server/src/api/routes/provider.ts index 323f69a46..cc03225d5 100644 --- a/packages/browseros-agent/apps/server/src/api/routes/provider.ts +++ b/packages/browseros-agent/apps/server/src/api/routes/provider.ts @@ -26,6 +26,13 @@ export function createProviderRoutes(deps: ProviderRouteDeps = {}) { model: config.model, }) + logger.info('Testing provider connection start', { + provider: config.provider, + model: config.model, + baseUrl: config.baseUrl, + hasApiKey: !!config.apiKey, + }) + const result = await testProviderConnection(config, deps.browserosId) logger.info('Provider test result', { diff --git a/packages/browseros-agent/apps/server/src/api/services/openclaw/openclaw-provider-map.ts b/packages/browseros-agent/apps/server/src/api/services/openclaw/openclaw-provider-map.ts index 3626dc9cf..7fb4a68ed 100644 --- a/packages/browseros-agent/apps/server/src/api/services/openclaw/openclaw-provider-map.ts +++ b/packages/browseros-agent/apps/server/src/api/services/openclaw/openclaw-provider-map.ts @@ -9,6 +9,7 @@ export const SUPPORTED_OPENCLAW_PROVIDERS = [ 'openai', 'anthropic', 'moonshot', + 'zai', ] as const export type SupportedOpenClawProvider = @@ -32,6 +33,7 @@ const PROVIDER_ENV_VARS: Record = { moonshot: 'MOONSHOT_API_KEY', openai: 'OPENAI_API_KEY', openrouter: 'OPENROUTER_API_KEY', + zai: 'ZAI_API_KEY', } export class UnsupportedOpenClawProviderError extends Error { diff --git a/packages/browseros-agent/apps/server/src/browser/browser.ts b/packages/browseros-agent/apps/server/src/browser/browser.ts index b49809fdf..b7966fe23 100644 --- a/packages/browseros-agent/apps/server/src/browser/browser.ts +++ b/packages/browseros-agent/apps/server/src/browser/browser.ts @@ -188,10 +188,43 @@ export class Browser { // --- Pages --- async listPages(): Promise { - const result = await this.cdp.Browser.getTabs({ includeHidden: true }) - const tabs = (result.tabs as TabInfo[]).filter( - (t) => !EXCLUDED_URL_PREFIXES.some((prefix) => t.url.startsWith(prefix)), - ) + let tabs: TabInfo[] + try { + const result = await this.cdp.Browser.getTabs({ includeHidden: true }) + tabs = (result.tabs as TabInfo[]).filter( + (t) => + !EXCLUDED_URL_PREFIXES.some((prefix) => t.url.startsWith(prefix)), + ) + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + // Fallback for CDP implementations without Browser.getTabs (e.g. some + // Chromium forks or older builds). + if (msg.includes("wasn't found") || msg.includes('not found')) { + const result = await this.cdp.Target.getTargets() + tabs = result.targetInfos + .filter((t) => t.type === 'page') + .map((t) => ({ + tabId: t.tabId ?? 0, + targetId: t.targetId, + url: t.url, + title: t.title, + isActive: false, + isLoading: false, + loadProgress: 1, + isPinned: false, + isHidden: false, + windowId: t.windowId, + index: 0, + groupId: undefined, + })) + .filter( + (t) => + !EXCLUDED_URL_PREFIXES.some((prefix) => t.url.startsWith(prefix)), + ) + } else { + throw err + } + } const seenTargetIds = new Set() diff --git a/packages/browseros-agent/apps/server/src/lib/clients/llm/provider.ts b/packages/browseros-agent/apps/server/src/lib/clients/llm/provider.ts index 8018ae69e..ed0e4df7e 100644 --- a/packages/browseros-agent/apps/server/src/lib/clients/llm/provider.ts +++ b/packages/browseros-agent/apps/server/src/lib/clients/llm/provider.ts @@ -152,6 +152,20 @@ function createMoonshotModel(config: ResolvedLLMConfig): LanguageModel { })(config.model) } +function createZaiModel(config: ResolvedLLMConfig): LanguageModel { + logger.info('createZaiModel', { + model: config.model, + baseUrl: config.baseUrl || EXTERNAL_URLS.ZAI_API, + hasApiKey: !!config.apiKey, + }) + if (!config.apiKey) throw new Error('z.ai provider requires apiKey') + return createOpenAICompatible({ + name: 'zai', + baseURL: config.baseUrl || EXTERNAL_URLS.ZAI_API, + apiKey: config.apiKey, + })(config.model) +} + function createQwenCodeModel(config: ResolvedLLMConfig): LanguageModel { if (!config.apiKey) throw new Error('Qwen Code requires OAuth authentication') return createOpenAICompatible({ @@ -196,6 +210,7 @@ const PROVIDER_FACTORIES: Record = { [LLM_PROVIDERS.CHATGPT_PRO]: createChatGPTProModel, [LLM_PROVIDERS.GITHUB_COPILOT]: createGitHubCopilotModel, [LLM_PROVIDERS.QWEN_CODE]: createQwenCodeModel, + [LLM_PROVIDERS.ZAI]: createZaiModel, } export function createLLMProvider(config: ResolvedLLMConfig): LanguageModel { diff --git a/packages/browseros-agent/apps/server/src/lib/clients/llm/test-provider.ts b/packages/browseros-agent/apps/server/src/lib/clients/llm/test-provider.ts index 04d017a17..c8c932ae5 100644 --- a/packages/browseros-agent/apps/server/src/lib/clients/llm/test-provider.ts +++ b/packages/browseros-agent/apps/server/src/lib/clients/llm/test-provider.ts @@ -6,7 +6,8 @@ import { TIMEOUTS } from '@browseros/shared/constants/timeouts' import type { LLMConfig } from '@browseros/shared/schemas/llm' -import { streamText } from 'ai' +import { generateText } from 'ai' +import { logger } from '../../logger' import { resolveLLMConfig } from './config' import { createLLMProvider } from './provider' @@ -30,18 +31,34 @@ export async function testProviderConnection( const startTime = performance.now() try { + logger.info('testProviderConnection start', { + provider: config.provider, + model: config.model, + baseUrl: config.baseUrl, + hasApiKey: !!config.apiKey, + }) const resolvedConfig = await resolveLLMConfig(config, browserosId) + logger.info('testProviderConnection resolved', { + provider: resolvedConfig.provider, + model: resolvedConfig.model, + baseUrl: resolvedConfig.baseUrl, + }) const model = createLLMProvider(resolvedConfig) + logger.info('testProviderConnection model created', { + provider: resolvedConfig.provider, + }) - // streamText works for all providers including Codex (which requires streaming) - const stream = streamText({ + // Use generateText for testing to get clear API errors (streamText wraps + // APICallError in NoOutputGeneratedError and loses responseBody details). + const result = await generateText({ model, messages: [{ role: 'user', content: TEST_PROMPT }], + maxRetries: 0, abortSignal: AbortSignal.timeout(TIMEOUTS.TEST_PROVIDER), }) - const text = await stream.text const responseTime = Math.round(performance.now() - startTime) + const text = result.text if (text) { const preview = text.length > 100 ? `${text.slice(0, 100)}...` : text return { @@ -58,7 +75,13 @@ export async function testProviderConnection( } } catch (error) { const responseTime = Math.round(performance.now() - startTime) - const errorMessage = error instanceof Error ? error.message : String(error) + logger.info('testProviderConnection caught error', { + provider: config.provider, + errorType: typeof error, + errorName: error instanceof Error ? error.name : undefined, + errorMessage: error instanceof Error ? error.message : String(error), + }) + const errorMessage = extractProviderErrorMessage(error, config.provider) return { success: false, @@ -67,3 +90,37 @@ export async function testProviderConnection( } } } + +function extractProviderErrorMessage( + error: unknown, + _provider: string, +): string { + // Check for API call error with response body (generateText preserves + // APICallError directly, so responseBody is available on the error object) + if ( + error != null && + typeof error === 'object' && + 'responseBody' in error && + typeof (error as { responseBody?: string }).responseBody === 'string' + ) { + try { + const parsed = JSON.parse( + (error as { responseBody: string }).responseBody, + ) + const msg = + parsed?.error?.message || + parsed?.message || + parsed?.error?.code || + (error instanceof Error ? error.message : String(error)) + return msg + } catch { + // Not valid JSON, fall through + } + } + + if (error instanceof Error) { + return error.message + } + + return String(error) +} diff --git a/packages/browseros-agent/packages/shared/src/constants/urls.ts b/packages/browseros-agent/packages/shared/src/constants/urls.ts index e762e1949..dc6f45c31 100644 --- a/packages/browseros-agent/packages/shared/src/constants/urls.ts +++ b/packages/browseros-agent/packages/shared/src/constants/urls.ts @@ -19,4 +19,5 @@ export const EXTERNAL_URLS = { QWEN_DEVICE_CODE: 'https://chat.qwen.ai/api/v1/oauth2/device/code', QWEN_OAUTH_TOKEN: 'https://chat.qwen.ai/api/v1/oauth2/token', QWEN_CODE_API: 'https://portal.qwen.ai/v1', + ZAI_API: 'https://api.z.ai/api/coding/paas/v4', } as const diff --git a/packages/browseros-agent/packages/shared/src/schemas/llm.ts b/packages/browseros-agent/packages/shared/src/schemas/llm.ts index 45e7cc029..c06820d0f 100644 --- a/packages/browseros-agent/packages/shared/src/schemas/llm.ts +++ b/packages/browseros-agent/packages/shared/src/schemas/llm.ts @@ -27,6 +27,7 @@ export const LLM_PROVIDERS = { CHATGPT_PRO: 'chatgpt-pro', GITHUB_COPILOT: 'github-copilot', QWEN_CODE: 'qwen-code', + ZAI: 'zai', } as const /** @@ -48,6 +49,7 @@ export const LLMProviderSchema: z.ZodEnum< 'chatgpt-pro', 'github-copilot', 'qwen-code', + 'zai', ] > = z.enum([ LLM_PROVIDERS.ANTHROPIC, @@ -64,6 +66,7 @@ export const LLMProviderSchema: z.ZodEnum< LLM_PROVIDERS.CHATGPT_PRO, LLM_PROVIDERS.GITHUB_COPILOT, LLM_PROVIDERS.QWEN_CODE, + LLM_PROVIDERS.ZAI, ]) export type LLMProvider = z.infer diff --git a/packages/browseros-agent/scripts/generate-models.ts b/packages/browseros-agent/scripts/generate-models.ts index 2644a45fd..16e08ba4e 100644 --- a/packages/browseros-agent/scripts/generate-models.ts +++ b/packages/browseros-agent/scripts/generate-models.ts @@ -70,6 +70,7 @@ const PROVIDER_MAP: Record = { lmstudio: 'lmstudio', moonshotai: 'moonshot', 'github-copilot': 'github-copilot', + zai: 'zai', } function transformModel(model: ModelsDevModel): OutputModel | null {