Complete local-first Rig provider migration
This commit is contained in:
+40
-16
@@ -7,7 +7,10 @@ use galaxy_agent_core::{
|
||||
turn_control, AgentError, AgentEvent, AgentRuntime, MessageContent, MessageRole, ToolCall,
|
||||
ToolCallDecision, ToolEvent, ToolPolicy, ToolResult, TurnCommand,
|
||||
};
|
||||
use galaxy_agent_rig::{OpenAICompatibleRuntime, OpenAICompatibleRuntimeConfig};
|
||||
use galaxy_agent_rig::{
|
||||
ChatGPTSubscriptionRuntime, ChatGPTSubscriptionRuntimeConfig, OpenAICompatibleRuntime,
|
||||
OpenAICompatibleRuntimeConfig,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use warp_multi_agent_api::ToolType;
|
||||
|
||||
@@ -24,6 +27,7 @@ use crate::ai::openai::client::OpenAIClientConfig;
|
||||
use crate::ai::provider::types::{ContentPart, ConversationMessage};
|
||||
use crate::ai::runtime::{RuntimeResponseConfig, RuntimeResponseTranslator};
|
||||
use crate::server::server_api::AIApiError;
|
||||
use crate::settings::OpenAIProviderKind;
|
||||
|
||||
pub(crate) fn rig_openai_response_stream(
|
||||
config: OpenAIClientConfig,
|
||||
@@ -35,21 +39,41 @@ pub(crate) fn rig_openai_response_stream(
|
||||
let skill_path_origin = params.session_context.skill_path_origin();
|
||||
let prepared = prepare_rig_turn(&config, params, supported_tools, supported_cli_agent_tools);
|
||||
let model_id = prepared.request.model.as_str().to_string();
|
||||
let runtime = OpenAICompatibleRuntime::new(OpenAICompatibleRuntimeConfig {
|
||||
base_url: config.base_url,
|
||||
api_key: config.api_key,
|
||||
model: model_id.clone(),
|
||||
max_output_tokens: config.max_output_tokens.map(u64::from),
|
||||
supports_system_messages: config.supports_system_messages,
|
||||
});
|
||||
rig_response_stream(
|
||||
runtime,
|
||||
prepared,
|
||||
skill_path_origin,
|
||||
config.max_input_tokens,
|
||||
"rig_openai_compatible",
|
||||
cancellation_rx,
|
||||
)
|
||||
match config.kind {
|
||||
OpenAIProviderKind::OpenAICompatible => {
|
||||
let runtime = OpenAICompatibleRuntime::new(OpenAICompatibleRuntimeConfig {
|
||||
base_url: config.base_url,
|
||||
api_key: config.api_key,
|
||||
model: model_id.clone(),
|
||||
max_output_tokens: config.max_output_tokens.map(u64::from),
|
||||
supports_system_messages: config.supports_system_messages,
|
||||
});
|
||||
rig_response_stream(
|
||||
runtime,
|
||||
prepared,
|
||||
skill_path_origin,
|
||||
config.max_input_tokens,
|
||||
"rig_openai_compatible",
|
||||
cancellation_rx,
|
||||
)
|
||||
}
|
||||
OpenAIProviderKind::ChatGPTSubscription => {
|
||||
let runtime = ChatGPTSubscriptionRuntime::new(ChatGPTSubscriptionRuntimeConfig {
|
||||
model: model_id,
|
||||
reasoning_effort: config.reasoning_effort,
|
||||
max_output_tokens: config.max_output_tokens.map(u64::from),
|
||||
auth_file: None,
|
||||
});
|
||||
rig_response_stream(
|
||||
runtime,
|
||||
prepared,
|
||||
skill_path_origin,
|
||||
config.max_input_tokens,
|
||||
"rig_chatgpt_subscription",
|
||||
cancellation_rx,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn rig_bedrock_response_stream(
|
||||
|
||||
@@ -113,7 +113,15 @@ fn prepare_rig_turn_for_provider(
|
||||
supported_tools
|
||||
}
|
||||
};
|
||||
let (tools, mcp_tool_aliases) = tool_definitions(&available_tools, mcp_context.as_ref());
|
||||
let (mut tools, mcp_tool_aliases) = tool_definitions(&available_tools, mcp_context.as_ref());
|
||||
if matches!(mode, RigRequestMode::Cli) {
|
||||
// History recall cannot advance a running command and is handled inline by the Rig
|
||||
// adapter (without producing a client action that can trigger another turn). Keeping it
|
||||
// in the CLI tool list lets the model spend its entire monitor turn recalling the prior
|
||||
// snapshot instead of scheduling `read_shell_command_output`, so make polling the only
|
||||
// way to inspect the active command here.
|
||||
tools.retain(|tool| tool.name != "recall_tool_history");
|
||||
}
|
||||
let system_prompt = build_system_prompt(&input, &tools, &global_rules, mode);
|
||||
|
||||
let mut new_messages = input_messages(input, tool_results);
|
||||
@@ -406,6 +414,16 @@ enum RigRequestMode {
|
||||
|
||||
fn request_mode(inputs: &[AIAgentInput]) -> RigRequestMode {
|
||||
for input in inputs {
|
||||
// A direct-provider follow-up carries an LRC snapshot as an action result rather than
|
||||
// as a user query with `running_command`. Treat that result as a CLI-monitor turn so the
|
||||
// request receives the dedicated polling instructions and CLI tool set. Without this,
|
||||
// the model sees a generic tool-result turn and may stop after inspecting the snapshot
|
||||
// (or call history recall) instead of scheduling the next output read.
|
||||
if let AIAgentInput::ActionResult { result, .. } = input {
|
||||
if result.result.triggers_server_subagent() {
|
||||
return RigRequestMode::Cli;
|
||||
}
|
||||
}
|
||||
if matches!(
|
||||
input,
|
||||
AIAgentInput::UserQuery {
|
||||
@@ -719,7 +737,7 @@ fn build_system_prompt(
|
||||
"## Orchestration Mode\nDelegate only independent, bounded work where parallelism materially helps, then synthesize the results.\n\n",
|
||||
),
|
||||
RigRequestMode::Cli => prompt.push_str(
|
||||
"## Running Command Monitor\nMonitor the existing command by its command ID. Never start a duplicate command. Poll briefly, respect stop conditions, and report only verified outcomes.\n\n",
|
||||
"## Running Command Monitor\nThis turn concerns a running or just-finished shell command. Act as its dedicated monitor while still following the user's steering messages. Use the command ID from the tool result for every read/write operation. If the result says the command finished, report its outcome and stop polling. Otherwise, poll with `read_shell_command_output` and use short delays. Never choose a poll interval that crosses a user-specified deadline or stop condition. When an explicit stop condition is met, call `interrupt_shell_command` immediately, then poll briefly to verify the outcome. Never start a duplicate command merely to check its state, and never report completion while a result says it is still running.\n\n",
|
||||
),
|
||||
}
|
||||
prompt.push_str("## Available Tools\n");
|
||||
|
||||
@@ -9,8 +9,10 @@ use warp_multi_agent_api::ToolType;
|
||||
|
||||
use super::{input_messages, prepare_bedrock_rig_turn, prepare_rig_turn, tool_definitions};
|
||||
use crate::ai::agent::api::RequestParams;
|
||||
use crate::ai::agent::task::TaskId;
|
||||
use crate::ai::agent::{
|
||||
AIAgentContext, AIAgentInput, AnyFileContent, FileContext, MCPContext, MCPServer, UserQueryMode,
|
||||
AIAgentActionId, AIAgentActionResult, AIAgentActionResultType, AIAgentContext, AIAgentInput,
|
||||
AnyFileContent, FileContext, MCPContext, MCPServer, RequestCommandOutputResult, UserQueryMode,
|
||||
};
|
||||
use crate::ai::llms::LLMId;
|
||||
use crate::ai::openai::client::OpenAIClientConfig;
|
||||
@@ -18,9 +20,11 @@ use crate::ai::skills::SkillDescriptor;
|
||||
|
||||
fn config() -> OpenAIClientConfig {
|
||||
OpenAIClientConfig {
|
||||
kind: crate::settings::OpenAIProviderKind::OpenAICompatible,
|
||||
base_url: "http://localhost:4000/v1".to_string(),
|
||||
api_key: None,
|
||||
model: Some("provider-model".to_string()),
|
||||
reasoning_effort: None,
|
||||
max_input_tokens: Some(128_000),
|
||||
max_output_tokens: Some(8_192),
|
||||
use_rig: true,
|
||||
@@ -131,6 +135,85 @@ fn rig_prompt_requires_follow_through_without_manual_continue_prompts() {
|
||||
assert!(prompt.contains("After each tool result, choose and perform the next necessary step"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lrc_snapshot_follow_up_uses_the_cli_monitor_prompt_and_tools() {
|
||||
let block_id: galaxy_terminal::model::BlockId = "precmd-lrc-test".to_string().into();
|
||||
let result = AIAgentActionResult {
|
||||
id: AIAgentActionId::from("run-call".to_owned()),
|
||||
task_id: TaskId::new("task".to_owned()),
|
||||
result: AIAgentActionResultType::RequestCommandOutput(
|
||||
RequestCommandOutputResult::LongRunningCommandSnapshot {
|
||||
block_id: block_id.clone(),
|
||||
command: "bash loop.sh".to_string(),
|
||||
grid_contents: "Running for 2 seconds...".to_string(),
|
||||
cursor: String::new(),
|
||||
is_alt_screen_active: false,
|
||||
},
|
||||
),
|
||||
};
|
||||
let snapshot_tool_result = ToolResult {
|
||||
call_id: "run-call".to_string(),
|
||||
content: result.result.model_content(),
|
||||
status: ToolResultStatus::Success,
|
||||
};
|
||||
let mut params = RequestParams::new_for_test();
|
||||
params.message_history = vec![galaxy_agent_core::ConversationMessage {
|
||||
role: MessageRole::Assistant,
|
||||
content: MessageContent::ToolUse {
|
||||
tool_use_id: "run-call".to_string(),
|
||||
name: "run_shell_command".to_string(),
|
||||
input: serde_json::json!({
|
||||
"command": "bash loop.sh",
|
||||
"wait_until_complete": false,
|
||||
}),
|
||||
},
|
||||
}];
|
||||
params.input = vec![AIAgentInput::ActionResult {
|
||||
result,
|
||||
context: Arc::from([]),
|
||||
}];
|
||||
params.tool_results = vec![snapshot_tool_result];
|
||||
|
||||
let prepared = prepare_rig_turn(
|
||||
&config(),
|
||||
params,
|
||||
vec![ToolType::RunShellCommand],
|
||||
vec![ToolType::ReadShellCommandOutput],
|
||||
);
|
||||
let prompt = prepared.request.system_prompt.expect("system prompt");
|
||||
|
||||
assert!(prompt.contains("## Running Command Monitor"));
|
||||
assert!(prompt.contains("poll with `read_shell_command_output`"));
|
||||
assert!(prepared
|
||||
.request
|
||||
.tools
|
||||
.iter()
|
||||
.any(|tool| tool.name == "read_shell_command_output"));
|
||||
assert!(!prepared
|
||||
.request
|
||||
.tools
|
||||
.iter()
|
||||
.any(|tool| tool.name == "recall_tool_history"));
|
||||
assert!(prepared
|
||||
.request
|
||||
.messages
|
||||
.iter()
|
||||
.any(|message| match &message.content {
|
||||
MessageContent::ToolResult { content, .. } => {
|
||||
content.contains("Command ID: precmd-lrc-test")
|
||||
&& content.contains("Continue monitoring with `read_shell_command_output`")
|
||||
}
|
||||
MessageContent::MultiPart(parts) => parts.iter().any(|part| {
|
||||
matches!(
|
||||
part,
|
||||
ContentPart::ToolResult { content, .. }
|
||||
if content.contains("Command ID: precmd-lrc-test")
|
||||
)
|
||||
}),
|
||||
_ => false,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rig_prompt_requires_matching_project_skills_to_be_read_before_action() {
|
||||
let skill_path = LocalOrRemotePath::Local(PathBuf::from(
|
||||
|
||||
Reference in New Issue
Block a user