refactor(open-sse): extract safeParseJSON util, dedup tryParseJSON (#4735)

Integrated into release/v3.8.36
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-24 12:39:03 -03:00
committed by GitHub
parent 6e28889aad
commit 4a1ae868ce
4 changed files with 71 additions and 15 deletions

View File

@@ -1,5 +1,7 @@
// Gemini helper functions for translator
import { safeParseJSON } from "./jsonUtil.ts";
type JsonRecord = Record<string, unknown>;
// Unsupported JSON Schema constraints that should be removed for Antigravity.
@@ -242,14 +244,9 @@ export function extractTextContent(content: unknown): string {
return "";
}
// Try parse JSON safely
// Try parse JSON safely (null fallback on parse error; re-export keeps legacy API).
export function tryParseJSON(str: unknown): unknown {
if (typeof str !== "string") return str;
try {
return JSON.parse(str);
} catch {
return null;
}
return safeParseJSON(str, null);
}
// Generate request ID

View File

@@ -0,0 +1,18 @@
// Safe JSON.parse helper shared by the translator layer.
//
// Behavior contract (must be preserved by all callers):
// - non-string input is returned unchanged (passthrough)
// - a valid JSON string is parsed and returned
// - on parse error the caller-chosen `fallback` is returned
//
// The `fallback` is explicit so the two historical `tryParseJSON` variants
// can keep their distinct semantics: geminiHelper returns `null` on error,
// while openai-to-claude returns the raw input string (passthrough).
export function safeParseJSON<TFallback>(str: unknown, fallback: TFallback): unknown {
if (typeof str !== "string") return str;
try {
return JSON.parse(str);
} catch {
return fallback;
}
}

View File

@@ -4,6 +4,7 @@ import { FORMATS } from "../formats.ts";
import { supportsClaudeMaxEffort, supportsXHighEffort } from "../../config/providerModels.ts";
import { adjustMaxTokens } from "../helpers/maxTokensHelper.ts";
import { sanitizeToolId } from "../helpers/schemaCoercion.ts";
import { safeParseJSON } from "../helpers/jsonUtil.ts";
import { DEFAULT_THINKING_CLAUDE_SIGNATURE } from "../../config/defaultThinkingSignature.ts";
import { capMaxOutputTokens } from "../../../src/lib/modelCapabilities.ts";
import { isAdaptiveThinkingOnly } from "../../../src/shared/constants/modelSpecs.ts";
@@ -701,14 +702,9 @@ function extractTextContent(content) {
return "";
}
// Try parse JSON
function tryParseJSON(str) {
if (typeof str !== "string") return str;
try {
return JSON.parse(str);
} catch {
return str;
}
// Try parse JSON (passthrough fallback: return the raw input string on parse error).
function tryParseJSON(str: unknown): unknown {
return safeParseJSON(str, str);
}
function stripCacheControl(value: unknown): unknown {

View File

@@ -0,0 +1,45 @@
import test from "node:test";
import assert from "node:assert/strict";
// Unit coverage for the extracted safeParseJSON util and the two distinct
// fallbacks its callers rely on:
// - geminiHelper.tryParseJSON -> null fallback on parse error
// - openai-to-claude tryParseJSON -> raw-string passthrough on parse error
const { safeParseJSON } = await import("../../open-sse/translator/helpers/jsonUtil.ts");
const gemini = await import("../../open-sse/translator/helpers/geminiHelper.ts");
test("safeParseJSON: non-string input is returned unchanged (passthrough)", () => {
assert.equal(safeParseJSON(42, null), 42);
assert.equal(safeParseJSON(null, "fb"), null);
assert.equal(safeParseJSON(undefined, "fb"), undefined);
const obj = { a: 1 };
assert.equal(safeParseJSON(obj, null), obj);
});
test("safeParseJSON: valid JSON strings parse regardless of fallback", () => {
assert.deepEqual(safeParseJSON('{"a":1}', null), { a: 1 });
assert.deepEqual(safeParseJSON("[1,2,3]", "ignored"), [1, 2, 3]);
assert.equal(safeParseJSON("42", null), 42);
assert.equal(safeParseJSON("true", null), true);
assert.equal(safeParseJSON("null", "ignored"), null);
});
test("safeParseJSON: invalid JSON returns the caller-chosen fallback", () => {
// null fallback (geminiHelper semantics)
assert.equal(safeParseJSON("not json", null), null);
assert.equal(safeParseJSON("{broken}", null), null);
assert.equal(safeParseJSON("", null), null);
// raw-string passthrough fallback (openai-to-claude semantics)
assert.equal(safeParseJSON("not json", "not json"), "not json");
assert.equal(safeParseJSON("{broken", "{broken"), "{broken");
});
test("geminiHelper.tryParseJSON still returns null on parse error (re-export delegates)", () => {
assert.deepEqual(gemini.tryParseJSON('{"ok":true}'), { ok: true });
assert.equal(gemini.tryParseJSON("{broken"), null);
assert.equal(gemini.tryParseJSON("not json"), null);
assert.equal(gemini.tryParseJSON(""), null);
// non-string passthrough preserved
assert.equal(gemini.tryParseJSON(123), 123);
});