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
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "a3s-code-core"
version = "4.2.7"
version = "4.2.8"
edition = "2021"
authors = ["A3S Lab Team"]
license = "MIT"
Expand Down
18 changes: 17 additions & 1 deletion core/src/llm/anthropic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,23 @@ impl AnthropicClient {
match result {
Ok(r) => r,
Err(e) => {
return AttemptOutcome::Fatal(anyhow::anyhow!("HTTP request failed: {}", e));
// A transient network error (timeout, reset,
// mid-flight drop — common on throttled
// endpoints) carries no HTTP status. Retry it
// with backoff like 429/5xx instead of failing
// the turn; a real fatal error still bails.
return if crate::retry::is_transient_error(&e) {
AttemptOutcome::Retryable {
status: reqwest::StatusCode::SERVICE_UNAVAILABLE,
body: format!("network error: {e}"),
retry_after: None,
}
} else {
AttemptOutcome::Fatal(anyhow::anyhow!(
"HTTP request failed: {}",
e
))
};
}
}
}
Expand Down
18 changes: 17 additions & 1 deletion core/src/llm/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,23 @@ impl OpenAiClient {
match result {
Ok(r) => r,
Err(e) => {
return AttemptOutcome::Fatal(anyhow::anyhow!("HTTP request failed: {}", e));
// Transient network error (timeout, reset,
// mid-flight drop — common on throttled
// endpoints): retry with backoff like 429/5xx
// instead of failing the turn. GLM and other
// OpenAI-compatible endpoints hit this most.
return if crate::retry::is_transient_error(&e) {
AttemptOutcome::Retryable {
status: reqwest::StatusCode::SERVICE_UNAVAILABLE,
body: format!("network error: {e}"),
retry_after: None,
}
} else {
AttemptOutcome::Fatal(anyhow::anyhow!(
"HTTP request failed: {}",
e
))
};
}
}
}
Expand Down
47 changes: 47 additions & 0 deletions core/src/retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,59 @@ where
)
}

/// Heuristic: is this a transient *network* error worth retrying — a timeout,
/// connection reset/refused/closed, broken pipe, DNS failure, or a request that
/// dropped mid-flight? These carry no HTTP status (so `is_retryable_status`
/// can't see them), yet Claude Code retries them just like 429/5xx. We only have
/// the error's rendered text (a `CodeError`/`anyhow::Error` chain) to classify.
pub fn is_transient_error<E: std::fmt::Display>(e: &E) -> bool {
let m = e.to_string().to_lowercase();
[
"timed out",
"timeout",
"connection reset",
"connection refused",
"connection closed",
"connection aborted",
"connection error",
"broken pipe",
"reset by peer",
"error sending request",
"incomplete message",
"unexpected eof",
"dns error",
"unreachable",
"tls handshake",
"request error",
"body error",
"decoding response",
"channel closed",
"stream closed",
]
.iter()
.any(|p| m.contains(p))
}

#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;

#[test]
fn transient_error_classification() {
let t = |s: &str| is_transient_error(&anyhow::anyhow!("{s}"));
// Transient network errors → retry.
assert!(t("error sending request for url: operation timed out"));
assert!(t("connection reset by peer"));
assert!(t("LLM error: connection closed before message completed"));
assert!(t("tls handshake eof"));
// Real application errors → do NOT retry.
assert!(!t("invalid api key"));
assert!(!t("model not found"));
assert!(!t("context length exceeded"));
}

// ========================================================================
// RetryConfig unit tests
// ========================================================================
Expand Down
4 changes: 2 additions & 2 deletions sdk/node/Cargo.lock

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

4 changes: 2 additions & 2 deletions sdk/node/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "a3s-code-node"
version = "4.2.7"
version = "4.2.8"
edition = "2021"
authors = ["A3S Lab Team"]
license = "MIT"
Expand All @@ -11,7 +11,7 @@ description = "A3S Code Node.js bindings - Native addon via napi-rs"
crate-type = ["cdylib"]

