agentic-json-repair 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["henrikdev.ag-quota"]
3
+ }
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+
3
+ type AgenticParseResult<T> = {
4
+ success: true;
5
+ data: T;
6
+ } | {
7
+ success: false;
8
+ error: string;
9
+ retryPrompt: string;
10
+ };
11
+ declare function safeParseAI<T>(aiOutput: string, schema: z.ZodType<T>): AgenticParseResult<T>;
12
+
13
+ export { type AgenticParseResult, safeParseAI };
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+
3
+ type AgenticParseResult<T> = {
4
+ success: true;
5
+ data: T;
6
+ } | {
7
+ success: false;
8
+ error: string;
9
+ retryPrompt: string;
10
+ };
11
+ declare function safeParseAI<T>(aiOutput: string, schema: z.ZodType<T>): AgenticParseResult<T>;
12
+
13
+ export { type AgenticParseResult, safeParseAI };
package/dist/index.js ADDED
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ safeParseAI: () => safeParseAI
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var import_jsonrepair = require("jsonrepair");
27
+ function extractJsonContent(aiOutput) {
28
+ const codeBlockMatch = aiOutput.match(/```(?:json)?\s*([\s\S]*?)```/);
29
+ if (codeBlockMatch) {
30
+ return codeBlockMatch[1].trim();
31
+ }
32
+ const firstBrace = aiOutput.indexOf("{");
33
+ const firstBracket = aiOutput.indexOf("[");
34
+ let startChar;
35
+ let endChar;
36
+ let startIndex;
37
+ if (firstBrace === -1 && firstBracket === -1) {
38
+ return aiOutput.trim();
39
+ } else if (firstBrace === -1) {
40
+ startChar = "[";
41
+ endChar = "]";
42
+ startIndex = firstBracket;
43
+ } else if (firstBracket === -1) {
44
+ startChar = "{";
45
+ endChar = "}";
46
+ startIndex = firstBrace;
47
+ } else {
48
+ if (firstBrace < firstBracket) {
49
+ startChar = "{";
50
+ endChar = "}";
51
+ startIndex = firstBrace;
52
+ } else {
53
+ startChar = "[";
54
+ endChar = "]";
55
+ startIndex = firstBracket;
56
+ }
57
+ }
58
+ let depth = 0;
59
+ let inString = false;
60
+ let escapeNext = false;
61
+ for (let i = startIndex; i < aiOutput.length; i++) {
62
+ const char = aiOutput[i];
63
+ if (escapeNext) {
64
+ escapeNext = false;
65
+ continue;
66
+ }
67
+ if (char === "\\" && inString) {
68
+ escapeNext = true;
69
+ continue;
70
+ }
71
+ if (char === '"') {
72
+ inString = !inString;
73
+ continue;
74
+ }
75
+ if (!inString) {
76
+ if (char === startChar) {
77
+ depth++;
78
+ } else if (char === endChar) {
79
+ depth--;
80
+ if (depth === 0) {
81
+ return aiOutput.slice(startIndex, i + 1);
82
+ }
83
+ }
84
+ }
85
+ }
86
+ return aiOutput.slice(startIndex).trim();
87
+ }
88
+ function safeParseAI(aiOutput, schema) {
89
+ try {
90
+ const cleanOutput = extractJsonContent(aiOutput);
91
+ const repairedJson = (0, import_jsonrepair.jsonrepair)(cleanOutput);
92
+ const parsedObject = JSON.parse(repairedJson);
93
+ const validation = schema.safeParse(parsedObject);
94
+ if (validation.success) {
95
+ return { success: true, data: validation.data };
96
+ } else {
97
+ const issues = validation.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
98
+ return {
99
+ success: false,
100
+ error: "Validation Failed",
101
+ retryPrompt: `The JSON was parsed but failed validation. Fix these issues: [${issues}] and return ONLY the JSON.`
102
+ };
103
+ }
104
+ } catch (error) {
105
+ return {
106
+ success: false,
107
+ error: "JSON Syntax Error",
108
+ retryPrompt: `The output was not valid JSON. Error: ${error.message}. Return ONLY valid JSON.`
109
+ };
110
+ }
111
+ }
112
+ // Annotate the CommonJS export names for ESM import in node:
113
+ 0 && (module.exports = {
114
+ safeParseAI
115
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,90 @@
1
+ // src/index.ts
2
+ import { jsonrepair } from "jsonrepair";
3
+ function extractJsonContent(aiOutput) {
4
+ const codeBlockMatch = aiOutput.match(/```(?:json)?\s*([\s\S]*?)```/);
5
+ if (codeBlockMatch) {
6
+ return codeBlockMatch[1].trim();
7
+ }
8
+ const firstBrace = aiOutput.indexOf("{");
9
+ const firstBracket = aiOutput.indexOf("[");
10
+ let startChar;
11
+ let endChar;
12
+ let startIndex;
13
+ if (firstBrace === -1 && firstBracket === -1) {
14
+ return aiOutput.trim();
15
+ } else if (firstBrace === -1) {
16
+ startChar = "[";
17
+ endChar = "]";
18
+ startIndex = firstBracket;
19
+ } else if (firstBracket === -1) {
20
+ startChar = "{";
21
+ endChar = "}";
22
+ startIndex = firstBrace;
23
+ } else {
24
+ if (firstBrace < firstBracket) {
25
+ startChar = "{";
26
+ endChar = "}";
27
+ startIndex = firstBrace;
28
+ } else {
29
+ startChar = "[";
30
+ endChar = "]";
31
+ startIndex = firstBracket;
32
+ }
33
+ }
34
+ let depth = 0;
35
+ let inString = false;
36
+ let escapeNext = false;
37
+ for (let i = startIndex; i < aiOutput.length; i++) {
38
+ const char = aiOutput[i];
39
+ if (escapeNext) {
40
+ escapeNext = false;
41
+ continue;
42
+ }
43
+ if (char === "\\" && inString) {
44
+ escapeNext = true;
45
+ continue;
46
+ }
47
+ if (char === '"') {
48
+ inString = !inString;
49
+ continue;
50
+ }
51
+ if (!inString) {
52
+ if (char === startChar) {
53
+ depth++;
54
+ } else if (char === endChar) {
55
+ depth--;
56
+ if (depth === 0) {
57
+ return aiOutput.slice(startIndex, i + 1);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ return aiOutput.slice(startIndex).trim();
63
+ }
64
+ function safeParseAI(aiOutput, schema) {
65
+ try {
66
+ const cleanOutput = extractJsonContent(aiOutput);
67
+ const repairedJson = jsonrepair(cleanOutput);
68
+ const parsedObject = JSON.parse(repairedJson);
69
+ const validation = schema.safeParse(parsedObject);
70
+ if (validation.success) {
71
+ return { success: true, data: validation.data };
72
+ } else {
73
+ const issues = validation.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
74
+ return {
75
+ success: false,
76
+ error: "Validation Failed",
77
+ retryPrompt: `The JSON was parsed but failed validation. Fix these issues: [${issues}] and return ONLY the JSON.`
78
+ };
79
+ }
80
+ } catch (error) {
81
+ return {
82
+ success: false,
83
+ error: "JSON Syntax Error",
84
+ retryPrompt: `The output was not valid JSON. Error: ${error.message}. Return ONLY valid JSON.`
85
+ };
86
+ }
87
+ }
88
+ export {
89
+ safeParseAI
90
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "agentic-json-repair",
3
+ "version": "1.0.0",
4
+ "description": "Repair and validate AI-generated JSON with auto-generated retry prompts.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
17
+ "test": "vitest run",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "ai",
22
+ "llm",
23
+ "json",
24
+ "repair",
25
+ "zod",
26
+ "agent"
27
+ ],
28
+ "author": "Your Name",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "jsonrepair": "^3.1.0",
32
+ "zod": "^3.22.0"
33
+ },
34
+ "devDependencies": {
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.0.0",
37
+ "vitest": "^1.0.0",
38
+ "@types/node": "^20.0.0"
39
+ }
40
+ }
@@ -0,0 +1,35 @@
1
+ import { expect, test } from 'vitest';
2
+ import { safeParseAI } from './index';
3
+ import { z } from 'zod';
4
+
5
+ test('fixes broken AI output', () => {
6
+ const brokenAiOutput = `
7
+ Sure! Here is the user:
8
+ \`\`\`json
9
+ {
10
+ name: "Alice",
11
+ age: 30,
12
+ }
13
+ \`\`\`
14
+ `;
15
+
16
+ const UserSchema = z.object({ name: z.string(), age: z.number() });
17
+ const result = safeParseAI(brokenAiOutput, UserSchema);
18
+
19
+ expect(result.success).toBe(true);
20
+ if (result.success) {
21
+ expect(result.data.name).toBe("Alice");
22
+ }
23
+ });
24
+
25
+ test('generates retry prompt on wrong schema', () => {
26
+ const wrongTypeOutput = `{"age": "thirty"}`; // string instead of number
27
+ const Schema = z.object({ age: z.number() });
28
+
29
+ const result = safeParseAI(wrongTypeOutput, Schema);
30
+
31
+ expect(result.success).toBe(false);
32
+ if (!result.success) {
33
+ expect(result.retryPrompt).toContain("expected number, received string");
34
+ }
35
+ });
package/src/index.ts ADDED
@@ -0,0 +1,128 @@
1
+ import { jsonrepair } from 'jsonrepair';
2
+ import { z } from 'zod';
3
+
4
+ export type AgenticParseResult<T> =
5
+ | { success: true; data: T }
6
+ | { success: false; error: string; retryPrompt: string };
7
+
8
+ /**
9
+ * Attempts to extract, repair, and validate JSON from an AI's text output.
10
+ * * @param aiOutput The raw string from the LLM (e.g., "Here is the data: ```json ...")
11
+ * @param schema The Zod schema to validate against
12
+ */
13
+ /**
14
+ * Extracts JSON content from AI output that may contain markdown code blocks or prose.
15
+ */
16
+ function extractJsonContent(aiOutput: string): string {
17
+ // First, try to extract from markdown code blocks
18
+ const codeBlockMatch = aiOutput.match(/```(?:json)?\s*([\s\S]*?)```/);
19
+ if (codeBlockMatch) {
20
+ return codeBlockMatch[1].trim();
21
+ }
22
+
23
+ // Otherwise, try to find the first JSON object or array
24
+ const firstBrace = aiOutput.indexOf('{');
25
+ const firstBracket = aiOutput.indexOf('[');
26
+
27
+ let startChar: '{' | '[';
28
+ let endChar: '}' | ']';
29
+ let startIndex: number;
30
+
31
+ if (firstBrace === -1 && firstBracket === -1) {
32
+ // No JSON structure found, return as-is for jsonrepair to handle
33
+ return aiOutput.trim();
34
+ } else if (firstBrace === -1) {
35
+ startChar = '[';
36
+ endChar = ']';
37
+ startIndex = firstBracket;
38
+ } else if (firstBracket === -1) {
39
+ startChar = '{';
40
+ endChar = '}';
41
+ startIndex = firstBrace;
42
+ } else {
43
+ // Both found, use whichever comes first
44
+ if (firstBrace < firstBracket) {
45
+ startChar = '{';
46
+ endChar = '}';
47
+ startIndex = firstBrace;
48
+ } else {
49
+ startChar = '[';
50
+ endChar = ']';
51
+ startIndex = firstBracket;
52
+ }
53
+ }
54
+
55
+ // Find the matching closing bracket/brace
56
+ let depth = 0;
57
+ let inString = false;
58
+ let escapeNext = false;
59
+
60
+ for (let i = startIndex; i < aiOutput.length; i++) {
61
+ const char = aiOutput[i];
62
+
63
+ if (escapeNext) {
64
+ escapeNext = false;
65
+ continue;
66
+ }
67
+
68
+ if (char === '\\' && inString) {
69
+ escapeNext = true;
70
+ continue;
71
+ }
72
+
73
+ if (char === '"') {
74
+ inString = !inString;
75
+ continue;
76
+ }
77
+
78
+ if (!inString) {
79
+ if (char === startChar) {
80
+ depth++;
81
+ } else if (char === endChar) {
82
+ depth--;
83
+ if (depth === 0) {
84
+ return aiOutput.slice(startIndex, i + 1);
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ // If we couldn't find matching brackets, return from startIndex to end
91
+ return aiOutput.slice(startIndex).trim();
92
+ }
93
+
94
+ export function safeParseAI<T>(aiOutput: string, schema: z.ZodType<T>): AgenticParseResult<T> {
95
+ try {
96
+ // 1. Extract JSON: Find the JSON content within the AI output
97
+ const cleanOutput = extractJsonContent(aiOutput);
98
+
99
+ // 2. Repair JSON: Fixes missing quotes, trailing commas, etc.
100
+ const repairedJson = jsonrepair(cleanOutput);
101
+
102
+ // 3. Parse JSON
103
+ const parsedObject = JSON.parse(repairedJson);
104
+
105
+ // 4. Validate Schema
106
+ const validation = schema.safeParse(parsedObject);
107
+
108
+ if (validation.success) {
109
+ return { success: true, data: validation.data };
110
+ } else {
111
+ // 5. Generate a "Self-Correction" prompt for the AI
112
+ const issues = validation.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join(', ');
113
+ return {
114
+ success: false,
115
+ error: "Validation Failed",
116
+ retryPrompt: `The JSON was parsed but failed validation. Fix these issues: [${issues}] and return ONLY the JSON.`
117
+ };
118
+ }
119
+
120
+ } catch (error) {
121
+ // 6. Handle Syntax Errors that jsonrepair couldn't fix
122
+ return {
123
+ success: false,
124
+ error: "JSON Syntax Error",
125
+ retryPrompt: `The output was not valid JSON. Error: ${(error as Error).message}. Return ONLY valid JSON.`
126
+ };
127
+ }
128
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "declaration": true,
9
+ "outDir": "dist",
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src"],
13
+ "exclude": ["node_modules", "dist"]
14
+ }