{"version":3,"file":"tool-placement.d.ts","sourceRoot":"","sources":["../../../../src/harness/runtime/drive/tool-placement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAqB,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,EAMN,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,cAAc,EAGnB,MAAM,wBAAwB,CAAC;AAShC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,MAAM,eAAe,GAAG;IAC7B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAClC,CAAC;AAEF,wBAAsB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,GAAG,SAAS,EAC5E,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,EACpB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,SAAS,GACd,OAAO,CAAC,eAAe,CAAC,CAkB1B;AAgBD,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,GAAG,aAAa,CAMnF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,GAAG,cAAc,CAEnF;AAsMD,wBAAsB,gBAAgB,CAAC,QAAQ,SAAS,MAAM,GAAG,SAAS,EACzE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,EACpB,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,cAAc,EAC1B,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAwCf","sourcesContent":["import type { AssistantMessage, ToolResultMessage } from \"@earendil-works/pi-ai\";\nimport type { AgentToolCall } from \"../../../types.ts\";\nimport type { HarnessEvent } from \"../../agent-harness.ts\";\nimport { insertEntry, insertUsage } from \"../../session/commit.ts\";\nimport { SessionInvariantError } from \"../../session/session.ts\";\nimport {\n\ttype CheckpointOperation,\n\ttype MessageEntry,\n\ttype NewEntry,\n\ttype OperationState,\n\toperationScopeOf,\n\ttype ToolBatch,\n\ttype ToolCall,\n\ttype ToolsOperation,\n\ttype UsageRow,\n\ttype Write,\n} from \"../../session/types.ts\";\nimport {\n\tbranchTip,\n\tdeleteValue,\n\tlaneConfig,\n\toperationToolArgsPrefix,\n\tpendingEntry,\n\tsetValue,\n} from \"../../session/values.ts\";\nimport type { Lane } from \"../lane.ts\";\nimport type { Drive } from \"../types.ts\";\n\nexport type ToolBatchSource = {\n\tassistant: AssistantMessage;\n\tcalls: Map<number, AgentToolCall>;\n};\n\nexport async function readToolBatchSource<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tbatch: ToolBatch,\n): Promise<ToolBatchSource> {\n\treturn lane.command<ToolBatchSource>(async (_state, reader) => {\n\t\tconst entry = (await reader.getEntries([batch.assistantEntryId], drive.context)).get(batch.assistantEntryId);\n\t\tif (entry?.type !== \"message\" || entry.message.role !== \"assistant\") {\n\t\t\tthrow new SessionInvariantError(\"Tool batch assistant entry is invalid\");\n\t\t}\n\t\tconst calls = new Map<number, AgentToolCall>();\n\t\tfor (const call of batch.calls) {\n\t\t\tconst block = entry.message.content[call.sourceIndex];\n\t\t\tif (block?.type !== \"toolCall\") {\n\t\t\t\tthrow new SessionInvariantError(\n\t\t\t\t\t`Tool call source index ${call.sourceIndex} does not name a tool-call block`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcalls.set(call.sourceIndex, block);\n\t\t}\n\t\treturn { kind: \"return\", result: { assistant: entry.message, calls } };\n\t}, drive.context);\n}\n\ntype PlacementItem = {\n\tcall: Extract<ToolCall, { status: \"outcome_ready\" }>;\n\tmessage: ToolResultMessage<unknown>;\n};\n\ntype PlacementRead = {\n\titems: PlacementItem[];\n\tturnResults?: ToolResultMessage<unknown>[];\n};\n\nfunction isToolResultMessage(value: unknown): value is ToolResultMessage<unknown> {\n\treturn typeof value === \"object\" && value !== null && \"role\" in value && value.role === \"toolResult\";\n}\n\nexport function toolCallFor(sources: ToolBatchSource, call: ToolCall): AgentToolCall {\n\tconst source = sources.calls.get(call.sourceIndex);\n\tif (source === undefined) {\n\t\tthrow new SessionInvariantError(`Tool call source index ${call.sourceIndex} is invalid`);\n\t}\n\treturn source;\n}\n\nexport function withToolBatch(run: ToolsOperation, batch: ToolBatch): ToolsOperation {\n\treturn { ...operationScopeOf(run), at: \"tools\", batch };\n}\n\nasync function readPlacement<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tsources: ToolBatchSource,\n): Promise<PlacementRead | undefined> {\n\treturn lane.command<PlacementRead | undefined>(async (state, reader) => {\n\t\tconst operation = state.operation;\n\t\tif (operation?.state.at !== \"tools\") return { kind: \"return\", result: undefined };\n\t\tconst current = operation.state.batch;\n\t\tlet first = current.calls.findIndex((call) => call.status !== \"completed\");\n\t\tif (first === -1) return { kind: \"return\", result: undefined };\n\t\tconst ready: Array<Extract<ToolCall, { status: \"outcome_ready\" }>> = [];\n\t\twhile (first < current.calls.length) {\n\t\t\tconst call = current.calls[first]!;\n\t\t\tif (call.status !== \"outcome_ready\") break;\n\t\t\tready.push(call);\n\t\t\tfirst += 1;\n\t\t}\n\t\tif (ready.length === 0) return { kind: \"return\", result: undefined };\n\n\t\tconst items: PlacementItem[] = [];\n\t\tfor (const call of ready) {\n\t\t\tconst stored = await reader.getValue(pendingEntry(call.resultEntryId), drive.context);\n\t\t\tif (stored?.value.type !== \"message\" || !isToolResultMessage(stored.value.payload)) {\n\t\t\t\tthrow new SessionInvariantError(`Tool call ${call.resultEntryId} is missing its staged result`);\n\t\t\t}\n\t\t\tconst source = toolCallFor(sources, call);\n\t\t\tif (stored.value.payload.toolCallId !== source.id || stored.value.payload.toolName !== source.name) {\n\t\t\t\tthrow new SessionInvariantError(`Tool call ${call.resultEntryId} has a mismatched staged result`);\n\t\t\t}\n\t\t\titems.push({ call, message: stored.value.payload });\n\t\t}\n\n\t\tlet turnResults: ToolResultMessage<unknown>[] | undefined;\n\t\tif (first === current.calls.length) {\n\t\t\tconst placedIds = current.calls\n\t\t\t\t.filter((call) => call.status === \"completed\")\n\t\t\t\t.map((call) => call.resultEntryId);\n\t\t\tconst placed = await reader.getEntries(placedIds, drive.context);\n\t\t\tconst staged = new Map(items.map((item) => [item.call.resultEntryId, item.message]));\n\t\t\tturnResults = current.calls.map((call) => {\n\t\t\t\tconst message =\n\t\t\t\t\tstaged.get(call.resultEntryId) ??\n\t\t\t\t\t(() => {\n\t\t\t\t\t\tconst entry = placed.get(call.resultEntryId);\n\t\t\t\t\t\treturn entry?.type === \"message\" && isToolResultMessage(entry.message) ? entry.message : undefined;\n\t\t\t\t\t})();\n\t\t\t\tif (message === undefined) {\n\t\t\t\t\tthrow new SessionInvariantError(`Completed tool call ${call.resultEntryId} is missing its result entry`);\n\t\t\t\t}\n\t\t\t\treturn message;\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\tkind: \"return\",\n\t\t\tresult: { items, ...(turnResults === undefined ? {} : { turnResults }) },\n\t\t};\n\t}, drive.context);\n}\n\nasync function commitPlacement<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tcapability: ToolsOperation,\n\tread: PlacementRead,\n): Promise<boolean> {\n\tconst usageIds = read.items.map((item) =>\n\t\titem.message.usage === undefined ? undefined : lane.session.idGenerator.next(),\n\t);\n\treturn lane.settleOperation<ToolsOperation, boolean>(\n\t\tcapability,\n\t\tasync (state, run, _meta, reader) => {\n\t\t\tconst current = run.batch;\n\t\t\tconst writes: Write[] = [];\n\t\t\tconst eventEntries: Array<{ entry: NewEntry<MessageEntry>; seqIndex: number }> = [];\n\t\t\tconst eventUsage: Array<{ row: Omit<UsageRow, \"seq\">; seqIndex: number }> = [];\n\t\t\tlet parentId = state.tipId;\n\t\t\tfor (const [index, item] of read.items.entries()) {\n\t\t\t\tconst entry: NewEntry<MessageEntry> = {\n\t\t\t\t\tid: item.call.resultEntryId,\n\t\t\t\t\tparentId,\n\t\t\t\t\ttype: \"message\",\n\t\t\t\t\tmessage: item.message,\n\t\t\t\t\t...(item.call.terminate ? { terminate: true } : {}),\n\t\t\t\t};\n\t\t\t\teventEntries.push({ entry, seqIndex: writes.length });\n\t\t\t\twrites.push(insertEntry(entry), deleteValue(pendingEntry(item.call.resultEntryId)));\n\t\t\t\tconst usageId = usageIds[index];\n\t\t\t\tif (usageId !== undefined && item.message.usage !== undefined) {\n\t\t\t\t\tconst row: Omit<UsageRow, \"seq\"> = {\n\t\t\t\t\t\tid: usageId,\n\t\t\t\t\t\tusage: item.message.usage,\n\t\t\t\t\t\tentryId: item.call.resultEntryId,\n\t\t\t\t\t\tadjustment: false,\n\t\t\t\t\t};\n\t\t\t\t\teventUsage.push({ row, seqIndex: writes.length });\n\t\t\t\t\twrites.push(insertUsage(row));\n\t\t\t\t}\n\t\t\t\tparentId = item.call.resultEntryId;\n\t\t\t}\n\n\t\t\tconst completedCalls = current.calls.map((call) => {\n\t\t\t\tconst item = read.items.find(\n\t\t\t\t\t(candidate) =>\n\t\t\t\t\t\tcandidate.call.sourceIndex === call.sourceIndex &&\n\t\t\t\t\t\tcandidate.call.resultEntryId === call.resultEntryId,\n\t\t\t\t);\n\t\t\t\treturn item === undefined\n\t\t\t\t\t? call\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tstatus: \"completed\" as const,\n\t\t\t\t\t\t\tsourceIndex: call.sourceIndex,\n\t\t\t\t\t\t\tresultEntryId: call.resultEntryId,\n\t\t\t\t\t\t\tterminate: item.call.terminate,\n\t\t\t\t\t\t};\n\t\t\t});\n\t\t\tconst complete = completedCalls.every((call) => call.status === \"completed\");\n\t\t\tlet nextConfiguration = state.configuration;\n\t\t\tconst addedNames: string[] = [];\n\t\t\tfor (const item of read.items) {\n\t\t\t\tfor (const name of item.message.addedToolNames ?? []) {\n\t\t\t\t\tif (!nextConfiguration.activeToolNames.includes(name) && !addedNames.includes(name)) {\n\t\t\t\t\t\taddedNames.push(name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (addedNames.length !== 0) {\n\t\t\t\tnextConfiguration = {\n\t\t\t\t\t...nextConfiguration,\n\t\t\t\t\tactiveToolNames: [...nextConfiguration.activeToolNames, ...addedNames],\n\t\t\t\t};\n\t\t\t\twrites.push(setValue(laneConfig(lane.name), nextConfiguration));\n\t\t\t}\n\t\t\twrites.push(setValue(branchTip(lane.name), parentId));\n\n\t\t\tlet nextRun: OperationState;\n\t\t\tif (complete) {\n\t\t\t\tconst allTerminate = completedCalls.every((call) => call.status === \"completed\" && call.terminate);\n\t\t\t\tconst checkpoint: CheckpointOperation = {\n\t\t\t\t\t...operationScopeOf(run),\n\t\t\t\t\tat: \"checkpoint\",\n\t\t\t\t\tcontinuation: allTerminate\n\t\t\t\t\t\t? { kind: \"may_finish\", includeFinalAssistant: false }\n\t\t\t\t\t\t: { kind: \"need_assistant\", overflowRecoveryUsed: false },\n\t\t\t\t\ttriggerEntryId: parentId!,\n\t\t\t\t};\n\t\t\t\tnextRun = checkpoint;\n\t\t\t\tconst args = await reader.scanValues(\n\t\t\t\t\toperationToolArgsPrefix(drive.operationId, capability.batch.turnId),\n\t\t\t\t\tdrive.context,\n\t\t\t\t);\n\t\t\t\twrites.push(...args.map(({ address }) => deleteValue(address)));\n\t\t\t} else {\n\t\t\t\tnextRun = withToolBatch(run, { ...current, calls: completedCalls });\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tkind: \"commit\",\n\t\t\t\twrites,\n\t\t\t\toperationState: nextRun,\n\t\t\t\tlane: { tipId: parentId, configuration: nextConfiguration },\n\t\t\t\tmaterialize: () => complete,\n\t\t\t\tevents: (commit) => {\n\t\t\t\t\tconst events: HarnessEvent[] = [];\n\t\t\t\t\tfor (const { entry, seqIndex } of eventEntries) {\n\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\ttype: \"entry_added\",\n\t\t\t\t\t\t\tlane: lane.name,\n\t\t\t\t\t\t\tentry: { ...entry, seq: commit.seqs[seqIndex]!, timestamp: commit.timestamp },\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst usage = eventUsage.find((candidate) => candidate.row.entryId === entry.id);\n\t\t\t\t\t\tif (usage !== undefined) {\n\t\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\t\ttype: \"usage\",\n\t\t\t\t\t\t\t\tlane: lane.name,\n\t\t\t\t\t\t\t\trow: { ...usage.row, seq: commit.seqs[usage.seqIndex]! },\n\t\t\t\t\t\t\t\ttotals: commit.stats.usage,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (addedNames.length !== 0) {\n\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\ttype: \"config_update\",\n\t\t\t\t\t\t\tlane: lane.name,\n\t\t\t\t\t\t\tproperty: \"activeTools\",\n\t\t\t\t\t\t\tprevious: state.configuration.activeToolNames,\n\t\t\t\t\t\t\tvalue: nextConfiguration.activeToolNames,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn events;\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tdrive.context,\n\t);\n}\n\nexport async function materializeReady<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tcapability: ToolsOperation,\n\tsources: ToolBatchSource,\n\trecovery: boolean,\n): Promise<void> {\n\tconst read = await readPlacement(lane, drive, sources);\n\tif (read === undefined) return;\n\tawait lane.emitBatch(\n\t\tread.items.flatMap(({ call, message }) => [\n\t\t\t{\n\t\t\t\ttype: \"message_start\" as const,\n\t\t\t\tlane: lane.name,\n\t\t\t\trunId: drive.operationId,\n\t\t\t\tmessage,\n\t\t\t\t...(recovery ? { recovery: true as const } : {}),\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: \"message_end\" as const,\n\t\t\t\tlane: lane.name,\n\t\t\t\trunId: drive.operationId,\n\t\t\t\tmessage,\n\t\t\t\tentryId: call.resultEntryId,\n\t\t\t\t...(recovery ? { recovery: true as const } : {}),\n\t\t\t},\n\t\t]),\n\t\tdrive.context,\n\t);\n\tconst complete = await commitPlacement(lane, drive, capability, read);\n\tif (complete && read.turnResults !== undefined) {\n\t\tawait lane.emitBatch(\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\ttype: \"turn_end\",\n\t\t\t\t\tlane: lane.name,\n\t\t\t\t\trunId: drive.operationId,\n\t\t\t\t\tturnId: capability.batch.turnId,\n\t\t\t\t\tmessage: sources.assistant,\n\t\t\t\t\ttoolResults: read.turnResults,\n\t\t\t\t\t...(recovery ? { recovery: true as const } : {}),\n\t\t\t\t},\n\t\t\t],\n\t\t\tdrive.context,\n\t\t);\n\t}\n}\n"]}