Merge branch 'main' of github.com:AutoMaker-Org/automaker into improve-context-page

This commit is contained in:
Test User
2025-12-22 00:50:55 -05:00
501 changed files with 17637 additions and 17437 deletions

View File

@@ -19,15 +19,15 @@ import type { ConversationMessage } from '@automaker/types';
export function extractTextFromContent(
content: string | Array<{ type: string; text?: string; source?: object }>
): string {
if (typeof content === "string") {
if (typeof content === 'string') {
return content;
}
// Extract text blocks only
return content
.filter((block) => block.type === "text")
.map((block) => block.text || "")
.join("\n");
.filter((block) => block.type === 'text')
.map((block) => block.text || '')
.join('\n');
}
/**
@@ -42,7 +42,7 @@ export function normalizeContentBlocks(
if (Array.isArray(content)) {
return content;
}
return [{ type: "text", text: content }];
return [{ type: 'text', text: content }];
}
/**
@@ -53,18 +53,18 @@ export function normalizeContentBlocks(
*/
export function formatHistoryAsText(history: ConversationMessage[]): string {
if (history.length === 0) {
return "";
return '';
}
let historyText = "Previous conversation:\n\n";
let historyText = 'Previous conversation:\n\n';
for (const msg of history) {
const contentText = extractTextFromContent(msg.content);
const role = msg.role === "user" ? "User" : "Assistant";
const role = msg.role === 'user' ? 'User' : 'Assistant';
historyText += `${role}: ${contentText}\n\n`;
}
historyText += "---\n\n";
historyText += '---\n\n';
return historyText;
}
@@ -74,20 +74,18 @@ export function formatHistoryAsText(history: ConversationMessage[]): string {
* @param history - Array of conversation messages
* @returns Array of Claude SDK formatted messages
*/
export function convertHistoryToMessages(
history: ConversationMessage[]
): Array<{
type: "user" | "assistant";
export function convertHistoryToMessages(history: ConversationMessage[]): Array<{
type: 'user' | 'assistant';
session_id: string;
message: {
role: "user" | "assistant";
role: 'user' | 'assistant';
content: Array<{ type: string; text?: string; source?: object }>;
};
parent_tool_use_id: null;
}> {
return history.map((historyMsg) => ({
type: historyMsg.role,
session_id: "",
session_id: '',
message: {
role: historyMsg.role,
content: normalizeContentBlocks(historyMsg.content),

View File

@@ -17,10 +17,7 @@ import type { ErrorType, ErrorInfo } from '@automaker/types';
* @returns True if the error is an abort error
*/
export function isAbortError(error: unknown): boolean {
return (
error instanceof Error &&
(error.name === "AbortError" || error.message.includes("abort"))
);
return error instanceof Error && (error.name === 'AbortError' || error.message.includes('abort'));
}
/**
@@ -32,10 +29,10 @@ export function isAbortError(error: unknown): boolean {
export function isCancellationError(errorMessage: string): boolean {
const lowerMessage = errorMessage.toLowerCase();
return (
lowerMessage.includes("cancelled") ||
lowerMessage.includes("canceled") ||
lowerMessage.includes("stopped") ||
lowerMessage.includes("aborted")
lowerMessage.includes('cancelled') ||
lowerMessage.includes('canceled') ||
lowerMessage.includes('stopped') ||
lowerMessage.includes('aborted')
);
}
@@ -47,10 +44,10 @@ export function isCancellationError(errorMessage: string): boolean {
*/
export function isAuthenticationError(errorMessage: string): boolean {
return (
errorMessage.includes("Authentication failed") ||
errorMessage.includes("Invalid API key") ||
errorMessage.includes("authentication_failed") ||
errorMessage.includes("Fix external API key")
errorMessage.includes('Authentication failed') ||
errorMessage.includes('Invalid API key') ||
errorMessage.includes('authentication_failed') ||
errorMessage.includes('Fix external API key')
);
}
@@ -61,22 +58,22 @@ export function isAuthenticationError(errorMessage: string): boolean {
* @returns Classified error information
*/
export function classifyError(error: unknown): ErrorInfo {
const message = error instanceof Error ? error.message : String(error || "Unknown error");
const message = error instanceof Error ? error.message : String(error || 'Unknown error');
const isAbort = isAbortError(error);
const isAuth = isAuthenticationError(message);
const isCancellation = isCancellationError(message);
let type: ErrorType;
if (isAuth) {
type = "authentication";
type = 'authentication';
} else if (isAbort) {
type = "abort";
type = 'abort';
} else if (isCancellation) {
type = "cancellation";
type = 'cancellation';
} else if (error instanceof Error) {
type = "execution";
type = 'execution';
} else {
type = "unknown";
type = 'unknown';
}
return {
@@ -99,11 +96,11 @@ export function getUserFriendlyErrorMessage(error: unknown): string {
const info = classifyError(error);
if (info.isAbort) {
return "Operation was cancelled";
return 'Operation was cancelled';
}
if (info.isAuth) {
return "Authentication failed. Please check your API key.";
return 'Authentication failed. Please check your API key.';
}
return info.message;
@@ -130,5 +127,5 @@ export function getUserFriendlyErrorMessage(error: unknown): string {
* ```
*/
export function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : "Unknown error";
return error instanceof Error ? error.message : 'Unknown error';
}

View File

@@ -2,8 +2,8 @@
* File system utilities that handle symlinks safely
*/
import { secureFs } from "@automaker/platform";
import path from "path";
import { secureFs } from '@automaker/platform';
import path from 'path';
/**
* Create a directory, handling symlinks safely to avoid ELOOP errors.
@@ -23,10 +23,10 @@ export async function mkdirSafe(dirPath: string): Promise<void> {
throw new Error(`Path exists and is not a directory: ${resolvedPath}`);
} catch (error: any) {
// ENOENT means path doesn't exist - we should create it
if (error.code !== "ENOENT") {
if (error.code !== 'ENOENT') {
// Some other error (could be ELOOP in parent path)
// If it's ELOOP, the path involves symlinks - don't try to create
if (error.code === "ELOOP") {
if (error.code === 'ELOOP') {
console.warn(`[fs-utils] Symlink loop detected at ${resolvedPath}, skipping mkdir`);
return;
}
@@ -39,7 +39,7 @@ export async function mkdirSafe(dirPath: string): Promise<void> {
await secureFs.mkdir(resolvedPath, { recursive: true });
} catch (error: any) {
// Handle race conditions and symlink issues
if (error.code === "EEXIST" || error.code === "ELOOP") {
if (error.code === 'EEXIST' || error.code === 'ELOOP') {
return;
}
throw error;
@@ -55,11 +55,11 @@ export async function existsSafe(filePath: string): Promise<boolean> {
await secureFs.lstat(filePath);
return true;
} catch (error: any) {
if (error.code === "ENOENT") {
if (error.code === 'ENOENT') {
return false;
}
// ELOOP or other errors - path exists but is problematic
if (error.code === "ELOOP") {
if (error.code === 'ELOOP') {
return true; // Symlink exists, even if looping
}
throw error;

View File

@@ -8,19 +8,19 @@
* - Path resolution (relative/absolute)
*/
import { secureFs } from "@automaker/platform";
import path from "path";
import { secureFs } from '@automaker/platform';
import path from 'path';
import type { ImageData, ImageContentBlock } from '@automaker/types';
/**
* MIME type mapping for image file extensions
*/
const IMAGE_MIME_TYPES: Record<string, string> = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".webp": "image/webp",
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.webp': 'image/webp',
} as const;
/**
@@ -31,7 +31,7 @@ const IMAGE_MIME_TYPES: Record<string, string> = {
*/
export function getMimeTypeForImage(imagePath: string): string {
const ext = path.extname(imagePath).toLowerCase();
return IMAGE_MIME_TYPES[ext] || "image/png";
return IMAGE_MIME_TYPES[ext] || 'image/png';
}
/**
@@ -42,8 +42,8 @@ export function getMimeTypeForImage(imagePath: string): string {
* @throws Error if file cannot be read
*/
export async function readImageAsBase64(imagePath: string): Promise<ImageData> {
const imageBuffer = await secureFs.readFile(imagePath) as Buffer;
const base64Data = imageBuffer.toString("base64");
const imageBuffer = (await secureFs.readFile(imagePath)) as Buffer;
const base64Data = imageBuffer.toString('base64');
const mimeType = getMimeTypeForImage(imagePath);
return {
@@ -71,16 +71,15 @@ export async function convertImagesToContentBlocks(
for (const imagePath of imagePaths) {
try {
// Resolve to absolute path if needed
const absolutePath = workDir && !path.isAbsolute(imagePath)
? path.join(workDir, imagePath)
: imagePath;
const absolutePath =
workDir && !path.isAbsolute(imagePath) ? path.join(workDir, imagePath) : imagePath;
const imageData = await readImageAsBase64(absolutePath);
blocks.push({
type: "image",
type: 'image',
source: {
type: "base64",
type: 'base64',
media_type: imageData.mimeType,
data: imageData.base64,
},
@@ -103,10 +102,10 @@ export async function convertImagesToContentBlocks(
*/
export function formatImagePathsForPrompt(imagePaths: string[]): string {
if (imagePaths.length === 0) {
return "";
return '';
}
let text = "\n\nAttached images:\n";
let text = '\n\nAttached images:\n';
for (const imagePath of imagePaths) {
text += `- ${imagePath}\n`;
}

View File

@@ -53,7 +53,7 @@ export function createLogger(context: string) {
debug: (...args: unknown[]): void => {
if (currentLogLevel >= LogLevel.DEBUG) {
console.log(prefix, "[DEBUG]", ...args);
console.log(prefix, '[DEBUG]', ...args);
}
},
};

View File

@@ -22,7 +22,7 @@
* ```
*/
export function normalizePath(p: string): string {
return p.replace(/\\/g, "/");
return p.replace(/\\/g, '/');
}
/**
@@ -45,10 +45,7 @@ export function normalizePath(p: string): string {
* pathsEqual(null, null); // true
* ```
*/
export function pathsEqual(
p1: string | undefined | null,
p2: string | undefined | null
): boolean {
export function pathsEqual(p1: string | undefined | null, p2: string | undefined | null): boolean {
if (!p1 || !p2) return p1 === p2;
return normalizePath(p1) === normalizePath(p2);
}

View File

@@ -8,16 +8,18 @@
* - Supports both vision and non-vision models
*/
import { convertImagesToContentBlocks, formatImagePathsForPrompt } from "./image-handler.js";
import { convertImagesToContentBlocks, formatImagePathsForPrompt } from './image-handler.js';
/**
* Content that can be either simple text or structured blocks
*/
export type PromptContent = string | Array<{
type: string;
text?: string;
source?: object;
}>;
export type PromptContent =
| string
| Array<{
type: string;
text?: string;
source?: object;
}>;
/**
* Result of building a prompt with optional images
@@ -62,7 +64,7 @@ export async function buildPromptWithImages(
// Add text block if we have text
if (textContent.trim()) {
contentBlocks.push({ type: "text", text: textContent });
contentBlocks.push({ type: 'text', text: textContent });
}
// Add image blocks
@@ -71,9 +73,7 @@ export async function buildPromptWithImages(
// Return appropriate format
const content: PromptContent =
contentBlocks.length > 1 || contentBlocks[0]?.type === "image"
? contentBlocks
: textContent;
contentBlocks.length > 1 || contentBlocks[0]?.type === 'image' ? contentBlocks : textContent;
return { content, hasImages: true };
}