Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
5 changes: 4 additions & 1 deletion src/background/handlers/ProvidersHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PortMessage } from '@/lib/runtime/PortMessaging'
import { LLMSettingsReader } from '@/lib/llm/settings/LLMSettingsReader'
import { langChainProvider } from '@/lib/llm/LangChainProvider'
import { BrowserOSProvidersConfigSchema, BROWSEROS_PREFERENCE_KEYS } from '@/lib/llm/settings/browserOSTypes'
import { clearCustomSystemPromptCache } from '@/lib/llm/settings/customSystemPrompt'
import { Logging } from '@/lib/utils/Logging'
import { PortManager } from '@/background/router/PortManager'

Expand Down Expand Up @@ -69,7 +70,8 @@ export class ProvidersHandler {
if (payload.providers) {
payload.providers = payload.providers.map((p: any) => ({
...p,
isDefault: p.isDefault !== undefined ? p.isDefault : (p.id === 'browseros')
isDefault: p.isDefault !== undefined ? p.isDefault : (p.id === 'browseros'),
systemPrompt: typeof p.systemPrompt === 'string' ? p.systemPrompt : ''
}))
}
const config = BrowserOSProvidersConfigSchema.parse(payload)
Expand Down Expand Up @@ -116,6 +118,7 @@ export class ProvidersHandler {
const success = browserOSSuccess || storageSuccess
if (success) {
try { langChainProvider.clearCache() } catch (_) {}
clearCustomSystemPromptCache()
this.lastProvidersConfigJson = configStr

this.broadcastProvidersConfig(config)
Expand Down
9 changes: 5 additions & 4 deletions src/lib/agent/BrowserAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Runnable } from "@langchain/core/runnables";
import { BaseLanguageModelInput } from "@langchain/core/language_models/base";
import { z } from "zod";
import { getLLM } from "@/lib/llm/LangChainProvider";
import { applyCustomSystemPrompt } from "@/lib/llm/settings/customSystemPrompt";
import BrowserPage from "@/lib/browser/BrowserPage";
import { PubSub } from "@/lib/pubsub";
import { PubSubChannel } from "@/lib/pubsub/PubSubChannel";
Expand Down Expand Up @@ -733,7 +734,7 @@ export class BrowserAgent {

// Get numbeer of tokens in full history
// System prompt for planner
const systemPrompt = generatePlannerPrompt(this.toolDescriptions || "");
const systemPrompt = await applyCustomSystemPrompt(generatePlannerPrompt(this.toolDescriptions || ""));

const systemPromptTokens = TokenCounter.countMessage(new SystemMessage(systemPrompt));
const fullHistoryTokens = TokenCounter.countMessage(new HumanMessage(fullHistory));
Expand Down Expand Up @@ -842,7 +843,7 @@ ${fullHistory}
): Promise<ExecutorResult> {
// Use the current iteration message manager from execution context
const executorMM = new MessageManager();
const systemPrompt = generateExecutorPrompt(this._buildExecutionContext());
const systemPrompt = await applyCustomSystemPrompt(generateExecutorPrompt(this._buildExecutionContext()));
const systemPromptTokens = TokenCounter.countMessage(new SystemMessage(systemPrompt));
executorMM.addSystem(systemPrompt);
const currentIterationToolMessages: string[] = [];
Expand Down Expand Up @@ -1359,7 +1360,7 @@ ${fullHistory}
// Get accumulated execution history from all iterations
let fullHistory = this._buildPlannerExecutionHistory();

const systemPrompt = generatePredefinedPlannerPrompt(this.toolDescriptions || "");
const systemPrompt = await applyCustomSystemPrompt(generatePredefinedPlannerPrompt(this.toolDescriptions || ""));
const systemPromptTokens = TokenCounter.countMessage(new SystemMessage(systemPrompt));
const fullHistoryTokens = TokenCounter.countMessage(new HumanMessage(fullHistory));
Logging.log("BrowserAgent", `Full execution history tokens: ${fullHistoryTokens}`, "info");
Expand Down Expand Up @@ -1538,7 +1539,7 @@ ${fullHistory}
intelligence: 'high'
});
const structuredLLM = llm.withStructuredOutput(ExecutionHistorySummarySchema);
const systemPrompt = generateExecutionHistorySummaryPrompt();
const systemPrompt = await applyCustomSystemPrompt(generateExecutionHistorySummaryPrompt());
const userPrompt = `Execution History: ${historyWithoutSections}`;
const messages = [
new SystemMessage(systemPrompt),
Expand Down
9 changes: 5 additions & 4 deletions src/lib/agent/LocalAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Runnable } from "@langchain/core/runnables";
import { BaseLanguageModelInput } from "@langchain/core/language_models/base";
import { z } from "zod";
import { getLLM } from "@/lib/llm/LangChainProvider";
import { applyCustomSystemPrompt } from "@/lib/llm/settings/customSystemPrompt";
import BrowserPage from "@/lib/browser/BrowserPage";
import { PubSub } from "@/lib/pubsub";
import { PubSubChannel } from "@/lib/pubsub/PubSubChannel";
Expand Down Expand Up @@ -691,7 +692,7 @@ export class LocalAgent {

// Get numbeer of tokens in full history
// System prompt for planner
const systemPrompt = generatePlannerPrompt(this.toolDescriptions || "");
const systemPrompt = await applyCustomSystemPrompt(generatePlannerPrompt(this.toolDescriptions || ""));

const systemPromptTokens = TokenCounter.countMessage(new SystemMessage(systemPrompt));
const fullHistoryTokens = TokenCounter.countMessage(new HumanMessage(fullHistory));
Expand Down Expand Up @@ -810,7 +811,7 @@ Continue upon the previous steps what has been done so far and suggest next step
): Promise<ExecutorResult> {
// Use the current iteration message manager from execution context
const executorMM = new MessageManager();
const systemPrompt = generateExecutorPrompt(this._buildExecutionContext());
const systemPrompt = await applyCustomSystemPrompt(generateExecutorPrompt(this._buildExecutionContext()));
const systemPromptTokens = TokenCounter.countMessage(new SystemMessage(systemPrompt));
executorMM.addSystem(systemPrompt);
const currentIterationToolMessages: string[] = [];
Expand Down Expand Up @@ -1327,7 +1328,7 @@ Continue upon the previous steps what has been done so far and suggest next step
// Get accumulated execution history from all iterations
let fullHistory = this._buildPlannerExecutionHistory();

const systemPrompt = generatePredefinedPlannerPrompt(this.toolDescriptions || "");
const systemPrompt = await applyCustomSystemPrompt(generatePredefinedPlannerPrompt(this.toolDescriptions || ""));
const systemPromptTokens = TokenCounter.countMessage(new SystemMessage(systemPrompt));
const fullHistoryTokens = TokenCounter.countMessage(new HumanMessage(fullHistory));
Logging.log("LocalAgent", `Full execution history tokens: ${fullHistoryTokens}`, "info");
Expand Down Expand Up @@ -1509,7 +1510,7 @@ Continue upon your previous steps what has been done so far and suggest next ste
temperature: 0.2,
maxTokens: 4096,
});
const systemPrompt = generateExecutionHistorySummaryPrompt();
const systemPrompt = await applyCustomSystemPrompt(generateExecutionHistorySummaryPrompt());
const userPrompt = `Execution History: ${historyWithoutSections}`;
const messages = [
new SystemMessage(systemPrompt),
Expand Down
20 changes: 18 additions & 2 deletions src/lib/llm/settings/LLMSettingsReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
createDefaultBrowserOSProvider,
createDefaultProvidersConfig
} from './browserOSTypes'
import { setCachedDefaultProvider } from './customSystemPrompt'

