Compare commits

..
2 Commits
2 changed files with 109 additions and 0 deletions

No files matched your search

+21
View File
@@ -170,6 +170,27 @@ 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/`.
+88
View File
@@ -9896,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");