3.5 KiB
3.5 KiB
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 {}):
// ❌ 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
// ❌ 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:
import { ollama } from 'ollama-ai-provider';
import type { LanguageModel } from 'ai';
const model = ollama('llama3.2') as unknown as LanguageModel;
js-tiktoken API
// ❌ 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:
// ❌ 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:
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:
// ❌ Risky - may throw
console.log(`Error: ${err}`);
// ✅ Safe
console.log(`Error: ${String(err)}`);
Helper: strArg
For safely extracting string arguments from Record<string, unknown>:
// 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:
// ❌ 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
- CLI flags (
-c,-m,-w,-M) - Environment variables (
NANOBOT_CONFIG,NANOBOT_MODEL,NANOBOT_WORKSPACE) - Config file (
~/.config/nanobot/config.json) - Zod schema defaults