mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-13 19:44:53 +08:00
Remove the deprecated Claude subscription login path and restore a green Rust workspace
ROADMAP #37 was still open even though several earlier backlog items were already closed. This change removes the local login/logout surface, stops startup auth resolution from treating saved OAuth credentials as a supported path, and updates diagnostics/help to point users at ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN only. While proving the change with the user-requested workspace gates, clippy surfaced additional pre-existing warning failures across the Rust workspace. Those were cleaned up in-place so the required `cargo fmt`, `cargo clippy --workspace --all-targets -- -D warnings`, and `cargo test --workspace` sequence now passes end to end. Constraint: User explicitly required full-workspace fmt/clippy/test before commit/push Constraint: Existing dirty leader worktree had to be stashed before attempted OMX team worktree launch Rejected: Keep login/logout but hide them from help | left unsupported auth flow and saved OAuth fallback intact Rejected: Stop after ROADMAP #37 targeted tests | did not satisfy required full-workspace verification gate Confidence: medium Scope-risk: moderate Reversibility: clean Directive: Do not reintroduce saved OAuth as a silent Anthropic startup fallback without an explicit supported auth policy Tested: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace Not-tested: Remote push effects beyond origin/main update
This commit is contained in:
@@ -58,10 +58,10 @@ impl OpenAiCompatConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Alibaba DashScope compatible-mode endpoint (Qwen family models).
|
||||
/// Alibaba `DashScope` compatible-mode endpoint (Qwen family models).
|
||||
/// Uses the OpenAI-compatible REST shape at /compatible-mode/v1.
|
||||
/// Requested via Discord #clawcode-get-help: native Alibaba API for
|
||||
/// higher rate limits than going through OpenRouter.
|
||||
/// higher rate limits than going through `OpenRouter`.
|
||||
#[must_use]
|
||||
pub const fn dashscope() -> Self {
|
||||
Self {
|
||||
@@ -170,7 +170,7 @@ impl OpenAiCompatClient {
|
||||
.to_string();
|
||||
let code = err_obj
|
||||
.get("code")
|
||||
.and_then(|c| c.as_u64())
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.map(|c| c as u16);
|
||||
return Err(ApiError::Api {
|
||||
status: reqwest::StatusCode::from_u16(code.unwrap_or(400))
|
||||
@@ -750,7 +750,7 @@ struct ErrorBody {
|
||||
}
|
||||
|
||||
/// Returns true for models known to reject tuning parameters like temperature,
|
||||
/// top_p, frequency_penalty, and presence_penalty. These are typically
|
||||
/// `top_p`, `frequency_penalty`, and `presence_penalty`. These are typically
|
||||
/// reasoning/chain-of-thought models with fixed sampling.
|
||||
fn is_reasoning_model(model: &str) -> bool {
|
||||
let lowered = model.to_ascii_lowercase();
|
||||
@@ -974,12 +974,11 @@ fn sanitize_tool_message_pairing(messages: Vec<Value>) -> Vec<Value> {
|
||||
}
|
||||
let paired = preceding
|
||||
.and_then(|m| m.get("tool_calls").and_then(|tc| tc.as_array()))
|
||||
.map(|tool_calls| {
|
||||
.is_some_and(|tool_calls| {
|
||||
tool_calls
|
||||
.iter()
|
||||
.any(|tc| tc.get("id").and_then(|v| v.as_str()) == Some(tool_call_id))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
});
|
||||
if !paired {
|
||||
drop_indices.insert(i);
|
||||
}
|
||||
@@ -1008,7 +1007,7 @@ fn flatten_tool_result_content(content: &[ToolResultContentBlock]) -> String {
|
||||
|
||||
/// Recursively ensure every object-type node in a JSON Schema has
|
||||
/// `"properties"` (at least `{}`) and `"additionalProperties": false`.
|
||||
/// The OpenAI `/responses` endpoint validates schemas strictly and rejects
|
||||
/// The `OpenAI` `/responses` endpoint validates schemas strictly and rejects
|
||||
/// objects that omit these fields; `/chat/completions` is lenient but also
|
||||
/// accepts them, so we normalise unconditionally.
|
||||
fn normalize_object_schema(schema: &mut Value) {
|
||||
@@ -1173,7 +1172,7 @@ fn parse_sse_frame(
|
||||
.to_string();
|
||||
let code = err_obj
|
||||
.get("code")
|
||||
.and_then(|c| c.as_u64())
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.map(|c| c as u16);
|
||||
let status = reqwest::StatusCode::from_u16(code.unwrap_or(400))
|
||||
.unwrap_or(reqwest::StatusCode::BAD_REQUEST);
|
||||
@@ -1185,7 +1184,7 @@ fn parse_sse_frame(
|
||||
.map(str::to_owned),
|
||||
message: Some(msg),
|
||||
request_id: None,
|
||||
body: payload.to_string(),
|
||||
body: payload.clone(),
|
||||
retryable: false,
|
||||
});
|
||||
}
|
||||
@@ -1642,6 +1641,16 @@ mod tests {
|
||||
/// Before the fix this produced: `invalid type: null, expected a sequence`.
|
||||
#[test]
|
||||
fn delta_with_null_tool_calls_deserializes_as_empty_vec() {
|
||||
use super::deserialize_null_as_empty_vec;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Delta {
|
||||
content: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
|
||||
tool_calls: Vec<super::DeltaToolCall>,
|
||||
}
|
||||
|
||||
// Simulate the exact shape observed in the wild (gaebal-gajae repro 2026-04-09)
|
||||
let json = r#"{
|
||||
"content": "",
|
||||
@@ -1650,15 +1659,6 @@ mod tests {
|
||||
"role": "assistant",
|
||||
"tool_calls": null
|
||||
}"#;
|
||||
|
||||
use super::deserialize_null_as_empty_vec;
|
||||
#[allow(dead_code)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Delta {
|
||||
content: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
|
||||
tool_calls: Vec<super::DeltaToolCall>,
|
||||
}
|
||||
let delta: Delta = serde_json::from_str(json)
|
||||
.expect("delta with tool_calls:null must deserialize without error");
|
||||
assert!(
|
||||
@@ -1670,7 +1670,7 @@ mod tests {
|
||||
/// Regression: when building a multi-turn request where a prior assistant
|
||||
/// turn has no tool calls, the serialized assistant message must NOT include
|
||||
/// `tool_calls: []`. Some providers reject requests that carry an empty
|
||||
/// tool_calls array on assistant turns (gaebal-gajae repro 2026-04-09).
|
||||
/// `tool_calls` array on assistant turns (gaebal-gajae repro 2026-04-09).
|
||||
#[test]
|
||||
fn assistant_message_without_tool_calls_omits_tool_calls_field() {
|
||||
use crate::types::{InputContentBlock, InputMessage};
|
||||
@@ -1695,13 +1695,12 @@ mod tests {
|
||||
.expect("assistant message must be present");
|
||||
assert!(
|
||||
assistant_msg.get("tool_calls").is_none(),
|
||||
"assistant message without tool calls must omit tool_calls field: {:?}",
|
||||
assistant_msg
|
||||
"assistant message without tool calls must omit tool_calls field: {assistant_msg:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression: assistant messages WITH tool calls must still include
|
||||
/// the tool_calls array (normal multi-turn tool-use flow).
|
||||
/// the `tool_calls` array (normal multi-turn tool-use flow).
|
||||
#[test]
|
||||
fn assistant_message_with_tool_calls_includes_tool_calls_field() {
|
||||
use crate::types::{InputContentBlock, InputMessage};
|
||||
@@ -1733,7 +1732,7 @@ mod tests {
|
||||
assert_eq!(tool_calls.as_array().unwrap().len(), 1);
|
||||
}
|
||||
|
||||
/// Orphaned tool messages (no preceding assistant tool_calls) must be
|
||||
/// Orphaned tool messages (no preceding assistant `tool_calls`) must be
|
||||
/// dropped by the request-builder sanitizer. Regression for the second
|
||||
/// layer of the tool-pairing invariant fix (gaebal-gajae 2026-04-10).
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user