Files
nanobot-ts/docs/Discoveries.md

152 lines
3.5 KiB
Markdown

# 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<string, unknown> { ... }
// 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<string, unknown>`:
```typescript
// src/agent/tools/base.ts
export function strArg(args: Record<string, unknown>, 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: `~/.config/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 (`~/.config/nanobot/config.json`)
4. Zod schema defaults