Skip to content

Commit ef79289

Browse files
committed
Refactor code for consistency: standardize import formatting, enhance error handling, and improve component rendering logic
1 parent a8d7537 commit ef79289

30 files changed

+634
-624
lines changed

src/agent/autofix.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// src/agent/autofix.ts
2+
// CORRECTED: Adjusted the function signature to resolve the complex generic type error with streamObject.
3+
4+
import {createOpenAI} from "@ai-sdk/openai";
5+
import {streamObject} from "ai";
6+
import {z} from "zod";
7+
import logger from "@/logger.js";
8+
9+
const getFixerClient = () => {
10+
const apiKey = process.env.OPENAI_API_KEY;
11+
if (!apiKey) {
12+
logger.warn("Autofix requires OPENAI_API_KEY to be set for a fixer model.");
13+
return null;
14+
}
15+
return createOpenAI({apiKey})("gpt-4.1-mini");
16+
};
17+
18+
const fixJsonPrompt = (badJson: string) => `
19+
The following string is invalid JSON produced by an AI. Your task is to fix it.
20+
Respond ONLY with the corrected JSON object. Do not add any commentary, just the JSON.
21+
22+
Invalid JSON:
23+
${badJson}
24+
`;
25+
26+
// The schema is now constrained to `z.ZodObject<any>`, which is more compatible with the AI SDK.
27+
export async function autofixJson(
28+
schema: z.ZodObject<any>,
29+
brokenJson: string,
30+
): Promise<z.infer<typeof schema> | null> {
31+
const fixer = getFixerClient();
32+
if (!fixer) return null;
33+
34+
try {
35+
// The call to streamObject now works with the simplified type.
36+
const {object} = await streamObject({
37+
model: fixer,
38+
prompt: fixJsonPrompt(brokenJson),
39+
schema: schema,
40+
});
41+
// We still parse here to ensure the final object conforms to the specific schema passed in.
42+
return schema.parse(object);
43+
} catch (e) {
44+
logger.error("JSON autofixing failed.", e);
45+
return null;
46+
}
47+
}

src/agent/errors.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// src/agent/errors.ts
2+
// REFACTORED: Centralized all custom agent errors in one place.
23

34
/**
45
* A base class for all custom errors in the application.
@@ -12,18 +13,39 @@ export class AppError extends Error {
1213

1314
/**
1415
* Represents an error that is fatal and should stop the agent's execution.
15-
* Examples: Invalid API key, configuration errors.
1616
*/
17-
export class FatalError extends AppError {}
17+
export class FatalError extends AppError {
18+
}
1819

1920
/**
2021
* Represents a transient error that might be resolved by retrying.
21-
* Examples: Temporary network issues, rate limiting.
2222
*/
23-
export class TransientError extends AppError {}
23+
export class TransientError extends AppError {
24+
}
2425

2526
/**
2627
* Represents an error related to tool execution.
27-
* Examples: Tool not found, invalid arguments, tool failed to run.
2828
*/
29-
export class ToolError extends AppError {}
29+
export class ToolError extends AppError {
30+
}
31+
32+
/**
33+
* Thrown when an edit is attempted on a file that has been modified
34+
* since it was last read by the agent.
35+
*/
36+
export class FileOutdatedError extends ToolError {
37+
constructor(message: string) {
38+
super(message);
39+
this.name = this.constructor.name;
40+
}
41+
}
42+
43+
/**
44+
* Thrown when a 'create' operation is attempted on a file that already exists.
45+
*/
46+
export class FileExistsError extends ToolError {
47+
constructor(message: string) {
48+
super(message);
49+
this.name = this.constructor.name;
50+
}
51+
}

src/agent/file-tracker.ts

Lines changed: 6 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,13 @@
11
// src/agent/file-tracker.ts
2+
// REFACTORED: Imports errors from the new centralized `errors.ts`.
3+
24
import fs from "fs/promises";
35
import path from "path";
4-
5-
// --- Custom Error Types ---
6-
export class FileOutdatedError extends Error {
7-
constructor(message: string) {
8-
super(message);
9-
this.name = this.constructor.name;
10-
}
11-
}
12-
13-
export class FileExistsError extends Error {
14-
constructor(message: string) {
15-
super(message);
16-
this.name = this.constructor.name;
17-
}
18-
}
6+
import {FileExistsError, FileOutdatedError} from "./errors.js";
197

