zizou-ai 0.1.0
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.
- package/.dockerignore +8 -0
- package/.vscode/settings.json +4 -0
- package/Dockerfile +33 -0
- package/README.md +175 -0
- package/build.ts +31 -0
- package/bun.lock +238 -0
- package/context.md +286 -0
- package/dist/cli.js +2254 -0
- package/index.html +1 -0
- package/main.html +25 -0
- package/package.json +34 -0
- package/powershell.cmd +1 -0
- package/src/agent/run-turn.ts +601 -0
- package/src/cli.tsx +25 -0
- package/src/commands/index.ts +356 -0
- package/src/config/api-keys.ts +166 -0
- package/src/config/index.ts +57 -0
- package/src/context/build-system-prompt.ts +126 -0
- package/src/context/repo-map.ts +182 -0
- package/src/provider/index.ts +61 -0
- package/src/provider/resolve-model.ts +102 -0
- package/src/tools/add-context.ts +24 -0
- package/src/tools/edit-file.test-manual.ts +48 -0
- package/src/tools/edit-file.ts +77 -0
- package/src/tools/glob.ts +56 -0
- package/src/tools/grep.ts +35 -0
- package/src/tools/index.ts +23 -0
- package/src/tools/list-dir.ts +60 -0
- package/src/tools/open-file.ts +81 -0
- package/src/tools/read-file.ts +63 -0
- package/src/tools/run-bash.ts +84 -0
- package/src/tools/types.ts +16 -0
- package/src/tools/write-file.ts +43 -0
- package/src/ui/ApiKeySetup.tsx +254 -0
- package/src/ui/App.tsx +51 -0
- package/src/ui/Chat.tsx +585 -0
- package/src/ui/Figurine.tsx +450 -0
- package/test-output.txt +0 -0
- package/tictactoe.html +109 -0
- package/todo.css +108 -0
- package/todo.js +83 -0
- package/tsconfig.json +20 -0
package/context.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Zizou Context & LLM Payload Reference
|
|
2
|
+
|
|
3
|
+
> **What does the LLM actually receive? Where does each piece of information come from?**
|
|
4
|
+
> This document is the authoritative answer. Read it alongside `zizou-debug.log`
|
|
5
|
+
> which is written fresh on every message you send.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview — The Four Inputs to Every LLM Call
|
|
10
|
+
|
|
11
|
+
Every time you press Enter in Zizou, `streamText()` (Vercel AI SDK) is called with exactly **four inputs**:
|
|
12
|
+
|
|
13
|
+
| Input | Key | Where it comes from |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| System prompt | `system` | `buildSystemPrompt()` in `src/context/` |
|
|
16
|
+
| Conversation history | `messages` | `history` ref in `Chat.tsx` |
|
|
17
|
+
| Tool definitions | `tools` | `src/tools/*.ts`, registered in `run-turn.ts` |
|
|
18
|
+
| Safety cap | `stopWhen` | `stepCountIs(15)` hardcoded in `run-turn.ts` |
|
|
19
|
+
|
|
20
|
+
The API request body sent to the provider (Groq / Anthropic / OpenAI / etc.) looks like:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"model": "llama-3.3-70b-versatile",
|
|
25
|
+
"system": "You are Zizou...\n\n--- SESSION CONTEXT ---\n...",
|
|
26
|
+
"messages": [
|
|
27
|
+
{ "role": "user", "content": "write an index.html file..." },
|
|
28
|
+
{ "role": "assistant", "content": [{"type":"tool-call",...}] },
|
|
29
|
+
{ "role": "tool", "content": [{"type":"tool-result",...}] }
|
|
30
|
+
],
|
|
31
|
+
"tools": [
|
|
32
|
+
{ "name": "readFile", "description": "...", "parameters": {...} },
|
|
33
|
+
{ "name": "writeFile", "description": "...", "parameters": {...} },
|
|
34
|
+
...
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 1. System Prompt — How It's Built
|
|
42
|
+
|
|
43
|
+
**File**: [`src/context/build-system-prompt.ts`](src/context/build-system-prompt.ts)
|
|
44
|
+
**Called once**: at app start in `Chat.tsx` `useEffect`. Cached in `systemPromptRef.current` for the whole session.
|
|
45
|
+
|
|
46
|
+
The final system prompt is assembled in three layers, concatenated in order:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
BASE_INSTRUCTIONS (static string, hardcoded)
|
|
50
|
+
+
|
|
51
|
+
SESSION_CONTEXT (dynamic, injected at runtime)
|
|
52
|
+
+
|
|
53
|
+
REPO MAP (dynamic, scanned from disk at startup)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Layer 1 — BASE_INSTRUCTIONS
|
|
57
|
+
Static text telling the agent:
|
|
58
|
+
- Its identity ("You are Zizou, an AI coding agent…")
|
|
59
|
+
- How to use each tool (`writeFile` for new files, `editFile` for targeted edits)
|
|
60
|
+
- When **not** to use tools (general knowledge questions)
|
|
61
|
+
- The critical rule about clean tool name formatting (prevents LLaMA tool-call corruption)
|
|
62
|
+
|
|
63
|
+
### Layer 2 — SESSION_CONTEXT
|
|
64
|
+
Injected dynamically at `buildSystemPrompt(projectRoot)` call time.
|
|
65
|
+
|
|
66
|
+
Contains:
|
|
67
|
+
- **Workspace root path** (`process.cwd()`) — so the LLM knows where it is
|
|
68
|
+
- **Path resolution rule** — relative paths resolve from workspace root
|
|
69
|
+
- **TOOL GUIDE** — a human-readable description of every tool, when to use each one, and what it returns
|
|
70
|
+
|
|
71
|
+
This is the key section that tells the LLM: *"You are operating in `/Users/Arnv/ZIZOUv1` — all file paths are relative to here."*
|
|
72
|
+
|
|
73
|
+
### Layer 3 — REPO MAP
|
|
74
|
+
**File**: [`src/context/repo-map.ts`](src/context/repo-map.ts)
|
|
75
|
+
**Function**: `buildRepoMap(projectRoot)`
|
|
76
|
+
|
|
77
|
+
This walks your entire project directory (excluding `node_modules`, `.git`, `dist`, `build`, `.next`) using `readdirSync` and extracts:
|
|
78
|
+
- Function names (`function foo(` / `const foo = (` / `async function foo`)
|
|
79
|
+
- Class names (`class Foo`)
|
|
80
|
+
- Interface names (`interface Foo`)
|
|
81
|
+
- Type aliases (`type Foo =`)
|
|
82
|
+
- Export statements
|
|
83
|
+
|
|
84
|
+
Each symbol is recorded as `filename:lineNumber: symbolSignature`.
|
|
85
|
+
|
|
86
|
+
The result is injected as:
|
|
87
|
+
```
|
|
88
|
+
--- REPO MAP ---
|
|
89
|
+
src/agent/run-turn.ts:84: export async function* runTurn(
|
|
90
|
+
src/tools/read-file.ts:37: export const readFile = tool({
|
|
91
|
+
...
|
|
92
|
+
--- END REPO MAP ---
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Why**: This gives the LLM a structural overview of your codebase before it makes any tool calls. Without it, the LLM would have to explore blindly with `glob`/`readFile` to understand what exists.
|
|
96
|
+
|
|
97
|
+
**Limitation**: Regex-based extraction — may miss symbols defined as `const foo = tool({...})` patterns. The agent is told to use `glob`/`grep` to verify when in doubt.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 2. Conversation History — How It Grows
|
|
102
|
+
|
|
103
|
+
**Managed in**: `Chat.tsx` → `history` ref (`useRef<ModelMessage[]>([])`)
|
|
104
|
+
|
|
105
|
+
The history array grows with every turn. Each element is a `ModelMessage`:
|
|
106
|
+
|
|
107
|
+
### Message Types in the History Array
|
|
108
|
+
|
|
109
|
+
#### `{ role: "user", content: string }`
|
|
110
|
+
Your raw text input. Added immediately when you press Enter, before the API call.
|
|
111
|
+
|
|
112
|
+
#### `{ role: "assistant", content: ContentPart[] }`
|
|
113
|
+
The LLM's response. Can contain a mix of:
|
|
114
|
+
- `{ type: "text", text: "..." }` — plain text tokens
|
|
115
|
+
- `{ type: "tool-call", toolCallId, toolName, input }` — a tool invocation request
|
|
116
|
+
|
|
117
|
+
#### `{ role: "tool", content: ToolResultPart[] }`
|
|
118
|
+
Injected **by the SDK** after `execute()` completes. Contains:
|
|
119
|
+
- `{ type: "tool-result", toolCallId, result: {...} }` — the return value of `execute()`
|
|
120
|
+
|
|
121
|
+
The SDK auto-appends tool-result messages mid-turn (before the LLM sees them) to close the tool-call loop. At the end of the turn, `result.responseMessages` is awaited and **manually appended** to `history.current` (line 421 of `run-turn.ts`).
|
|
122
|
+
|
|
123
|
+
> **Important**: If this manual append is missing, the LLM has no memory of previous turns.
|
|
124
|
+
|
|
125
|
+
### Example — History After "write a calculator"
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
[0] user "write an index.html file with a working calculator"
|
|
129
|
+
[1] assistant [tool-call: listDir({path: "."})]
|
|
130
|
+
[2] tool [tool-result: {success: true, entries: [...]}]
|
|
131
|
+
[3] assistant [tool-call: writeFile({path: "index.html", contents: "..."})]
|
|
132
|
+
[4] tool [tool-result: {success: true, message: "Written to C:\...\index.html"}]
|
|
133
|
+
[5] assistant [text: "I've created index.html with a working calculator..."]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
On your **next** message, all 6 messages above are sent alongside it.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 3. Tools — How They're Passed to the LLM
|
|
141
|
+
|
|
142
|
+
**Registration**: `src/agent/run-turn.ts` — inside `streamText({ tools: {...} })`
|
|
143
|
+
**Definitions**: `src/tools/*.ts` — each exported via `src/tools/index.ts`
|
|
144
|
+
|
|
145
|
+
### How Tool Schemas Are Transmitted
|
|
146
|
+
|
|
147
|
+
The Vercel AI SDK converts each tool's `inputSchema` (Zod schema) into a **JSON Schema** object. This is sent in the `tools` array of the API request. The LLM receives:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"name": "writeFile",
|
|
152
|
+
"description": "Create a new file or completely overwrite...",
|
|
153
|
+
"parameters": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"properties": {
|
|
156
|
+
"path": { "type": "string", "description": "Path to the file..." },
|
|
157
|
+
"contents": { "type": "string", "description": "The complete string content..." }
|
|
158
|
+
},
|
|
159
|
+
"required": ["path", "contents"]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**The LLM does not see `execute()`** — it only sees the name, description, and parameter schema.
|
|
165
|
+
|
|
166
|
+
### Tool Call Lifecycle (one round trip)
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
1. LLM outputs: {"type": "tool-call", "toolName": "writeFile", "input": {...}}
|
|
170
|
+
2. SDK intercepts: calls writeFile.execute({path, contents}) locally
|
|
171
|
+
3. execute() runs: path.resolve(cwd, path) → mkdirSync → writeFileSync
|
|
172
|
+
4. execute() returns: {success: true, message: "Written to ..."}
|
|
173
|
+
5. SDK injects: {"role":"tool","content":[{"type":"tool-result","result":{...}}]}
|
|
174
|
+
6. SDK re-prompts: sends the full updated history back to the LLM
|
|
175
|
+
7. LLM continues: reads the tool-result and decides what to do next
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Steps 2–7 happen **automatically** inside `streamText()` for each tool call, up to `maxSteps=15` rounds.
|
|
179
|
+
|
|
180
|
+
### All Registered Tools
|
|
181
|
+
|
|
182
|
+
| Tool | Input Schema | What `execute()` does |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| `readFile` | `path: string` | `readFileSync(path, "utf-8")` |
|
|
185
|
+
| `writeFile` | `path, contents` | `resolve(cwd, path)` → `mkdirSync` → `writeFileSync` |
|
|
186
|
+
| `editFile` | `path, old_string, new_string` | Read → count occurrences → replace exactly 1 → write |
|
|
187
|
+
| `glob` | `pattern: string` | `readdirSync` walk, regex match filenames, cap 50 results |
|
|
188
|
+
| `grep` | `query: string` | Walk files, `indexOf` search inside contents |
|
|
189
|
+
| `listDir` | `path?: string` | `readdirSync + statSync` on resolved dir, skip node_modules |
|
|
190
|
+
| `openFile` | `path: string` | `spawn("start"/"open"/"xdg-open", [absPath])` detached |
|
|
191
|
+
| `runBash` | `command: string` | ConfirmFn → user y/n → `exec(command, {timeout: 15s})` |
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 4. The `stopWhen` Safety Cap
|
|
196
|
+
|
|
197
|
+
`stopWhen: stepCountIs(15)` in `run-turn.ts`.
|
|
198
|
+
|
|
199
|
+
Each "step" is one complete round trip:
|
|
200
|
+
```
|
|
201
|
+
LLM generates → [possible tool calls run] → LLM re-prompted with results
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
After 15 such steps, `streamText()` stops, returns what it has, and the generator ends. This prevents infinite loops where a confused model keeps calling tools indefinitely.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 5. Debug Log — Reading `zizou-debug.log`
|
|
209
|
+
|
|
210
|
+
Written to `<workspace-root>/zizou-debug.log` on **every message**. Sections:
|
|
211
|
+
|
|
212
|
+
| Section | What it contains |
|
|
213
|
+
|---|---|
|
|
214
|
+
| **Header** | Timestamp, cwd, model ID, message count, prompt size |
|
|
215
|
+
| **1 · System Prompt** | Full text of the system prompt sent to the LLM |
|
|
216
|
+
| **2 · Section Breakdown** | Which layers are present (BASE / SESSION_CONTEXT / REPO MAP) |
|
|
217
|
+
| **3 · Conversation History** | Every message, expanded: role, content type, full content |
|
|
218
|
+
| **4 · Tools** | All 8 tools with descriptions, parameter schemas, and how each runs |
|
|
219
|
+
| **5 · SDK Call Parameters** | Exact arguments passed to `streamText()` |
|
|
220
|
+
| **6 · Live Stream Events** | Appended in real time: STEP-START, TEXT-DELTA, TOOL-CALL, TOOL-RESULT, TOOL-ERROR, STEP-FINISH, FINISH |
|
|
221
|
+
| **Final Usage** | Input/output token counts from the provider |
|
|
222
|
+
|
|
223
|
+
### Reading Live Stream Events
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
[timestamp] STEP-START step=1
|
|
227
|
+
All tool-calls below belong to step 1 until STEP-FINISH.
|
|
228
|
+
|
|
229
|
+
[timestamp] TOOL-CALL ► writeFile (id=call_abc123)
|
|
230
|
+
INPUT ARGUMENTS:
|
|
231
|
+
{
|
|
232
|
+
"path": "index.html",
|
|
233
|
+
"contents": "<!DOCTYPE html>..."
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
[timestamp] TOOL-RESULT ◄ writeFile (id=call_abc123)
|
|
237
|
+
execute() finished. Result injected as a tool-result message.
|
|
238
|
+
RESULT OUTPUT:
|
|
239
|
+
{
|
|
240
|
+
"success": true,
|
|
241
|
+
"message": "Successfully wrote file to C:\\Users\\Arnv\\ZIZOUv1\\index.html"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
[timestamp] STEP-FINISH step=1 finishReason="tool-calls"
|
|
245
|
+
|
|
246
|
+
[timestamp] TEXT-DELTA chars=42 fragment="I've created index.html with..."
|
|
247
|
+
[timestamp] FINISH finishReason="stop" usage={inputTokens:1200, outputTokens:80}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 6. Context Retrieval Flow — End to End
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
User types message → Enter
|
|
256
|
+
↓
|
|
257
|
+
Chat.tsx handleSubmit()
|
|
258
|
+
├─ push to history.current
|
|
259
|
+
├─ call resolveModel(provider) → LanguageModel from @ai-sdk/*
|
|
260
|
+
└─ call runTurn({ history, model, systemPrompt, onConfirm })
|
|
261
|
+
↓
|
|
262
|
+
run-turn.ts runTurn()
|
|
263
|
+
├─ [WRITE] zizou-debug.log sections 1–5
|
|
264
|
+
└─ streamText({ system, messages: history, tools, stopWhen: stepCountIs(15) })
|
|
265
|
+
↓
|
|
266
|
+
[API REQUEST sent to provider]
|
|
267
|
+
↓ (streaming response)
|
|
268
|
+
for await (part of result.fullStream)
|
|
269
|
+
├─ text-delta → yield AgentEvent → Chat.tsx renders token
|
|
270
|
+
├─ tool-call → [APPEND] debug log → yield AgentEvent (shows ■ tool)
|
|
271
|
+
│ ↓ SDK auto-calls execute()
|
|
272
|
+
│ execute() runs locally
|
|
273
|
+
│ ↓
|
|
274
|
+
└─ tool-result → [APPEND] debug log → yield AgentEvent (shows ✓ done)
|
|
275
|
+
SDK injects tool-result message into conversation
|
|
276
|
+
LLM re-prompted with full updated context
|
|
277
|
+
↓ (repeat up to 15 steps)
|
|
278
|
+
[APPEND] "Turn ended" + final token usage to debug log
|
|
279
|
+
↓
|
|
280
|
+
yield turn-complete + finish events
|
|
281
|
+
↓
|
|
282
|
+
await result.responseMessages
|
|
283
|
+
return [...history, ...responseMessages] ← new history saved to ref
|
|
284
|
+
↓
|
|
285
|
+
Chat.tsx stores new history → ready for next turn
|
|
286
|
+
```
|