Skip to content
Open
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
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, clearCustomSystemPromptCache } 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
105 changes: 105 additions & 0 deletions src/lib/llm/settings/customSystemPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { BrowserOSProvider, BrowserOSProvidersConfig } from './browserOSTypes'
import { LLMSettingsReader } from './LLMSettingsReader'

let cachedBrowserOSProvider: BrowserOSProvider | null = null
let cachedDefaultProvider: 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
}

/**
* Extract the BrowserOS provider specifically (where custom prompts are stored)
*/
const extractBrowserOSProvider = (config: BrowserOSProvidersConfig | null): BrowserOSProvider | null => {
if (!config) return null
// Look for the BrowserOS provider specifically (it has type 'browseros')
const browserOSProvider = config.providers.find(p => p.type === 'browseros')
return browserOSProvider ? { ...browserOSProvider } : 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
}
}

/**
* Read the BrowserOS provider specifically (for custom system prompts)
*/
const readBrowserOSProvider = async (): Promise<BrowserOSProvider | null> => {
try {
const config = await LLMSettingsReader.readAllProviders()
return extractBrowserOSProvider(config)
} catch (error) {
console.warn('[customSystemPrompt] Failed to read BrowserOS provider:', error)
return null
}
}

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

export const setCachedDefaultProvider = (provider: BrowserOSProvider | null): void => {
cachedDefaultProvider = cloneProvider(provider)
// If this is a BrowserOS provider, also cache it as the BrowserOS provider
if (provider && provider.type === 'browseros') {
cachedBrowserOSProvider = cloneProvider(provider)
}
}

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

/**
* Get the cached BrowserOS provider (where custom prompts are stored)
*/
const getCachedBrowserOSProvider = async (): Promise<BrowserOSProvider | null> => {
if (cachedBrowserOSProvider) {
return cachedBrowserOSProvider
}
const provider = await readBrowserOSProvider()
cachedBrowserOSProvider = cloneProvider(provider)
return cachedBrowserOSProvider
}

export const applyCustomSystemPrompt = async (basePrompt: string): Promise<string> => {
try {
// Always read custom prompt from the BrowserOS provider, regardless of which provider is currently active
// Custom prompts are stored in the BrowserOS provider configuration even when using other providers
const browserOSProvider = await getCachedBrowserOSProvider()
if (!browserOSProvider) {
// No BrowserOS provider found, just return the base prompt
return basePrompt
}

const customPrompt = (browserOSProvider.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