208
export class FileTracker {
21-
// A map to store the last known modification time of a file
229
private readTimestamps = new Map<string, number>();
2310

24-
/**
25-
* Reads a file, records its modification time, and returns the content.
26-
* All tool reads should use this method.
27-
*/
2811
async read(filePath: string): Promise<string> {
2912
const absolutePath = path.resolve(filePath);
3013
const content = await fs.readFile(absolutePath, "utf8");
@@ -33,44 +16,27 @@ export class FileTracker {
3316
return content;
3417
}
3518

36-
/**
37-
* Writes to a file, and updates its modification time in the tracker.
38-
* All tool writes should use this method.
39-
*/
4019
async write(filePath: string, content: string): Promise<void> {
4120
const absolutePath = path.resolve(filePath);
4221
const dir = path.dirname(absolutePath);
43-
await fs.mkdir(dir, { recursive: true });
22+
await fs.mkdir(dir, {recursive: true});
4423
await fs.writeFile(absolutePath, content, "utf8");
4524
const modified = await this.getModifiedTime(absolutePath);
4625
this.readTimestamps.set(absolutePath, modified);
4726
}
4827

49-
/**
50-
* Throws an error if a file exists. Used by the 'create' tool.
51-
*/
5228
async assertCanCreate(filePath: string): Promise<void> {
5329
try {
5430
await fs.access(filePath);
55-
// If fs.access doesn't throw, the file exists.
5631
throw new FileExistsError(
5732
`File already exists at ${filePath}. Use the 'edit' tool to modify it.`,
5833
);
5934
} catch (error) {
60-
if (error instanceof FileExistsError) {
61-
throw error;
62-
}
63-
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
64-
throw error;
65-
}
66-
// Any other error (like ENOENT) means the file doesn't exist, which is good.
35+
if (error instanceof FileExistsError) throw error;
36+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
6737
}
6838
}
6939

70-
/**
71-
* Throws an error if a file has been modified since it was last read/written by the tracker.
72-
* This is the core safety check for the 'edit' tool.
73-
*/
7440
async assertCanEdit(filePath: string): Promise<void> {
7541
const absolutePath = path.resolve(filePath);
7642
if (!this.readTimestamps.has(absolutePath)) {
@@ -81,12 +47,10 @@ export class FileTracker {
8147

8248
const lastReadTime = this.readTimestamps.get(absolutePath)!;
8349
let currentModifiedTime;
84-
8550
try {
8651
currentModifiedTime = await this.getModifiedTime(absolutePath);
8752
} catch (error) {
8853
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
89-
// If the file was deleted after being read
9054
throw new FileOutdatedError(
9155
"File seems to have been deleted after it was last read.",
9256
);
@@ -95,7 +59,6 @@ export class FileTracker {
9559
}
9660

9761
if (currentModifiedTime > lastReadTime) {
98-
// Invalidate the timestamp and throw
9962
this.readTimestamps.delete(absolutePath);
10063
throw new FileOutdatedError(
10164
"File was modified since it was last read. Please read the file again to get the latest version.",
@@ -109,5 +72,4 @@ export class FileTracker {
10972
}
11073
}
11174

112-
// Export a single instance for the entire application to use
11375
export const fileTracker = new FileTracker();

src/agent/history.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,47 @@
11
// src/agent/history.ts
2-
import type { AssistantToolCall, Message, ToolCall } from "./types.js";
2+
// REFACTORED: This file now defines the rich, structured history for the agent.
3+
// This is a core concept from Octofriend that enables more intelligent UI and logic.
34

4-
export type { Message, AssistantToolCall, ToolCall };
5+
import type {AssistantContent} from "ai";
6+
import type {ToolCall} from "./types.js";
7+
8+
export type UserMessageItem = {
9+
id: string;
10+
role: "user";
11+
content: string;
12+
};
13+
14+
export type AssistantMessageItem = {
15+
id: string;
16+
role: "assistant";
17+
content: AssistantContent | string;
18+
};
19+
20+
export type ToolRequestItem = {
21+
id: string;
22+
role: "tool-request";
23+
calls: ToolCall[];
24+
};
25+
26+
export type ToolResultItem = {
27+
id: string;
28+
role: "tool-result";
29+
toolCallId: string;
30+
toolName: string;
31+
output: unknown;
32+
};
33+
34+
export type ToolFailureItem = {
35+
id: string;
36+
role: "tool-failure";
37+
toolCallId: string;
38+
toolName: string;
39+
error: string;
40+
};
41+
42+
export type HistoryItem =
43+
| UserMessageItem
44+
| AssistantMessageItem
45+
| ToolRequestItem
46+
| ToolResultItem
47+
| ToolFailureItem;

0 commit comments

Comments
 (0)