Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/lib/models/providers/ollama/ollamaLLM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { parse } from 'partial-json';
import crypto from 'crypto';
import { Message } from '@/lib/types';
import { repairJson } from '@toolsycc/json-repair';
import { stripMarkdownFences } from '@/lib/utils/parseJson';

type OllamaConfig = {
baseURL: string;
Expand Down Expand Up @@ -249,7 +250,7 @@ class OllamaLLM extends BaseLLM<OllamaConfig> {
recievedObj += chunk.message.content;

try {
yield parse(recievedObj) as T;
yield parse(stripMarkdownFences(recievedObj)) as T;
} catch (err) {
console.log('Error parsing partial object from Ollama:', err);
yield {} as T;
Expand Down
7 changes: 4 additions & 3 deletions src/lib/models/providers/openai/openaiLLM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from 'openai/resources/index.mjs';
import { Message } from '@/lib/types';
import { repairJson } from '@toolsycc/json-repair';
import { safeParseJson, stripMarkdownFences } from '@/lib/utils/parseJson';

type OpenAIConfig = {
apiKey: string;
Expand Down Expand Up @@ -110,7 +111,7 @@ class OpenAILLM extends BaseLLM<OpenAIConfig> {
return {
name: tc.function.name,
id: tc.id,
arguments: JSON.parse(tc.function.arguments),
arguments: safeParseJson(tc.function.arguments),
};
}
})
Expand Down Expand Up @@ -256,14 +257,14 @@ class OpenAILLM extends BaseLLM<OpenAIConfig> {
recievedObj += chunk.delta;

try {
yield parse(recievedObj) as T;
yield parse(stripMarkdownFences(recievedObj)) as T;
} catch (err) {
console.log('Error parsing partial object from OpenAI:', err);
yield {} as T;
}
} else if (chunk.type === 'response.output_text.done' && chunk.text) {
try {
yield parse(chunk.text) as T;
yield parse(stripMarkdownFences(chunk.text)) as T;
} catch (err) {
throw new Error(`Error parsing response from OpenAI: ${err}`);
}
Expand Down
21 changes: 21 additions & 0 deletions src/lib/utils/parseJson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Strips markdown code fences that some LLM providers (Claude, models via
* LiteLLM/OpenRouter) wrap around JSON output.
*
* Handles ```json ... ```, ``` ... ```, and raw JSON (no-op).
*/
export function stripMarkdownFences(text: string): string {
return text
.trim()
.replace(/^```(?:json)?\s*/i, '')
.replace(/\s*```$/, '')
.trim();
}

/**
* Strips markdown code fences from LLM output before JSON.parse.
* Fixes issue #959: Claude/LiteLLM models wrap JSON in markdown blocks.
*/
export function safeParseJson<T>(text: string): T {
return JSON.parse(stripMarkdownFences(text)) as T;
}
Loading