qazen-cli 0.2.0 → 0.2.1
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/dist/lib/visionNavigator.js +79 -2
- package/package.json +1 -1
|
@@ -10,6 +10,76 @@ function firstTextBlock(content) {
|
|
|
10
10
|
const block = content[0];
|
|
11
11
|
return block && block.type === "text" ? block.text : "";
|
|
12
12
|
}
|
|
13
|
+
function salvageJson(text) {
|
|
14
|
+
const cleaned = stripCodeFences(text);
|
|
15
|
+
// First try clean parse
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(cleaned);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Fall through to salvage
|
|
21
|
+
}
|
|
22
|
+
// Salvage truncated JSON: extract metadata + every complete {...} element block.
|
|
23
|
+
try {
|
|
24
|
+
const descMatch = cleaned.match(/"pageDescription"\s*:\s*"([^"]+)"/);
|
|
25
|
+
const workflowMatch = cleaned.match(/"workflow"\s*:\s*"([^"]+)"/);
|
|
26
|
+
const pageTypeMatch = cleaned.match(/"pageType"\s*:\s*"([^"]+)"/);
|
|
27
|
+
const elements = [];
|
|
28
|
+
let depth = 0;
|
|
29
|
+
let start = -1;
|
|
30
|
+
let inString = false;
|
|
31
|
+
let escape = false;
|
|
32
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
33
|
+
const ch = cleaned[i];
|
|
34
|
+
if (escape) {
|
|
35
|
+
escape = false;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ch === "\\") {
|
|
39
|
+
escape = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (ch === '"') {
|
|
43
|
+
inString = !inString;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (inString)
|
|
47
|
+
continue;
|
|
48
|
+
if (ch === "{") {
|
|
49
|
+
if (depth === 0)
|
|
50
|
+
start = i;
|
|
51
|
+
depth++;
|
|
52
|
+
}
|
|
53
|
+
else if (ch === "}") {
|
|
54
|
+
depth--;
|
|
55
|
+
if (depth === 0 && start !== -1) {
|
|
56
|
+
try {
|
|
57
|
+
const obj = JSON.parse(cleaned.substring(start, i + 1));
|
|
58
|
+
if (typeof obj.description === "string" &&
|
|
59
|
+
typeof obj.elementType === "string") {
|
|
60
|
+
elements.push(obj);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// skip incomplete object
|
|
65
|
+
}
|
|
66
|
+
start = -1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (elements.length === 0)
|
|
71
|
+
return null;
|
|
72
|
+
return {
|
|
73
|
+
pageDescription: descMatch?.[1] ?? "Page analysis (partial)",
|
|
74
|
+
workflow: workflowMatch?.[1] ?? "Unknown",
|
|
75
|
+
pageType: pageTypeMatch?.[1] ?? "other",
|
|
76
|
+
elements,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
13
83
|
export class VisionNavigator {
|
|
14
84
|
anthropic;
|
|
15
85
|
baseUrl;
|
|
@@ -221,7 +291,7 @@ export class VisionNavigator {
|
|
|
221
291
|
try {
|
|
222
292
|
const response = await this.anthropic.messages.create({
|
|
223
293
|
model: MODEL,
|
|
224
|
-
max_tokens:
|
|
294
|
+
max_tokens: 4000,
|
|
225
295
|
messages: [
|
|
226
296
|
{
|
|
227
297
|
role: "user",
|
|
@@ -342,7 +412,14 @@ Return ONLY valid JSON — no markdown, no explanation:
|
|
|
342
412
|
],
|
|
343
413
|
});
|
|
344
414
|
const text = firstTextBlock(response.content);
|
|
345
|
-
const parsed =
|
|
415
|
+
const parsed = salvageJson(text);
|
|
416
|
+
if (!parsed) {
|
|
417
|
+
onEvent({
|
|
418
|
+
type: "error",
|
|
419
|
+
message: "Vision analysis failed: could not parse or salvage JSON response",
|
|
420
|
+
});
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
346
423
|
onEvent({
|
|
347
424
|
type: "vision_analysis",
|
|
348
425
|
message: parsed.pageDescription + (parsed.workflow ? ` (${parsed.workflow})` : ""),
|