sigit-code 0.1.1__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sigit_code-0.1.2/.agents/skills/tool-calling/SKILL.md +290 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.github/workflows/release-homebrew.yml +1 -1
- {sigit_code-0.1.1 → sigit_code-0.1.2}/Cargo.lock +9 -8
- {sigit_code-0.1.1 → sigit_code-0.1.2}/Cargo.toml +4 -3
- {sigit_code-0.1.1 → sigit_code-0.1.2}/PKG-INFO +1 -1
- {sigit_code-0.1.1 → sigit_code-0.1.2}/README.md +13 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/src/chat.rs +27 -5
- sigit_code-0.1.2/src/main.rs +874 -0
- sigit_code-0.1.2/src/setup.rs +728 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/src/tools.rs +472 -76
- sigit_code-0.1.1/.agents/skills/tool-calling/SKILL.md +0 -283
- sigit_code-0.1.1/src/main.rs +0 -527
- sigit_code-0.1.1/src/setup.rs +0 -89
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.agents/AGENTS.md +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.agents/skills/agent-client-protocol/SKILL.md +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.agents/skills/ai-assisted-coding/SKILL.md +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.github/workflows/ci.yml +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.github/workflows/release-github.yml +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.github/workflows/release-npm.yml +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.github/workflows/release-pypi.yml +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.gitignore +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/.nvmrc +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/LICENSE +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/README.md.tmpl +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/package-main.json.tmpl +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/package.json.tmpl +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/scripts/render-main-package.cjs +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/scripts/render-platform-package.cjs +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/sigit/.gitignore +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/sigit/README.md +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/sigit/package.json +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/sigit/src/index.ts +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/npm/sigit/tsconfig.json +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/pypi/README.md +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/pypi/pyproject.toml +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/pyproject.toml +0 -0
- {sigit_code-0.1.1 → sigit_code-0.1.2}/rust-toolchain.toml +0 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Skill: Tool Calling in siGit Code
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
siGit Code supports **agentic tool calling** — the LLM invokes tools (read/write files, run commands, read websites) to operate on the user's codebase. This works in both **interactive TUI mode** and **ACP server mode** (Zed editor).
|
|
6
|
+
|
|
7
|
+
Tool calling spans three layers:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
siGit (agent loop + tool execution)
|
|
11
|
+
→ onde (ChatEngine with tool-aware API)
|
|
12
|
+
→ mistral.rs (model inference + tool call parsing)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Model Requirement
|
|
18
|
+
|
|
19
|
+
**Only Qwen 3 supports tool calling.** Qwen 2.5 does NOT — mistral.rs only has a parser for Qwen 3's `<tool_call>...</tool_call>` XML format.
|
|
20
|
+
|
|
21
|
+
| Model | Constructor | Size | Tool calling | Default |
|
|
22
|
+
|-------|-----------|------|:---:|:---:|
|
|
23
|
+
| Qwen 3 8B (Q4_K_M) | `GgufModelConfig::qwen3_8b()` | ~5 GB | ✅ | ✅ **default** |
|
|
24
|
+
| Qwen 3 4B (Q4_K_M) | `GgufModelConfig::qwen3_4b()` | ~2.7 GB | ✅ | |
|
|
25
|
+
| Qwen 3 1.7B (Q4_K_M) | `GgufModelConfig::qwen3_1_7b()` | ~1.3 GB | ✅ | |
|
|
26
|
+
| Qwen 2.5 Coder 3B | `GgufModelConfig::qwen25_coder_3b()` | ~1.93 GB | ❌ | |
|
|
27
|
+
| Qwen 2.5 Coder 1.5B | `GgufModelConfig::qwen25_coder_1_5b()` | ~941 MB | ❌ | |
|
|
28
|
+
|
|
29
|
+
siGit uses **Qwen 3 8B** by default with `max_tokens: 8192` (set in `main.rs` for both TUI and ACP modes).
|
|
30
|
+
|
|
31
|
+
### Why 8B over 4B
|
|
32
|
+
|
|
33
|
+
4B can't do `edit_file` reliably. It reads a file, then fails to reproduce the exact `old_text` it just saw. This spirals into 7+ retry rounds that burn through `max_tokens` on `<think>` blocks and return nothing. 8B is the smallest model that actually lands edits.
|
|
34
|
+
|
|
35
|
+
### bartowski GGUF naming convention
|
|
36
|
+
|
|
37
|
+
bartowski's repos use the publisher name as a prefix with an underscore:
|
|
38
|
+
|
|
39
|
+
| Constant | Value |
|
|
40
|
+
|----------|-------|
|
|
41
|
+
| `BARTOWSKI_QWEN3_8B_GGUF` | `"bartowski/Qwen_Qwen3-8B-GGUF"` |
|
|
42
|
+
| `QWEN3_8B_GGUF_FILE` | `"Qwen_Qwen3-8B-Q4_K_M.gguf"` |
|
|
43
|
+
| `BARTOWSKI_QWEN3_4B_GGUF` | `"bartowski/Qwen_Qwen3-4B-GGUF"` |
|
|
44
|
+
| `QWEN3_4B_GGUF_FILE` | `"Qwen_Qwen3-4B-Q4_K_M.gguf"` |
|
|
45
|
+
|
|
46
|
+
These constants live in `onde/src/inference/models.rs`.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Tools (9 total)
|
|
51
|
+
|
|
52
|
+
Defined in `sigit/src/tools.rs` via `all_tools()`:
|
|
53
|
+
|
|
54
|
+
| # | Tool | Parameters | Behavior |
|
|
55
|
+
|---|------|-----------|----------|
|
|
56
|
+
| 1 | `read_file` | `path` | Reads file contents, truncates at 10,000 chars |
|
|
57
|
+
| 2 | `create_directory` | `path` | Creates directory and all parents |
|
|
58
|
+
| 3 | `list_directory` | `path` | Lists entries with `[DIR]`/`[FILE]` prefix, dirs first |
|
|
59
|
+
| 4 | `search_files` | `pattern`, `path` (optional) | Recursive regex search, max 50 matches |
|
|
60
|
+
| 5 | `read_website` | `url` | Fetches HTTP/HTTPS, strips HTML, returns text |
|
|
61
|
+
| 6 | `create_file` | `path`, `content` | Creates new file (fails if exists) |
|
|
62
|
+
| 7 | `edit_file` | `path`, `old_text`, `new_text` | Find-and-replace (must match exactly once) |
|
|
63
|
+
| 8 | `delete_file` | `path` | Deletes file or empty directory |
|
|
64
|
+
| 9 | `run_command` | `command`, `cwd` (optional) | Shell command with 120s timeout |
|
|
65
|
+
|
|
66
|
+
### Async handling
|
|
67
|
+
|
|
68
|
+
`execute_tool()` is `async`. Most tools run synchronously, except:
|
|
69
|
+
|
|
70
|
+
- **`read_website`** — uses `tokio::task::spawn_blocking` because `reqwest::blocking::Client` panics inside a tokio runtime ("Cannot start a runtime from within a runtime")
|
|
71
|
+
|
|
72
|
+
### Tool gating by model
|
|
73
|
+
|
|
74
|
+
In TUI mode, `run_inference_task()` takes a `tools_enabled: bool` parameter. When the model's `ModelOption.tool_calling` is `false` (Qwen 2.5), an empty tool list is passed so the model doesn't receive tool schemas it can't use.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Architecture
|
|
79
|
+
|
|
80
|
+
### Layer 1: mistral.rs (model-level)
|
|
81
|
+
|
|
82
|
+
- `RequestBuilder::set_tools(Vec<Tool>)` — attach tool definitions
|
|
83
|
+
- `RequestBuilder::set_tool_choice(ToolChoice::Auto)` — let model decide
|
|
84
|
+
- `QwenParser` detects `<tool_call>...</tool_call>` tags in output
|
|
85
|
+
- Grammar-constrained decoding forces valid JSON inside tool calls
|
|
86
|
+
- `<think>...</think>` reasoning is separated from tool calls by the reasoning parser
|
|
87
|
+
- Works identically for GGUF and full-precision models
|
|
88
|
+
|
|
89
|
+
### Layer 2: onde (engine-level)
|
|
90
|
+
|
|
91
|
+
#### Key types (`onde/src/inference/types.rs`)
|
|
92
|
+
|
|
93
|
+
| Type | Purpose |
|
|
94
|
+
|------|---------|
|
|
95
|
+
| `ToolDefinition` | `{ name, description, parameters_schema: String }` |
|
|
96
|
+
| `ToolCallRequest` | `{ id, function_name, arguments: String }` |
|
|
97
|
+
| `ToolResult` | `{ tool_call_id, content: String }` |
|
|
98
|
+
| `ToolAwareResult` | `{ text, tool_calls: Vec<ToolCallRequest>, duration_secs, ... }` |
|
|
99
|
+
|
|
100
|
+
#### Key methods (`onde/src/inference/engine.rs`)
|
|
101
|
+
|
|
102
|
+
| Method | Purpose |
|
|
103
|
+
|--------|---------|
|
|
104
|
+
| `send_message_with_tools(msg, &[ToolDefinition])` | Returns `ToolAwareResult` with possible tool calls |
|
|
105
|
+
| `send_tool_results(Vec<ToolResult>, Option<&[ToolDefinition]>)` | Feed results back; `None` forces text response |
|
|
106
|
+
|
|
107
|
+
#### Internal details
|
|
108
|
+
|
|
109
|
+
- `attach_tools()` converts `ToolDefinition` → mistral.rs `Tool`, sets `ToolChoice::Auto` and `strict: Some(true)`
|
|
110
|
+
- `parse_tool_calls()` extracts tool calls from `choice.message.tool_calls`, generates fallback IDs if empty
|
|
111
|
+
- `replay_history_with_tools()` uses `.enumerate()` for correct sequential `index` values
|
|
112
|
+
- Malformed `parameters_schema` JSON logs a warning instead of silently producing empty params
|
|
113
|
+
- Malformed tool call `arguments` JSON logs a warning for debugging
|
|
114
|
+
|
|
115
|
+
### Layer 3: siGit (agent-level)
|
|
116
|
+
|
|
117
|
+
#### ACP session handling (`src/main.rs`)
|
|
118
|
+
|
|
119
|
+
All session handlers (`load_session`, `fork_session`, `new_session`) do:
|
|
120
|
+
|
|
121
|
+
1. **Store `args.cwd`** in `session_cwd: Mutex<Option<PathBuf>>`
|
|
122
|
+
2. **`std::env::set_current_dir(&args.cwd)`** — so relative paths in tool calls resolve correctly
|
|
123
|
+
3. **`engine.clear_history()`** — siGit doesn't persist sessions
|
|
124
|
+
4. **`engine.push_history(ChatMessage::system(...))`** — injects: *"The user's project working directory is {cwd}. Always use absolute paths..."*
|
|
125
|
+
|
|
126
|
+
Without step 4, the model uses the process `cwd` (often `$HOME`) and creates files in the wrong directory.
|
|
127
|
+
|
|
128
|
+
#### ACP content block handling (`prompt()`)
|
|
129
|
+
|
|
130
|
+
The `prompt()` handler processes all ACP content block types:
|
|
131
|
+
|
|
132
|
+
- **`ContentBlock::Text`** — passed through as-is
|
|
133
|
+
- **`ContentBlock::Resource` (EmbeddedResource)** — `TextResourceContents` inlined as `--- {uri} ---\n{text}\n--- end ---`
|
|
134
|
+
- **`ContentBlock::ResourceLink`** — `file://` URIs are read from disk. **Line range fragments** like `#L207:219` are parsed: the `#` fragment is stripped from the path, and only lines 207–219 are extracted and sent to the model
|
|
135
|
+
|
|
136
|
+
Example: Zed sends `@ index.html (207:219)` as:
|
|
137
|
+
```
|
|
138
|
+
ResourceLink(name="index.html (207:219)", uri="file:///path/to/index.html#L207:219")
|
|
139
|
+
```
|
|
140
|
+
siGit parses this into path `/path/to/index.html` + lines 207–219.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## The Agentic Loop
|
|
145
|
+
|
|
146
|
+
Both ACP mode (`SiGitAgent::prompt()`) and TUI mode (`run_inference_task()`) implement:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
1. engine.send_message_with_tools(user_text, &tools) → ToolAwareResult
|
|
150
|
+
2. while result.tool_calls is non-empty AND round < MAX_TOOL_ROUNDS (10):
|
|
151
|
+
a. For each tool_call:
|
|
152
|
+
- Log: → tool_name(arguments)
|
|
153
|
+
- Execute: tools::execute_tool(name, arguments).await
|
|
154
|
+
- Log: ← N chars
|
|
155
|
+
- Collect ToolResult { tool_call_id, content }
|
|
156
|
+
b. Decide next_tools:
|
|
157
|
+
- round < MAX_TOOL_ROUNDS → Some(&tools) (allow more calls)
|
|
158
|
+
- else → None (force text response)
|
|
159
|
+
c. engine.send_tool_results(results, next_tools) → ToolAwareResult
|
|
160
|
+
3. Send final result.text to user
|
|
161
|
+
- Empty reply after tool rounds → log warning (ACP) or show error (TUI)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## System Prompt
|
|
167
|
+
|
|
168
|
+
The `SYSTEM_PROMPT` in `main.rs` (~122 lines) includes critical instructions:
|
|
169
|
+
|
|
170
|
+
- **Never tell the user to run commands** — use `run_command` tool instead
|
|
171
|
+
- **Can access websites** — use `read_website` tool (overrides RLHF refusal training)
|
|
172
|
+
- **Prefer absolute paths** in all tool arguments
|
|
173
|
+
- **Git operations** — always use `run_command` with absolute cwd
|
|
174
|
+
- **smbCloud domain knowledge** — auth boundaries, deploy flows, project structure
|
|
175
|
+
|
|
176
|
+
The session `cwd` is injected as a separate system message at session creation time (not part of the static prompt).
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Model Cache
|
|
181
|
+
|
|
182
|
+
Models are stored in the shared Onde App Group container on macOS:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
~/Library/Group Containers/group.com.ondeinference.apps/models/hub/
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`setup.rs` sets `HF_HOME` and `HF_HUB_CACHE` to point there at startup, so siGit reuses models downloaded by the Onde desktop app (and vice versa).
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Adding a New Tool
|
|
193
|
+
|
|
194
|
+
1. Add an `AgentTool` entry to `all_tools()` in `src/tools.rs`
|
|
195
|
+
2. Add a match arm to `execute_tool()` — use `spawn_blocking` if the implementation blocks
|
|
196
|
+
3. Write `exec_your_tool(arguments: &str) -> String`
|
|
197
|
+
4. Update `test_all_tools_count` test (currently expects 9)
|
|
198
|
+
|
|
199
|
+
No changes needed in onde or mistral.rs — tool definitions are passed dynamically.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Adding a New Model
|
|
204
|
+
|
|
205
|
+
1. **`onde/src/inference/models.rs`** — add `pub const` for repo ID and GGUF filename, add to `SUPPORTED_MODELS` array and `SUPPORTED_MODEL_INFO`
|
|
206
|
+
2. **`onde/src/inference/engine.rs`** — add `pub fn model_name() -> Self` constructor to `impl GgufModelConfig`
|
|
207
|
+
3. **`sigit/src/chat.rs`** — add `ModelOption` entry to `SIGIT_MODELS` with `tool_calling: true/false`
|
|
208
|
+
4. **`sigit/src/main.rs`** — update `run_interactive()` and `run_acp_server()` if changing the default
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Debugging
|
|
213
|
+
|
|
214
|
+
### Log locations
|
|
215
|
+
|
|
216
|
+
- **TUI mode:** `$TMPDIR/sigit.log` (e.g. `/var/folders/.../sigit.log`)
|
|
217
|
+
- **ACP mode (Zed):** `~/Library/Logs/Zed/Zed.log` — grep for `agent stderr:.*sigit`
|
|
218
|
+
|
|
219
|
+
### Key log patterns
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
# Model loaded successfully
|
|
223
|
+
ChatEngine: model Qwen 3 8B loaded in 6.9s
|
|
224
|
+
|
|
225
|
+
# Session cwd captured
|
|
226
|
+
load_session: id=..., cwd=/path/to/project, additional_directories=[...]
|
|
227
|
+
|
|
228
|
+
# Tool call parsed by mistral.rs
|
|
229
|
+
ChatEngine: tool inference END — 12.3s — tool_calls: 1
|
|
230
|
+
|
|
231
|
+
# Tool executed
|
|
232
|
+
→ read_file({"path":"/absolute/path/to/file.rs"})
|
|
233
|
+
← 6506 chars
|
|
234
|
+
|
|
235
|
+
# Tool result sent back
|
|
236
|
+
ChatEngine: tool results inference START — 1 results
|
|
237
|
+
|
|
238
|
+
# Model returned empty (exhausted max_tokens on thinking)
|
|
239
|
+
model returned empty reply after 7 tool round(s)
|
|
240
|
+
|
|
241
|
+
# ResourceLink received from Zed
|
|
242
|
+
block[1]: ResourceLink(name=index.html (207:219), uri=file:///path/to/index.html#L207:219)
|
|
243
|
+
|
|
244
|
+
# ResourceLink read failed (fragment not stripped — old bug, now fixed)
|
|
245
|
+
could not read ResourceLink file:///path/to/index.html#L207:219: No such file or directory
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Common issues
|
|
249
|
+
|
|
250
|
+
| Symptom | Cause | Fix |
|
|
251
|
+
|---------|-------|-----|
|
|
252
|
+
| Model says "I cannot access websites" | RLHF refusal override not in system prompt | System prompt now has CRITICAL block about `read_website` |
|
|
253
|
+
| `0 tool call(s)` for every prompt | Wrong model loaded (Qwen 2.5) | Check log for `loading GGUF model` — must be Qwen 3 |
|
|
254
|
+
| `edit_file` returns `← 161 chars` repeatedly | `old_text not found` — model can't match exact text | Use Qwen 3 8B (not 4B); consider line-based edit tool |
|
|
255
|
+
| Files created in wrong directory | `cwd` not captured from ACP session | Session handlers must call `set_current_dir` + `push_history` with cwd |
|
|
256
|
+
| `@ file.html (207:219)` context missing | `#L207:219` fragment not stripped from file path | `prompt()` now parses URI fragments and extracts line ranges |
|
|
257
|
+
| `read_website` panics/hangs | `reqwest::blocking` inside tokio runtime | `exec_read_website` wrapped in `spawn_blocking` |
|
|
258
|
+
| Empty reply after many tool rounds | Model exhausted `max_tokens` on `<think>` blocks | Set `max_tokens: 8192`; 8B model wastes fewer tokens on thinking |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Cargo Dependency Note
|
|
263
|
+
|
|
264
|
+
For local development, `sigit/Cargo.toml` must use the path dependency:
|
|
265
|
+
|
|
266
|
+
```toml
|
|
267
|
+
onde = { path = "../onde" }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
For CI/release, switch to the git dependency (after pushing Onde changes):
|
|
271
|
+
|
|
272
|
+
```toml
|
|
273
|
+
onde = { git = "https://github.com/ondeinference/onde", branch = "development" }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The `qwen3_8b()` constructor only exists in the local Onde SDK until it's pushed to the `development` branch.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## File Map
|
|
281
|
+
|
|
282
|
+
| File | What it does |
|
|
283
|
+
|------|-------------|
|
|
284
|
+
| `sigit/src/tools.rs` | 9 tool schemas (`all_tools()`), `execute_tool()` dispatch, all `exec_*` implementations |
|
|
285
|
+
| `sigit/src/main.rs` | `SYSTEM_PROMPT`, `SiGitAgent` struct with `session_cwd`, ACP session handlers (cwd + push_history), `prompt()` with content block parsing, model selection (`qwen3_8b`), `MAX_TOOL_ROUNDS` |
|
|
286
|
+
| `sigit/src/chat.rs` | `SIGIT_MODELS` array (4 models), `run_inference_task()` with `tools_enabled` gate, TUI tool loop |
|
|
287
|
+
| `sigit/src/setup.rs` | HF cache setup pointing to shared App Group container |
|
|
288
|
+
| `onde/src/inference/types.rs` | `ToolDefinition`, `ToolCallRequest`, `ToolResult`, `ToolAwareResult` |
|
|
289
|
+
| `onde/src/inference/engine.rs` | `send_message_with_tools()`, `send_tool_results()`, `attach_tools()`, `parse_tool_calls()`, `replay_history_with_tools()`, `GgufModelConfig::qwen3_8b()` |
|
|
290
|
+
| `onde/src/inference/models.rs` | Model constants and `SUPPORTED_MODELS` array |
|
|
@@ -741,9 +741,9 @@ dependencies = [
|
|
|
741
741
|
|
|
742
742
|
[[package]]
|
|
743
743
|
name = "cc"
|
|
744
|
-
version = "1.2.
|
|
744
|
+
version = "1.2.61"
|
|
745
745
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
746
|
-
checksum = "
|
|
746
|
+
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
|
747
747
|
dependencies = [
|
|
748
748
|
"find-msvc-tools",
|
|
749
749
|
"jobserver",
|
|
@@ -1264,9 +1264,9 @@ dependencies = [
|
|
|
1264
1264
|
|
|
1265
1265
|
[[package]]
|
|
1266
1266
|
name = "data-encoding"
|
|
1267
|
-
version = "2.
|
|
1267
|
+
version = "2.11.0"
|
|
1268
1268
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1269
|
-
checksum = "
|
|
1269
|
+
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
|
1270
1270
|
|
|
1271
1271
|
[[package]]
|
|
1272
1272
|
name = "defmac"
|
|
@@ -3799,7 +3799,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
|
3799
3799
|
[[package]]
|
|
3800
3800
|
name = "onde"
|
|
3801
3801
|
version = "0.1.8"
|
|
3802
|
-
source = "git+https://github.com/ondeinference/onde?branch=development#
|
|
3802
|
+
source = "git+https://github.com/ondeinference/onde?branch=development#8321bc566cfbca8ff1d4b71f187f2b007fd98433"
|
|
3803
3803
|
dependencies = [
|
|
3804
3804
|
"anyhow",
|
|
3805
3805
|
"cc",
|
|
@@ -4807,9 +4807,9 @@ dependencies = [
|
|
|
4807
4807
|
|
|
4808
4808
|
[[package]]
|
|
4809
4809
|
name = "rustls-pki-types"
|
|
4810
|
-
version = "1.14.
|
|
4810
|
+
version = "1.14.1"
|
|
4811
4811
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4812
|
-
checksum = "
|
|
4812
|
+
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
|
4813
4813
|
dependencies = [
|
|
4814
4814
|
"web-time",
|
|
4815
4815
|
"zeroize",
|
|
@@ -5271,7 +5271,7 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
|
5271
5271
|
|
|
5272
5272
|
[[package]]
|
|
5273
5273
|
name = "sigit"
|
|
5274
|
-
version = "0.1.
|
|
5274
|
+
version = "0.1.2"
|
|
5275
5275
|
dependencies = [
|
|
5276
5276
|
"agent-client-protocol",
|
|
5277
5277
|
"anyhow",
|
|
@@ -5283,6 +5283,7 @@ dependencies = [
|
|
|
5283
5283
|
"onde",
|
|
5284
5284
|
"ratatui",
|
|
5285
5285
|
"regex",
|
|
5286
|
+
"reqwest 0.12.28",
|
|
5286
5287
|
"serde_json",
|
|
5287
5288
|
"tokio",
|
|
5288
5289
|
"tokio-util",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "sigit"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
description = "siGit Code — ACP-compatible AI coding agent for smbCloud platform."
|
|
6
6
|
documentation = "https://github.com/getsigit/sigit"
|
|
@@ -18,10 +18,10 @@ path = "src/main.rs"
|
|
|
18
18
|
|
|
19
19
|
[dependencies]
|
|
20
20
|
# ACP protocol SDK
|
|
21
|
-
agent-client-protocol = { version = "0.10.4", features = ["unstable_session_fork"] }
|
|
21
|
+
agent-client-protocol = { version = "0.10.4", features = ["unstable_session_fork", "unstable_session_additional_directories"] }
|
|
22
22
|
|
|
23
23
|
# Onde Inference engine (local LLM)
|
|
24
|
-
#
|
|
24
|
+
# onde = { path = "../onde" }
|
|
25
25
|
onde = { git = "https://github.com/ondeinference/onde", branch = "development" }
|
|
26
26
|
|
|
27
27
|
# Async runtime
|
|
@@ -41,4 +41,5 @@ log = "0.4"
|
|
|
41
41
|
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
|
|
42
42
|
serde_json = "1"
|
|
43
43
|
regex = "1"
|
|
44
|
+
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] }
|
|
44
45
|
uuid = { version = "1", features = ["v4"] }
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
A coding agent for [smbCloud](https://smbcloud.xyz/) that runs entirely on your machine. No API keys. No cloud round-trips. The model lives in your local HuggingFace cache.
|
|
6
6
|
|
|
7
|
+
siGit is meant to be a general coding agent, but it is especially good in smbCloud codebases. It already knows the rough shape of the platform: Rust workspaces with focused crates, Rails services, deploy flows, auth boundaries, and platform-managed services like GresIQ. In smbCloud repos, that means it can usually give more grounded answers with less back-and-forth.
|
|
8
|
+
|
|
7
9
|
siGit has two modes:
|
|
8
10
|
|
|
9
11
|
- ACP mode, where Zed or another ACP-compatible editor starts it over stdio
|
|
@@ -15,6 +17,17 @@ Current platform support:
|
|
|
15
17
|
- Linux: ACP mode and interactive terminal mode
|
|
16
18
|
- Windows: ACP mode only for now
|
|
17
19
|
|
|
20
|
+
## What siGit knows about smbCloud
|
|
21
|
+
|
|
22
|
+
When siGit is working in an smbCloud repo, it should lean on platform context instead of treating everything like a generic cloud app. That includes things like:
|
|
23
|
+
|
|
24
|
+
- the difference between platform user flows and tenant app auth flows
|
|
25
|
+
- the fact that `Project` is the umbrella workspace, while app-like resources such as `FrontendApp`, `AuthApp`, and GresIQ are separate deployable units
|
|
26
|
+
- the fact that Next.js SSR deploys are not the same as the generic git-push path
|
|
27
|
+
- the fact that smbCloud repos usually prefer existing workspace patterns and crate boundaries over new abstractions
|
|
28
|
+
|
|
29
|
+
Outside smbCloud, it should still behave like a normal coding agent and not force platform-specific advice where it does not belong.
|
|
30
|
+
|
|
18
31
|
## Install
|
|
19
32
|
|
|
20
33
|
```sh
|
|
@@ -120,6 +120,8 @@ struct App {
|
|
|
120
120
|
load_start: Instant,
|
|
121
121
|
/// Display name of the model being loaded (shown in the spinner line).
|
|
122
122
|
load_model_name: String,
|
|
123
|
+
/// Whether the currently loaded model supports tool calling.
|
|
124
|
+
tool_calling: bool,
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
const BANNER_ART: &str = "\
|
|
@@ -142,6 +144,11 @@ const THINKING_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "
|
|
|
142
144
|
|
|
143
145
|
impl App {
|
|
144
146
|
fn new(load_model_name: String) -> Self {
|
|
147
|
+
let tool_calling = SIGIT_MODELS
|
|
148
|
+
.iter()
|
|
149
|
+
.find(|m| m.name == load_model_name)
|
|
150
|
+
.map(|m| m.tool_calling)
|
|
151
|
+
.unwrap_or(true);
|
|
145
152
|
Self {
|
|
146
153
|
messages: Vec::new(),
|
|
147
154
|
input: String::new(),
|
|
@@ -160,6 +167,7 @@ impl App {
|
|
|
160
167
|
load_error: None,
|
|
161
168
|
load_start: Instant::now(),
|
|
162
169
|
load_model_name,
|
|
170
|
+
tool_calling,
|
|
163
171
|
}
|
|
164
172
|
}
|
|
165
173
|
|
|
@@ -303,6 +311,13 @@ struct ModelOption {
|
|
|
303
311
|
}
|
|
304
312
|
|
|
305
313
|
const SIGIT_MODELS: &[ModelOption] = &[
|
|
314
|
+
ModelOption {
|
|
315
|
+
name: "Qwen 3 8B (Q4_K_M)",
|
|
316
|
+
description: "~5 GB",
|
|
317
|
+
tool_calling: true,
|
|
318
|
+
max_tokens: 4096,
|
|
319
|
+
config_fn: GgufModelConfig::qwen3_8b,
|
|
320
|
+
},
|
|
306
321
|
ModelOption {
|
|
307
322
|
name: "Qwen 3 4B (Q4_K_M)",
|
|
308
323
|
description: "~2.7 GB",
|
|
@@ -840,6 +855,7 @@ async fn exec_slash<B: ratatui::backend::Backend>(
|
|
|
840
855
|
match engine.load_gguf_model(config, None, Some(sampling)).await {
|
|
841
856
|
Ok(_) => {
|
|
842
857
|
engine.clear_history().await;
|
|
858
|
+
app.tool_calling = model.tool_calling;
|
|
843
859
|
app.messages.push(ChatMessage::system(format!(
|
|
844
860
|
"✓ Switched to {}",
|
|
845
861
|
model.name
|
|
@@ -892,8 +908,13 @@ async fn run_inference_task(
|
|
|
892
908
|
engine: Arc<ChatEngine>,
|
|
893
909
|
text: String,
|
|
894
910
|
tx: mpsc::Sender<InferenceUpdate>,
|
|
911
|
+
tools_enabled: bool,
|
|
895
912
|
) {
|
|
896
|
-
let onde_tools =
|
|
913
|
+
let onde_tools = if tools_enabled {
|
|
914
|
+
build_onde_tools()
|
|
915
|
+
} else {
|
|
916
|
+
vec![]
|
|
917
|
+
};
|
|
897
918
|
|
|
898
919
|
let mut result = match engine.send_message_with_tools(&text, &onde_tools).await {
|
|
899
920
|
Ok(r) => r,
|
|
@@ -923,8 +944,8 @@ async fn run_inference_task(
|
|
|
923
944
|
.send(InferenceUpdate::ToolUse(tc.function_name.clone()))
|
|
924
945
|
.await;
|
|
925
946
|
|
|
926
|
-
// Execute the tool (
|
|
927
|
-
let output = crate::tools::execute_tool(&tc.function_name, &tc.arguments);
|
|
947
|
+
// Execute the tool (async — read_website uses spawn_blocking internally).
|
|
948
|
+
let output = crate::tools::execute_tool(&tc.function_name, &tc.arguments).await;
|
|
928
949
|
log::info!(" ← {} chars", output.len());
|
|
929
950
|
|
|
930
951
|
tool_results.push(ToolResult {
|
|
@@ -985,7 +1006,7 @@ pub async fn run_with<B: ratatui::backend::Backend>(
|
|
|
985
1006
|
engine: Arc<ChatEngine>,
|
|
986
1007
|
load_rx: std_mpsc::Receiver<Result<(), String>>,
|
|
987
1008
|
) -> Result<()> {
|
|
988
|
-
let config = GgufModelConfig::
|
|
1009
|
+
let config = GgufModelConfig::qwen3_8b();
|
|
989
1010
|
let model_name = config.display_name.clone();
|
|
990
1011
|
event_loop(terminal, engine, load_rx, model_name).await
|
|
991
1012
|
}
|
|
@@ -1149,8 +1170,9 @@ async fn event_loop<B: ratatui::backend::Backend>(
|
|
|
1149
1170
|
|
|
1150
1171
|
let engine_handle = Arc::clone(&engine);
|
|
1151
1172
|
let user_text = text.clone();
|
|
1173
|
+
let tools_enabled = app.tool_calling;
|
|
1152
1174
|
tokio::spawn(async move {
|
|
1153
|
-
run_inference_task(engine_handle, user_text, tx).await;
|
|
1175
|
+
run_inference_task(engine_handle, user_text, tx, tools_enabled).await;
|
|
1154
1176
|
});
|
|
1155
1177
|
}
|
|
1156
1178
|
}
|