Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"check:tsgo": "tsgo -p tsconfig.check.json",
"check": "tsc -p tsconfig.check.json",
"dev": "COMMIT_SHA=$(git rev-parse HEAD) CONSOLA_LEVEL=9999 FRONTEND_DIR=../frontend/dist DOCS_DIR=docs bun --watch --hot --no-clear-screen --inspect src/index.ts",
"test": "bun test",
"fmt": "oxfmt .",
"fmt:check": "oxfmt --check .",
"lint": "oxlint --type-aware"
Expand Down
8 changes: 3 additions & 5 deletions backend/src/adapters/request/openai-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
ToolResultContentBlock,
ToolUseContentBlock,
} from "../types";
import { safeParseToolArgs } from "@/utils/json";

// =============================================================================
// Helper Functions
Expand Down Expand Up @@ -191,7 +192,7 @@ function convertToolCalls(
type: "tool_use" as const,
id: tc.id,
name: tc.function.name,
input: JSON.parse(tc.function.arguments) as Record<string, unknown>,
input: safeParseToolArgs(tc.function.arguments),
}));
}

Expand Down Expand Up @@ -229,10 +230,7 @@ function convertMessage(msg: OpenAIChatMessage): InternalMessage {
type: "tool_use" as const,
id: "legacy_function_call",
name: msg.function_call.name,
input: JSON.parse(msg.function_call.arguments) as Record<
string,
unknown
>,
input: safeParseToolArgs(msg.function_call.arguments),
},
]
: undefined;
Expand Down
14 changes: 2 additions & 12 deletions backend/src/adapters/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
JsonSchema,
ToolUseContentBlock,
} from "./types";
import { safeParseToolArgs } from "@/utils/json";

// =============================================================================
// OpenAI Tool Types
Expand Down Expand Up @@ -135,7 +136,7 @@ export function fromOpenAIToolCalls(
type: "tool_use" as const,
id: tc.id,
name: tc.function.name,
input: parseJsonSafe(tc.function.arguments),
input: safeParseToolArgs(tc.function.arguments),
}));
}

Expand Down Expand Up @@ -283,17 +284,6 @@ export function fromAnthropicToolChoice(
// Utility Functions
// =============================================================================

/**
* Safely parse JSON string, returning empty object on failure
*/
function parseJsonSafe(jsonString: string): Record<string, unknown> {
try {
return JSON.parse(jsonString) as Record<string, unknown>;
} catch {
return {};
}
}

/**
* Generate a unique tool call ID
*/
Expand Down
3 changes: 2 additions & 1 deletion backend/src/adapters/upstream/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
ToolUseContentBlock,
UpstreamAdapter,
} from "../types";
import { parseJsonResponse } from "@/utils/json";

// =============================================================================
// Anthropic Request/Response Types
Expand Down Expand Up @@ -487,7 +488,7 @@ export const anthropicUpstreamAdapter: UpstreamAdapter = {

async parseResponse(response: Response): Promise<InternalResponse> {
const text = await response.text();
const json = JSON.parse(text) as AnthropicResponse;
const json = parseJsonResponse<AnthropicResponse>(text, "Anthropic");
return convertResponse(json);
},

Expand Down
5 changes: 3 additions & 2 deletions backend/src/adapters/upstream/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
UpstreamAdapter,
} from "../types";
import { convertImageToUrl, hasImages } from "./utils";
import { safeParseToolArgs, parseJsonResponse } from "@/utils/json";

// =============================================================================
// Response API Types
Expand Down Expand Up @@ -237,7 +238,7 @@ function convertResponse(resp: ResponseApiResponse): InternalResponse {
id: output.call_id || output.id || "",
name: output.name || "",
input: output.arguments
? (JSON.parse(output.arguments) as Record<string, unknown>)
? safeParseToolArgs(output.arguments)
: {},
} as ToolUseContentBlock);
}
Expand Down Expand Up @@ -365,7 +366,7 @@ export const openaiResponsesUpstreamAdapter: UpstreamAdapter = {

async parseResponse(response: Response): Promise<InternalResponse> {
const text = await response.text();
const json = JSON.parse(text) as ResponseApiResponse;
const json = parseJsonResponse<ResponseApiResponse>(text, "OpenAI Responses");
return convertResponse(json);
},

Expand Down
5 changes: 3 additions & 2 deletions backend/src/adapters/upstream/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
UpstreamAdapter,
} from "../types";
import { convertImageToUrl, hasImages } from "./utils";
import { safeParseToolArgs, parseJsonResponse } from "@/utils/json";

