feat: separated efficient agent mode (lite mode) for small language models
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"general": {
|
"general": {
|
||||||
"language": "auto",
|
"language": "auto",
|
||||||
|
"agent_mode": "regular",
|
||||||
"llm_provider": "local_browser",
|
"llm_provider": "local_browser",
|
||||||
"persist_api_keys_non_localhost": false,
|
"persist_api_keys_non_localhost": false,
|
||||||
"auto_compact_threshold_percent": 90,
|
"auto_compact_threshold_percent": 90,
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ import { AgentCore } from './AgentCore';
|
|||||||
import type { LLMProvider } from '../llm/LLMProvider';
|
import type { LLMProvider } from '../llm/LLMProvider';
|
||||||
import type { Message, ToolCall } from './AgentState';
|
import type { Message, ToolCall } from './AgentState';
|
||||||
import type { StreamChunk } from '../llm/StreamingTypes';
|
import type { StreamChunk } from '../llm/StreamingTypes';
|
||||||
|
import type { OpenAIToolDefinition } from '../tools/BaseTool';
|
||||||
|
import { ReadMusicTool } from '../tools/ReadMusicTool';
|
||||||
|
|
||||||
|
const configState = new Map<string, unknown>([
|
||||||
|
['general.agent_mode', 'regular'],
|
||||||
|
['general.llm_provider', 'openai'],
|
||||||
|
['general.auto_compact_threshold_percent', 90],
|
||||||
|
]);
|
||||||
|
|
||||||
vi.mock('../../stores/projectStore', () => ({
|
vi.mock('../../stores/projectStore', () => ({
|
||||||
useProjectStore: {
|
useProjectStore: {
|
||||||
@@ -14,17 +22,35 @@ vi.mock('../../stores/projectStore', () => ({
|
|||||||
|
|
||||||
vi.mock('./SystemPrompts', () => ({
|
vi.mock('./SystemPrompts', () => ({
|
||||||
SystemPrompts: {
|
SystemPrompts: {
|
||||||
getSystemPromptWithContext: vi.fn(async () => 'system prompt'),
|
getSystemPromptWithContext: vi.fn(async (templatePath?: string) => `system prompt:${templatePath ?? 'default'}`),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../core/config/ConfigManager', () => ({
|
||||||
|
ConfigManager: {
|
||||||
|
instance: () => ({
|
||||||
|
getIsInitialized: () => true,
|
||||||
|
initialize: vi.fn(async () => undefined),
|
||||||
|
get: (key: string) => configState.get(key),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
class ScriptedProvider implements LLMProvider {
|
class ScriptedProvider implements LLMProvider {
|
||||||
public calls: Message[][] = [];
|
public calls: Message[][] = [];
|
||||||
|
public systemPrompts: Array<string | undefined> = [];
|
||||||
|
public tools: OpenAIToolDefinition[][] = [];
|
||||||
|
|
||||||
constructor(private readonly scripts: StreamChunk[][]) {}
|
constructor(private readonly scripts: StreamChunk[][]) {}
|
||||||
|
|
||||||
async *generateStream(messages: Message[]): AsyncIterableIterator<StreamChunk> {
|
async *generateStream(
|
||||||
|
messages: Message[],
|
||||||
|
systemPrompt?: string,
|
||||||
|
tools?: OpenAIToolDefinition[],
|
||||||
|
): AsyncIterableIterator<StreamChunk> {
|
||||||
this.calls.push(messages.map(message => ({ ...message })));
|
this.calls.push(messages.map(message => ({ ...message })));
|
||||||
|
this.systemPrompts.push(systemPrompt);
|
||||||
|
this.tools.push(tools ?? []);
|
||||||
const script = this.scripts.shift() ?? [{ type: 'done', content: '', finishReason: 'stop' }];
|
const script = this.scripts.shift() ?? [{ type: 'done', content: '', finishReason: 'stop' }];
|
||||||
for (const chunk of script) {
|
for (const chunk of script) {
|
||||||
yield chunk;
|
yield chunk;
|
||||||
@@ -53,6 +79,9 @@ async function collectChunks(input: string): Promise<StreamChunk[]> {
|
|||||||
|
|
||||||
describe('AgentCore todo integration', () => {
|
describe('AgentCore todo integration', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configState.set('general.agent_mode', 'regular');
|
||||||
|
configState.set('general.llm_provider', 'openai');
|
||||||
|
configState.set('general.auto_compact_threshold_percent', 90);
|
||||||
AgentCore.instance().clearConversation();
|
AgentCore.instance().clearConversation();
|
||||||
AgentCore.instance().setLLMProvider(new ScriptedProvider([
|
AgentCore.instance().setLLMProvider(new ScriptedProvider([
|
||||||
[{ type: 'done', content: '', finishReason: 'stop' }],
|
[{ type: 'done', content: '', finishReason: 'stop' }],
|
||||||
@@ -211,4 +240,81 @@ describe('AgentCore todo integration', () => {
|
|||||||
{ id: 'todo-1', text: 'Continue work', status: 'in_progress', updatedAt: 3 },
|
{ id: 'todo-1', text: 'Continue work', status: 'in_progress', updatedAt: 3 },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses the regular system prompt in regular mode', async () => {
|
||||||
|
configState.set('general.agent_mode', 'regular');
|
||||||
|
const provider = new ScriptedProvider([
|
||||||
|
[{ type: 'done', content: '', finishReason: 'stop' }],
|
||||||
|
]);
|
||||||
|
AgentCore.instance().setLLMProvider(provider);
|
||||||
|
|
||||||
|
await collectChunks('Read the current region.');
|
||||||
|
|
||||||
|
expect(provider.systemPrompts[0]).toBe('system prompt:prompts/system.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the compact system prompt in efficient mode', async () => {
|
||||||
|
configState.set('general.agent_mode', 'efficient');
|
||||||
|
const provider = new ScriptedProvider([
|
||||||
|
[{ type: 'done', content: '', finishReason: 'stop' }],
|
||||||
|
]);
|
||||||
|
AgentCore.instance().setLLMProvider(provider);
|
||||||
|
|
||||||
|
await collectChunks('Read the current region.');
|
||||||
|
|
||||||
|
expect(provider.systemPrompts[0]).toBe('system prompt:prompts/system_compact.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forces efficient mode when the local browser provider is selected', async () => {
|
||||||
|
configState.set('general.agent_mode', 'regular');
|
||||||
|
configState.set('general.llm_provider', 'local_browser');
|
||||||
|
const provider = new ScriptedProvider([
|
||||||
|
[{ type: 'done', content: '', finishReason: 'stop' }],
|
||||||
|
]);
|
||||||
|
AgentCore.instance().setLLMProvider(provider);
|
||||||
|
|
||||||
|
await collectChunks('Read the current region.');
|
||||||
|
|
||||||
|
expect(provider.systemPrompts[0]).toBe('system prompt:prompts/system_compact.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters tool definitions by the active agent mode', async () => {
|
||||||
|
configState.set('general.agent_mode', 'efficient');
|
||||||
|
const provider = new ScriptedProvider([
|
||||||
|
[{ type: 'done', content: '', finishReason: 'stop' }],
|
||||||
|
]);
|
||||||
|
AgentCore.instance().setLLMProvider(provider);
|
||||||
|
|
||||||
|
const spy = vi.spyOn(ReadMusicTool.prototype, 'isAvailableInEfficientMode')
|
||||||
|
.mockReturnValue(false);
|
||||||
|
|
||||||
|
await collectChunks('Read the current region.');
|
||||||
|
|
||||||
|
expect(provider.tools[0].map(tool => tool.function.name)).not.toContain('read_music');
|
||||||
|
spy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects tool calls for tools unavailable in the active mode', async () => {
|
||||||
|
configState.set('general.agent_mode', 'efficient');
|
||||||
|
const availabilitySpy = vi.spyOn(ReadMusicTool.prototype, 'isAvailableInEfficientMode')
|
||||||
|
.mockReturnValue(false);
|
||||||
|
const provider = new ScriptedProvider([
|
||||||
|
[
|
||||||
|
{ type: 'tool_call', content: '', toolCall: makeToolCall('read_music', {}, 'tool_1') },
|
||||||
|
{ type: 'done', content: '', finishReason: 'tool_calls' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ type: 'text', content: 'Done' },
|
||||||
|
{ type: 'done', content: '', finishReason: 'stop' },
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
AgentCore.instance().setLLMProvider(provider);
|
||||||
|
|
||||||
|
const chunks = await collectChunks('Read the current region.');
|
||||||
|
const toolResultChunk = chunks.find(chunk => chunk.type === 'tool_result' && chunk.toolResult?.name === 'read_music');
|
||||||
|
|
||||||
|
expect(toolResultChunk?.toolResult?.success).toBe(false);
|
||||||
|
expect(toolResultChunk?.toolResult?.result).toBe("Tool 'read_music' is not available in Efficient Mode.");
|
||||||
|
availabilitySpy.mockRestore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+43
-11
@@ -5,11 +5,13 @@ import { AVAILABLE_TOOLS, createToolInstance } from '../tools';
|
|||||||
import { useProjectStore } from '../../stores/projectStore';
|
import { useProjectStore } from '../../stores/projectStore';
|
||||||
import type { StreamChunk, ToolApprovalDecision } from '../llm/StreamingTypes';
|
import type { StreamChunk, ToolApprovalDecision } from '../llm/StreamingTypes';
|
||||||
import type { ToolCall } from './AgentState';
|
import type { ToolCall } from './AgentState';
|
||||||
import type { OpenAIToolDefinition } from '../tools/BaseTool';
|
import type { BaseTool, OpenAIToolDefinition } from '../tools/BaseTool';
|
||||||
import { ConversationCompactor, type CompactProgress } from '../compact/ConversationCompactor';
|
import { ConversationCompactor, type CompactProgress } from '../compact/ConversationCompactor';
|
||||||
import { ConfigManager } from '../../core/config/ConfigManager';
|
import { ConfigManager } from '../../core/config/ConfigManager';
|
||||||
import { buildTodoContext } from './todo';
|
import { buildTodoContext } from './todo';
|
||||||
import type { SavedConversationDocument } from '../../types/conversationTypes';
|
import type { SavedConversationDocument } from '../../types/conversationTypes';
|
||||||
|
import type { AgentMode } from '../../util/agentMode';
|
||||||
|
import { getEffectiveAgentMode, getSystemPromptPathForAgentMode } from '../../util/agentMode';
|
||||||
|
|
||||||
export interface CompactConversationOptions {
|
export interface CompactConversationOptions {
|
||||||
trigger: 'manual' | 'auto';
|
trigger: 'manual' | 'auto';
|
||||||
@@ -69,26 +71,54 @@ export class AgentCore {
|
|||||||
/**
|
/**
|
||||||
* Get OpenAI tool definitions for all available tools
|
* Get OpenAI tool definitions for all available tools
|
||||||
*/
|
*/
|
||||||
private getToolDefinitions(): OpenAIToolDefinition[] {
|
private getToolDefinitions(agentMode: AgentMode): OpenAIToolDefinition[] {
|
||||||
return Object.values(AVAILABLE_TOOLS).map(ToolClass => {
|
return Object.values(AVAILABLE_TOOLS).map(ToolClass => {
|
||||||
const tool = new ToolClass();
|
const tool = new ToolClass();
|
||||||
return tool.getDefinition();
|
return this.isToolAvailableInMode(tool, agentMode) ? tool.getDefinition() : null;
|
||||||
});
|
}).filter((tool): tool is OpenAIToolDefinition => tool !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSystemPrompt(templatePath?: string): Promise<string> {
|
private async getSystemPrompt(templatePath?: string): Promise<string> {
|
||||||
return SystemPrompts.getSystemPromptWithContext(templatePath);
|
return SystemPrompts.getSystemPromptWithContext(templatePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getEffectiveAgentMode(): AgentMode {
|
||||||
|
return getEffectiveAgentMode(ConfigManager.instance());
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSystemPromptTemplatePath(agentMode: AgentMode): string {
|
||||||
|
return getSystemPromptPathForAgentMode(agentMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private isToolAvailableInMode(toolInstance: BaseTool | null, agentMode: AgentMode): boolean {
|
||||||
|
if (!toolInstance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return agentMode === 'efficient'
|
||||||
|
? toolInstance.isAvailableInEfficientMode()
|
||||||
|
: toolInstance.isAvailableInRegularMode();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a single tool call and return the result
|
* Execute a single tool call and return the result
|
||||||
*/
|
*/
|
||||||
private async executeTool(toolCall: ToolCall): Promise<{ success: boolean; result: string }> {
|
private async executeTool(
|
||||||
|
toolCall: ToolCall,
|
||||||
|
agentMode: AgentMode,
|
||||||
|
): Promise<{ success: boolean; result: string }> {
|
||||||
const toolInstance = createToolInstance(toolCall.function.name);
|
const toolInstance = createToolInstance(toolCall.function.name);
|
||||||
if (!toolInstance) {
|
if (!toolInstance) {
|
||||||
return { success: false, result: `Unknown tool: ${toolCall.function.name}` };
|
return { success: false, result: `Unknown tool: ${toolCall.function.name}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.isToolAvailableInMode(toolInstance, agentMode)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
result: `Tool '${toolCall.function.name}' is not available in ${agentMode === 'efficient' ? 'Efficient Mode' : 'Regular Mode'}.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const params = JSON.parse(toolCall.function.arguments);
|
const params = JSON.parse(toolCall.function.arguments);
|
||||||
const result = await toolInstance.execute(params);
|
const result = await toolInstance.execute(params);
|
||||||
@@ -121,10 +151,11 @@ export class AgentCore {
|
|||||||
this.currentUserMessageId = this.agentState.addMessage('user', userInput);
|
this.currentUserMessageId = this.agentState.addMessage('user', userInput);
|
||||||
this.currentTurnLikelyMultiStep = this.isLikelyMultiStepTask(userInput);
|
this.currentTurnLikelyMultiStep = this.isLikelyMultiStepTask(userInput);
|
||||||
|
|
||||||
|
const agentMode = this.getEffectiveAgentMode();
|
||||||
const systemPrompt = await SystemPrompts.getSystemPromptWithContext(
|
const systemPrompt = await SystemPrompts.getSystemPromptWithContext(
|
||||||
this.llmProvider.getPreferredSystemPromptPath?.(),
|
this.getSystemPromptTemplatePath(agentMode),
|
||||||
);
|
);
|
||||||
const tools = this.getToolDefinitions();
|
const tools = this.getToolDefinitions(agentMode);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Agentic loop: stream → check for tool calls → execute → repeat
|
// Agentic loop: stream → check for tool calls → execute → repeat
|
||||||
@@ -179,7 +210,7 @@ export class AgentCore {
|
|||||||
|
|
||||||
const result = denied
|
const result = denied
|
||||||
? { success: false, result: 'Execution was denied by the user.' }
|
? { success: false, result: 'Execution was denied by the user.' }
|
||||||
: await this.executeTool(toolCall);
|
: await this.executeTool(toolCall, agentMode);
|
||||||
|
|
||||||
// Add tool result message to conversation history
|
// Add tool result message to conversation history
|
||||||
this.agentState.addMessage('tool', JSON.stringify(result), {
|
this.agentState.addMessage('tool', JSON.stringify(result), {
|
||||||
@@ -295,9 +326,10 @@ export class AgentCore {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tools = this.getToolDefinitions();
|
const agentMode = this.getEffectiveAgentMode();
|
||||||
|
const tools = this.getToolDefinitions(agentMode);
|
||||||
const systemPrompt = await this.getSystemPrompt(
|
const systemPrompt = await this.getSystemPrompt(
|
||||||
this.llmProvider.getPreferredSystemPromptPath?.(),
|
this.getSystemPromptTemplatePath(agentMode),
|
||||||
);
|
);
|
||||||
const thresholdPercent = await this.getAutoCompactThresholdPercent();
|
const thresholdPercent = await this.getAutoCompactThresholdPercent();
|
||||||
const reservedOutputTokens = this.llmProvider.getReservedOutputTokens?.() ?? 4096;
|
const reservedOutputTokens = this.llmProvider.getReservedOutputTokens?.() ?? 4096;
|
||||||
@@ -345,7 +377,7 @@ export class AgentCore {
|
|||||||
const compactor = new ConversationCompactor({
|
const compactor = new ConversationCompactor({
|
||||||
provider: this.llmProvider,
|
provider: this.llmProvider,
|
||||||
systemPrompt: compactionPrompt,
|
systemPrompt: compactionPrompt,
|
||||||
tools: this.getToolDefinitions(),
|
tools: this.getToolDefinitions(this.getEffectiveAgentMode()),
|
||||||
focus: options.focus,
|
focus: options.focus,
|
||||||
onProgress: options.onProgress,
|
onProgress: options.onProgress,
|
||||||
supplementalContext: buildTodoContext(this.agentState.getTodos()),
|
supplementalContext: buildTodoContext(this.agentState.getTodos()),
|
||||||
|
|||||||
@@ -56,10 +56,6 @@ async function importMediaPipe(): Promise<MediaPipeGenAI> {
|
|||||||
export class LocalBrowserLLMProvider implements LLMProvider {
|
export class LocalBrowserLLMProvider implements LLMProvider {
|
||||||
private inference: GemmaInference | null = null;
|
private inference: GemmaInference | null = null;
|
||||||
|
|
||||||
getPreferredSystemPromptPath(): string | undefined {
|
|
||||||
return 'prompts/system_compact.md';
|
|
||||||
}
|
|
||||||
|
|
||||||
getContextWindow(): number {
|
getContextWindow(): number {
|
||||||
return this.getConfiguredContextLength();
|
return this.getConfiguredContextLength();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,20 @@ export abstract class BaseTool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the tool is available when the assistant runs in Regular Mode.
|
||||||
|
*/
|
||||||
|
isAvailableInRegularMode(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the tool is available when the assistant runs in Efficient Mode.
|
||||||
|
*/
|
||||||
|
isAvailableInEfficientMode(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optionally build a compact UI summary for a successful tool result.
|
* Optionally build a compact UI summary for a successful tool result.
|
||||||
* The raw tool result remains the canonical output stored in agent history.
|
* The raw tool result remains the canonical output stored in agent history.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { KGConversationStorage } from '../core/io/KGConversationStorage';
|
|||||||
import { SAVED_CONVERSATION_VERSION, type SavedConversationDocument, type SavedConversationMeta } from '../types/conversationTypes';
|
import { SAVED_CONVERSATION_VERSION, type SavedConversationDocument, type SavedConversationMeta } from '../types/conversationTypes';
|
||||||
import type { Message } from '../agent/core/AgentState';
|
import type { Message } from '../agent/core/AgentState';
|
||||||
import { showConfirm } from '../util/dialogUtil';
|
import { showConfirm } from '../util/dialogUtil';
|
||||||
|
import { getEffectiveAgentMode, getSystemPromptPathForAgentMode } from '../util/agentMode';
|
||||||
|
|
||||||
import type { ChatMessage } from '../types/projectTypes';
|
import type { ChatMessage } from '../types/projectTypes';
|
||||||
|
|
||||||
@@ -631,9 +632,9 @@ const ChatBox: React.FC<ChatBoxProps> = ({ isVisible }) => {
|
|||||||
// Log system prompt only for first message
|
// Log system prompt only for first message
|
||||||
if (isFirstMessage) {
|
if (isFirstMessage) {
|
||||||
try {
|
try {
|
||||||
const provider = AgentCore.instance().getLLMProvider();
|
const agentMode = getEffectiveAgentMode(ConfigManager.instance());
|
||||||
const systemPrompt = await SystemPrompts.getSystemPromptWithContext(
|
const systemPrompt = await SystemPrompts.getSystemPromptWithContext(
|
||||||
provider?.getPreferredSystemPromptPath?.(),
|
getSystemPromptPathForAgentMode(agentMode),
|
||||||
);
|
);
|
||||||
console.log('------------ SYSTEM ------------');
|
console.log('------------ SYSTEM ------------');
|
||||||
console.log(systemPrompt);
|
console.log(systemPrompt);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const { localSeparatorModelCacheMock } = vi.hoisted(() => ({
|
|||||||
|
|
||||||
const configState = new Map<string, unknown>([
|
const configState = new Map<string, unknown>([
|
||||||
['general.language', 'auto'],
|
['general.language', 'auto'],
|
||||||
|
['general.agent_mode', 'regular'],
|
||||||
['general.llm_provider', 'local_browser'],
|
['general.llm_provider', 'local_browser'],
|
||||||
['general.persist_api_keys_non_localhost', false],
|
['general.persist_api_keys_non_localhost', false],
|
||||||
['general.auto_compact_threshold_percent', 90],
|
['general.auto_compact_threshold_percent', 90],
|
||||||
@@ -107,6 +108,8 @@ describe('GeneralSettings', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
configState.set('general.language', 'auto');
|
configState.set('general.language', 'auto');
|
||||||
|
configState.set('general.agent_mode', 'regular');
|
||||||
|
configState.set('general.llm_provider', 'local_browser');
|
||||||
configState.set('general.local_browser.context_length', 65536);
|
configState.set('general.local_browser.context_length', 65536);
|
||||||
configState.set('general.auto_compact_threshold_percent', 90);
|
configState.set('general.auto_compact_threshold_percent', 90);
|
||||||
configManagerMock.get.mockClear();
|
configManagerMock.get.mockClear();
|
||||||
@@ -196,6 +199,41 @@ describe('GeneralSettings', () => {
|
|||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders the music assistant section and initializes the agent mode selector', async () => {
|
||||||
|
configState.set('general.llm_provider', 'openai');
|
||||||
|
configState.set('general.agent_mode', 'efficient');
|
||||||
|
|
||||||
|
renderSettings();
|
||||||
|
|
||||||
|
expect(await screen.findByText('K.G.Studio Music Assistant')).toBeTruthy();
|
||||||
|
const select = screen.getByLabelText('Agent Mode');
|
||||||
|
expect((select as HTMLSelectElement).value).toBe('efficient');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('persists agent mode changes for non-local providers', async () => {
|
||||||
|
configState.set('general.llm_provider', 'openai');
|
||||||
|
|
||||||
|
renderSettings();
|
||||||
|
|
||||||
|
const select = await screen.findByLabelText('Agent Mode');
|
||||||
|
fireEvent.change(select, { target: { value: 'efficient' } });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(configManagerMock.set).toHaveBeenCalledWith('general.agent_mode', 'efficient');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables the agent mode selector for the local browser provider and shows override help', async () => {
|
||||||
|
configState.set('general.llm_provider', 'local_browser');
|
||||||
|
configState.set('general.agent_mode', 'regular');
|
||||||
|
|
||||||
|
renderSettings();
|
||||||
|
|
||||||
|
const select = await screen.findByLabelText('Agent Mode');
|
||||||
|
expect(select).toBeDisabled();
|
||||||
|
expect(screen.getByText('Local LLM (Browser) always runs the assistant in Efficient Mode.')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders and persists local runtime download URLs', async () => {
|
it('renders and persists local runtime download URLs', async () => {
|
||||||
renderSettings();
|
renderSettings();
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ import {
|
|||||||
LOCAL_SEPARATOR_MODEL_CONFIGS,
|
LOCAL_SEPARATOR_MODEL_CONFIGS,
|
||||||
LOCAL_SEPARATOR_MODEL_IDS,
|
LOCAL_SEPARATOR_MODEL_IDS,
|
||||||
} from '../../../util/local-separator/config';
|
} from '../../../util/local-separator/config';
|
||||||
|
import {
|
||||||
|
DEFAULT_AGENT_MODE,
|
||||||
|
getEffectiveAgentMode,
|
||||||
|
isAgentModeForcedByProvider,
|
||||||
|
normalizeAgentMode,
|
||||||
|
type AgentMode,
|
||||||
|
} from '../../../util/agentMode';
|
||||||
|
|
||||||
const LANGUAGE_OPTION_LABELS: Record<Exclude<LanguageSetting, 'auto'>, string> = {
|
const LANGUAGE_OPTION_LABELS: Record<Exclude<LanguageSetting, 'auto'>, string> = {
|
||||||
en_us: 'English',
|
en_us: 'English',
|
||||||
@@ -29,6 +36,7 @@ const LANGUAGE_OPTION_LABELS: Record<Exclude<LanguageSetting, 'auto'>, string> =
|
|||||||
const GeneralSettings: React.FC = () => {
|
const GeneralSettings: React.FC = () => {
|
||||||
const { t, setLanguageSetting } = useI18n();
|
const { t, setLanguageSetting } = useI18n();
|
||||||
const [language, setLanguage] = useState<LanguageSetting>('auto');
|
const [language, setLanguage] = useState<LanguageSetting>('auto');
|
||||||
|
const [agentMode, setAgentMode] = useState<AgentMode>(DEFAULT_AGENT_MODE);
|
||||||
const [llmProvider, setLlmProvider] = useState<string>(LOCAL_LLM_PROVIDER_KEY);
|
const [llmProvider, setLlmProvider] = useState<string>(LOCAL_LLM_PROVIDER_KEY);
|
||||||
const [openaiKey, setOpenaiKey] = useState<string>('');
|
const [openaiKey, setOpenaiKey] = useState<string>('');
|
||||||
const [openaiModel, setOpenaiModel] = useState<string>('');
|
const [openaiModel, setOpenaiModel] = useState<string>('');
|
||||||
@@ -103,6 +111,7 @@ const GeneralSettings: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLanguage(((configManager.get('general.language') as LanguageSetting | undefined) ?? 'auto'));
|
setLanguage(((configManager.get('general.language') as LanguageSetting | undefined) ?? 'auto'));
|
||||||
|
setAgentMode(normalizeAgentMode(configManager.get('general.agent_mode')));
|
||||||
setLlmProvider((configManager.get('general.llm_provider') as string) || LOCAL_LLM_PROVIDER_KEY);
|
setLlmProvider((configManager.get('general.llm_provider') as string) || LOCAL_LLM_PROVIDER_KEY);
|
||||||
setOpenaiKey((configManager.get('general.openai.api_key') as string) || '');
|
setOpenaiKey((configManager.get('general.openai.api_key') as string) || '');
|
||||||
setOpenaiModel((configManager.get('general.openai.model') as string) || '');
|
setOpenaiModel((configManager.get('general.openai.model') as string) || '');
|
||||||
@@ -169,6 +178,16 @@ const GeneralSettings: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAgentModeChange = async (value: AgentMode) => {
|
||||||
|
setAgentMode(value);
|
||||||
|
try {
|
||||||
|
await configManager.set('general.agent_mode', value);
|
||||||
|
console.log('Agent mode changed to:', value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save agent mode:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleLanguageChange = async (value: LanguageSetting) => {
|
const handleLanguageChange = async (value: LanguageSetting) => {
|
||||||
setLanguage(value);
|
setLanguage(value);
|
||||||
try {
|
try {
|
||||||
@@ -354,6 +373,8 @@ const GeneralSettings: React.FC = () => {
|
|||||||
|
|
||||||
const localRuntimeMessage = localModelState.runtimeSupport.reason;
|
const localRuntimeMessage = localModelState.runtimeSupport.reason;
|
||||||
const hasLocalRuntimeHardFailure = !localModelState.runtimeSupport.supported;
|
const hasLocalRuntimeHardFailure = !localModelState.runtimeSupport.supported;
|
||||||
|
const isAgentModeOverriddenByLocalProvider = isAgentModeForcedByProvider(llmProvider);
|
||||||
|
const effectiveAgentMode = getEffectiveAgentMode(configManager);
|
||||||
|
|
||||||
// NOTE: Gemini and Claude are not supported yet due to CORS issues.
|
// NOTE: Gemini and Claude are not supported yet due to CORS issues.
|
||||||
return (
|
return (
|
||||||
@@ -444,6 +465,38 @@ const GeneralSettings: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-group">
|
||||||
|
<h4>{t('settings.general.musicAssistant.section')}</h4>
|
||||||
|
|
||||||
|
<div className="settings-item">
|
||||||
|
<label className="settings-label" htmlFor="general-agent-mode-select">
|
||||||
|
{t('settings.general.musicAssistant.agentMode.label')}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="general-agent-mode-select"
|
||||||
|
className="settings-select"
|
||||||
|
value={agentMode}
|
||||||
|
onChange={(e) => void handleAgentModeChange(e.target.value as AgentMode)}
|
||||||
|
disabled={isAgentModeOverriddenByLocalProvider}
|
||||||
|
>
|
||||||
|
<option value="regular">{t('settings.general.musicAssistant.agentMode.regular')}</option>
|
||||||
|
<option value="efficient">{t('settings.general.musicAssistant.agentMode.efficient')}</option>
|
||||||
|
</select>
|
||||||
|
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||||
|
{isAgentModeOverriddenByLocalProvider
|
||||||
|
? t('settings.general.musicAssistant.agentMode.localOverride')
|
||||||
|
: t('settings.general.musicAssistant.agentMode.help')}
|
||||||
|
</div>
|
||||||
|
{effectiveAgentMode !== agentMode && (
|
||||||
|
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||||
|
{t('settings.general.musicAssistant.agentMode.effectiveValue', {
|
||||||
|
mode: t('settings.general.musicAssistant.agentMode.efficient'),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="settings-group">
|
<div className="settings-group">
|
||||||
<h4>{LOCAL_LLM_DISPLAY_NAME} Local Runtime</h4>
|
<h4>{LOCAL_LLM_DISPLAY_NAME} Local Runtime</h4>
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export const OPFS_CONSTANTS = {
|
|||||||
|
|
||||||
export const CONFIG_UPGRADER_CONSTANTS = {
|
export const CONFIG_UPGRADER_CONSTANTS = {
|
||||||
VERSION_KEY: '__config_version',
|
VERSION_KEY: '__config_version',
|
||||||
CURRENT_VERSION: 4,
|
CURRENT_VERSION: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const URL_CONSTANTS = {
|
export const URL_CONSTANTS = {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { upgradeConfigToV1 } from './upgradeConfigToV1';
|
|||||||
import { upgradeConfigToV2 } from './upgradeConfigToV2';
|
import { upgradeConfigToV2 } from './upgradeConfigToV2';
|
||||||
import { upgradeConfigToV3 } from './upgradeConfigToV3';
|
import { upgradeConfigToV3 } from './upgradeConfigToV3';
|
||||||
import { upgradeConfigToV4 } from './upgradeConfigToV4';
|
import { upgradeConfigToV4 } from './upgradeConfigToV4';
|
||||||
|
import { upgradeConfigToV5 } from './upgradeConfigToV5';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KGConfigUpgrader — Orchestrates app-level migrations (e.g., storage backend changes).
|
* KGConfigUpgrader — Orchestrates app-level migrations (e.g., storage backend changes).
|
||||||
@@ -48,6 +49,10 @@ export class KGConfigUpgrader {
|
|||||||
await upgradeConfigToV4();
|
await upgradeConfigToV4();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 5: {
|
||||||
|
await upgradeConfigToV5();
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error(`No config upgrader found for version ${nextVersion}`);
|
throw new Error(`No config upgrader found for version ${nextVersion}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
const getRawMock = vi.fn();
|
||||||
|
const saveRawMock = vi.fn();
|
||||||
|
|
||||||
|
vi.mock('../io/KGConfigStorage', () => ({
|
||||||
|
KGConfigStorage: {
|
||||||
|
getInstance: vi.fn(() => ({
|
||||||
|
getRaw: getRawMock,
|
||||||
|
saveRaw: saveRawMock,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { upgradeConfigToV5 } from './upgradeConfigToV5';
|
||||||
|
|
||||||
|
describe('upgradeConfigToV5', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults general.agent_mode to regular when the key is missing', async () => {
|
||||||
|
const config: Record<string, unknown> = {
|
||||||
|
general: {
|
||||||
|
llm_provider: 'openai',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
getRawMock.mockResolvedValue(config);
|
||||||
|
|
||||||
|
await upgradeConfigToV5();
|
||||||
|
|
||||||
|
expect(config.general).toEqual({
|
||||||
|
llm_provider: 'openai',
|
||||||
|
agent_mode: 'regular',
|
||||||
|
});
|
||||||
|
expect(saveRawMock).toHaveBeenCalledWith('userConfig', config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves an explicit general.agent_mode value', async () => {
|
||||||
|
const config: Record<string, unknown> = {
|
||||||
|
general: {
|
||||||
|
agent_mode: 'efficient',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
getRawMock.mockResolvedValue(config);
|
||||||
|
|
||||||
|
await upgradeConfigToV5();
|
||||||
|
|
||||||
|
expect(saveRawMock).not.toHaveBeenCalled();
|
||||||
|
expect((config.general as Record<string, unknown>).agent_mode).toBe('efficient');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { KGConfigStorage } from '../io/KGConfigStorage';
|
||||||
|
|
||||||
|
const CONFIG_KEY = 'userConfig';
|
||||||
|
|
||||||
|
export async function upgradeConfigToV5(): Promise<void> {
|
||||||
|
const storage = KGConfigStorage.getInstance();
|
||||||
|
const rawConfig = await storage.getRaw(CONFIG_KEY);
|
||||||
|
if (!rawConfig || typeof rawConfig !== 'object') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = rawConfig as Record<string, unknown>;
|
||||||
|
const general = config.general;
|
||||||
|
|
||||||
|
if (!general || typeof general !== 'object') {
|
||||||
|
config.general = {
|
||||||
|
agent_mode: 'regular',
|
||||||
|
};
|
||||||
|
await storage.saveRaw(CONFIG_KEY, config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const generalRecord = general as Record<string, unknown>;
|
||||||
|
if ('agent_mode' in generalRecord) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
generalRecord.agent_mode = 'regular';
|
||||||
|
await storage.saveRaw(CONFIG_KEY, config);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ChordGuideCustomConfig } from '../ChordGuideTypes';
|
import type { ChordGuideCustomConfig } from '../ChordGuideTypes';
|
||||||
import { KGConfigStorage } from '../io/KGConfigStorage';
|
import { KGConfigStorage } from '../io/KGConfigStorage';
|
||||||
import type { LanguageSetting } from '../../i18n/types';
|
import type { LanguageSetting } from '../../i18n/types';
|
||||||
|
import type { AgentMode } from '../../util/agentMode';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application configuration interface
|
* Application configuration interface
|
||||||
@@ -8,6 +9,7 @@ import type { LanguageSetting } from '../../i18n/types';
|
|||||||
interface AppConfig {
|
interface AppConfig {
|
||||||
general: {
|
general: {
|
||||||
language: LanguageSetting;
|
language: LanguageSetting;
|
||||||
|
agent_mode: AgentMode;
|
||||||
llm_provider: 'local_browser' | 'openai' | 'gemini' | 'claude' | 'claude_openrouter' | 'openai_compatible';
|
llm_provider: 'local_browser' | 'openai' | 'gemini' | 'claude' | 'claude_openrouter' | 'openai_compatible';
|
||||||
persist_api_keys_non_localhost: boolean;
|
persist_api_keys_non_localhost: boolean;
|
||||||
auto_compact_threshold_percent: 80 | 90 | 95;
|
auto_compact_threshold_percent: 80 | 90 | 95;
|
||||||
@@ -208,6 +210,7 @@ export class ConfigManager {
|
|||||||
this.defaultConfig = {
|
this.defaultConfig = {
|
||||||
general: {
|
general: {
|
||||||
language: 'auto',
|
language: 'auto',
|
||||||
|
agent_mode: 'regular',
|
||||||
llm_provider: 'local_browser',
|
llm_provider: 'local_browser',
|
||||||
persist_api_keys_non_localhost: false,
|
persist_api_keys_non_localhost: false,
|
||||||
auto_compact_threshold_percent: 90,
|
auto_compact_threshold_percent: 90,
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ export const enUsMessages: TranslationMessages = {
|
|||||||
'settings.general.autoCompactThreshold.conservative': 'Conservative (95%)',
|
'settings.general.autoCompactThreshold.conservative': 'Conservative (95%)',
|
||||||
'settings.general.autoCompactThreshold.standard': 'Standard (90%)',
|
'settings.general.autoCompactThreshold.standard': 'Standard (90%)',
|
||||||
'settings.general.autoCompactThreshold.early': 'Early (80%)',
|
'settings.general.autoCompactThreshold.early': 'Early (80%)',
|
||||||
|
'settings.general.musicAssistant.section': 'K.G.Studio Music Assistant',
|
||||||
|
'settings.general.musicAssistant.agentMode.label': 'Agent Mode',
|
||||||
|
'settings.general.musicAssistant.agentMode.help': 'Regular Mode uses the standard system prompt. Efficient Mode uses a compact system prompt and only tools marked for Efficient Mode.',
|
||||||
|
'settings.general.musicAssistant.agentMode.localOverride': 'Local LLM (Browser) always runs the assistant in Efficient Mode.',
|
||||||
|
'settings.general.musicAssistant.agentMode.effectiveValue': 'Effective mode: {mode}.',
|
||||||
|
'settings.general.musicAssistant.agentMode.regular': 'Regular Mode',
|
||||||
|
'settings.general.musicAssistant.agentMode.efficient': 'Efficient Mode',
|
||||||
'settings.general.localRuntime.cachedStatus': 'Cached Model Status',
|
'settings.general.localRuntime.cachedStatus': 'Cached Model Status',
|
||||||
'settings.general.localRuntime.cacheChecking': 'Checking local model cache...',
|
'settings.general.localRuntime.cacheChecking': 'Checking local model cache...',
|
||||||
'settings.general.localRuntime.cacheDownloaded': 'Downloaded in browser cache.',
|
'settings.general.localRuntime.cacheDownloaded': 'Downloaded in browser cache.',
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ export const frFrMessages: TranslationMessages = {
|
|||||||
'settings.general.autoCompactThreshold.conservative': 'Prudent (95 %)',
|
'settings.general.autoCompactThreshold.conservative': 'Prudent (95 %)',
|
||||||
'settings.general.autoCompactThreshold.standard': 'Standard (90 %)',
|
'settings.general.autoCompactThreshold.standard': 'Standard (90 %)',
|
||||||
'settings.general.autoCompactThreshold.early': 'Précoce (80 %)',
|
'settings.general.autoCompactThreshold.early': 'Précoce (80 %)',
|
||||||
|
'settings.general.musicAssistant.section': 'Assistant musical K.G.Studio',
|
||||||
|
'settings.general.musicAssistant.agentMode.label': 'Mode agent',
|
||||||
|
'settings.general.musicAssistant.agentMode.help': 'Le mode Régulier utilise le prompt système standard. Le mode Efficace utilise un prompt système compact et seulement les outils marqués pour le mode Efficace.',
|
||||||
|
'settings.general.musicAssistant.agentMode.localOverride': 'Le LLM local (navigateur) exécute toujours l’assistant en mode Efficace.',
|
||||||
|
'settings.general.musicAssistant.agentMode.effectiveValue': 'Mode effectif : {mode}.',
|
||||||
|
'settings.general.musicAssistant.agentMode.regular': 'Mode régulier',
|
||||||
|
'settings.general.musicAssistant.agentMode.efficient': 'Mode efficace',
|
||||||
'settings.general.localRuntime.cachedStatus': 'État du modèle en cache',
|
'settings.general.localRuntime.cachedStatus': 'État du modèle en cache',
|
||||||
'settings.general.localRuntime.cacheChecking': 'Vérification du cache du modèle local...',
|
'settings.general.localRuntime.cacheChecking': 'Vérification du cache du modèle local...',
|
||||||
'settings.general.localRuntime.cacheDownloaded': 'Téléchargé dans le cache du navigateur.',
|
'settings.general.localRuntime.cacheDownloaded': 'Téléchargé dans le cache du navigateur.',
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ export const zhCnMessages: TranslationMessages = {
|
|||||||
'settings.general.autoCompactThreshold.conservative': '保守(95%)',
|
'settings.general.autoCompactThreshold.conservative': '保守(95%)',
|
||||||
'settings.general.autoCompactThreshold.standard': '标准(90%)',
|
'settings.general.autoCompactThreshold.standard': '标准(90%)',
|
||||||
'settings.general.autoCompactThreshold.early': '提前(80%)',
|
'settings.general.autoCompactThreshold.early': '提前(80%)',
|
||||||
|
'settings.general.musicAssistant.section': 'K.G.Studio 音乐创作助手',
|
||||||
|
'settings.general.musicAssistant.agentMode.label': 'Agent 模式',
|
||||||
|
'settings.general.musicAssistant.agentMode.help': '常规模式使用标准系统提示词。高效模式使用紧凑系统提示词,并且只允许使用标记为可用于高效模式的工具。',
|
||||||
|
'settings.general.musicAssistant.agentMode.localOverride': '本地 LLM(浏览器)始终以高效模式运行助手。',
|
||||||
|
'settings.general.musicAssistant.agentMode.effectiveValue': '当前生效模式:{mode}。',
|
||||||
|
'settings.general.musicAssistant.agentMode.regular': '常规模式',
|
||||||
|
'settings.general.musicAssistant.agentMode.efficient': '高效模式',
|
||||||
'settings.general.localRuntime.cachedStatus': '缓存模型状态',
|
'settings.general.localRuntime.cachedStatus': '缓存模型状态',
|
||||||
'settings.general.localRuntime.cacheChecking': '正在检查本地模型缓存...',
|
'settings.general.localRuntime.cacheChecking': '正在检查本地模型缓存...',
|
||||||
'settings.general.localRuntime.cacheDownloaded': '已下载到浏览器缓存。',
|
'settings.general.localRuntime.cacheDownloaded': '已下载到浏览器缓存。',
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ export const zhHkMessages: TranslationMessages = {
|
|||||||
'settings.general.autoCompactThreshold.conservative': '保守(95%)',
|
'settings.general.autoCompactThreshold.conservative': '保守(95%)',
|
||||||
'settings.general.autoCompactThreshold.standard': '標準(90%)',
|
'settings.general.autoCompactThreshold.standard': '標準(90%)',
|
||||||
'settings.general.autoCompactThreshold.early': '提前(80%)',
|
'settings.general.autoCompactThreshold.early': '提前(80%)',
|
||||||
|
'settings.general.musicAssistant.section': 'K.G.Studio 音樂創作助手',
|
||||||
|
'settings.general.musicAssistant.agentMode.label': 'Agent 模式',
|
||||||
|
'settings.general.musicAssistant.agentMode.help': '常規模式使用標準系統提示詞。高效模式使用精簡系統提示詞,並且只允許使用標記為可用於高效模式的工具。',
|
||||||
|
'settings.general.musicAssistant.agentMode.localOverride': '本地 LLM(瀏覽器)會固定以高效模式執行助手。',
|
||||||
|
'settings.general.musicAssistant.agentMode.effectiveValue': '目前生效模式:{mode}。',
|
||||||
|
'settings.general.musicAssistant.agentMode.regular': '常規模式',
|
||||||
|
'settings.general.musicAssistant.agentMode.efficient': '高效模式',
|
||||||
'settings.general.localRuntime.cachedStatus': '緩存模型狀態',
|
'settings.general.localRuntime.cachedStatus': '緩存模型狀態',
|
||||||
'settings.general.localRuntime.cacheChecking': '正在檢查本地模型緩存...',
|
'settings.general.localRuntime.cacheChecking': '正在檢查本地模型緩存...',
|
||||||
'settings.general.localRuntime.cacheDownloaded': '已下載到瀏覽器緩存。',
|
'settings.general.localRuntime.cacheDownloaded': '已下載到瀏覽器緩存。',
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { ConfigManager } from '../core/config/ConfigManager';
|
||||||
|
import { LOCAL_LLM_PROVIDER_KEY } from './localLLMConfig';
|
||||||
|
|
||||||
|
export type AgentMode = 'regular' | 'efficient';
|
||||||
|
|
||||||
|
export const DEFAULT_AGENT_MODE: AgentMode = 'regular';
|
||||||
|
export const EFFICIENT_AGENT_MODE: AgentMode = 'efficient';
|
||||||
|
|
||||||
|
export function normalizeAgentMode(value: unknown): AgentMode {
|
||||||
|
return value === EFFICIENT_AGENT_MODE ? EFFICIENT_AGENT_MODE : DEFAULT_AGENT_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAgentModeForcedByProvider(providerType: string | null | undefined): boolean {
|
||||||
|
return providerType === LOCAL_LLM_PROVIDER_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConfiguredAgentMode(configManager: ConfigManager = ConfigManager.instance()): AgentMode {
|
||||||
|
return normalizeAgentMode(configManager.get('general.agent_mode'));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEffectiveAgentMode(configManager: ConfigManager = ConfigManager.instance()): AgentMode {
|
||||||
|
const providerType = configManager.get('general.llm_provider');
|
||||||
|
if (typeof providerType === 'string' && isAgentModeForcedByProvider(providerType)) {
|
||||||
|
return EFFICIENT_AGENT_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getConfiguredAgentMode(configManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSystemPromptPathForAgentMode(mode: AgentMode): string {
|
||||||
|
return mode === EFFICIENT_AGENT_MODE ? 'prompts/system_compact.md' : 'prompts/system.md';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user