Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,12 @@ jobs:
- name: Rust tests including platform security controls
if: needs.checks.outputs.rust == 'true'
run: cargo test --locked --workspace
- name: Harbor-only container environment feature
if: needs.checks.outputs.rust == 'true'
run: |
cargo clippy --locked -p s-code-platform-runtime -p s-code-tool-runtime -p s-code-daemon --all-targets --features harbor-container-env -- -D warnings
cargo test --locked -p s-code-platform-runtime -p s-code-tool-runtime --features harbor-container-env
cargo test --locked -p s-code-daemon --features harbor-container-env --bin s-code-daemon
- name: CLI end-to-end
if: needs.checks.outputs.runtime == 'true'
run: tests/test-cli-e2e.sh
Expand Down Expand Up @@ -342,6 +348,8 @@ jobs:
- run: npm ci --prefix tests/benchmarks/runner --no-audit --no-fund
- run: python3 tests/test-harness-benchmark.py validate
- run: tests/test-harness-grader-integrity.sh
- run: python3 tests/test-harbor-adapter.py
- run: python3 tests/test-harbor-bundle.py
- if: needs.checks.outputs.audit == 'true'
run: npm audit --prefix tests/benchmarks/runner --audit-level=high

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 77 additions & 2 deletions crates/agent-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,14 @@ impl TurnMachine {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AgentRunRequest {
pub model: String,
pub temperature: f32,
pub temperature: f64,
pub messages: Vec<ModelMessage>,
pub tools: Vec<ToolDefinition>,
pub max_output_tokens: u32,
/// Keep `max_output_tokens` as a fixed cap instead of doubling it after a
/// cut-off response.
#[serde(default)]
pub fixed_max_output_tokens: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub routing: Option<ModelRoutingPolicy>,
/// Tool media for the lifetime of this in-memory run segment. It is
Expand Down Expand Up @@ -944,7 +948,10 @@ impl AgentRunner {
matches!(reason, "length" | "max_tokens" | "max_output_tokens")
}) || call_output_tokens
>= u64::from(request.max_output_tokens);
if exhausted_budget && request.max_output_tokens < MAX_ADAPTIVE_OUTPUT_TOKENS {
if exhausted_budget
&& !request.fixed_max_output_tokens
&& request.max_output_tokens < MAX_ADAPTIVE_OUTPUT_TOKENS
{
request.max_output_tokens = request
.max_output_tokens
.saturating_mul(2)
Expand Down Expand Up @@ -983,6 +990,7 @@ impl AgentRunner {
});
}
if exhausted_output_budget
&& !request.fixed_max_output_tokens
&& request.max_output_tokens < MAX_ADAPTIVE_OUTPUT_TOKENS
{
request.max_output_tokens = request
Expand Down Expand Up @@ -2816,6 +2824,7 @@ mod tests {
parameters: json!({"type":"object"}),
}],
max_output_tokens: 100,
fixed_max_output_tokens: false,
routing: None,
transient_tool_media: BTreeMap::new(),
}
Expand Down Expand Up @@ -3852,6 +3861,72 @@ mod tests {
assert_eq!(requests[1].max_output_tokens, 200);
}

#[tokio::test]
async fn fixed_output_cap_does_not_grow_after_an_exhausted_budget() {
struct CapturingProvider {
requests: Arc<Mutex<Vec<ModelRequest>>>,
responses: Mutex<VecDeque<Vec<ModelEvent>>>,
}
#[async_trait]
impl ModelProvider for CapturingProvider {
async fn stream(
&self,
request: ModelRequest,
) -> Result<s_code_model_gateway::ModelStream, GatewayError> {
self.requests.lock().unwrap().push(request);
let events = self.responses.lock().unwrap().pop_front().unwrap();
Ok(Box::pin(futures_util::stream::iter(
events.into_iter().map(Ok),
)))
}
}
let requests = Arc::new(Mutex::new(Vec::new()));
let provider = Arc::new(CapturingProvider {
requests: requests.clone(),
responses: Mutex::new(VecDeque::from([
vec![
ModelEvent::Usage {
input_tokens: 10,
output_tokens: 100,
},
ModelEvent::Completed {
finish_reason: Some("length".into()),
},
],
vec![
ModelEvent::TextDelta {
text: "done with room".into(),
},
ModelEvent::Completed {
finish_reason: Some("stop".into()),
},
],
])),
});
let result = AgentRunner::new(
provider,
Arc::new(FakeExecutor {
outcome: AgentToolResult::Completed { value: json!(null) },
}),
TurnLimits::default(),
)
.run(
AgentRunRequest {
fixed_max_output_tokens: true,
..request()
},
CancellationToken::new(),
)
.await
.unwrap();

assert_eq!(result.status, AgentRunStatus::Completed);
let requests = requests.lock().unwrap();
assert_eq!(requests.len(), 2);
assert_eq!(requests[0].max_output_tokens, 100);
assert_eq!(requests[1].max_output_tokens, 100);
}

#[tokio::test]
async fn legitimate_answer_may_explain_provider_tool_markup() {
let provider = Arc::new(FakeProvider {
Expand Down
11 changes: 10 additions & 1 deletion crates/cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub(crate) enum OutputMode {
Text,
Jsonl,
StreamJson,
RawEventsJsonl,
}

#[derive(Debug, Default, PartialEq, Eq)]
Expand Down Expand Up @@ -133,7 +134,8 @@ Options:
--network Request network access for `sandbox`
--yes Confirm an explicit sandbox request in automation
--jsonl Emit versioned JSON Lines
--stream-json Emit raw versioned event JSON Lines
--stream-json Emit legacy-compatible streaming JSON Lines
--raw-events-jsonl Emit every raw LiveEvent as versioned JSON Lines
--output-last-message <file>
--output-schema <file> Require a JSON response matching the schema
--timeout <seconds> Non-interactive timeout (default: 600)
Expand Down Expand Up @@ -323,6 +325,13 @@ pub(crate) fn parse_args(values: impl IntoIterator<Item = String>) -> Result<Opt
parsed.output_mode = OutputMode::StreamJson;
parsed.print = true;
}
"--raw-events-jsonl" => {
if parsed.output_mode != OutputMode::Text {
return Err(anyhow!("choose only one machine output mode"));
}
parsed.output_mode = OutputMode::RawEventsJsonl;
parsed.print = true;
}
"--output-last-message" => {
parsed.output_last_message = Some(PathBuf::from(
args.next()
Expand Down
Loading
Loading