// =============================================================================
// OpenAI Request/Response Types
Expand Down Expand Up @@ -321,7 +322,7 @@ function convertResponse(resp: OpenAIChatResponse): InternalResponse {
type: "tool_use",
id: tc.id,
name: tc.function.name,
input: JSON.parse(tc.function.arguments) as Record<string, unknown>,
input: safeParseToolArgs(tc.function.arguments),
} as ToolUseContentBlock);
}
}
Expand Down Expand Up @@ -450,7 +451,7 @@ export const openaiUpstreamAdapter: UpstreamAdapter = {

async parseResponse(response: Response): Promise<InternalResponse> {
const text = await response.text();
const json = JSON.parse(text) as OpenAIChatResponse;
const json = parseJsonResponse<OpenAIChatResponse>(text, "OpenAI Chat");
return convertResponse(json);
},

Expand Down
24 changes: 22 additions & 2 deletions backend/src/api/v1/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,17 @@ export const completionsApi = new Elysia({

if (errorResult.type === "upstream_error") {
set.status = errorResult.status;
return JSON.parse(errorResult.body) as Record<string, unknown>;
try {
return JSON.parse(errorResult.body) as Record<string, unknown>;
} catch {
return {
error: {
message: errorResult.body,
type: "upstream_error",
code: "unparseable_error",
},
};
}
}

set.status = 502;
Expand Down Expand Up @@ -730,7 +740,17 @@ export const completionsApi = new Elysia({

if (errorResult.type === "upstream_error") {
set.status = errorResult.status;
return JSON.parse(errorResult.body) as Record<string, unknown>;
try {
return JSON.parse(errorResult.body) as Record<string, unknown>;
} catch {
return {
error: {
message: errorResult.body,
type: "upstream_error",
code: "unparseable_error",
},
};
}
}

set.status = 502;
Expand Down
29 changes: 26 additions & 3 deletions backend/src/api/v1/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
type ReqIdContext,
} from "@/utils/reqIdHandler";
import type { CachedResponseType } from "@/db/schema";
import { safeParseToolArgs } from "@/utils/json";

const logger = consola.withTag("messagesApi");

Expand Down Expand Up @@ -292,7 +293,7 @@ async function* processStreamingResponse(
type: "tool_use",
id: tc.id,
name: tc.function.name,
input: JSON.parse(tc.function.arguments || "{}"),
input: safeParseToolArgs(tc.function.arguments || "{}"),
});
}
}
Expand Down Expand Up @@ -625,7 +626,18 @@ export const messagesApi = new Elysia({

if (errorResult.type === "upstream_error") {
set.status = errorResult.status;
return JSON.parse(errorResult.body) as Record<string, unknown>;
try {
return JSON.parse(errorResult.body) as Record<string, unknown>;
} catch {
return {
type: "error",
error: {
type: "api_error",
message: errorResult.body,
code: "unparseable_error",
},
};
}
}

set.status = 502;
Expand Down Expand Up @@ -713,7 +725,18 @@ export const messagesApi = new Elysia({

if (errorResult.type === "upstream_error") {
set.status = errorResult.status;
return JSON.parse(errorResult.body) as Record<string, unknown>;
try {
return JSON.parse(errorResult.body) as Record<string, unknown>;
} catch {
return {
type: "error",
error: {
type: "api_error",
message: errorResult.body,
code: "unparseable_error",
},
};
}
}

set.status = 502;
Expand Down
26 changes: 24 additions & 2 deletions backend/src/api/v1/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,18 @@ export const responsesApi = new Elysia({

if (errorResult.type === "upstream_error") {
set.status = errorResult.status;
return JSON.parse(errorResult.body) as Record<string, unknown>;
try {
return JSON.parse(errorResult.body) as Record<string, unknown>;
} catch {
return {
object: "error",
error: {
type: "upstream_error",
message: errorResult.body,
code: "unparseable_error",
},
};
}
}

set.status = 502;
Expand Down Expand Up @@ -763,7 +774,18 @@ export const responsesApi = new Elysia({

if (errorResult.type === "upstream_error") {
set.status = errorResult.status;
return JSON.parse(errorResult.body) as Record<string, unknown>;
try {
return JSON.parse(errorResult.body) as Record<string, unknown>;
} catch {
return {
object: "error",
error: {
type: "upstream_error",
message: errorResult.body,
code: "unparseable_error",
},
};
}
}

set.status = 502;
Expand Down
Loading