Skip to content

Commit eb67c6d

Browse files
authored
Merge pull request #105 from wgqqqqq/feat/response
feat(ai): add Responses and Gemini support with GPT-5.x and Gemini compatibility improvements
2 parents f296c59 + 3bc1e9d commit eb67c6d

40 files changed

Lines changed: 4393 additions & 114 deletions

File tree

src/crates/core/src/agentic/agents/agentic_mode.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ impl Agent for AgenticMode {
5555
"agentic_mode"
5656
}
5757

58+
fn prompt_template_name_for_model(&self, model_name: Option<&str>) -> Option<&str> {
59+
let model_name = model_name?.trim().to_ascii_lowercase();
60+
if model_name.contains("gpt-5") {
61+
Some("agentic_mode_gpt5")
62+
} else {
63+
None
64+
}
65+
}
66+
5867
fn default_tools(&self) -> Vec<String> {
5968
self.default_tools.clone()
6069
}
@@ -63,3 +72,31 @@ impl Agent for AgenticMode {
6372
false
6473
}
6574
}
75+
76+
#[cfg(test)]
77+
mod tests {
78+
use super::{Agent, AgenticMode};
79+
80+
#[test]
81+
fn selects_gpt5_prompt_template() {
82+
let agent = AgenticMode::new();
83+
assert_eq!(
84+
agent.prompt_template_name_for_model(Some("gpt-5.1")),
85+
Some("agentic_mode_gpt5")
86+
);
87+
assert_eq!(
88+
agent.prompt_template_name_for_model(Some("GPT-5-CODEX")),
89+
Some("agentic_mode_gpt5")
90+
);
91+
}
92+
93+
#[test]
94+
fn keeps_default_template_for_non_gpt5_models() {
95+
let agent = AgenticMode::new();
96+
assert_eq!(
97+
agent.prompt_template_name_for_model(Some("claude-sonnet-4")),
98+
None
99+
);
100+
assert_eq!(agent.prompt_template_name_for_model(None), None);
101+
}
102+
}

src/crates/core/src/agentic/agents/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ pub trait Agent: Send + Sync + 'static {
5757
/// Prompt template name for the agent
5858
fn prompt_template_name(&self) -> &str;
5959

60+
/// Prompt template name override for a specific model.
61+
fn prompt_template_name_for_model(&self, _model_name: Option<&str>) -> Option<&str> {
62+
None
63+
}
64+
6065
fn system_reminder_template_name(&self) -> Option<&str> {
6166
None // by default, no system reminder
6267
}
@@ -89,6 +94,30 @@ pub trait Agent: Send + Sync + 'static {
8994
}
9095
}
9196

