refactor: eliminate code duplication with shared utilities

Created 5 new utility modules in apps/server/src/lib/ to eliminate ~320 lines of duplicated code:
- image-handler.ts: Centralized image processing (MIME types, base64, content blocks)
- prompt-builder.ts: Standardized prompt building with image attachments
- model-resolver.ts: Model alias resolution and provider routing
- conversation-utils.ts: Conversation history processing for providers
- error-handler.ts: Error classification and user-friendly messages

Updated services and providers to use shared utilities:
- agent-service.ts: -51 lines (removed duplicate image handling, model logic)
- auto-mode-service.ts: -75 lines (removed MODEL_MAP, duplicate utilities)
- claude-provider.ts: -10 lines (uses conversation-utils)
- codex-provider.ts: -5 lines (uses conversation-utils)

Added comprehensive documentation:
- docs/server/utilities.md: Complete reference for all 9 lib utilities
- docs/server/providers.md: Provider architecture guide with examples

Benefits:
- Single source of truth for critical business logic
- Improved maintainability and testability
- Consistent behavior across services and providers
- Better documentation for future development

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2025-12-13 04:26:58 +01:00
parent 0519aba820
commit 7cbdb3db73
11 changed files with 2008 additions and 188 deletions

View File

@@ -7,6 +7,7 @@
import { query, type Options } from "@anthropic-ai/claude-agent-sdk";
import { BaseProvider } from "./base-provider.js";
import { convertHistoryToMessages, normalizeContentBlocks } from "../lib/conversation-utils.js";
import type {
ExecuteOptions,
ProviderMessage,
@@ -64,19 +65,10 @@ export class ClaudeProvider extends BaseProvider {
if (conversationHistory && conversationHistory.length > 0) {
// Multi-turn conversation with history
promptPayload = (async function* () {
// Yield all previous messages first
for (const historyMsg of conversationHistory) {
yield {
type: historyMsg.role,
session_id: "",
message: {
role: historyMsg.role,
content: Array.isArray(historyMsg.content)
? historyMsg.content
: [{ type: "text", text: historyMsg.content }],
},
parent_tool_use_id: null,
};
// Yield history messages using utility
const historyMessages = convertHistoryToMessages(conversationHistory);
for (const msg of historyMessages) {
yield msg;
}
// Yield current prompt
@@ -85,9 +77,7 @@ export class ClaudeProvider extends BaseProvider {
session_id: "",
message: {
role: "user" as const,
content: Array.isArray(prompt)
? prompt
: [{ type: "text", text: prompt }],
content: normalizeContentBlocks(prompt),
},
parent_tool_use_id: null,
};

View File

@@ -9,6 +9,7 @@ import { BaseProvider } from "./base-provider.js";
import { CodexCliDetector } from "./codex-cli-detector.js";
import { codexConfigManager } from "./codex-config-manager.js";
import { spawnJSONLProcess } from "../lib/subprocess-manager.js";
import { formatHistoryAsText } from "../lib/conversation-utils.js";
import type {
ExecuteOptions,
ProviderMessage,
@@ -99,14 +100,8 @@ export class CodexProvider extends BaseProvider {
// Add conversation history
if (conversationHistory && conversationHistory.length > 0) {
let historyText = "Previous conversation:\n\n";
for (const msg of conversationHistory) {
const contentText = typeof msg.content === "string"
? msg.content
: msg.content.map(c => c.text || "").join("");
historyText += `${msg.role === "user" ? "User" : "Assistant"}: ${contentText}\n\n`;
}
combinedPrompt = `${historyText}---\n\nCurrent request:\n${combinedPrompt}`;
const historyText = formatHistoryAsText(conversationHistory);
combinedPrompt = `${historyText}Current request:\n${combinedPrompt}`;
}
// Build command arguments