# Discoveries Empirical learnings from implementation that future sessions should know. ## Zod v4 Specifics ### `.default()` on Nested Objects Zod v4 requires factory functions for nested object defaults, and the factory must return the **full output type** (not just `{}`): ```typescript // ❌ Wrong - empty object won't match the schema const Config = z.object({ nested: NestedSchema.default({}), }); // ✅ Correct - factory returning full type const Config = z.object({ nested: NestedSchema.default(() => ({ field: value, ... })), }); ``` ### `z.record()` Requires Two Arguments ```typescript // ❌ Wrong z.record(z.string()) // ✅ Correct z.record(z.string(), z.unknown()) ``` ## AI SDK v6 Changes | v4/v5 | v6 | |-------|-----| | `LanguageModelV2` | `LanguageModel` | | `maxTokens` | `maxOutputTokens` | | `maxSteps` | `stopWhen: stepCountIs(n)` | | `usage.promptTokens` | `usage.inputTokens` | | `usage.completionTokens` | `usage.outputTokens` | ## ollama-ai-provider Compatibility `ollama-ai-provider` v1.2.0 returns `LanguageModelV1`, not the expected `LanguageModel` (v2/v3). Cast at call site: ```typescript import { ollama } from 'ollama-ai-provider'; import type { LanguageModel } from 'ai'; const model = ollama('llama3.2') as unknown as LanguageModel; ``` ## js-tiktoken API ```typescript // ❌ Wrong (Python-style) import { get_encoding } from 'js-tiktoken'; // ✅ Correct import { getEncoding } from 'js-tiktoken'; ``` ## Bun/Node Globals `Document` is not available as a global in Bun/Node. For DOM-like operations: ```typescript // ❌ Wrong function makeDocument(): Document { ... } // ✅ Correct function makePseudoDocument(): Record { ... } // Cast at call site if needed ``` ## WebSocket Error Types WebSocket `onerror` handler receives an `Event`, not an `Error`: ```typescript socket.onerror = (err: Event) => { console.error(`WebSocket error: ${err.type}`); }; ``` ## Template Literals with Unknown Types When interpolating `unknown` types in template literals, explicitly convert to string: ```typescript // ❌ Risky - may throw console.log(`Error: ${err}`); // ✅ Safe console.log(`Error: ${String(err)}`); ``` ## Helper: strArg For safely extracting string arguments from `Record`: ```typescript // src/agent/tools/base.ts export function strArg(args: Record, key: string, fallback = ''): string { const val = args[key]; return typeof val === 'string' ? val : fallback; } ``` Usage: ```typescript // ❌ Verbose const path = String(args['path'] ?? ''); // ✅ Cleaner const path = strArg(args, 'path'); const timeout = parseInt(strArg(args, 'timeout', '30'), 10); ``` ## Mattermost WebSocket - Uses raw `WebSocket` + `fetch` (no mattermostdriver library) - Auth via hello message with token - Event types: `posted`, `post_edited`, `reaction_added`, etc. - Group channel policy: `mention` (default), `open`, `allowlist` ## Session Persistence - Format: JSONL (one JSON object per line) - Location: `~/.nanobot/sessions/{sessionKey}.jsonl` - Tool results truncated at 16,000 characters - Memory consolidation triggered when approaching context window limit ## Retry Logic `LLMProvider.chatWithRetry()` retries on: - HTTP 429 (rate limit) - HTTP 5xx (server errors) - Timeouts - Network errors Max 3 attempts with exponential backoff. ## Config Precedence 1. CLI flags (`-c`, `-m`, `-w`, `-M`) 2. Environment variables (`NANOBOT_CONFIG`, `NANOBOT_MODEL`, `NANOBOT_WORKSPACE`) 3. Config file (`~/.nanobot/config.json`) 4. Zod schema defaults