diff --git a/.gitignore b/.gitignore
index 3317a008c..090909467 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,3 +46,6 @@ k8s-*/*
tests/**/creds.json
ui/simple-saas
*.tar
+# AI tool config directories (user-specific)
+.gemini/
+.opencode/
diff --git a/internal/agent/loop_types.go b/internal/agent/loop_types.go
index ecdbaa31e..8a1d20772 100644
--- a/internal/agent/loop_types.go
+++ b/internal/agent/loop_types.go
@@ -119,6 +119,9 @@ type Loop struct {
skillEvolve bool
skillNudgeInterval int // nudge every N tool calls (0 = disabled, 15 = default)
+ // Strip trailing assistant messages before LLM call (for proxy providers)
+ stripAssistantPrefill bool
+
// Config permission store for group file writer checks
configPermStore store.ConfigPermissionStore
@@ -229,6 +232,9 @@ type LoopConfig struct {
// Thinking level: "off", "low", "medium", "high" (from agent other_config)
ThinkingLevel string
+ // Strip trailing assistant messages before LLM call (for proxy providers)
+ StripAssistantPrefill bool
+
// Self-evolve: predefined agents can update SOUL.md (style/tone) through chat
SelfEvolve bool
@@ -335,6 +341,7 @@ func NewLoop(cfg LoopConfig) *Loop {
selfEvolve: cfg.SelfEvolve,
skillEvolve: cfg.SkillEvolve,
skillNudgeInterval: cfg.SkillNudgeInterval,
+ stripAssistantPrefill: cfg.StripAssistantPrefill,
configPermStore: cfg.ConfigPermStore,
teamStore: cfg.TeamStore,
secureCLIStore: cfg.SecureCLIStore,
diff --git a/internal/agent/resolver.go b/internal/agent/resolver.go
index 803592f84..e414c3c7b 100644
--- a/internal/agent/resolver.go
+++ b/internal/agent/resolver.go
@@ -347,6 +347,7 @@ func NewManagedResolver(deps ResolverDeps) ResolverFunc {
SelfEvolve: ag.ParseSelfEvolve(),
SkillEvolve: ag.AgentType == store.AgentTypePredefined && ag.ParseSkillEvolve(),
SkillNudgeInterval: ag.ParseSkillNudgeInterval(),
+ StripAssistantPrefill: ag.ParseStripAssistantPrefill(),
WorkspaceSharing: ag.ParseWorkspaceSharing(),
ShellDenyGroups: ag.ParseShellDenyGroups(),
ConfigPermStore: deps.ConfigPermStore,
diff --git a/internal/store/agent_store.go b/internal/store/agent_store.go
index da1576a99..fa5b83646 100644
--- a/internal/store/agent_store.go
+++ b/internal/store/agent_store.go
@@ -204,6 +204,21 @@ func (a *AgentData) ParseSkillNudgeInterval() int {
return *cfg.SkillNudgeInterval
}
+// ParseStripAssistantPrefill extracts strip_assistant_prefill from other_config JSONB.
+// When true, trailing assistant messages are removed before LLM call (for proxy providers).
+func (a *AgentData) ParseStripAssistantPrefill() bool {
+ if len(a.OtherConfig) == 0 {
+ return false
+ }
+ var cfg struct {
+ StripAssistantPrefill bool `json:"strip_assistant_prefill"`
+ }
+ if json.Unmarshal(a.OtherConfig, &cfg) != nil {
+ return false
+ }
+ return cfg.StripAssistantPrefill
+}
+
// WorkspaceSharingConfig controls per-user workspace isolation.
// When shared_dm/shared_group is true, users share the base workspace directory
// instead of each getting an isolated subfolder.
diff --git a/ui/web/src/i18n/locales/en/agents.json b/ui/web/src/i18n/locales/en/agents.json
index 7fa0fe5e3..1bf6fa10e 100644
--- a/ui/web/src/i18n/locales/en/agents.json
+++ b/ui/web/src/i18n/locales/en/agents.json
@@ -147,7 +147,9 @@
"contextWindow": "Context Window",
"contextWindowHint": "Token limit for the model context.",
"maxToolIterations": "Max Tool Iterations",
- "maxToolIterationsHint": "Max tool calls per request."
+ "maxToolIterationsHint": "Max tool calls per request.",
+ "stripAssistantPrefill": "Strip Assistant Prefill",
+ "stripAssistantPrefillHint": "Remove trailing assistant messages before LLM call. Enable for proxy providers that reject prefill."
},
"workspace": {
"title": "Workspace",
diff --git a/ui/web/src/i18n/locales/vi/agents.json b/ui/web/src/i18n/locales/vi/agents.json
index 0e0761951..f2b9bdebd 100644
--- a/ui/web/src/i18n/locales/vi/agents.json
+++ b/ui/web/src/i18n/locales/vi/agents.json
@@ -147,7 +147,9 @@
"contextWindow": "Cửa sổ ngữ cảnh",
"contextWindowHint": "Giới hạn token cho ngữ cảnh model.",
"maxToolIterations": "Số lần lặp công cụ tối đa",
- "maxToolIterationsHint": "Số lần gọi công cụ tối đa mỗi yêu cầu."
+ "maxToolIterationsHint": "Số lần gọi công cụ tối đa mỗi yêu cầu.",
+ "stripAssistantPrefill": "Loại bỏ Assistant Prefill",
+ "stripAssistantPrefillHint": "Xóa tin nhắn assistant cuối cùng trước khi gọi LLM. Bật cho proxy provider không hỗ trợ prefill."
},
"workspace": {
"title": "Workspace",
diff --git a/ui/web/src/i18n/locales/zh/agents.json b/ui/web/src/i18n/locales/zh/agents.json
index 315c4fdf2..ccbe56c38 100644
--- a/ui/web/src/i18n/locales/zh/agents.json
+++ b/ui/web/src/i18n/locales/zh/agents.json
@@ -147,7 +147,9 @@
"contextWindow": "上下文窗口",
"contextWindowHint": "模型上下文的令牌限制。",
"maxToolIterations": "最大工具迭代次数",
- "maxToolIterationsHint": "每次请求最大工具调用次数。"
+ "maxToolIterationsHint": "每次请求最大工具调用次数。",
+ "stripAssistantPrefill": "移除助手预填充",
+ "stripAssistantPrefillHint": "在调用LLM前移除末尾的助手消息。为不支持预填充的代理提供商启用。"
},
"workspace": {
"title": "工作区",
diff --git a/ui/web/src/pages/agents/agent-detail/agent-overview-tab.tsx b/ui/web/src/pages/agents/agent-detail/agent-overview-tab.tsx
index b717b1820..8d91f4eea 100644
--- a/ui/web/src/pages/agents/agent-detail/agent-overview-tab.tsx
+++ b/ui/web/src/pages/agents/agent-detail/agent-overview-tab.tsx
@@ -38,6 +38,8 @@ export function AgentOverviewTab({ agent, onUpdate, heartbeat }: AgentOverviewTa
const [budgetDollars, setBudgetDollars] = useState(
agent.budget_monthly_cents ? String(agent.budget_monthly_cents / 100) : "",
);
+ const [stripAssistantPrefill, setStripAssistantPrefill] = useState(Boolean(otherCfg.strip_assistant_prefill));
+
// Evolution (predefined only)
const [selfEvolve, setSelfEvolve] = useState(Boolean(otherCfg.self_evolve));
const [skillEvolve, setSkillEvolve] = useState(Boolean(otherCfg.skill_evolve));
@@ -70,6 +72,7 @@ export function AgentOverviewTab({ agent, onUpdate, heartbeat }: AgentOverviewTa
self_evolve: selfEvolve,
skill_evolve: skillEvolve,
skill_nudge_interval: skillEvolve ? skillNudgeInterval : undefined,
+ strip_assistant_prefill: stripAssistantPrefill || undefined,
};
const budgetCents = budgetDollars ? Math.round(parseFloat(budgetDollars) * 100) : null;
await onUpdate({
@@ -128,6 +131,8 @@ export function AgentOverviewTab({ agent, onUpdate, heartbeat }: AgentOverviewTa
budgetDollars={budgetDollars}
onBudgetDollarsChange={setBudgetDollars}
onSaveBlockedChange={setLlmSaveBlocked}
+ stripAssistantPrefill={stripAssistantPrefill}
+ onStripAssistantPrefillChange={setStripAssistantPrefill}
/>
{t("general.budgetHint")}
+ + {onStripAssistantPrefillChange && ( +{t("llmConfig.stripAssistantPrefillHint")}
+