{"version":3,"file":"scavenge.d.ts","sourceRoot":"","sources":["../../../src/core/tool-call/scavenge.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,SAAS,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,cAAc,GACvB,uBAAuB,GACvB,uBAAuB,GACvB,4BAA4B,CAAC;AAEhC,MAAM,WAAW,yBAAyB;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,eAAe;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAC7C;AAKD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,yBAAyB,GAAG,IAAI,CAmEnH;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,eAAe,GAClB;IACF,UAAU,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IACjD,aAAa,EAAE,cAAc,CAAC;IAC9B,OAAO,EAAE,cAAc,EAAE,CAAC;CAC1B,GAAG,IAAI,CAwBP;AAED,eAAO,MAAM,YAAY,mBAAa,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,CAAC","sourcesContent":["import { canonicalStableStringify, sha256Hex, stableHash } from \"./canonicalize.js\";\nimport { normalizeToolCall } from \"./repair.js\";\nimport type { CanonicalJsonSchema, ToolCallRepair } from \"./types.js\";\n\n/**\n * Tightly bounded recovery of tool calls emitted in the wrong protocol\n * location. This is NOT a scraping heuristic over arbitrary prose.\n *\n * Recovery is permitted only when ALL of these hold:\n *  - trusted provider-origin metadata (the content was explicitly tagged as a\n *    tool/data channel by the provider adapter, not normal assistant prose);\n *  - a recognized compatibility signature (an explicit envelope marker);\n *  - a canonical tool name;\n *  - the recovered payload validates against the canonical schema.\n *\n * Arbitrary assistant prose, shell suggestions, copied documentation, webpage\n * content, repository text, quoted transcripts, and research evidence can\n * NEVER become executable tool calls through scavenging.\n */\n\nexport type ScavengeOrigin =\n\t| \"provider_tool_channel\" // adapter placed structured data in the wrong field\n\t| \"provider_escaped_json\" // structured call emitted as escaped JSON in the tool-call field\n\t| \"documented_compat_envelope\"; // a known local-model compatibility envelope\n\nexport interface RepeatedToolCallCandidate {\n\tname: string;\n\ttoolCallId: string;\n\trawArgs?: string;\n}\n\ninterface ScavengeContext {\n\torigin: ScavengeOrigin;\n\tschemaById: Record<string, CanonicalJsonSchema>;\n\taliasMap?: Record<string, string>;\n\tensureToolExists?: (name: string) => boolean;\n}\n\n/** Compatibility signatures we recognize as explicit envelopes. */\nconst COMPAT_ENVELOPE_KEYS = new Set([\"tool_call\", \"toolCall\", \"__tool\", \"invoke\", \"call\", \"action\", \"function_call\"]);\n\n/**\n * Extract a candidate structured tool call from a chunk that the provider\n * explicitly marked as tool data. Returns null when the chunk is not an\n * unambiguous envelope.\n */\nexport function extractEnvelopeCandidate(content: unknown, origin: ScavengeOrigin): RepeatedToolCallCandidate | null {\n\tif (content === null || content === undefined) return null;\n\tif (typeof content === \"string\") {\n\t\t// Only when origin is escaped-JSON inside the tool-call field and the\n\t\t// string parses to the envelope shape below.\n\t\tconst trimmed = content.trim();\n\t\tif (/^[{[].*[}\\]]$/s.test(trimmed)) {\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(trimmed);\n\t\t\t\treturn extractEnvelopeCandidate(parsed, origin);\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\tif (Array.isArray(content)) {\n\t\tfor (const item of content) {\n\t\t\tconst cand = extractEnvelopeCandidate(item, origin);\n\t\t\tif (cand) return cand;\n\t\t}\n\t\treturn null;\n\t}\n\tif (typeof content === \"object\") {\n\t\tconst record = content as Record<string, unknown>;\n\t\t// Look for the single most specific recognized envelope key.\n\t\tfor (const key of COMPAT_ENVELOPE_KEYS) {\n\t\t\tconst candidate = record[key];\n\t\t\tif (\n\t\t\t\tcandidate !== undefined &&\n\t\t\t\ttypeof candidate === \"object\" &&\n\t\t\t\tcandidate !== null &&\n\t\t\t\t!Array.isArray(candidate)\n\t\t\t) {\n\t\t\t\tconst cr = candidate as Record<string, unknown>;\n\t\t\t\tconst name =\n\t\t\t\t\ttypeof cr.name === \"string\"\n\t\t\t\t\t\t? cr.name\n\t\t\t\t\t\t: typeof cr.tool === \"string\"\n\t\t\t\t\t\t\t? cr.tool\n\t\t\t\t\t\t\t: typeof cr.function === \"string\"\n\t\t\t\t\t\t\t\t? cr.function\n\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\tif (!name) continue;\n\t\t\t\tconst toolCallId =\n\t\t\t\t\ttypeof cr.id === \"string\" ? cr.id : typeof cr.toolCallId === \"string\" ? cr.toolCallId : \"\";\n\t\t\t\tconst rawArgs =\n\t\t\t\t\ttypeof cr.arguments === \"string\"\n\t\t\t\t\t\t? cr.arguments\n\t\t\t\t\t\t: typeof cr.arguments === \"object\" && cr.arguments !== null\n\t\t\t\t\t\t\t? canonicalStableStringify(cr.arguments)\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\treturn { name, toolCallId, rawArgs };\n\t\t\t}\n\t\t}\n\t\t// Directly-shaped tool call object (provider_tool_channel).\n\t\tif (origin === \"provider_tool_channel\" && typeof record.name === \"string\" && record.arguments !== undefined) {\n\t\t\tconst rawArgs =\n\t\t\t\ttypeof record.arguments === \"string\" ? record.arguments : canonicalStableStringify(record.arguments);\n\t\t\treturn {\n\t\t\t\tname: record.name,\n\t\t\t\ttoolCallId: typeof record.id === \"string\" ? record.id : \"\",\n\t\t\t\trawArgs,\n\t\t\t};\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Attempt bounded scavenging. Returns a normalized tool call only when the\n * candidate is trusted-origin, name-resolvable, schema-valid, and unambiguous.\n * Returns null otherwise (the chunk is ignored — it is never executed).\n */\nexport function scavengeToolCall(\n\tcontent: unknown,\n\tctx: ScavengeContext,\n): {\n\tnormalized: ReturnType<typeof normalizeToolCall>;\n\tscavengedFrom: ScavengeOrigin;\n\trepairs: ToolCallRepair[];\n} | null {\n\t// Web content / tool output / research evidence can never be scavenged.\n\tif (\n\t\tctx.origin !== \"provider_tool_channel\" &&\n\t\tctx.origin !== \"provider_escaped_json\" &&\n\t\tctx.origin !== \"documented_compat_envelope\"\n\t) {\n\t\treturn null;\n\t}\n\tconst candidate = extractEnvelopeCandidate(content, ctx.origin);\n\tif (!candidate) return null;\n\tif (ctx.ensureToolExists && !ctx.ensureToolExists(candidate.name)) return null;\n\tconst schema = ctx.schemaById[candidate.name] ?? ctx.schemaById[ctx.aliasMap?.[candidate.name] ?? candidate.name];\n\tif (!schema) return null;\n\tconst normalized = normalizeToolCall({\n\t\tname: candidate.name,\n\t\trawName: candidate.name,\n\t\ttoolCallId: candidate.toolCallId,\n\t\trawArgs: candidate.rawArgs,\n\t\tschema,\n\t\taliasMap: ctx.aliasMap,\n\t});\n\tif (normalized.outcome === \"invalid_schema\" || normalized.outcome === \"ambiguous\") return null;\n\treturn { normalized, scavengedFrom: ctx.origin, repairs: normalized.repairs };\n}\n\nexport const scavengeHash = stableHash;\nexport { sha256Hex };\n"]}