[dependencies]
a3s-code-core = { version = "4.2.7", path = "../../core", features = ["ahp", "s3", "serve"] }
a3s-code-core = { version = "4.2.8", path = "../../core", features = ["ahp", "s3", "serve"] }
napi = { version = "2", features = ["async", "napi6", "serde-json"] }
napi-derive = "2"
tokio = { version = "1.35", features = ["full"] }
Expand Down
14 changes: 7 additions & 7 deletions sdk/node/examples/package-lock.json

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

16 changes: 8 additions & 8 deletions sdk/node/package-lock.json

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

14 changes: 7 additions & 7 deletions sdk/node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@a3s-lab/code",
"version": "4.2.7",
"version": "4.2.8",
"description": "A3S Code - Native Node.js bindings for the coding-agent runtime",
"main": "index.js",
"types": "index.d.ts",
Expand Down Expand Up @@ -43,11 +43,11 @@
"test:helpers": "node test-helpers.mjs"
},
"optionalDependencies": {
"@a3s-lab/code-darwin-arm64": "4.2.7",
"@a3s-lab/code-linux-x64-gnu": "4.2.7",
"@a3s-lab/code-linux-x64-musl": "4.2.7",
"@a3s-lab/code-linux-arm64-gnu": "4.2.7",
"@a3s-lab/code-linux-arm64-musl": "4.2.7",
"@a3s-lab/code-win32-x64-msvc": "4.2.7"
"@a3s-lab/code-darwin-arm64": "4.2.8",
"@a3s-lab/code-linux-x64-gnu": "4.2.8",
"@a3s-lab/code-linux-x64-musl": "4.2.8",
"@a3s-lab/code-linux-arm64-gnu": "4.2.8",
"@a3s-lab/code-linux-arm64-musl": "4.2.8",
"@a3s-lab/code-win32-x64-msvc": "4.2.8"
}
}
4 changes: 3 additions & 1 deletion sdk/node/ptc_soak.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ function script(n) {
return JSON.stringify(res);
}`;
}
// NO explicit limits → exercises the DEFAULT script timeout. With 50-word
// subtasks (each >30s on this model), 4.2.6's 30s default would time out; 4.2.7
// gives delegation-capable scripts 10min, so it should complete.
const promptFor = (n) =>
'Call the `program` tool exactly once, now, with these arguments, then stop.\n\nArguments:\n' +
JSON.stringify({
type: 'script',
language: 'javascript',
source: script(n),
limits: { timeoutMs: 180000, maxToolCalls: 20 },
});

const agent = await Agent.create(CONFIG);
Expand Down
2 changes: 1 addition & 1 deletion sdk/python-bootstrap/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "a3s-code"
# Keep in sync with crates/code core release. The bootstrap loader fetches
# the matching native wheel from `https://github.com/AI45Lab/Code/releases/tag/v<version>`
# at import time.
version = "4.2.7"
version = "4.2.8"
description = "A3S Code Python SDK — pure-Python bootstrap that fetches the native wheel from GitHub Releases"
readme = "README.md"
license = {text = "MIT"}
Expand Down
2 changes: 1 addition & 1 deletion sdk/python-bootstrap/src/a3s_code/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

# Version is the bootstrap's own version, which equals the matching native
# wheel version on GH Releases. Bumped by the release workflow.
__version__ = "4.2.7"
__version__ = "4.2.8"

_DEFAULT_BASE_URL = "https://github.com/AI45Lab/Code/releases/download"
_REQUEST_TIMEOUT_S = 120
Expand Down
4 changes: 2 additions & 2 deletions sdk/python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "a3s-code-py"
version = "4.2.7"
version = "4.2.8"
edition = "2021"
authors = ["A3S Lab Team"]
license = "MIT"
Expand All @@ -12,7 +12,7 @@ name = "a3s_code"
crate-type = ["cdylib"]

[dependencies]
a3s-code-core = { version = "4.2.7", path = "../../core", features = ["ahp", "s3", "serve"] }
a3s-code-core = { version = "4.2.8", path = "../../core", features = ["ahp", "s3", "serve"] }
pyo3 = "0.23"
tokio = { version = "1.35", features = ["full"] }
serde_json = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion sdk/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "maturin"

[project]
name = "a3s-code"
version = "4.2.7"
version = "4.2.8"
description = "A3S Code - Native Python bindings for the coding-agent runtime"
readme = "README.md"
license = {text = "MIT"}
Expand Down
Loading