{"version":3,"file":"child-transcript.d.ts","sourceRoot":"","sources":["../../../src/shared/child-transcript.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AA2B/C,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAGnD,KAAK,qBAAqB,GAAG,YAAY,GAAG,OAAO,CAAC;AAGpD,KAAK,sBAAsB,GAAG,OAAO,GAAG;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,UAAU,oBAAoB;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,0BAA0B;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACnD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,IAAI,MAAM,GAAG,SAAS,CAAC;CAC/B;AAmCD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,0BAA0B,GAAG,qBAAqB,CAkKpG","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Message } from \"@lpb-work/pi-ai\";\nimport { extractTextFromContent, extractToolArgsPreview } from \"./utils.ts\";\n\nconst MAX_TOOL_PAYLOAD_BYTES = 32 * 1024;\nconst TOOL_PAYLOAD_TRUNCATION_MARKER = \"\\n\\n… payload truncated\";\n\nfunction boundedPayload(value: unknown, maxBytes = MAX_TOOL_PAYLOAD_BYTES): string | undefined {\n\tlet text: string;\n\tif (typeof value === \"string\") text = value;\n\telse {\n\t\ttry {\n\t\t\tconst serialized = JSON.stringify(value, null, 2);\n\t\t\tif (serialized === undefined) return undefined;\n\t\t\ttext = serialized;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\tif (!text.trim()) return undefined;\n\tconst payload = Buffer.from(text, \"utf-8\");\n\tif (payload.length <= maxBytes) return text;\n\tconst markerBytes = Buffer.byteLength(TOOL_PAYLOAD_TRUNCATION_MARKER, \"utf-8\");\n\tlet end = Math.max(0, maxBytes - markerBytes);\n\twhile (end > 0 && (payload[end]! & 0xc0) === 0x80) end--;\n\treturn `${payload.subarray(0, end).toString(\"utf-8\")}${TOOL_PAYLOAD_TRUNCATION_MARKER}`;\n}\n\nexport const CHILD_TRANSCRIPT_ARTIFACT_VERSION = 1;\nconst DEFAULT_MAX_CHILD_TRANSCRIPT_BYTES = 50 * 1024 * 1024;\n\ntype ChildTranscriptSource = \"foreground\" | \"async\";\ntype ChildTranscriptRecordType = \"message\" | \"tool_start\" | \"tool_end\" | \"stdout\" | \"stderr\" | \"truncated\";\n\ntype ChildTranscriptMessage = Message & {\n\tmodel?: string;\n\terrorMessage?: string;\n\tstopReason?: string;\n\tusage?: unknown;\n};\n\ninterface ChildTranscriptEvent {\n\ttype?: string;\n\tmessage?: ChildTranscriptMessage;\n\ttoolCallId?: string;\n\ttoolName?: string;\n\targs?: unknown;\n\tisError?: boolean;\n}\n\ninterface ChildTranscriptWriterInput {\n\ttranscriptPath: string;\n\tsource: ChildTranscriptSource;\n\trunId: string;\n\tagent: string;\n\tchildIndex?: number;\n\tcwd: string;\n\tmaxBytes?: number;\n}\n\nexport interface ChildTranscriptWriter {\n\tpath: string;\n\twriteInitialUserMessage(prompt: string): void;\n\twriteChildEvent(event: ChildTranscriptEvent): void;\n\twriteStdoutLine(line: string): void;\n\twriteStderrLine(line: string): void;\n\twriteStderrText(text: string): void;\n\tgetError(): string | undefined;\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction normalizeUsage(\n\tvalue: unknown,\n): { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number } | undefined {\n\tif (!value || typeof value !== \"object\") return undefined;\n\tconst raw = value as Record<string, unknown>;\n\tconst rawCost = raw.cost;\n\tconst cost =\n\t\trawCost && typeof rawCost === \"object\"\n\t\t\t? (finiteNumber((rawCost as { total?: unknown }).total) ?? 0)\n\t\t\t: (finiteNumber(rawCost) ?? 0);\n\treturn {\n\t\tinput: finiteNumber(raw.input) ?? finiteNumber(raw.inputTokens) ?? 0,\n\t\toutput: finiteNumber(raw.output) ?? finiteNumber(raw.outputTokens) ?? 0,\n\t\tcacheRead: finiteNumber(raw.cacheRead) ?? 0,\n\t\tcacheWrite: finiteNumber(raw.cacheWrite) ?? 0,\n\t\tcost,\n\t};\n}\n\nfunction eventArgs(event: ChildTranscriptEvent): Record<string, unknown> {\n\treturn event.args && typeof event.args === \"object\" && !Array.isArray(event.args)\n\t\t? (event.args as Record<string, unknown>)\n\t\t: {};\n}\n\nexport function createChildTranscriptWriter(input: ChildTranscriptWriterInput): ChildTranscriptWriter {\n\tlet bytesWritten = 0;\n\tlet writeError: string | undefined;\n\tlet truncated = false;\n\tconst maxBytes = input.maxBytes ?? DEFAULT_MAX_CHILD_TRANSCRIPT_BYTES;\n\n\tconst baseRecord = (recordType: ChildTranscriptRecordType) => {\n\t\tconst ts = Date.now();\n\t\treturn {\n\t\t\tversion: CHILD_TRANSCRIPT_ARTIFACT_VERSION,\n\t\t\trecordType,\n\t\t\tsource: input.source,\n\t\t\trunId: input.runId,\n\t\t\tagent: input.agent,\n\t\t\t...(input.childIndex !== undefined ? { childIndex: input.childIndex } : {}),\n\t\t\tcwd: input.cwd,\n\t\t\tts,\n\t\t\ttimestamp: new Date(ts).toISOString(),\n\t\t};\n\t};\n\n\tconst writeTruncatedMarker = () => {\n\t\ttruncated = true;\n\t\tconst marker = `${JSON.stringify({\n\t\t\t...baseRecord(\"truncated\"),\n\t\t\tmaxBytes,\n\t\t\tmessage: `Child transcript exceeded ${maxBytes} bytes; further records were omitted.`,\n\t\t})}\\n`;\n\t\tconst markerBytes = Buffer.byteLength(marker, \"utf-8\");\n\t\tif (bytesWritten + markerBytes > maxBytes) return false;\n\t\ttry {\n\t\t\tfs.appendFileSync(input.transcriptPath, marker, \"utf-8\");\n\t\t\tbytesWritten += markerBytes;\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\twriteError = `Failed to write child transcript '${input.transcriptPath}': ${errorMessage(error)}`;\n\t\t\treturn false;\n\t\t}\n\t};\n\n\tconst writeRecord = (record: Record<string, unknown>) => {\n\t\tif (writeError || truncated) return;\n\t\tconst line = `${JSON.stringify(record)}\\n`;\n\t\tconst bytes = Buffer.byteLength(line, \"utf-8\");\n\t\tif (bytesWritten + bytes > maxBytes) {\n\t\t\twriteTruncatedMarker();\n\t\t\treturn;\n\t\t}\n\t\tconst markerProbe = `${JSON.stringify({\n\t\t\t...baseRecord(\"truncated\"),\n\t\t\tmaxBytes,\n\t\t\tmessage: `Child transcript exceeded ${maxBytes} bytes; further records were omitted.`,\n\t\t})}\\n`;\n\t\tif (bytesWritten + bytes + Buffer.byteLength(markerProbe, \"utf-8\") > maxBytes) {\n\t\t\twriteTruncatedMarker();\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tfs.appendFileSync(input.transcriptPath, line, \"utf-8\");\n\t\t\tbytesWritten += bytes;\n\t\t} catch (error) {\n\t\t\twriteError = `Failed to write child transcript '${input.transcriptPath}': ${errorMessage(error)}`;\n\t\t}\n\t};\n\n\ttry {\n\t\tfs.mkdirSync(path.dirname(input.transcriptPath), { recursive: true });\n\t\tfs.writeFileSync(input.transcriptPath, \"\", \"utf-8\");\n\t} catch (error) {\n\t\twriteError = `Failed to initialize child transcript '${input.transcriptPath}': ${errorMessage(error)}`;\n\t}\n\n\tconst writeMessage = (sourceEventType: string, message: ChildTranscriptMessage) => {\n\t\tconst text = extractTextFromContent(message.content);\n\t\tif (message.role === \"toolResult\") {\n\t\t\tconst output = boundedPayload(text);\n\t\t\twriteRecord({\n\t\t\t\t...baseRecord(\"message\"),\n\t\t\t\tsourceEventType,\n\t\t\t\trole: message.role,\n\t\t\t\ttoolCallId: message.toolCallId,\n\t\t\t\ttoolName: message.toolName,\n\t\t\t\tisError: message.isError,\n\t\t\t\t...(output ? { text: output, outputTruncated: output.includes(\"… payload truncated\") } : {}),\n\t\t\t\tmessage: {\n\t\t\t\t\trole: message.role,\n\t\t\t\t\ttoolCallId: message.toolCallId,\n\t\t\t\t\ttoolName: message.toolName,\n\t\t\t\t\tisError: message.isError,\n\t\t\t\t\tcontent: output ? [{ type: \"text\", text: output }] : [],\n\t\t\t\t\t...(message.timestamp !== undefined ? { timestamp: message.timestamp } : {}),\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\twriteRecord({\n\t\t\t...baseRecord(\"message\"),\n\t\t\tsourceEventType,\n\t\t\trole: message.role,\n\t\t\t...(text ? { text } : {}),\n\t\t\t...(message.model ? { model: message.model } : {}),\n\t\t\t...(message.stopReason ? { stopReason: message.stopReason } : {}),\n\t\t\t...(message.errorMessage ? { errorMessage: message.errorMessage } : {}),\n\t\t\t...(message.usage ? { usage: normalizeUsage(message.usage) } : {}),\n\t\t\tmessage,\n\t\t});\n\t};\n\n\treturn {\n\t\tpath: input.transcriptPath,\n\t\twriteInitialUserMessage(prompt: string) {\n\t\t\twriteRecord({\n\t\t\t\t...baseRecord(\"message\"),\n\t\t\t\tsourceEventType: \"initial_prompt\",\n\t\t\t\trole: \"user\",\n\t\t\t\ttext: prompt,\n\t\t\t\tmessage: { role: \"user\", content: [{ type: \"text\", text: prompt }] },\n\t\t\t});\n\t\t},\n\t\twriteChildEvent(event: ChildTranscriptEvent) {\n\t\t\tif ((event.type === \"message_end\" || event.type === \"tool_result_end\") && event.message) {\n\t\t\t\twriteMessage(event.type, event.message);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (event.type === \"tool_execution_start\" && event.toolName) {\n\t\t\t\tconst args = eventArgs(event);\n\t\t\t\tconst argsPayload = boundedPayload(args);\n\t\t\t\twriteRecord({\n\t\t\t\t\t...baseRecord(\"tool_start\"),\n\t\t\t\t\tsourceEventType: event.type,\n\t\t\t\t\t...(event.toolCallId ? { toolCallId: event.toolCallId } : {}),\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\t...(Object.keys(args).length > 0 ? { argsPreview: extractToolArgsPreview(args) } : {}),\n\t\t\t\t\t...(argsPayload ? { argsPayload } : {}),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (event.type === \"tool_execution_end\") {\n\t\t\t\twriteRecord({\n\t\t\t\t\t...baseRecord(\"tool_end\"),\n\t\t\t\t\tsourceEventType: event.type,\n\t\t\t\t\t...(event.toolCallId ? { toolCallId: event.toolCallId } : {}),\n\t\t\t\t\t...(event.toolName ? { toolName: event.toolName } : {}),\n\t\t\t\t\t...(typeof event.isError === \"boolean\" ? { isError: event.isError } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\twriteStdoutLine(line: string) {\n\t\t\tif (!line.trim()) return;\n\t\t\twriteRecord({ ...baseRecord(\"stdout\"), text: line });\n\t\t},\n\t\twriteStderrLine(line: string) {\n\t\t\tif (!line.trim()) return;\n\t\t\twriteRecord({ ...baseRecord(\"stderr\"), text: line });\n\t\t},\n\t\twriteStderrText(text: string) {\n\t\t\tfor (const line of text.split(/\\r?\\n/)) this.writeStderrLine(line);\n\t\t},\n\t\tgetError() {\n\t\t\treturn writeError;\n\t\t},\n\t};\n}\n"]}