mirror of
https://github.com/instructkr/claw-code.git
synced 2026-11-07 09:37:54 +01:00
Compare commits
6
Commits
51cfca114c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca48ec7500 | ||
|
|
ab8561dc37 | ||
|
|
747e160b59 | ||
|
|
6ac59ab90b | ||
|
|
67e5783894 | ||
|
|
db58b190bd |
No files matched your search
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.1.11] - 2026-11-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- NotebookEdit no longer reuses an existing cell id for a newly inserted cell after a cell has been deleted, so later edits by id target the intended cell (#3371)
|
||||
|
||||
## [0.1.10] - 2026-11-02
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -149,6 +149,48 @@ curl -sS "$OPENAI_BASE_URL/models" \
|
||||
|
||||
For local configuration, see your gateway's documentation for the correct base URL, API key format, and available model IDs. The same OPENAI_BASE_URL + OPENAI_API_KEY pattern applies to any OpenAI-compatible remote gateway.
|
||||
|
||||
## LLMTR
|
||||
|
||||
[LLMTR](https://llmtr.com) is an OpenAI-compatible gateway. Use the same client: set `OPENAI_BASE_URL` to `https://llmtr.com/v1` and `OPENAI_API_KEY` to a bearer key (`llmtr-...`).
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="https://llmtr.com/v1"
|
||||
export OPENAI_API_KEY="llmtr-..."
|
||||
claw --model "local/llmtr/qwen3-8-27b" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
Send the catalog id unchanged. `llmtr/qwen3-8-27b` already starts with the gateway name, so that prefix stays on the wire. The `local/` hatch only selects the OpenAI-compatible transport and is removed before the request. A bare `qwen/` or `qwen-` name is a DashScope route and will not reach LLMTR, so vendor-prefixed ids need the same hatch: `local/<vendor>/<model>`.
|
||||
|
||||
Claw asks for up to 64,000 output tokens on these routes. LLMTR rejects a request whose `max_tokens` is above the model's output limit, so pick a model that allows at least that much. `llmtr/qwen3-8-27b` allows 262,144. Each model's limit is listed as `top_provider.max_completion_tokens` in the catalog below.
|
||||
|
||||
The public model list does not need a key:
|
||||
|
||||
```bash
|
||||
curl -sS "https://llmtr.com/v1/models" \
|
||||
| jq -r '.data[] | "\(.id)\t\(.top_provider.max_completion_tokens)"'
|
||||
```
|
||||
|
||||
## UberLLM
|
||||
|
||||
[UberLLM](https://uberllm.dev) is a pay-as-you-go OpenAI-compatible gateway (prepaid, no free tier). Set `OPENAI_BASE_URL` to `https://api.uberllm.dev/v1` and `OPENAI_API_KEY` to a bearer key (`ull_...`).
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="https://api.uberllm.dev/v1"
|
||||
export OPENAI_API_KEY="ull_..."
|
||||
claw --model "local/deepseek/deepseek-v4-flash" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
UberLLM ids are vendor-prefixed (`deepseek/deepseek-v4-flash`, `moonshotai/kimi-k2.6`, `qwen/qwen3.8-flash`). Put `local/` in front of every one: the hatch selects the OpenAI-compatible transport and is removed, so the catalog id goes out unchanged. Some prefixes would otherwise pick a native route; a bare `qwen/qwen3.8-flash`, for example, goes to DashScope and asks for `DASHSCOPE_API_KEY`.
|
||||
|
||||
Claw asks for up to 64,000 output tokens on these routes. UberLLM does not reject that on a model with a smaller output limit (for example `aion-labs/aion-3-0-mini`, 32,768); the answer is simply bounded by the model's limit, listed as `max_output_tokens` in the catalog below.
|
||||
|
||||
The public model list does not need a key:
|
||||
|
||||
```bash
|
||||
curl -sS "https://api.uberllm.dev/v1/models" \
|
||||
| jq -r '.data[] | "\(.id)\t\(.max_output_tokens)"'
|
||||
```
|
||||
|
||||
## Local skills install from disk
|
||||
|
||||
Skills are discovered from Claw skill roots such as `.claw/skills/` in a workspace and `~/.claw/skills/` for user-level installs. Legacy `.codex/skills/` roots may also be scanned for compatibility, but new local Claw projects should prefer `.claw/skills/`.
|
||||
|
||||
Generated
+11
-11
@@ -93,7 +93,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "api"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"reqwest",
|
||||
@@ -478,7 +478,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "claw-analog"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"api",
|
||||
"clap",
|
||||
@@ -498,7 +498,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "claw-rag-service"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"blake3",
|
||||
@@ -531,7 +531,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "commands"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"plugins",
|
||||
"runtime",
|
||||
@@ -540,7 +540,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "compat-harness"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"commands",
|
||||
"runtime",
|
||||
@@ -1568,7 +1568,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mock-anthropic-service"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"api",
|
||||
"serde_json",
|
||||
@@ -1761,7 +1761,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "plugins"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2136,7 +2136,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "runtime"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"plugins",
|
||||
@@ -2261,7 +2261,7 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "rusty-claude-cli"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"api",
|
||||
"commands",
|
||||
@@ -2622,7 +2622,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "telemetry"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2902,7 +2902,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tools"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"api",
|
||||
"aspect-core",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ members = ["crates/*"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
@@ -82,7 +82,7 @@ async fn send_message_posts_json_and_parses_response() {
|
||||
);
|
||||
assert_eq!(
|
||||
request.headers.get("user-agent").map(String::as_str),
|
||||
Some("claude-code/0.1.10")
|
||||
Some("claude-code/0.1.11")
|
||||
);
|
||||
assert_eq!(
|
||||
request.headers.get("anthropic-beta").map(String::as_str),
|
||||
|
||||
@@ -5997,7 +5997,7 @@ fn execute_notebook_edit(input: NotebookEditInput) -> Result<NotebookEditOutput,
|
||||
NotebookEditMode::Insert => {
|
||||
let resolved_cell_type = resolved_cell_type
|
||||
.ok_or_else(|| String::from("insert mode requires a cell type"))?;
|
||||
let new_id = make_cell_id(cells.len());
|
||||
let new_id = make_cell_id(cells);
|
||||
let new_cell = build_notebook_cell(&new_id, resolved_cell_type, &new_source);
|
||||
let insert_at = target_index.map_or(cells.len(), |index| index + 1);
|
||||
cells.insert(insert_at, new_cell);
|
||||
@@ -7022,8 +7022,11 @@ fn format_notebook_edit_mode(mode: NotebookEditMode) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_cell_id(index: usize) -> String {
|
||||
format!("cell-{}", index + 1)
|
||||
fn make_cell_id(cells: &[Value]) -> String {
|
||||
(cells.len() + 1..)
|
||||
.map(|n| format!("cell-{n}"))
|
||||
.find(|id| !cells.iter().any(|cell| cell["id"] == id.as_str()))
|
||||
.expect("there is always a free id")
|
||||
}
|
||||
|
||||
fn parse_skill_description(contents: &str) -> Option<String> {
|
||||
@@ -9869,11 +9872,23 @@ mod tests {
|
||||
assert!(deleted_output["cell_type"].is_null());
|
||||
assert_eq!(deleted_output["new_source"], "");
|
||||
|
||||
let reinserted = execute_tool(
|
||||
"NotebookEdit",
|
||||
&json!({
|
||||
"notebook_path": path.display().to_string(),
|
||||
"new_source": "print(4)\n",
|
||||
"edit_mode": "insert"
|
||||
}),
|
||||
)
|
||||
.expect("NotebookEdit insert after delete should succeed");
|
||||
let reinserted_output: serde_json::Value = serde_json::from_str(&reinserted).expect("json");
|
||||
assert_eq!(reinserted_output["cell_id"], "cell-4");
|
||||
|
||||
let final_notebook: serde_json::Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(&path).expect("read notebook"))
|
||||
.expect("valid notebook json");
|
||||
let cells = final_notebook["cells"].as_array().expect("cells array");
|
||||
assert_eq!(cells.len(), 2);
|
||||
assert_eq!(cells.len(), 3);
|
||||
assert_eq!(cells[0]["cell_type"], "markdown");
|
||||
assert!(cells[0].get("outputs").is_none());
|
||||
assert_eq!(cells[1]["cell_type"], "code");
|
||||
@@ -9881,6 +9896,94 @@ mod tests {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_edit_replace_switches_cell_type_and_rejects_unknown_ids() {
|
||||
let path = temp_path("retype.ipynb");
|
||||
std::fs::write(
|
||||
&path,
|
||||
r##"{
|
||||
"cells": [
|
||||
{"cell_type": "markdown", "id": "cell-2", "metadata": {}, "source": ["# title\n"]}
|
||||
],
|
||||
"metadata": {},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}"##,
|
||||
)
|
||||
.expect("write notebook");
|
||||
|
||||
let kept_type = execute_tool(
|
||||
"NotebookEdit",
|
||||
&json!({
|
||||
"notebook_path": path.display().to_string(),
|
||||
"cell_id": "cell-2",
|
||||
"new_source": "# renamed\n"
|
||||
}),
|
||||
)
|
||||
.expect("NotebookEdit default replace should succeed");
|
||||
let kept_output: serde_json::Value = serde_json::from_str(&kept_type).expect("json");
|
||||
assert_eq!(kept_output["edit_mode"], "replace");
|
||||
assert_eq!(kept_output["cell_type"], "markdown");
|
||||
assert_eq!(kept_output["language"], "python");
|
||||
|
||||
let retyped = execute_tool(
|
||||
"NotebookEdit",
|
||||
&json!({
|
||||
"notebook_path": path.display().to_string(),
|
||||
"cell_id": "cell-2",
|
||||
"new_source": "print(1)\n",
|
||||
"cell_type": "code",
|
||||
"edit_mode": "replace"
|
||||
}),
|
||||
)
|
||||
.expect("NotebookEdit replace to code should succeed");
|
||||
let retyped_output: serde_json::Value = serde_json::from_str(&retyped).expect("json");
|
||||
assert_eq!(retyped_output["cell_type"], "code");
|
||||
|
||||
let inserted = execute_tool(
|
||||
"NotebookEdit",
|
||||
&json!({
|
||||
"notebook_path": path.display().to_string(),
|
||||
"new_source": "print(2)\n",
|
||||
"edit_mode": "insert"
|
||||
}),
|
||||
)
|
||||
.expect("NotebookEdit insert should succeed");
|
||||
let inserted_output: serde_json::Value = serde_json::from_str(&inserted).expect("json");
|
||||
assert_eq!(inserted_output["cell_id"], "cell-3");
|
||||
|
||||
let before_missing = std::fs::read_to_string(&path).expect("read notebook");
|
||||
let missing = execute_tool(
|
||||
"NotebookEdit",
|
||||
&json!({
|
||||
"notebook_path": path.display().to_string(),
|
||||
"cell_id": "missing",
|
||||
"new_source": "print(3)\n",
|
||||
"edit_mode": "replace"
|
||||
}),
|
||||
)
|
||||
.expect_err("unknown cell id should fail");
|
||||
assert!(missing.contains("Cell id not found: missing"));
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(&path).expect("read notebook"),
|
||||
before_missing
|
||||
);
|
||||
|
||||
let final_notebook: serde_json::Value =
|
||||
serde_json::from_str(&before_missing).expect("valid notebook json");
|
||||
let cells = final_notebook["cells"].as_array().expect("cells array");
|
||||
assert_eq!(cells.len(), 2);
|
||||
assert_eq!(cells[0]["cell_type"], "code");
|
||||
assert_eq!(cells[0]["source"], json!(["print(1)\n"]));
|
||||
assert_eq!(cells[0]["outputs"], json!([]));
|
||||
assert_eq!(
|
||||
cells[0].get("execution_count"),
|
||||
Some(&serde_json::Value::Null)
|
||||
);
|
||||
assert_eq!(cells[1]["id"], "cell-3");
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_edit_rejects_invalid_inputs() {
|
||||
let text_path = temp_path("notebook.txt");
|
||||
|
||||
Reference in new issue
Block a user