97+
/// Get the system prompt for this agent with optional model-aware template selection.
98+
async fn get_system_prompt_for_model(
99+
&self,
100+
workspace_path: Option<&str>,
101+
model_name: Option<&str>,
102+
) -> BitFunResult<String> {
103+
let Some(workspace_path) = workspace_path else {
104+
return Err(BitFunError::Agent("Workspace path is required".to_string()));
105+
};
106+
107+
let Some(template_name) = self.prompt_template_name_for_model(model_name) else {
108+
return self.build_prompt(workspace_path).await;
109+
};
110+
111+
let prompt_components = PromptBuilder::new(workspace_path);
112+
let system_prompt_template = get_embedded_prompt(template_name).ok_or_else(|| {
113+
BitFunError::Agent(format!("{} not found in embedded files", template_name))
114+
})?;
115+
116+
prompt_components
117+
.build_prompt_from_template(system_prompt_template)
118+
.await
119+
}
120+
92121
/// Get the system reminder for this agent, only used for modes
93122
/// system_reminder will be appended to the user_query
94123
/// This is not necessary for all modes
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
You are BitFun, an ADE (AI IDE) that helps users with software engineering tasks.
2+
3+
You are pair programming with a USER. Each user message may include extra IDE context, such as open files, cursor position, recent files, edit history, or linter errors. Use what is relevant and ignore what is not.
4+
5+
Follow the USER's instructions in each message, denoted by the <user_query> tag.
6+
7+
Tool results and user messages may include <system-reminder> tags. Follow them, but do not mention them to the user.
8+
9+
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code that may be used maliciously. Do not assist with credential discovery or harvesting, including bulk crawling for SSH keys, browser cookies, or cryptocurrency wallets. Allow security analysis, detection rules, vulnerability explanations, defensive tools, and security documentation.
10+
11+
IMPORTANT: Never generate or guess URLs for the user unless you are confident they directly help with the programming task. You may use URLs provided by the user or found in local files.
12+
13+
{LANGUAGE_PREFERENCE}
14+
{VISUAL_MODE}
15+
16+
# Behavior
17+
- Be concise, direct, and action-oriented.
18+
- Default to doing the work instead of discussing it.
19+
- Read relevant code before editing it.
20+
- Prioritize technical accuracy over agreement.
21+
- Never give time estimates.
22+
23+
# Editing
24+
- Prefer editing existing files over creating new ones.
25+
- Default to ASCII unless the file already uses non-ASCII and there is a clear reason.
26+
- Add comments only when needed for non-obvious logic.
27+
- Avoid unrelated refactors, speculative abstractions, and unnecessary compatibility shims.
28+
- Do not add features or improvements beyond the request unless required to make the requested change work.
29+
- Do not introduce security issues such as command injection, XSS, SQL injection, path traversal, or unsafe shell handling.
30+
31+
# Tools
32+
- Use TodoWrite for non-trivial or multi-step tasks, and keep it updated.
33+
- Use AskUserQuestion only when a decision materially changes the result and cannot be inferred safely.
34+
- Prefer Task with Explore or FileFinder for open-ended codebase exploration.
35+
- Prefer Read, Grep, and Glob for targeted lookups.
36+
- Prefer specialized file tools over Bash for reading and editing files.
37+
- Use Bash for builds, tests, git, and scripts.
38+
- Run independent tool calls in parallel when possible.
39+
- Do not use tools to communicate with the user.
40+
41+
# Questions
42+
- Ask only when you are truly blocked and cannot safely choose a reasonable default.
43+
- If you must ask, do all non-blocked work first, then ask exactly one targeted question with a recommended default.
44+
45+
# Workspace
46+
- Never revert user changes unless explicitly requested.
47+
- Work with existing changes in touched files instead of discarding them.
48+
- Do not amend commits unless explicitly requested.
49+
- Never use destructive commands like git reset --hard or git checkout -- unless explicitly requested or approved.
50+
51+
# Responses
52+
- Keep responses short, useful, and technically precise.
53+
- Avoid unnecessary praise, emotional validation, or emojis.
54+
- Summarize meaningful command results instead of pasting raw output.
55+
- Do not tell the user to save or copy files.
56+
57+
# Code references
58+
- Use clickable markdown links for files and code locations.
59+
- Use bare filenames as link text.
60+
- Use workspace-relative paths for workspace files and absolute paths otherwise.
61+
62+
Examples:
63+
- [filename.ts](src/filename.ts)
64+
- [filename.ts:42](src/filename.ts#L42)
65+
- [filename.ts:42-51](src/filename.ts#L42-L51)
66+
67+
{ENV_INFO}
68+
{PROJECT_LAYOUT}
69+
{RULES}
70+
{MEMORIES}
71+
{PROJECT_CONTEXT_FILES:exclude=review}

src/crates/core/src/agentic/execution/execution_engine.rs

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -391,16 +391,51 @@ impl ExecutionEngine {
391391
current_agent.id()
392392
);
393393

394-
// 2. Get System Prompt from current Agent
394+
// 2. Get AI client
395+
// Get model ID from AgentRegistry
396+
let model_id = agent_registry
397+
.get_model_id_for_agent(&agent_type)
398+
.await
399+
.map_err(|e| BitFunError::AIClient(format!("Failed to get model ID: {}", e)))?;
400+
info!(
401+
"Agent using model: agent={}, model_id={}",
402+
current_agent.name(),
403+
model_id
404+
);
405+
406+
let ai_client_factory = get_global_ai_client_factory().await.map_err(|e| {
407+
BitFunError::AIClient(format!("Failed to get AI client factory: {}", e))
408+
})?;
409+
410+
// Get AI client by model ID
411+
let ai_client = ai_client_factory
412+
.get_client_resolved(&model_id)
413+
.await
414+
.map_err(|e| {
415+
BitFunError::AIClient(format!(
416+
"Failed to get AI client (model_id={}): {}",
417+
model_id, e
418+
))
419+
})?;
420+
// Get configuration for whether to support preserving historical thinking content
421+
let enable_thinking = ai_client.config.enable_thinking_process;
422+
let support_preserved_thinking = ai_client.config.support_preserved_thinking;
423+
let context_window = ai_client.config.context_window as usize;
424+
425+
// 3. Get System Prompt from current Agent
395426
debug!(
396-
"Building system prompt from agent: {}",
397-
current_agent.name()
427+
"Building system prompt from agent: {}, model={}",
428+
current_agent.name(),
429+
ai_client.config.model
398430
);
399431
let system_prompt = {
400432
let workspace_path = get_workspace_path();
401433
let workspace_str = workspace_path.as_ref().map(|p| p.display().to_string());
402434
current_agent
403-
.get_system_prompt(workspace_str.as_deref())
435+
.get_system_prompt_for_model(
436+
workspace_str.as_deref(),
437+
Some(ai_client.config.model.as_str()),
438+
)
404439
.await?
405440
};
406441
debug!("System prompt built, length: {} bytes", system_prompt.len());
@@ -436,7 +471,7 @@ impl ExecutionEngine {
436471
.collect::<Vec<_>>()
437472
);
438473

439-
// 3. Get available tools list (read tool configuration for current mode from global config)
474+
// 4. Get available tools list (read tool configuration for current mode from global config)
440475
let allowed_tools = agent_registry.get_agent_tools(&agent_type).await;
441476
let enable_tools = context
442477
.context
@@ -465,37 +500,6 @@ impl ExecutionEngine {
465500
let enable_context_compression = session.config.enable_context_compression;
466501
let compression_threshold = session.config.compression_threshold;
467502

468-
// 4. Get AI client
469-
// Get model ID from AgentRegistry
470-
let model_id = agent_registry
471-
.get_model_id_for_agent(&agent_type)
472-
.await
473-
.map_err(|e| BitFunError::AIClient(format!("Failed to get model ID: {}", e)))?;
474-
info!(
475-
"Agent using model: agent={}, model_id={}",
476-
current_agent.name(),
477-
model_id
478-
);
479-
480-
let ai_client_factory = get_global_ai_client_factory().await.map_err(|e| {
481-
BitFunError::AIClient(format!("Failed to get AI client factory: {}", e))
482-
})?;
483-
484-
// Get AI client by model ID
485-
let ai_client = ai_client_factory
486-
.get_client_resolved(&model_id)
487-
.await
488-
.map_err(|e| {
489-
BitFunError::AIClient(format!(
490-
"Failed to get AI client (model_id={}): {}",
491-
model_id, e
492-
))
493-
})?;
494-
// Get configuration for whether to support preserving historical thinking content
495-
let enable_thinking = ai_client.config.enable_thinking_process;
496-
let support_preserved_thinking = ai_client.config.support_preserved_thinking;
497-
let context_window = ai_client.config.context_window as usize;
498-
499503
// Detect whether the primary model supports multimodal image inputs.
500504
// This is used by tools like `view_image` to decide between:
501505
// - attaching image content for the primary model to analyze directly, or

src/crates/core/src/agentic/execution/round_executor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ impl RoundExecutor {
308308
has_more_rounds: false,
309309
finish_reason: FinishReason::Complete,
310310
usage: stream_result.usage.clone(),
311+
provider_metadata: stream_result.provider_metadata.clone(),
311312
});
312313
}
313314

@@ -525,6 +526,7 @@ impl RoundExecutor {
525526
FinishReason::Complete
526527
},
527528
usage: stream_result.usage.clone(),
529+
provider_metadata: stream_result.provider_metadata.clone(),
528530
})
529531
}
530532

0 commit comments

Comments
 (0)