opencode-antigravity-auth 1.2.6 → 1.2.7-beta.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/README.md +224 -153
- package/dist/src/constants.d.ts +8 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +8 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/plugin/config/schema.d.ts +12 -0
- package/dist/src/plugin/config/schema.d.ts.map +1 -1
- package/dist/src/plugin/config/schema.js +14 -0
- package/dist/src/plugin/config/schema.js.map +1 -1
- package/dist/src/plugin/core/streaming/index.d.ts +3 -0
- package/dist/src/plugin/core/streaming/index.d.ts.map +1 -0
- package/dist/src/plugin/core/streaming/index.js +3 -0
- package/dist/src/plugin/core/streaming/index.js.map +1 -0
- package/dist/src/plugin/core/streaming/transformer.d.ts +9 -0
- package/dist/src/plugin/core/streaming/transformer.d.ts.map +1 -0
- package/dist/src/plugin/core/streaming/transformer.js +134 -0
- package/dist/src/plugin/core/streaming/transformer.js.map +1 -0
- package/dist/src/plugin/core/streaming/types.d.ts +26 -0
- package/dist/src/plugin/core/streaming/types.d.ts.map +1 -0
- package/dist/src/plugin/core/streaming/types.js +1 -0
- package/dist/src/plugin/core/streaming/types.js.map +1 -0
- package/dist/src/plugin/logger.d.ts.map +1 -1
- package/dist/src/plugin/logger.js +3 -2
- package/dist/src/plugin/logger.js.map +1 -1
- package/dist/src/plugin/recovery.d.ts.map +1 -1
- package/dist/src/plugin/recovery.js +42 -14
- package/dist/src/plugin/recovery.js.map +1 -1
- package/dist/src/plugin/request-helpers.d.ts +38 -0
- package/dist/src/plugin/request-helpers.d.ts.map +1 -1
- package/dist/src/plugin/request-helpers.js +250 -19
- package/dist/src/plugin/request-helpers.js.map +1 -1
- package/dist/src/plugin/request.d.ts +50 -3
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +89 -177
- package/dist/src/plugin/request.js.map +1 -1
- package/dist/src/plugin/storage.d.ts +1 -0
- package/dist/src/plugin/storage.d.ts.map +1 -1
- package/dist/src/plugin/storage.js +70 -5
- package/dist/src/plugin/storage.js.map +1 -1
- package/dist/src/plugin/stores/signature-store.d.ts +5 -0
- package/dist/src/plugin/stores/signature-store.d.ts.map +1 -0
- package/dist/src/plugin/stores/signature-store.js +25 -0
- package/dist/src/plugin/stores/signature-store.js.map +1 -0
- package/dist/src/plugin/transform/claude.d.ts.map +1 -1
- package/dist/src/plugin/transform/claude.js +30 -8
- package/dist/src/plugin/transform/claude.js.map +1 -1
- package/dist/src/plugin/transform/model-resolver.d.ts +12 -5
- package/dist/src/plugin/transform/model-resolver.d.ts.map +1 -1
- package/dist/src/plugin/transform/model-resolver.js +73 -20
- package/dist/src/plugin/transform/model-resolver.js.map +1 -1
- package/dist/src/plugin/transform/types.d.ts +3 -5
- package/dist/src/plugin/transform/types.d.ts.map +1 -1
- package/dist/src/plugin/transform/types.js +0 -5
- package/dist/src/plugin/transform/types.js.map +1 -1
- package/dist/src/plugin.d.ts.map +1 -1
- package/dist/src/plugin.js +113 -44
- package/dist/src/plugin.js.map +1 -1
- package/package.json +7 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { KEEP_THINKING_BLOCKS } from "../constants.js";
|
|
2
2
|
import { createLogger } from "./logger";
|
|
3
|
+
import { EMPTY_SCHEMA_PLACEHOLDER_NAME, EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION, } from "../constants";
|
|
3
4
|
const log = createLogger("request-helpers");
|
|
4
5
|
const ANTIGRAVITY_PREVIEW_LINK = "https://goo.gle/enable-preview-features"; // TODO: Update to Antigravity link if available
|
|
5
6
|
// ============================================================================
|
|
@@ -244,10 +245,61 @@ function scoreSchemaOption(schema) {
|
|
|
244
245
|
// Null or no type
|
|
245
246
|
return { score: 0, typeName: type || "null" };
|
|
246
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Checks if an anyOf/oneOf array represents enum choices.
|
|
250
|
+
* Returns the merged enum values if so, otherwise null.
|
|
251
|
+
*
|
|
252
|
+
* Handles patterns like:
|
|
253
|
+
* - anyOf: [{ const: "a" }, { const: "b" }]
|
|
254
|
+
* - anyOf: [{ enum: ["a"] }, { enum: ["b"] }]
|
|
255
|
+
* - anyOf: [{ type: "string", const: "a" }, { type: "string", const: "b" }]
|
|
256
|
+
*/
|
|
257
|
+
function tryMergeEnumFromUnion(options) {
|
|
258
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const enumValues = [];
|
|
262
|
+
for (const option of options) {
|
|
263
|
+
if (!option || typeof option !== "object") {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
// Check for const value
|
|
267
|
+
if (option.const !== undefined) {
|
|
268
|
+
enumValues.push(String(option.const));
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
// Check for single-value enum
|
|
272
|
+
if (Array.isArray(option.enum) && option.enum.length === 1) {
|
|
273
|
+
enumValues.push(String(option.enum[0]));
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// Check for multi-value enum (merge all values)
|
|
277
|
+
if (Array.isArray(option.enum) && option.enum.length > 0) {
|
|
278
|
+
for (const val of option.enum) {
|
|
279
|
+
enumValues.push(String(val));
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
// If option has complex structure (properties, items, etc.), it's not a simple enum
|
|
284
|
+
if (option.properties || option.items || option.anyOf || option.oneOf || option.allOf) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
// If option has only type (no const/enum), it's not an enum pattern
|
|
288
|
+
if (option.type && !option.const && !option.enum) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Only return if we found actual enum values
|
|
293
|
+
return enumValues.length > 0 ? enumValues : null;
|
|
294
|
+
}
|
|
247
295
|
/**
|
|
248
296
|
* Phase 2b: Flattens anyOf/oneOf to the best option with type hints.
|
|
249
297
|
* { anyOf: [{ type: "string" }, { type: "number" }] }
|
|
250
298
|
* → { type: "string", description: "(Accepts: string | number)" }
|
|
299
|
+
*
|
|
300
|
+
* Special handling for enum patterns:
|
|
301
|
+
* { anyOf: [{ const: "a" }, { const: "b" }] }
|
|
302
|
+
* → { type: "string", enum: ["a", "b"] }
|
|
251
303
|
*/
|
|
252
304
|
function flattenAnyOfOneOf(schema) {
|
|
253
305
|
if (!schema || typeof schema !== "object") {
|
|
@@ -262,6 +314,24 @@ function flattenAnyOfOneOf(schema) {
|
|
|
262
314
|
if (Array.isArray(result[unionKey]) && result[unionKey].length > 0) {
|
|
263
315
|
const options = result[unionKey];
|
|
264
316
|
const parentDesc = typeof result.description === "string" ? result.description : "";
|
|
317
|
+
// First, check if this is an enum pattern (anyOf with const/enum values)
|
|
318
|
+
// This is crucial for tools like WebFetch where format: anyOf[{const:"text"},{const:"markdown"},{const:"html"}]
|
|
319
|
+
const mergedEnum = tryMergeEnumFromUnion(options);
|
|
320
|
+
if (mergedEnum !== null) {
|
|
321
|
+
// This is an enum pattern - merge all values into a single enum
|
|
322
|
+
const { [unionKey]: _, ...rest } = result;
|
|
323
|
+
result = {
|
|
324
|
+
...rest,
|
|
325
|
+
type: "string",
|
|
326
|
+
enum: mergedEnum,
|
|
327
|
+
};
|
|
328
|
+
// Preserve parent description
|
|
329
|
+
if (parentDesc) {
|
|
330
|
+
result.description = parentDesc;
|
|
331
|
+
}
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
// Not an enum pattern - use standard flattening logic
|
|
265
335
|
// Score each option and find the best
|
|
266
336
|
let bestIdx = 0;
|
|
267
337
|
let bestScore = -1;
|
|
@@ -376,23 +446,31 @@ function flattenTypeArrays(schema, nullableFields, currentPath) {
|
|
|
376
446
|
}
|
|
377
447
|
/**
|
|
378
448
|
* Phase 3: Removes unsupported keywords after hints have been extracted.
|
|
449
|
+
* @param insideProperties - When true, keys are property NAMES (preserve); when false, keys are JSON Schema keywords (filter).
|
|
379
450
|
*/
|
|
380
|
-
function removeUnsupportedKeywords(schema) {
|
|
451
|
+
function removeUnsupportedKeywords(schema, insideProperties = false) {
|
|
381
452
|
if (!schema || typeof schema !== "object") {
|
|
382
453
|
return schema;
|
|
383
454
|
}
|
|
384
455
|
if (Array.isArray(schema)) {
|
|
385
|
-
return schema.map(item => removeUnsupportedKeywords(item));
|
|
456
|
+
return schema.map(item => removeUnsupportedKeywords(item, false));
|
|
386
457
|
}
|
|
387
458
|
const result = {};
|
|
388
459
|
for (const [key, value] of Object.entries(schema)) {
|
|
389
|
-
|
|
390
|
-
if (UNSUPPORTED_KEYWORDS.includes(key)) {
|
|
460
|
+
if (!insideProperties && UNSUPPORTED_KEYWORDS.includes(key)) {
|
|
391
461
|
continue;
|
|
392
462
|
}
|
|
393
|
-
// Recursively process nested objects
|
|
394
463
|
if (typeof value === "object" && value !== null) {
|
|
395
|
-
|
|
464
|
+
if (key === "properties") {
|
|
465
|
+
const propertiesResult = {};
|
|
466
|
+
for (const [propName, propSchema] of Object.entries(value)) {
|
|
467
|
+
propertiesResult[propName] = removeUnsupportedKeywords(propSchema, false);
|
|
468
|
+
}
|
|
469
|
+
result[key] = propertiesResult;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
result[key] = removeUnsupportedKeywords(value, false);
|
|
473
|
+
}
|
|
396
474
|
}
|
|
397
475
|
else {
|
|
398
476
|
result[key] = value;
|
|
@@ -449,12 +527,12 @@ function addEmptySchemaPlaceholder(schema) {
|
|
|
449
527
|
Object.keys(result.properties).length > 0;
|
|
450
528
|
if (!hasProperties) {
|
|
451
529
|
result.properties = {
|
|
452
|
-
|
|
453
|
-
type: "
|
|
454
|
-
description:
|
|
530
|
+
[EMPTY_SCHEMA_PLACEHOLDER_NAME]: {
|
|
531
|
+
type: "boolean",
|
|
532
|
+
description: EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION,
|
|
455
533
|
},
|
|
456
534
|
};
|
|
457
|
-
result.required = [
|
|
535
|
+
result.required = [EMPTY_SCHEMA_PLACEHOLDER_NAME];
|
|
458
536
|
}
|
|
459
537
|
}
|
|
460
538
|
// Recursively process nested objects
|
|
@@ -750,7 +828,16 @@ function sanitizeThinkingPart(part) {
|
|
|
750
828
|
// Fallback: strip cache_control recursively.
|
|
751
829
|
return stripCacheControlRecursively(part);
|
|
752
830
|
}
|
|
753
|
-
function
|
|
831
|
+
function findLastAssistantIndex(contents, roleValue) {
|
|
832
|
+
for (let i = contents.length - 1; i >= 0; i--) {
|
|
833
|
+
const content = contents[i];
|
|
834
|
+
if (content && typeof content === "object" && content.role === roleValue) {
|
|
835
|
+
return i;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return -1;
|
|
839
|
+
}
|
|
840
|
+
function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isClaudeModel, isLastAssistantMessage) {
|
|
754
841
|
// For Claude models, strip thinking blocks by default for reliability
|
|
755
842
|
// User can opt-in to keep thinking via OPENCODE_ANTIGRAVITY_KEEP_THINKING=1
|
|
756
843
|
if (isClaudeModel && !KEEP_THINKING_BLOCKS) {
|
|
@@ -772,6 +859,13 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
772
859
|
filtered.push(item);
|
|
773
860
|
continue;
|
|
774
861
|
}
|
|
862
|
+
// CRITICAL: For the LAST assistant message, thinking blocks MUST remain byte-for-byte
|
|
863
|
+
// identical to what the API returned. Anthropic rejects any modification.
|
|
864
|
+
// Pass through unchanged - do NOT sanitize or reconstruct.
|
|
865
|
+
if (isLastAssistantMessage && (isThinking || hasSignature)) {
|
|
866
|
+
filtered.push(item);
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
775
869
|
if (isOurCachedSignature(item, sessionId, getCachedSignatureFn)) {
|
|
776
870
|
filtered.push(sanitizeThinkingPart(item));
|
|
777
871
|
continue;
|
|
@@ -805,12 +899,14 @@ function filterContentArray(contentArray, sessionId, getCachedSignatureFn, isCla
|
|
|
805
899
|
* @param getCachedSignatureFn - Optional function to retrieve cached signatures
|
|
806
900
|
*/
|
|
807
901
|
export function filterUnsignedThinkingBlocks(contents, sessionId, getCachedSignatureFn, isClaudeModel) {
|
|
808
|
-
|
|
902
|
+
const lastAssistantIdx = findLastAssistantIndex(contents, "model");
|
|
903
|
+
return contents.map((content, idx) => {
|
|
809
904
|
if (!content || typeof content !== "object") {
|
|
810
905
|
return content;
|
|
811
906
|
}
|
|
907
|
+
const isLastAssistant = idx === lastAssistantIdx;
|
|
812
908
|
if (Array.isArray(content.parts)) {
|
|
813
|
-
const filteredParts = filterContentArray(content.parts, sessionId, getCachedSignatureFn, isClaudeModel);
|
|
909
|
+
const filteredParts = filterContentArray(content.parts, sessionId, getCachedSignatureFn, isClaudeModel, isLastAssistant);
|
|
814
910
|
const trimmedParts = content.role === "model" && !isClaudeModel
|
|
815
911
|
? removeTrailingThinkingBlocks(filteredParts, sessionId, getCachedSignatureFn)
|
|
816
912
|
: filteredParts;
|
|
@@ -818,7 +914,9 @@ export function filterUnsignedThinkingBlocks(contents, sessionId, getCachedSigna
|
|
|
818
914
|
}
|
|
819
915
|
if (Array.isArray(content.content)) {
|
|
820
916
|
const isAssistantRole = content.role === "assistant";
|
|
821
|
-
const
|
|
917
|
+
const isLastAssistantContent = idx === lastAssistantIdx ||
|
|
918
|
+
(isAssistantRole && idx === findLastAssistantIndex(contents, "assistant"));
|
|
919
|
+
const filteredContent = filterContentArray(content.content, sessionId, getCachedSignatureFn, isClaudeModel, isLastAssistantContent);
|
|
822
920
|
const trimmedContent = isAssistantRole && !isClaudeModel
|
|
823
921
|
? removeTrailingThinkingBlocks(filteredContent, sessionId, getCachedSignatureFn)
|
|
824
922
|
: filteredContent;
|
|
@@ -831,13 +929,15 @@ export function filterUnsignedThinkingBlocks(contents, sessionId, getCachedSigna
|
|
|
831
929
|
* Filters thinking blocks from Anthropic-style messages[] payloads using cached signatures.
|
|
832
930
|
*/
|
|
833
931
|
export function filterMessagesThinkingBlocks(messages, sessionId, getCachedSignatureFn, isClaudeModel) {
|
|
834
|
-
|
|
932
|
+
const lastAssistantIdx = findLastAssistantIndex(messages, "assistant");
|
|
933
|
+
return messages.map((message, idx) => {
|
|
835
934
|
if (!message || typeof message !== "object") {
|
|
836
935
|
return message;
|
|
837
936
|
}
|
|
838
937
|
if (Array.isArray(message.content)) {
|
|
839
938
|
const isAssistantRole = message.role === "assistant";
|
|
840
|
-
const
|
|
939
|
+
const isLastAssistant = isAssistantRole && idx === lastAssistantIdx;
|
|
940
|
+
const filteredContent = filterContentArray(message.content, sessionId, getCachedSignatureFn, isClaudeModel, isLastAssistant);
|
|
841
941
|
const trimmedContent = isAssistantRole && !isClaudeModel
|
|
842
942
|
? removeTrailingThinkingBlocks(filteredContent, sessionId, getCachedSignatureFn)
|
|
843
943
|
: filteredContent;
|
|
@@ -893,18 +993,24 @@ function transformGeminiCandidate(candidate) {
|
|
|
893
993
|
// Handle Gemini-style: thought: true
|
|
894
994
|
if (part.thought === true) {
|
|
895
995
|
thinkingTexts.push(part.text || "");
|
|
896
|
-
|
|
996
|
+
const transformed = { ...part, type: "reasoning" };
|
|
997
|
+
if (part.cache_control)
|
|
998
|
+
transformed.cache_control = part.cache_control;
|
|
999
|
+
return transformed;
|
|
897
1000
|
}
|
|
898
1001
|
// Handle Anthropic-style in candidates: type: "thinking"
|
|
899
1002
|
if (part.type === "thinking") {
|
|
900
1003
|
const thinkingText = part.thinking || part.text || "";
|
|
901
1004
|
thinkingTexts.push(thinkingText);
|
|
902
|
-
|
|
1005
|
+
const transformed = {
|
|
903
1006
|
...part,
|
|
904
1007
|
type: "reasoning",
|
|
905
1008
|
text: thinkingText,
|
|
906
1009
|
thought: true,
|
|
907
1010
|
};
|
|
1011
|
+
if (part.cache_control)
|
|
1012
|
+
transformed.cache_control = part.cache_control;
|
|
1013
|
+
return transformed;
|
|
908
1014
|
}
|
|
909
1015
|
// Handle functionCall: parse JSON strings in args
|
|
910
1016
|
// (Ported from LLM-API-Key-Proxy's _extract_tool_call)
|
|
@@ -1033,6 +1139,7 @@ export function extractUsageMetadata(body) {
|
|
|
1033
1139
|
promptTokenCount: toNumber(asRecord.promptTokenCount),
|
|
1034
1140
|
candidatesTokenCount: toNumber(asRecord.candidatesTokenCount),
|
|
1035
1141
|
cachedContentTokenCount: toNumber(asRecord.cachedContentTokenCount),
|
|
1142
|
+
thoughtsTokenCount: toNumber(asRecord.thoughtsTokenCount),
|
|
1036
1143
|
};
|
|
1037
1144
|
}
|
|
1038
1145
|
/**
|
|
@@ -1773,6 +1880,10 @@ export function injectParameterSignatures(tools, promptTemplate = "\n\n⚠️ ST
|
|
|
1773
1880
|
if (!Array.isArray(declarations))
|
|
1774
1881
|
return tool;
|
|
1775
1882
|
const newDeclarations = declarations.map((decl) => {
|
|
1883
|
+
// Skip if signature already injected (avoids duplicate injection)
|
|
1884
|
+
if (decl.description?.includes("STRICT PARAMETERS:")) {
|
|
1885
|
+
return decl;
|
|
1886
|
+
}
|
|
1776
1887
|
const schema = decl.parameters || decl.parametersJsonSchema;
|
|
1777
1888
|
if (!schema)
|
|
1778
1889
|
return decl;
|
|
@@ -1804,9 +1915,16 @@ export function injectParameterSignatures(tools, promptTemplate = "\n\n⚠️ ST
|
|
|
1804
1915
|
export function injectToolHardeningInstruction(payload, instructionText) {
|
|
1805
1916
|
if (!instructionText)
|
|
1806
1917
|
return;
|
|
1918
|
+
// Skip if instruction already present (avoids duplicate injection)
|
|
1919
|
+
const existing = payload.systemInstruction;
|
|
1920
|
+
if (existing && typeof existing === "object" && "parts" in existing) {
|
|
1921
|
+
const parts = existing.parts;
|
|
1922
|
+
if (Array.isArray(parts) && parts.some(p => p.text?.includes("CRITICAL TOOL USAGE INSTRUCTIONS"))) {
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1807
1926
|
const instructionPart = { text: instructionText };
|
|
1808
1927
|
if (payload.systemInstruction) {
|
|
1809
|
-
const existing = payload.systemInstruction;
|
|
1810
1928
|
if (existing && typeof existing === "object" && "parts" in existing) {
|
|
1811
1929
|
const parts = existing.parts;
|
|
1812
1930
|
if (Array.isArray(parts)) {
|
|
@@ -1833,4 +1951,117 @@ export function injectToolHardeningInstruction(payload, instructionText) {
|
|
|
1833
1951
|
};
|
|
1834
1952
|
}
|
|
1835
1953
|
}
|
|
1954
|
+
// ============================================================================
|
|
1955
|
+
// TOOL PROCESSING FOR WRAPPED REQUESTS
|
|
1956
|
+
// Shared logic for assigning tool IDs and fixing tool pairing
|
|
1957
|
+
// ============================================================================
|
|
1958
|
+
/**
|
|
1959
|
+
* Assigns IDs to functionCall parts and returns the pending call IDs by name.
|
|
1960
|
+
* This is the first pass of tool ID assignment.
|
|
1961
|
+
*
|
|
1962
|
+
* @param contents - Gemini-style contents array
|
|
1963
|
+
* @returns Object with modified contents and pending call IDs map
|
|
1964
|
+
*/
|
|
1965
|
+
export function assignToolIdsToContents(contents) {
|
|
1966
|
+
if (!Array.isArray(contents)) {
|
|
1967
|
+
return { contents, pendingCallIdsByName: new Map(), toolCallCounter: 0 };
|
|
1968
|
+
}
|
|
1969
|
+
let toolCallCounter = 0;
|
|
1970
|
+
const pendingCallIdsByName = new Map();
|
|
1971
|
+
const newContents = contents.map((content) => {
|
|
1972
|
+
if (!content || !Array.isArray(content.parts)) {
|
|
1973
|
+
return content;
|
|
1974
|
+
}
|
|
1975
|
+
const newParts = content.parts.map((part) => {
|
|
1976
|
+
if (part && typeof part === "object" && part.functionCall) {
|
|
1977
|
+
const call = { ...part.functionCall };
|
|
1978
|
+
if (!call.id) {
|
|
1979
|
+
call.id = `tool-call-${++toolCallCounter}`;
|
|
1980
|
+
}
|
|
1981
|
+
const nameKey = typeof call.name === "string" ? call.name : `tool-${toolCallCounter}`;
|
|
1982
|
+
const queue = pendingCallIdsByName.get(nameKey) || [];
|
|
1983
|
+
queue.push(call.id);
|
|
1984
|
+
pendingCallIdsByName.set(nameKey, queue);
|
|
1985
|
+
return { ...part, functionCall: call };
|
|
1986
|
+
}
|
|
1987
|
+
return part;
|
|
1988
|
+
});
|
|
1989
|
+
return { ...content, parts: newParts };
|
|
1990
|
+
});
|
|
1991
|
+
return { contents: newContents, pendingCallIdsByName, toolCallCounter };
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Matches functionResponse IDs to their corresponding functionCall IDs.
|
|
1995
|
+
* This is the second pass of tool ID assignment.
|
|
1996
|
+
*
|
|
1997
|
+
* @param contents - Gemini-style contents array
|
|
1998
|
+
* @param pendingCallIdsByName - Map of function names to pending call IDs
|
|
1999
|
+
* @returns Modified contents with matched response IDs
|
|
2000
|
+
*/
|
|
2001
|
+
export function matchResponseIdsToContents(contents, pendingCallIdsByName) {
|
|
2002
|
+
if (!Array.isArray(contents)) {
|
|
2003
|
+
return contents;
|
|
2004
|
+
}
|
|
2005
|
+
return contents.map((content) => {
|
|
2006
|
+
if (!content || !Array.isArray(content.parts)) {
|
|
2007
|
+
return content;
|
|
2008
|
+
}
|
|
2009
|
+
const newParts = content.parts.map((part) => {
|
|
2010
|
+
if (part && typeof part === "object" && part.functionResponse) {
|
|
2011
|
+
const resp = { ...part.functionResponse };
|
|
2012
|
+
if (!resp.id && typeof resp.name === "string") {
|
|
2013
|
+
const queue = pendingCallIdsByName.get(resp.name);
|
|
2014
|
+
if (queue && queue.length > 0) {
|
|
2015
|
+
resp.id = queue.shift();
|
|
2016
|
+
pendingCallIdsByName.set(resp.name, queue);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
return { ...part, functionResponse: resp };
|
|
2020
|
+
}
|
|
2021
|
+
return part;
|
|
2022
|
+
});
|
|
2023
|
+
return { ...content, parts: newParts };
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Applies all tool fixes to a request payload for Claude models.
|
|
2028
|
+
* This includes:
|
|
2029
|
+
* 1. Tool ID assignment for functionCalls
|
|
2030
|
+
* 2. Response ID matching for functionResponses
|
|
2031
|
+
* 3. Orphan recovery via fixToolResponseGrouping
|
|
2032
|
+
* 4. Claude format pairing fix via validateAndFixClaudeToolPairing
|
|
2033
|
+
*
|
|
2034
|
+
* @param payload - Request payload object
|
|
2035
|
+
* @param isClaude - Whether this is a Claude model request
|
|
2036
|
+
* @returns Object with fix applied status
|
|
2037
|
+
*/
|
|
2038
|
+
export function applyToolPairingFixes(payload, isClaude) {
|
|
2039
|
+
let contentsFixed = false;
|
|
2040
|
+
let messagesFixed = false;
|
|
2041
|
+
if (!isClaude) {
|
|
2042
|
+
return { contentsFixed, messagesFixed };
|
|
2043
|
+
}
|
|
2044
|
+
// Fix Gemini format (contents[])
|
|
2045
|
+
if (Array.isArray(payload.contents)) {
|
|
2046
|
+
// First pass: assign IDs to functionCalls
|
|
2047
|
+
const { contents: contentsWithIds, pendingCallIdsByName } = assignToolIdsToContents(payload.contents);
|
|
2048
|
+
// Second pass: match functionResponse IDs
|
|
2049
|
+
const contentsWithMatchedIds = matchResponseIdsToContents(contentsWithIds, pendingCallIdsByName);
|
|
2050
|
+
// Third pass: fix orphan recovery
|
|
2051
|
+
payload.contents = fixToolResponseGrouping(contentsWithMatchedIds);
|
|
2052
|
+
contentsFixed = true;
|
|
2053
|
+
log.debug("Applied tool pairing fixes to contents[]", {
|
|
2054
|
+
originalLength: payload.contents.length,
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
// Fix Claude format (messages[])
|
|
2058
|
+
if (Array.isArray(payload.messages)) {
|
|
2059
|
+
payload.messages = validateAndFixClaudeToolPairing(payload.messages);
|
|
2060
|
+
messagesFixed = true;
|
|
2061
|
+
log.debug("Applied tool pairing fixes to messages[]", {
|
|
2062
|
+
originalLength: payload.messages.length,
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
return { contentsFixed, messagesFixed };
|
|
2066
|
+
}
|
|
1836
2067
|
//# sourceMappingURL=request-helpers.js.map
|