- Changed model identifier from `claude-opus-4-5-20251101` to `claude-opus-4-6` across various files, including documentation and code references. - Updated the SDK to support adaptive thinking for Opus 4.6, allowing the model to determine its own reasoning depth. - Enhanced the thinking level options to include 'adaptive' and adjusted related components to reflect this change. - Updated tests to ensure compatibility with the new model and its features. These changes improve the model's capabilities and user experience by leveraging adaptive reasoning.
16 KiB
Server Utilities Reference
This document describes all utility modules available in apps/server/src/lib/. These utilities provide reusable functionality for image handling, prompt building, model resolution, conversation management, and error handling.
Table of Contents
- Image Handler
- Prompt Builder
- Model Resolver
- Conversation Utils
- Error Handler
- Subprocess Manager
- Events
- Auth
- Security
Image Handler
Location: apps/server/src/lib/image-handler.ts
Centralized utilities for processing image files, including MIME type detection, base64 encoding, and content block generation for Claude SDK format.
Functions
getMimeTypeForImage(imagePath: string): string
Get MIME type for an image file based on its extension.
Supported formats:
.jpg,.jpeg→image/jpeg.png→image/png.gif→image/gif.webp→image/webp- Default:
image/png
Example:
import { getMimeTypeForImage } from '../lib/image-handler.js';
const mimeType = getMimeTypeForImage('/path/to/image.jpg');
// Returns: "image/jpeg"
readImageAsBase64(imagePath: string): Promise<ImageData>
Read an image file and convert to base64 with metadata.
Returns: ImageData
interface ImageData {
base64: string; // Base64-encoded image data
mimeType: string; // MIME type
filename: string; // File basename
originalPath: string; // Original file path
}
Example:
const imageData = await readImageAsBase64('/path/to/photo.png');
console.log(imageData.base64); // "iVBORw0KG..."
console.log(imageData.mimeType); // "image/png"
console.log(imageData.filename); // "photo.png"
convertImagesToContentBlocks(imagePaths: string[], workDir?: string): Promise<ImageContentBlock[]>
Convert image paths to content blocks in Claude SDK format. Handles both relative and absolute paths.
Parameters:
imagePaths- Array of image file pathsworkDir- Optional working directory for resolving relative paths
Returns: Array of ImageContentBlock
interface ImageContentBlock {
type: 'image';
source: {
type: 'base64';
media_type: string;
data: string;
};
}
Example:
const imageBlocks = await convertImagesToContentBlocks(
['./screenshot.png', '/absolute/path/diagram.jpg'],
'/project/root'
);
// Use in prompt content
const contentBlocks = [{ type: 'text', text: 'Analyze these images:' }, ...imageBlocks];
formatImagePathsForPrompt(imagePaths: string[]): string
Format image paths as a bulleted list for inclusion in text prompts.
Returns: Formatted string with image paths, or empty string if no images.
Example:
const pathsList = formatImagePathsForPrompt([
'/screenshots/login.png',
'/diagrams/architecture.png',
]);
// Returns:
// "\n\nAttached images:\n- /screenshots/login.png\n- /diagrams/architecture.png\n"
Prompt Builder
Location: apps/server/src/lib/prompt-builder.ts
Standardized prompt building that combines text prompts with image attachments.
Functions
buildPromptWithImages(basePrompt: string, imagePaths?: string[], workDir?: string, includeImagePaths: boolean = false): Promise<PromptWithImages>
Build a prompt with optional image attachments.
Parameters:
basePrompt- The text promptimagePaths- Optional array of image file pathsworkDir- Optional working directory for resolving relative pathsincludeImagePaths- Whether to append image paths to the text (default: false)
Returns: PromptWithImages
interface PromptWithImages {
content: PromptContent; // string | Array<ContentBlock>
hasImages: boolean;
}
type PromptContent =
| string
| Array<{
type: string;
text?: string;
source?: object;
}>;
Example:
import { buildPromptWithImages } from '../lib/prompt-builder.js';
// Without images
const { content } = await buildPromptWithImages('What is 2+2?');
// content: "What is 2+2?" (simple string)
// With images
const { content, hasImages } = await buildPromptWithImages(
'Analyze this screenshot',
['/path/to/screenshot.png'],
'/project/root',
true // include image paths in text
);
// content: [
// { type: "text", text: "Analyze this screenshot\n\nAttached images:\n- /path/to/screenshot.png\n" },
// { type: "image", source: { type: "base64", media_type: "image/png", data: "..." } }
// ]
// hasImages: true
Use Cases:
- AgentService: Set
includeImagePaths: trueto list paths for Read tool access - AutoModeService: Set
includeImagePaths: falseto avoid duplication in feature descriptions
Model Resolver
Location: apps/server/src/lib/model-resolver.ts
Centralized model string mapping and resolution for handling model aliases and provider detection.
Constants
CLAUDE_MODEL_MAP
Model alias mapping for Claude models.
export const CLAUDE_MODEL_MAP: Record<string, string> = {
haiku: 'claude-haiku-4-5',
sonnet: 'claude-sonnet-4-20250514',
opus: 'claude-opus-4-6',
} as const;
DEFAULT_MODELS
Default models per provider.
export const DEFAULT_MODELS = {
claude: 'claude-opus-4-6',
openai: 'gpt-5.2',
} as const;
Functions
resolveModelString(modelKey?: string, defaultModel: string = DEFAULT_MODELS.claude): string
Resolve a model key/alias to a full model string.
Logic:
- If
modelKeyis undefined → returndefaultModel - If starts with
"gpt-"or"o"→ pass through (OpenAI/Codex model) - If includes
"claude-"→ pass through (full Claude model string) - If in
CLAUDE_MODEL_MAP→ return mapped value - Otherwise → return
defaultModelwith warning
Example:
import { resolveModelString, DEFAULT_MODELS } from '../lib/model-resolver.js';
resolveModelString('opus');
// Returns: "claude-opus-4-6"
// Logs: "[ModelResolver] Resolved model alias: "opus" -> "claude-opus-4-6""
resolveModelString('gpt-5.2');
// Returns: "gpt-5.2"
// Logs: "[ModelResolver] Using OpenAI/Codex model: gpt-5.2"
resolveModelString('claude-sonnet-4-20250514');
// Returns: "claude-sonnet-4-20250514"
// Logs: "[ModelResolver] Using full Claude model string: claude-sonnet-4-20250514"
resolveModelString('invalid-model');
// Returns: "claude-opus-4-6"
// Logs: "[ModelResolver] Unknown model key "invalid-model", using default: "claude-opus-4-6""
getEffectiveModel(explicitModel?: string, sessionModel?: string, defaultModel?: string): string
Get the effective model from multiple sources with priority.
Priority: explicit model > session model > default model
Example:
import { getEffectiveModel } from '../lib/model-resolver.js';
// Explicit model takes precedence
getEffectiveModel('sonnet', 'opus');
// Returns: "claude-sonnet-4-20250514"
// Falls back to session model
getEffectiveModel(undefined, 'haiku');
// Returns: "claude-haiku-4-5"
// Falls back to default
getEffectiveModel(undefined, undefined, 'gpt-5.2');
// Returns: "gpt-5.2"
Conversation Utils
Location: apps/server/src/lib/conversation-utils.ts
Standardized conversation history processing for both SDK-based and CLI-based providers.
Types
import type { ConversationMessage } from '../providers/types.js';
interface ConversationMessage {
role: 'user' | 'assistant';
content: string | Array<{ type: string; text?: string; source?: object }>;
}
Functions
extractTextFromContent(content: string | Array<ContentBlock>): string
Extract plain text from message content (handles both string and array formats).
Example:
import { extractTextFromContent } from "../lib/conversation-utils.js";
// String content
extractTextFromContent("Hello world");
// Returns: "Hello world"
// Array content
extractTextFromContent([
{ type: "text", text: "Hello" },
{ type: "image", source: {...} },
{ type: "text", text: "world" }
]);
// Returns: "Hello\nworld"
normalizeContentBlocks(content: string | Array<ContentBlock>): Array<ContentBlock>
Normalize message content to array format.
Example:
// String → array
normalizeContentBlocks('Hello');
// Returns: [{ type: "text", text: "Hello" }]
// Array → pass through
normalizeContentBlocks([{ type: 'text', text: 'Hello' }]);
// Returns: [{ type: "text", text: "Hello" }]
formatHistoryAsText(history: ConversationMessage[]): string
Format conversation history as plain text for CLI-based providers (e.g., Codex).
Returns: Formatted text with role labels, or empty string if no history.
Example:
const history = [
{ role: 'user', content: 'What is 2+2?' },
{ role: 'assistant', content: '2+2 equals 4.' },
];
const formatted = formatHistoryAsText(history);
// Returns:
// "Previous conversation:
//
// User: What is 2+2?
//
// Assistant: 2+2 equals 4.
//
// ---
//
// "
convertHistoryToMessages(history: ConversationMessage[]): Array<SDKMessage>
Convert conversation history to Claude SDK message format.
Returns: Array of SDK-formatted messages ready to yield in async generator.
Example:
const history = [
{ role: 'user', content: 'Hello' },
{ role: 'assistant', content: 'Hi there!' },
];
const messages = convertHistoryToMessages(history);
// Returns:
// [
// {
// type: "user",
// session_id: "",
// message: {
// role: "user",
// content: [{ type: "text", text: "Hello" }]
// },
// parent_tool_use_id: null
// },
// {
// type: "assistant",
// session_id: "",
// message: {
// role: "assistant",
// content: [{ type: "text", text: "Hi there!" }]
// },
// parent_tool_use_id: null
// }
// ]
Error Handler
Location: apps/server/src/lib/error-handler.ts
Standardized error classification and handling utilities.
Types
export type ErrorType = 'authentication' | 'abort' | 'execution' | 'unknown';
export interface ErrorInfo {
type: ErrorType;
message: string;
isAbort: boolean;
isAuth: boolean;
originalError: unknown;
}
Functions
isAbortError(error: unknown): boolean
Check if an error is an abort/cancellation error.
Example:
import { isAbortError } from '../lib/error-handler.js';
try {
// ... operation
} catch (error) {
if (isAbortError(error)) {
console.log('Operation was cancelled');
return { success: false, aborted: true };
}
}
isAuthenticationError(errorMessage: string): boolean
Check if an error is an authentication/API key error.
Detects:
- "Authentication failed"
- "Invalid API key"
- "authentication_failed"
- "Fix external API key"
Example:
if (isAuthenticationError(error.message)) {
console.error('Please check your API key configuration');
}
classifyError(error: unknown): ErrorInfo
Classify an error into a specific type.
Example:
import { classifyError } from '../lib/error-handler.js';
try {
// ... operation
} catch (error) {
const errorInfo = classifyError(error);
switch (errorInfo.type) {
case 'authentication':
// Handle auth errors
break;
case 'abort':
// Handle cancellation
break;
case 'execution':
// Handle other errors
break;
}
}
getUserFriendlyErrorMessage(error: unknown): string
Get a user-friendly error message.
Example:
try {
// ... operation
} catch (error) {
const friendlyMessage = getUserFriendlyErrorMessage(error);
// "Operation was cancelled" for abort errors
// "Authentication failed. Please check your API key." for auth errors
// Original error message for other errors
}
Subprocess Manager
Location: apps/server/src/lib/subprocess-manager.ts
Utilities for spawning CLI processes and parsing JSONL streams (used by Codex provider).
Types
export interface SubprocessOptions {
command: string;
args: string[];
cwd: string;
env?: Record<string, string>;
abortController?: AbortController;
timeout?: number; // Milliseconds of no output before timeout
}
export interface SubprocessResult {
stdout: string;
stderr: string;
exitCode: number | null;
}
Functions
async function* spawnJSONLProcess(options: SubprocessOptions): AsyncGenerator<unknown>
Spawns a subprocess and streams JSONL output line-by-line.
Features:
- Parses each line as JSON
- Handles abort signals
- 30-second timeout detection for hanging processes
- Collects stderr for error reporting
- Continues processing other lines if one fails to parse
Example:
import { spawnJSONLProcess } from '../lib/subprocess-manager.js';
const stream = spawnJSONLProcess({
command: 'codex',
args: ['exec', '--model', 'gpt-5.2', '--json', '--full-auto', 'Fix the bug'],
cwd: '/project/path',
env: { OPENAI_API_KEY: 'sk-...' },
abortController: new AbortController(),
timeout: 30000,
});
for await (const event of stream) {
console.log('Received event:', event);
// Process JSONL events
}
async function spawnProcess(options: SubprocessOptions): Promise<SubprocessResult>
Spawns a subprocess and collects all output.
Example:
const result = await spawnProcess({
command: 'git',
args: ['status'],
cwd: '/project/path',
});
console.log(result.stdout); // Git status output
console.log(result.exitCode); // 0 for success
Events
Location: apps/server/src/lib/events.ts
Event emitter system for WebSocket communication.
Documented separately - see existing codebase for event types and usage.
Auth
Location: apps/server/src/lib/auth.ts
Authentication utilities for API endpoints.
Documented separately - see existing codebase for authentication flow.
Security
Location: apps/server/src/lib/security.ts
Security utilities for input validation and sanitization.
Documented separately - see existing codebase for security patterns.
Best Practices
When to Use Which Utility
-
Image handling → Always use
image-handler.tsutilities- ✅ Do:
convertImagesToContentBlocks(imagePaths, workDir) - ❌ Don't: Manually read files and encode base64
- ✅ Do:
-
Prompt building → Use
prompt-builder.tsfor consistency- ✅ Do:
buildPromptWithImages(text, images, workDir, includePathsInText) - ❌ Don't: Manually construct content block arrays
- ✅ Do:
-
Model resolution → Use
model-resolver.tsfor all model handling- ✅ Do:
resolveModelString(feature.model, DEFAULT_MODELS.claude) - ❌ Don't: Inline model mapping logic
- ✅ Do:
-
Error handling → Use
error-handler.tsfor classification- ✅ Do:
if (isAbortError(error)) { ... } - ❌ Don't:
if (error instanceof AbortError || error.name === "AbortError") { ... }
- ✅ Do:
Importing Utilities
Always use .js extension in imports for ESM compatibility:
// ✅ Correct
import { buildPromptWithImages } from '../lib/prompt-builder.js';
// ❌ Incorrect
import { buildPromptWithImages } from '../lib/prompt-builder';
Testing Utilities
When writing tests for utilities:
- Unit tests - Test each function in isolation
- Integration tests - Test utilities working together
- Mock external dependencies - File system, child processes
Example:
describe('image-handler', () => {
it('should detect MIME type correctly', () => {
expect(getMimeTypeForImage('photo.jpg')).toBe('image/jpeg');
expect(getMimeTypeForImage('diagram.png')).toBe('image/png');
});
});