// Type definitions for chrome.browserOS API (callback-based)
declare global {
Expand Down Expand Up @@ -57,6 +58,7 @@ export class LLMSettingsReader {
...provider,
isDefault: provider.id === defaultProviderId,
isBuiltIn: provider.isBuiltIn ?? false,
systemPrompt: typeof provider.systemPrompt === 'string' ? provider.systemPrompt : '',
createdAt: provider.createdAt ?? new Date().toISOString(),
updatedAt: provider.updatedAt ?? new Date().toISOString()
}))
Expand Down Expand Up @@ -95,11 +97,14 @@ export class LLMSettingsReader {
const provider = config.providers.find(p => p.id === config.defaultProviderId)
|| config.providers[0]
|| this.getDefaultBrowserOSProvider()
setCachedDefaultProvider(provider)
return provider
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
Logging.log('LLMSettingsReader', `Failed to read settings: ${errorMessage}`, 'error')
return this.getDefaultBrowserOSProvider()
const fallback = this.getDefaultBrowserOSProvider()
setCachedDefaultProvider(fallback)
return fallback
}
}

Expand All @@ -110,14 +115,20 @@ export class LLMSettingsReader {
try {
const config = await this.readProvidersConfig()
if (config) {
const defaultProvider = config.providers.find(p => p.id === config.defaultProviderId)
|| config.providers[0]
|| null
setCachedDefaultProvider(defaultProvider)
return config
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
Logging.log('LLMSettingsReader', `Failed to read providers: ${errorMessage}`, 'error')
}

return createDefaultProvidersConfig()
const fallback = createDefaultProvidersConfig()
setCachedDefaultProvider(fallback.providers[0] || null)
return fallback
}

/**
Expand Down Expand Up @@ -300,6 +311,11 @@ export class LLMSettingsReader {
const success = browserOSSuccess || storageSuccess
if (!success) {
Logging.log('LLMSettingsReader', 'Failed to save to any storage mechanism', 'error')
} else {
const defaultProvider = normalized.providers.find(p => p.id === normalized.defaultProviderId)
|| normalized.providers[0]
|| null
setCachedDefaultProvider(defaultProvider)
}
return success
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/llm/settings/browserOSTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const BrowserOSProviderSchema = z.object({
baseUrl: z.string().optional(), // API base URL
apiKey: z.string().optional(), // API key for authentication
modelId: z.string().optional(), // Model identifier
systemPrompt: z.string().optional(), // Custom system prompt override
capabilities: ProviderCapabilitiesSchema.optional(), // Provider capabilities
modelConfig: ModelConfigSchema.optional(), // Model configuration
createdAt: z.string(), // ISO timestamp of creation
Expand Down Expand Up @@ -94,6 +95,7 @@ export function createDefaultBrowserOSProvider(): BrowserOSProvider {
type: 'browseros',
isDefault: true,
isBuiltIn: true,
systemPrompt: '',
createdAt: timestamp,
updatedAt: timestamp
}
Expand Down
59 changes: 59 additions & 0 deletions src/lib/llm/settings/customSystemPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { BrowserOSProvider, BrowserOSProvidersConfig } from './browserOSTypes'
import { LLMSettingsReader } from './LLMSettingsReader'

let cachedProvider: BrowserOSProvider | null = null

const cloneProvider = (provider: BrowserOSProvider | null): BrowserOSProvider | null => {
if (!provider) return null
return { ...provider }
}

const extractDefaultProvider = (config: BrowserOSProvidersConfig | null): BrowserOSProvider | null => {
if (!config) return null
const provider = config.providers.find(p => p.id === config.defaultProviderId) || config.providers[0] || null
return provider ? { ...provider } : null
}

const readDefaultProvider = async (): Promise<BrowserOSProvider | null> => {
try {
const config = await LLMSettingsReader.readAllProviders()
return extractDefaultProvider(config)
} catch (error) {
console.warn('[customSystemPrompt] Failed to read providers config:', error)
return null
}
}

export const clearCustomSystemPromptCache = (): void => {
cachedProvider = null
}

export const setCachedDefaultProvider = (provider: BrowserOSProvider | null): void => {
cachedProvider = cloneProvider(provider)
}

export const getCachedDefaultProvider = async (): Promise<BrowserOSProvider | null> => {
if (cachedProvider) {
return cachedProvider
}
const provider = await readDefaultProvider()
cachedProvider = cloneProvider(provider)
return cachedProvider
}

export const applyCustomSystemPrompt = async (basePrompt: string): Promise<string> => {
try {
const provider = await getCachedDefaultProvider()
if (!provider || provider.type !== 'browseros') {
return basePrompt
}
const customPrompt = (provider.systemPrompt ?? '').trim()
if (!customPrompt) {
return basePrompt
}
return `${customPrompt}\n\n${basePrompt}`
} catch (error) {
console.warn('[customSystemPrompt] Failed to apply custom prompt:', error)
return basePrompt
}
}
Loading