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.
Files changed (58) hide show
  1. package/README.md +224 -153
  2. package/dist/src/constants.d.ts +8 -0
  3. package/dist/src/constants.d.ts.map +1 -1
  4. package/dist/src/constants.js +8 -0
  5. package/dist/src/constants.js.map +1 -1
  6. package/dist/src/plugin/config/schema.d.ts +12 -0
  7. package/dist/src/plugin/config/schema.d.ts.map +1 -1
  8. package/dist/src/plugin/config/schema.js +14 -0
  9. package/dist/src/plugin/config/schema.js.map +1 -1
  10. package/dist/src/plugin/core/streaming/index.d.ts +3 -0
  11. package/dist/src/plugin/core/streaming/index.d.ts.map +1 -0
  12. package/dist/src/plugin/core/streaming/index.js +3 -0
  13. package/dist/src/plugin/core/streaming/index.js.map +1 -0
  14. package/dist/src/plugin/core/streaming/transformer.d.ts +9 -0
  15. package/dist/src/plugin/core/streaming/transformer.d.ts.map +1 -0
  16. package/dist/src/plugin/core/streaming/transformer.js +134 -0
  17. package/dist/src/plugin/core/streaming/transformer.js.map +1 -0
  18. package/dist/src/plugin/core/streaming/types.d.ts +26 -0
  19. package/dist/src/plugin/core/streaming/types.d.ts.map +1 -0
  20. package/dist/src/plugin/core/streaming/types.js +1 -0
  21. package/dist/src/plugin/core/streaming/types.js.map +1 -0
  22. package/dist/src/plugin/logger.d.ts.map +1 -1
  23. package/dist/src/plugin/logger.js +3 -2
  24. package/dist/src/plugin/logger.js.map +1 -1
  25. package/dist/src/plugin/recovery.d.ts.map +1 -1
  26. package/dist/src/plugin/recovery.js +42 -14
  27. package/dist/src/plugin/recovery.js.map +1 -1
  28. package/dist/src/plugin/request-helpers.d.ts +38 -0
  29. package/dist/src/plugin/request-helpers.d.ts.map +1 -1
  30. package/dist/src/plugin/request-helpers.js +250 -19
  31. package/dist/src/plugin/request-helpers.js.map +1 -1
  32. package/dist/src/plugin/request.d.ts +50 -3
  33. package/dist/src/plugin/request.d.ts.map +1 -1
  34. package/dist/src/plugin/request.js +89 -177
  35. package/dist/src/plugin/request.js.map +1 -1
  36. package/dist/src/plugin/storage.d.ts +1 -0
  37. package/dist/src/plugin/storage.d.ts.map +1 -1
  38. package/dist/src/plugin/storage.js +70 -5
  39. package/dist/src/plugin/storage.js.map +1 -1
  40. package/dist/src/plugin/stores/signature-store.d.ts +5 -0
  41. package/dist/src/plugin/stores/signature-store.d.ts.map +1 -0
  42. package/dist/src/plugin/stores/signature-store.js +25 -0
  43. package/dist/src/plugin/stores/signature-store.js.map +1 -0
  44. package/dist/src/plugin/transform/claude.d.ts.map +1 -1
  45. package/dist/src/plugin/transform/claude.js +30 -8
  46. package/dist/src/plugin/transform/claude.js.map +1 -1
  47. package/dist/src/plugin/transform/model-resolver.d.ts +12 -5
  48. package/dist/src/plugin/transform/model-resolver.d.ts.map +1 -1
  49. package/dist/src/plugin/transform/model-resolver.js +73 -20
  50. package/dist/src/plugin/transform/model-resolver.js.map +1 -1
  51. package/dist/src/plugin/transform/types.d.ts +3 -5
  52. package/dist/src/plugin/transform/types.d.ts.map +1 -1
  53. package/dist/src/plugin/transform/types.js +0 -5
  54. package/dist/src/plugin/transform/types.js.map +1 -1
  55. package/dist/src/plugin.d.ts.map +1 -1
  56. package/dist/src/plugin.js +113 -44
  57. package/dist/src/plugin.js.map +1 -1
  58. 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
- // Skip unsupported keywords
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
- result[key] = removeUnsupportedKeywords(value);
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
- reason: {
453
- type: "string",
454
- description: "Brief explanation of why you are calling this tool",
530
+ [EMPTY_SCHEMA_PLACEHOLDER_NAME]: {
531
+ type: "boolean",
532
+ description: EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION,
455
533
  },
456
534
  };
457
- result.required = ["reason"];
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 filterContentArray(contentArray, sessionId, getCachedSignatureFn, isClaudeModel) {
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
- return contents.map((content) => {
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 filteredContent = filterContentArray(content.content, sessionId, getCachedSignatureFn, isClaudeModel);
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
- return messages.map((message) => {
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 filteredContent = filterContentArray(message.content, sessionId, getCachedSignatureFn, isClaudeModel);
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
- return { ...part, type: "reasoning" };
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
- return {
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