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/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
+ ```