mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-06 09:53:07 +00:00
- 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.
700 lines
16 KiB
Markdown
700 lines
16 KiB
Markdown
# 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
|
|
|
|
1. [Image Handler](#image-handler)
|
|
2. [Prompt Builder](#prompt-builder)
|
|
3. [Model Resolver](#model-resolver)
|
|
4. [Conversation Utils](#conversation-utils)
|
|
5. [Error Handler](#error-handler)
|
|
6. [Subprocess Manager](#subprocess-manager)
|
|
7. [Events](#events)
|
|
8. [Auth](#auth)
|
|
9. [Security](#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**:
|
|
|
|
```typescript
|
|
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`
|
|
|
|
```typescript
|
|
interface ImageData {
|
|
base64: string; // Base64-encoded image data
|
|
mimeType: string; // MIME type
|
|
filename: string; // File basename
|
|
originalPath: string; // Original file path
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
|
|
```typescript
|
|
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 paths
|
|
- `workDir` - Optional working directory for resolving relative paths
|
|
|
|
**Returns**: Array of `ImageContentBlock`
|
|
|
|
```typescript
|
|
interface ImageContentBlock {
|
|
type: 'image';
|
|
source: {
|
|
type: 'base64';
|
|
media_type: string;
|
|
data: string;
|
|
};
|
|
}
|
|
```
|
|
|
|
**Example**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
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 prompt
|
|
- `imagePaths` - Optional array of image file paths
|
|
- `workDir` - Optional working directory for resolving relative paths
|
|
- `includeImagePaths` - Whether to append image paths to the text (default: false)
|
|
|
|
**Returns**: `PromptWithImages`
|
|
|
|
```typescript
|
|
interface PromptWithImages {
|
|
content: PromptContent; // string | Array<ContentBlock>
|
|
hasImages: boolean;
|
|
}
|
|
|
|
type PromptContent =
|
|
| string
|
|
| Array<{
|
|
type: string;
|
|
text?: string;
|
|
source?: object;
|
|
}>;
|
|
```
|
|
|
|
**Example**:
|
|
|
|
```typescript
|
|
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: true` to list paths for Read tool access
|
|
- **AutoModeService**: Set `includeImagePaths: false` to 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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
1. If `modelKey` is undefined → return `defaultModel`
|
|
2. If starts with `"gpt-"` or `"o"` → pass through (OpenAI/Codex model)
|
|
3. If includes `"claude-"` → pass through (full Claude model string)
|
|
4. If in `CLAUDE_MODEL_MAP` → return mapped value
|
|
5. Otherwise → return `defaultModel` with warning
|
|
|
|
**Example**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
// 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**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
if (isAuthenticationError(error.message)) {
|
|
console.error('Please check your API key configuration');
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `classifyError(error: unknown): ErrorInfo`
|
|
|
|
Classify an error into a specific type.
|
|
|
|
**Example**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
```typescript
|
|
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
|
|
|
|
1. **Image handling** → Always use `image-handler.ts` utilities
|
|
- ✅ Do: `convertImagesToContentBlocks(imagePaths, workDir)`
|
|
- ❌ Don't: Manually read files and encode base64
|
|
|
|
2. **Prompt building** → Use `prompt-builder.ts` for consistency
|
|
- ✅ Do: `buildPromptWithImages(text, images, workDir, includePathsInText)`
|
|
- ❌ Don't: Manually construct content block arrays
|
|
|
|
3. **Model resolution** → Use `model-resolver.ts` for all model handling
|
|
- ✅ Do: `resolveModelString(feature.model, DEFAULT_MODELS.claude)`
|
|
- ❌ Don't: Inline model mapping logic
|
|
|
|
4. **Error handling** → Use `error-handler.ts` for classification
|
|
- ✅ Do: `if (isAbortError(error)) { ... }`
|
|
- ❌ Don't: `if (error instanceof AbortError || error.name === "AbortError") { ... }`
|
|
|
|
### Importing Utilities
|
|
|
|
Always use `.js` extension in imports for ESM compatibility:
|
|
|
|
```typescript
|
|
// ✅ Correct
|
|
import { buildPromptWithImages } from '../lib/prompt-builder.js';
|
|
|
|
// ❌ Incorrect
|
|
import { buildPromptWithImages } from '../lib/prompt-builder';
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Utilities
|
|
|
|
When writing tests for utilities:
|
|
|
|
1. **Unit tests** - Test each function in isolation
|
|
2. **Integration tests** - Test utilities working together
|
|
3. **Mock external dependencies** - File system, child processes
|
|
|
|
Example:
|
|
|
|
```typescript
|
|
describe('image-handler', () => {
|
|
it('should detect MIME type correctly', () => {
|
|
expect(getMimeTypeForImage('photo.jpg')).toBe('image/jpeg');
|
|
expect(getMimeTypeForImage('diagram.png')).toBe('image/png');
|
|
});
|
|
});
|
|
```
|