@sudobility/shapeshyft_engine 1.0.5 → 1.0.8
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.
- package/CLAUDE.md +4 -0
- package/dist/config/providers.d.ts +27 -0
- package/dist/config/providers.d.ts.map +1 -1
- package/dist/config/providers.js +70 -0
- package/dist/config/providers.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/api-helper.d.ts.map +1 -1
- package/dist/lib/api-helper.js +2 -0
- package/dist/lib/api-helper.js.map +1 -1
- package/dist/services/llm/gemini.d.ts.map +1 -1
- package/dist/services/llm/gemini.js +2 -1
- package/dist/services/llm/gemini.js.map +1 -1
- package/dist/services/llm/groq.d.ts.map +1 -1
- package/dist/services/llm/groq.js +2 -1
- package/dist/services/llm/groq.js.map +1 -1
- package/dist/services/llm/index.d.ts.map +1 -1
- package/dist/services/llm/index.js +4 -0
- package/dist/services/llm/index.js.map +1 -1
- package/dist/services/llm/jev.d.ts +83 -0
- package/dist/services/llm/jev.d.ts.map +1 -0
- package/dist/services/llm/jev.js +224 -0
- package/dist/services/llm/jev.js.map +1 -0
- package/dist/services/llm/json-repair.d.ts +70 -0
- package/dist/services/llm/json-repair.d.ts.map +1 -0
- package/dist/services/llm/json-repair.js +522 -0
- package/dist/services/llm/json-repair.js.map +1 -0
- package/dist/services/llm/openai.d.ts.map +1 -1
- package/dist/services/llm/openai.js +4 -3
- package/dist/services/llm/openai.js.map +1 -1
- package/dist/types/index.d.ts +13 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Jev (TypeSafe AI) LLM provider
|
|
3
|
+
* @description Jev is not a text-generation model. It is a "System One"
|
|
4
|
+
* model that answers pre-declared Choice/Score/Noul questions -- evaluated in
|
|
5
|
+
* parallel -- with calibrated probabilities instead of writing free-form
|
|
6
|
+
* text or arbitrary JSON. This adapter maps an endpoint's `outputSchema` onto
|
|
7
|
+
* those primitives one top-level property at a time; see
|
|
8
|
+
* {@link classifyJevField} for the exact rule, and {@link checkJevCompatibility}
|
|
9
|
+
* to check a schema without calling Jev.
|
|
10
|
+
*
|
|
11
|
+
* Confirmed against https://docs.typesafe.ai on 2026-09-23. A page at
|
|
12
|
+
* jevapi.org advertises a different endpoint domain ("tokenra.io") for the
|
|
13
|
+
* same model; every official TypeSafe AI source (docs.typesafe.ai, its
|
|
14
|
+
* Wikipedia entry, and the `@typesafe-ai/sdk` npm package's own metadata)
|
|
15
|
+
* agrees only on api.typesafe.ai, so that's the only endpoint this adapter
|
|
16
|
+
* will ever call.
|
|
17
|
+
*
|
|
18
|
+
* @see https://docs.typesafe.ai/api.md
|
|
19
|
+
* @see https://docs.typesafe.ai/primitives.md
|
|
20
|
+
*/
|
|
21
|
+
import type { ILLMProvider, LLMRequest, LLMResponse, ProviderConfig } from "./types.js";
|
|
22
|
+
import type { JsonSchema } from "../../types/index.js";
|
|
23
|
+
type JevQuestion = {
|
|
24
|
+
type: "noul";
|
|
25
|
+
instructions: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: "choice";
|
|
28
|
+
instructions: string;
|
|
29
|
+
criteria: Record<string, null>;
|
|
30
|
+
} | {
|
|
31
|
+
type: "score";
|
|
32
|
+
instructions: string;
|
|
33
|
+
criteria: string[];
|
|
34
|
+
};
|
|
35
|
+
interface JevField {
|
|
36
|
+
key: string;
|
|
37
|
+
question: JevQuestion;
|
|
38
|
+
/** Ordered rubric levels, for a "score" question only (low to high). */
|
|
39
|
+
levels?: string[];
|
|
40
|
+
}
|
|
41
|
+
export interface JevCompatibility {
|
|
42
|
+
compatible: boolean;
|
|
43
|
+
/** One message per top-level field Jev cannot answer, naming the field. */
|
|
44
|
+
errors: string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Classify one top-level schema property into a Jev question, or explain why
|
|
48
|
+
* it cannot be one:
|
|
49
|
+
*
|
|
50
|
+
* - `{ type: "boolean" }` -> Noul
|
|
51
|
+
* - `{ type: "string", enum: [...] }` -> Choice (enum values become options)
|
|
52
|
+
* - `{ type: "string", enum: [...], "x-jev-kind": "score" }` -> Score (the
|
|
53
|
+
* enum, in the given order, becomes the rubric's levels from low to high)
|
|
54
|
+
*
|
|
55
|
+
* Anything else (free text, numbers, arrays, nested objects) has no mapping.
|
|
56
|
+
*
|
|
57
|
+
* Exported so a frontend schema editor can give the same answer without a
|
|
58
|
+
* round trip to `generate()`. Keep any such copy in sync with this one until
|
|
59
|
+
* it can be shared as a published dependency instead.
|
|
60
|
+
*/
|
|
61
|
+
export declare function classifyJevField(key: string, schema: JsonSchema): {
|
|
62
|
+
field: JevField;
|
|
63
|
+
} | {
|
|
64
|
+
error: string;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Check whether an endpoint's `outputSchema` can be answered by Jev at all,
|
|
68
|
+
* without calling it. `generate()` and `buildApiPayload()` both call this and
|
|
69
|
+
* throw when it fails; it is also safe to call from a schema editor for live
|
|
70
|
+
* feedback before an endpoint is saved.
|
|
71
|
+
*/
|
|
72
|
+
export declare function checkJevCompatibility(schema: JsonSchema): JevCompatibility;
|
|
73
|
+
export declare class JevProvider implements ILLMProvider {
|
|
74
|
+
readonly providerName: "jev";
|
|
75
|
+
private apiKey;
|
|
76
|
+
private defaultModel;
|
|
77
|
+
constructor(config: ProviderConfig);
|
|
78
|
+
generate(request: LLMRequest): Promise<LLMResponse>;
|
|
79
|
+
buildApiPayload(request: LLMRequest): Record<string, unknown>;
|
|
80
|
+
private buildPayload;
|
|
81
|
+
}
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=jev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jev.d.ts","sourceRoot":"","sources":["../../../src/services/llm/jev.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,WAAW,EAEX,cAAc,EACf,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAQvD,KAAK,WAAW,GACZ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;CAAE,GACxE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAkBhE,UAAU,QAAQ;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,WAAW,CAAC;IACtB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU,GACjB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAoDzC;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,CAmB1E;AAsCD,qBAAa,WAAY,YAAW,YAAY;IAC9C,QAAQ,CAAC,YAAY,EAAG,KAAK,CAAU;IACvC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAS;gBAEjB,MAAM,EAAE,cAAc;IAQ5B,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IA8EzD,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAM7D,OAAO,CAAC,YAAY;CAgBrB"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Jev (TypeSafe AI) LLM provider
|
|
3
|
+
* @description Jev is not a text-generation model. It is a "System One"
|
|
4
|
+
* model that answers pre-declared Choice/Score/Noul questions -- evaluated in
|
|
5
|
+
* parallel -- with calibrated probabilities instead of writing free-form
|
|
6
|
+
* text or arbitrary JSON. This adapter maps an endpoint's `outputSchema` onto
|
|
7
|
+
* those primitives one top-level property at a time; see
|
|
8
|
+
* {@link classifyJevField} for the exact rule, and {@link checkJevCompatibility}
|
|
9
|
+
* to check a schema without calling Jev.
|
|
10
|
+
*
|
|
11
|
+
* Confirmed against https://docs.typesafe.ai on 2026-09-23. A page at
|
|
12
|
+
* jevapi.org advertises a different endpoint domain ("tokenra.io") for the
|
|
13
|
+
* same model; every official TypeSafe AI source (docs.typesafe.ai, its
|
|
14
|
+
* Wikipedia entry, and the `@typesafe-ai/sdk` npm package's own metadata)
|
|
15
|
+
* agrees only on api.typesafe.ai, so that's the only endpoint this adapter
|
|
16
|
+
* will ever call.
|
|
17
|
+
*
|
|
18
|
+
* @see https://docs.typesafe.ai/api.md
|
|
19
|
+
* @see https://docs.typesafe.ai/primitives.md
|
|
20
|
+
*/
|
|
21
|
+
import { attachUsage } from "./usage-error.js";
|
|
22
|
+
const DEFAULT_MODEL = "jev-latest";
|
|
23
|
+
/** The only endpoint TypeSafe AI documents for Jev -- see the file header. */
|
|
24
|
+
const JEV_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
|
|
25
|
+
/**
|
|
26
|
+
* Classify one top-level schema property into a Jev question, or explain why
|
|
27
|
+
* it cannot be one:
|
|
28
|
+
*
|
|
29
|
+
* - `{ type: "boolean" }` -> Noul
|
|
30
|
+
* - `{ type: "string", enum: [...] }` -> Choice (enum values become options)
|
|
31
|
+
* - `{ type: "string", enum: [...], "x-jev-kind": "score" }` -> Score (the
|
|
32
|
+
* enum, in the given order, becomes the rubric's levels from low to high)
|
|
33
|
+
*
|
|
34
|
+
* Anything else (free text, numbers, arrays, nested objects) has no mapping.
|
|
35
|
+
*
|
|
36
|
+
* Exported so a frontend schema editor can give the same answer without a
|
|
37
|
+
* round trip to `generate()`. Keep any such copy in sync with this one until
|
|
38
|
+
* it can be shared as a published dependency instead.
|
|
39
|
+
*/
|
|
40
|
+
export function classifyJevField(key, schema) {
|
|
41
|
+
const instructions = schema.description ?? key;
|
|
42
|
+
if (schema.type === "boolean") {
|
|
43
|
+
return { field: { key, question: { type: "noul", instructions } } };
|
|
44
|
+
}
|
|
45
|
+
if (schema.type === "string" &&
|
|
46
|
+
Array.isArray(schema.enum) &&
|
|
47
|
+
schema.enum.length > 0) {
|
|
48
|
+
const levels = schema.enum.map(v => String(v));
|
|
49
|
+
if (schema["x-jev-kind"] === "score") {
|
|
50
|
+
if (levels.length < 2 || levels.length > 10) {
|
|
51
|
+
return {
|
|
52
|
+
error: `"${key}" has ${levels.length} enum values, but a Jev score rubric ("x-jev-kind": "score") needs between 2 and 10 ordered levels`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
field: {
|
|
57
|
+
key,
|
|
58
|
+
question: { type: "score", instructions, criteria: levels },
|
|
59
|
+
levels,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (levels.length > 255) {
|
|
64
|
+
return {
|
|
65
|
+
error: `"${key}" has ${levels.length} enum values, but Jev's Choice primitive supports at most 255 options`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
field: {
|
|
70
|
+
key,
|
|
71
|
+
question: {
|
|
72
|
+
type: "choice",
|
|
73
|
+
instructions,
|
|
74
|
+
criteria: Object.fromEntries(levels.map(v => [v, null])),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
error: `"${key}" is a ${schema.type ?? "untyped"} field` +
|
|
81
|
+
(schema.type === "string" ? " without an enum" : "") +
|
|
82
|
+
`; Jev only answers boolean fields (Noul) or string fields with an "enum" (Choice, or Score with "x-jev-kind": "score")`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check whether an endpoint's `outputSchema` can be answered by Jev at all,
|
|
87
|
+
* without calling it. `generate()` and `buildApiPayload()` both call this and
|
|
88
|
+
* throw when it fails; it is also safe to call from a schema editor for live
|
|
89
|
+
* feedback before an endpoint is saved.
|
|
90
|
+
*/
|
|
91
|
+
export function checkJevCompatibility(schema) {
|
|
92
|
+
if (schema.type !== "object" ||
|
|
93
|
+
!schema.properties ||
|
|
94
|
+
Object.keys(schema.properties).length === 0) {
|
|
95
|
+
return {
|
|
96
|
+
compatible: false,
|
|
97
|
+
errors: [
|
|
98
|
+
"Jev endpoints need an output schema that is a JSON object with at least one property, and each property must be a boolean or a string with an enum",
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const errors = [];
|
|
103
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
104
|
+
const result = classifyJevField(key, propSchema);
|
|
105
|
+
if ("error" in result)
|
|
106
|
+
errors.push(result.error);
|
|
107
|
+
}
|
|
108
|
+
return { compatible: errors.length === 0, errors };
|
|
109
|
+
}
|
|
110
|
+
/** Builds the per-field question list, or throws with every incompatible field named. */
|
|
111
|
+
function buildFields(schema) {
|
|
112
|
+
const check = checkJevCompatibility(schema);
|
|
113
|
+
if (!check.compatible) {
|
|
114
|
+
throw new Error(`Output schema is not compatible with Jev: ${check.errors.join("; ")}`);
|
|
115
|
+
}
|
|
116
|
+
return Object.entries(schema.properties).map(([key, propSchema]) => {
|
|
117
|
+
const result = classifyJevField(key, propSchema);
|
|
118
|
+
if ("error" in result) {
|
|
119
|
+
// Unreachable: checkJevCompatibility above already rejected this schema.
|
|
120
|
+
throw new Error(result.error);
|
|
121
|
+
}
|
|
122
|
+
return result.field;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** Turn one of Jev's answers back into the value its field's schema declared. */
|
|
126
|
+
function decodeAnswer(field, answer) {
|
|
127
|
+
switch (answer.type) {
|
|
128
|
+
case "noul":
|
|
129
|
+
return answer.noul >= 0.5;
|
|
130
|
+
case "choice":
|
|
131
|
+
return answer.choice;
|
|
132
|
+
case "score": {
|
|
133
|
+
const levels = field.levels ?? [];
|
|
134
|
+
const index = Math.min(levels.length - 1, Math.max(0, Math.round(answer.score)));
|
|
135
|
+
return levels[index];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export class JevProvider {
|
|
140
|
+
providerName = "jev";
|
|
141
|
+
apiKey;
|
|
142
|
+
defaultModel;
|
|
143
|
+
constructor(config) {
|
|
144
|
+
if (!config.apiKey) {
|
|
145
|
+
throw new Error("Jev (TypeSafe AI) API key is required");
|
|
146
|
+
}
|
|
147
|
+
this.apiKey = config.apiKey;
|
|
148
|
+
this.defaultModel = config.model ?? DEFAULT_MODEL;
|
|
149
|
+
}
|
|
150
|
+
async generate(request) {
|
|
151
|
+
if (request.media?.length) {
|
|
152
|
+
throw new Error("Jev is text-only (state must be a string, JSON object, or array of text) and cannot accept media input");
|
|
153
|
+
}
|
|
154
|
+
const model = request.model ?? this.defaultModel;
|
|
155
|
+
const fields = buildFields(request.outputSchema);
|
|
156
|
+
const payload = this.buildPayload(model, request, fields);
|
|
157
|
+
const startTime = Date.now();
|
|
158
|
+
let response;
|
|
159
|
+
try {
|
|
160
|
+
response = await fetch(JEV_ENDPOINT, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: {
|
|
163
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
164
|
+
"Content-Type": "application/json",
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify(payload),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (fetchError) {
|
|
170
|
+
const message = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
171
|
+
throw new Error(`Failed to reach Jev at ${JEV_ENDPOINT}: ${message}`);
|
|
172
|
+
}
|
|
173
|
+
const latencyMs = Date.now() - startTime;
|
|
174
|
+
if (!response.ok) {
|
|
175
|
+
const errorText = await response.text().catch(() => "");
|
|
176
|
+
throw new Error(`Jev error (${response.status}): ${errorText || response.statusText}`);
|
|
177
|
+
}
|
|
178
|
+
const body = (await response.json());
|
|
179
|
+
const usage = {
|
|
180
|
+
promptTokens: body.usage?.input_tokens ?? 0,
|
|
181
|
+
completionTokens: body.usage?.output_tokens ?? 0,
|
|
182
|
+
totalTokens: (body.usage?.input_tokens ?? 0) + (body.usage?.output_tokens ?? 0),
|
|
183
|
+
};
|
|
184
|
+
const answers = body.answers ?? {};
|
|
185
|
+
const content = {};
|
|
186
|
+
for (const field of fields) {
|
|
187
|
+
const answer = answers[field.key];
|
|
188
|
+
if (!answer) {
|
|
189
|
+
// The call was billed even though this field came back empty.
|
|
190
|
+
throw attachUsage(new Error(`Jev did not answer "${field.key}"`), usage, body.model ?? model);
|
|
191
|
+
}
|
|
192
|
+
content[field.key] = decodeAnswer(field, answer);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
content,
|
|
196
|
+
rawResponse: JSON.stringify(body),
|
|
197
|
+
usage,
|
|
198
|
+
model: body.model ?? model,
|
|
199
|
+
provider: this.providerName,
|
|
200
|
+
latencyMs,
|
|
201
|
+
// Jev answers every declared question in one parallel pass -- there is
|
|
202
|
+
// no partial or truncated generation for it to report.
|
|
203
|
+
finishReason: "stop",
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
buildApiPayload(request) {
|
|
207
|
+
const model = request.model ?? this.defaultModel;
|
|
208
|
+
const fields = buildFields(request.outputSchema);
|
|
209
|
+
return this.buildPayload(model, request, fields);
|
|
210
|
+
}
|
|
211
|
+
buildPayload(model, request, fields) {
|
|
212
|
+
return {
|
|
213
|
+
model,
|
|
214
|
+
// Docs recommend objects for most requests, and keeping state (the
|
|
215
|
+
// material to evaluate) separate from questions (the judgments to
|
|
216
|
+
// make about it) -- https://docs.typesafe.ai/concepts/state.md
|
|
217
|
+
state: request.systemPrompt
|
|
218
|
+
? { instructions: request.systemPrompt, input: request.prompt }
|
|
219
|
+
: request.prompt,
|
|
220
|
+
questions: Object.fromEntries(fields.map(f => [f.key, f.question])),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=jev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jev.js","sourceRoot":"","sources":["../../../src/services/llm/jev.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Read model JSON that is nearly, but not quite, JSON.
|
|
3
|
+
* @description A model writing a long answer slips now and then: a trailing
|
|
4
|
+
* comma, a missing one, a bracket left open, a number like `0480`, an unescaped
|
|
5
|
+
* quote inside a lyric. Function calling guarantees the SHAPE of the payload,
|
|
6
|
+
* not that the text is valid — and a strict `JSON.parse` turns any of those
|
|
7
|
+
* into a failed, billed call that then has to be asked for again.
|
|
8
|
+
*
|
|
9
|
+
* Measured on one product's history through this server: 1,045 of 1,205 failed
|
|
10
|
+
* calls were parse failures, every one starting with a well-formed `{` and
|
|
11
|
+
* breaking somewhere after it, at 500-1,700 tokens against a ceiling ten times
|
|
12
|
+
* that. Most were "Unable to parse JSON string", "Expected ']'", "Invalid
|
|
13
|
+
* number" and "Property name must be a string literal". At temperature 0 the
|
|
14
|
+
* same prompt gives the same slip, so asking again does not help; reading the
|
|
15
|
+
* answer tolerantly does, and costs nothing.
|
|
16
|
+
*
|
|
17
|
+
* What a real DeepSeek answer actually got wrong, from the first patched run
|
|
18
|
+
* (6 repairs): five were TRAILING TEXT — a complete document, then the model
|
|
19
|
+
* closed the outer object one brace early and carried on writing its notes,
|
|
20
|
+
* which is what "Unable to parse JSON string" (55% of the history) was — and one
|
|
21
|
+
* was arithmetic in a number, `[1920 - 480, 480, "F3", 106]`. Failed calls in
|
|
22
|
+
* that run's styles fell from 6, 3 and 2 per song to one across three.
|
|
23
|
+
*
|
|
24
|
+
* The rule that keeps this safe: **strict parsing is tried first and is what
|
|
25
|
+
* valid input gets**, byte for byte. The tolerant reader only runs on text
|
|
26
|
+
* `JSON.parse` has already refused, and it reports every repair it made, so the
|
|
27
|
+
* caller can log it. It never guesses at meaning — it closes what is open,
|
|
28
|
+
* inserts what is missing between two values, and drops what is stray. A repaired
|
|
29
|
+
* answer is still only as good as the shape the caller validates it against.
|
|
30
|
+
*/
|
|
31
|
+
/** One thing the reader had to fix to make the text parse. */
|
|
32
|
+
export type JsonRepair = {
|
|
33
|
+
kind: string;
|
|
34
|
+
at: number;
|
|
35
|
+
};
|
|
36
|
+
/** A parsed answer and what it took to parse it. */
|
|
37
|
+
export type ParsedModelJson = {
|
|
38
|
+
value: unknown;
|
|
39
|
+
repairs: JsonRepair[];
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Read `text` tolerantly, or return null when it is not recoverable JSON.
|
|
43
|
+
*
|
|
44
|
+
* Exported for tests. Callers want `parseModelJson`, which is strict first.
|
|
45
|
+
*/
|
|
46
|
+
export declare function lenientParse(text: string): ParsedModelJson | null;
|
|
47
|
+
/**
|
|
48
|
+
* `JSON.parse`, then — only if that refuses — the tolerant reader.
|
|
49
|
+
*
|
|
50
|
+
* Valid JSON is returned exactly as `JSON.parse` returns it, with no repairs,
|
|
51
|
+
* so putting this where `JSON.parse` was cannot change what a good answer
|
|
52
|
+
* means. Throws the strict parser's own error when the text cannot be repaired,
|
|
53
|
+
* so a caller's failure handling is unchanged.
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseModelJson(text: string): ParsedModelJson;
|
|
56
|
+
/** A short window of `text` around the first repair, for a log line. */
|
|
57
|
+
export declare function windowAroundFirstRepair(text: string, repairs: readonly JsonRepair[], radius?: number): string;
|
|
58
|
+
/** "trailing comma x2, missing comma" — what was repaired, once each. */
|
|
59
|
+
export declare function summarizeRepairs(repairs: readonly JsonRepair[]): string;
|
|
60
|
+
/**
|
|
61
|
+
* `parseModelJson` for an adapter: same result, and a log line when a repair
|
|
62
|
+
* was needed.
|
|
63
|
+
*
|
|
64
|
+
* The line names what was fixed and shows the text around the first fix, which
|
|
65
|
+
* is the only record there will be of what a model actually got wrong — the
|
|
66
|
+
* failed answers were never kept, and a repair that works leaves no error to
|
|
67
|
+
* read. `source` says which provider produced it.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readModelJson(text: string, source: string): unknown;
|
|
70
|
+
//# sourceMappingURL=json-repair.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-repair.d.ts","sourceRoot":"","sources":["../../../src/services/llm/json-repair.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,oDAAoD;AACpD,MAAM,MAAM,eAAe,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAsZxE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CASjE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAQ5D;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,MAAM,SAAK,GACV,MAAM,CAMR;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAMvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAUnE"}
|