/** * Swappable translation helpers for rendering ADK state into OpenAI Responses requests. * * @module @nhtio/adk/batteries/llm/openai_responses/helpers * * @remarks * The swappable translation helpers that turn ADK primitives into OpenAI Responses wire shapes. * Each helper is exported under its unprefixed name AND under a `default*` alias so consumers can * compose partial overrides. Helpers that compose other helpers receive their dependents via * explicit input arguments — never via module-level closure — so a swap at any layer propagates * correctly. * * The wire-shape-AGNOSTIC helpers (`renderUntrustedContent`, `renderMemories`, * `renderChatCompletionsSystemPrompt`, `descriptionToChatCompletionsJsonSchema`, * `canonicalFingerprint`, …) live in the shared, internal `../chat_common/helpers` submodule and * are re-exported here under their original names so every existing import keeps resolving. Only * the Responses-WIRE-SPECIFIC helpers (media mapping, timeline-message rendering, tool-call-result * rendering, the `buildOpenAIResponsesInput` assembler, the reasoning-replay machinery, the * fingerprint wrapper, the tool-declaration translator, and the streaming output-slot state * machine) are defined here. */ import { Media } from "../../../common"; import type { DispatchContext } from "../../../types"; import type { ChatHelpersCommon } from "../chat_common/types"; import type { Tool, ArtifactTool, Tokenizable, Message, Thought, ToolCall, SpooledArtifact } from "../../../common"; import type { OpenAIResponsesInputContentBlock, OpenAIResponsesInputItem, OpenAIResponsesReasoningItem, OpenAIResponsesTool, OpenAIResponsesHelpers, ResponsesOutputSlotMachine, UnsupportedMediaPolicy, JsonSchema, ReasoningReplayMode } from "./types"; /** De-collides a Responses id while preserving a valid `fc_` item discriminator. */ export declare const deCollideOpenAIResponsesToolCallIds: (id: string, ctx: DispatchContext) => string; export { descriptionToChatCompletionsJsonSchema, defaultDescriptionToChatCompletionsJsonSchema, renderUntrustedContent, defaultRenderUntrustedContent, renderTrustedContent, defaultRenderTrustedContent, renderStandingInstructions, defaultRenderStandingInstructions, neutraliseDeveloperRulesTag, stripEnvelopeSpecialTokens, sanitizeMimeType, sanitizeFilenameForDescription, floorTrustTier, renderMemories, defaultRenderMemories, renderRetrievableSafetyDirective, defaultRenderRetrievableSafetyDirective, renderFirstPartyRetrievables, defaultRenderFirstPartyRetrievables, renderThirdPartyPublicRetrievables, defaultRenderThirdPartyPublicRetrievables, renderThirdPartyPrivateRetrievables, defaultRenderThirdPartyPrivateRetrievables, renderRetrievables, defaultRenderRetrievables, renderRetrievableHandleBody, defaultRenderRetrievableHandleBody, renderArtifactHandleBody, defaultRenderArtifactHandleBody, renderThought, defaultRenderThought, filterThoughts, defaultFilterThoughts, toolsToChatCompletionsTools, defaultToolsToChatCompletionsTools, renderChatCompletionsSystemPrompt, defaultRenderChatCompletionsSystemPrompt, canonicalFingerprint, defaultCanonicalFingerprint, looksLikeSpooledArtifact, } from "../chat_common/helpers"; /** * Implements {@link OpenAIResponsesHelpers.renderOpenAIResponsesMediaBlocks}. * * @remarks * `image` maps to `input_image` (a data URI). `document` maps to `input_file` using the * NOW-CONFIRMED `file_data: 'data:;base64,'` shape (a live probe against the real * Responses API accepted this exact shape with `filename` alongside it — see the battery's design * notes). `audio`/`video` — and any other kind/mime this battery cannot natively express — route * through `unsupportedMediaPolicy`, unchanged. */ export declare const renderOpenAIResponsesMediaBlocks: (input: { media: Media; unsupportedMediaPolicy: UnsupportedMediaPolicy; renderUntrustedContent: ChatHelpersCommon["renderUntrustedContent"]; renderTrustedContent: ChatHelpersCommon["renderTrustedContent"]; warn?: (msg: string) => void; }) => Promise; /** Default OpenAI Responses media renderer; alias of {@link renderOpenAIResponsesMediaBlocks}. */ export declare const defaultRenderOpenAIResponsesMediaBlocks: (input: { media: Media; unsupportedMediaPolicy: UnsupportedMediaPolicy; renderUntrustedContent: ChatHelpersCommon["renderUntrustedContent"]; renderTrustedContent: ChatHelpersCommon["renderTrustedContent"]; warn?: (msg: string) => void; }) => Promise; /** * Implements {@link OpenAIResponsesHelpers.renderOpenAIResponsesTimelineMessage}. * * @remarks * A USER message, or a peer-identity ASSISTANT message (a message from an assistant identity that * is not this adapter's own `selfIdentity`), renders as a plain `role`-carrying input message item * — the ADK's own prior assistant turns are NOT routed through this renderer; the caller renders * those via {@link renderOpenAIResponsesOwnAssistantMessage} instead, into the OUTPUT-message shape * the reasoning-pairing validator expects (see the module's Known Gotchas). */ export declare const renderOpenAIResponsesTimelineMessage: (input: { message: Message; selfIdentity: string; unsupportedMediaPolicy: UnsupportedMediaPolicy; renderOpenAIResponsesMediaBlocks: OpenAIResponsesHelpers["renderOpenAIResponsesMediaBlocks"]; renderUntrustedContent: ChatHelpersCommon["renderUntrustedContent"]; renderTrustedContent: ChatHelpersCommon["renderTrustedContent"]; warn?: (msg: string) => void; }) => Promise; /** Default timeline-message renderer; alias of {@link renderOpenAIResponsesTimelineMessage}. */ export declare const defaultRenderOpenAIResponsesTimelineMessage: (input: { message: Message; selfIdentity: string; unsupportedMediaPolicy: UnsupportedMediaPolicy; renderOpenAIResponsesMediaBlocks: OpenAIResponsesHelpers["renderOpenAIResponsesMediaBlocks"]; renderUntrustedContent: ChatHelpersCommon["renderUntrustedContent"]; renderTrustedContent: ChatHelpersCommon["renderTrustedContent"]; warn?: (msg: string) => void; }) => Promise; /** * Implements {@link OpenAIResponsesHelpers.renderOpenAIResponsesToolCallResult}. * * @remarks * Renders a tool call's result(s) into the shape a `function_call_output` item's `output` field * accepts — either a plain string or an array of content blocks when the result carries media — * mirroring `openai_chat_completions/helpers.ts`'s `renderChatCompletionsToolCallResult` structure. */ export declare const renderOpenAIResponsesToolCallResult: (input: { toolCall: ToolCall; results: Tokenizable | SpooledArtifact | SpooledArtifact[] | Media | Media[]; tool: Tool | ArtifactTool | undefined; renderUntrustedContent: ChatHelpersCommon["renderUntrustedContent"]; renderTrustedContent: ChatHelpersCommon["renderTrustedContent"]; renderOpenAIResponsesMediaBlocks: OpenAIResponsesHelpers["renderOpenAIResponsesMediaBlocks"]; renderArtifactHandleBody?: ChatHelpersCommon["renderArtifactHandleBody"]; unsupportedMediaPolicy: UnsupportedMediaPolicy; warn?: (msg: string) => void; }) => Promise; /** Default tool-call-result renderer; alias of {@link renderOpenAIResponsesToolCallResult}. */ export declare const defaultRenderOpenAIResponsesToolCallResult: (input: { toolCall: ToolCall; results: Tokenizable | SpooledArtifact | SpooledArtifact[] | Media | Media[]; tool: Tool | ArtifactTool | undefined; renderUntrustedContent: ChatHelpersCommon["renderUntrustedContent"]; renderTrustedContent: ChatHelpersCommon["renderTrustedContent"]; renderOpenAIResponsesMediaBlocks: OpenAIResponsesHelpers["renderOpenAIResponsesMediaBlocks"]; renderArtifactHandleBody?: ChatHelpersCommon["renderArtifactHandleBody"]; unsupportedMediaPolicy: UnsupportedMediaPolicy; warn?: (msg: string) => void; }) => Promise; /** * Implements {@link OpenAIResponsesHelpers.toolsToOpenAIResponsesTools}. * * @remarks * `name` is TOP-LEVEL, unlike Chat Completions' `{type:'function', function:{name, ...}}` nesting. * Reuses the shared, unmodified `descriptionToChatCompletionsJsonSchema` for the JSON Schema body — * only the envelope differs. */ export declare const toolsToOpenAIResponsesTools: (tools: ReadonlyArray, deps: { descriptionToChatCompletionsJsonSchema: (d: unknown) => JsonSchema; strict?: boolean; }) => OpenAIResponsesTool[]; /** Default tool-translation helper; alias of {@link toolsToOpenAIResponsesTools}. */ export declare const defaultToolsToOpenAIResponsesTools: (tools: ReadonlyArray, deps: { descriptionToChatCompletionsJsonSchema: (d: unknown) => JsonSchema; strict?: boolean; }) => OpenAIResponsesTool[]; /** * Implements {@link OpenAIResponsesHelpers.fingerprintOpenAIResponsesPrefix}. * * @remarks * Computes the fingerprint used for OpenAI Responses reasoning-item replay. The input is the exact * assembled request prefix: `model`, `instructions`, `tools`, and `input` through (but not * including) the item at `throughItem`. Assembles the Responses-shaped prefix object and delegates * canonicalisation + hashing to the shared, wire-agnostic {@link canonicalFingerprint} primitive — * mirroring `fingerprintAnthropicMessagesPrefix`'s shape-and-slice pattern exactly: object keys are * recursively sorted; array order and item boundaries are preserved. */ export declare const fingerprintOpenAIResponsesPrefix: (input: { model: string; instructions?: string; tools?: OpenAIResponsesTool[]; input: OpenAIResponsesInputItem[]; throughItem?: number; }) => Promise; /** * Implements {@link OpenAIResponsesHelpers.renderOpenAIResponsesReasoningItem}. * * @remarks * Converts an eligible stored {@link OpenAIResponsesReasoningReplayPayload} into a wire `reasoning` * item, or `undefined` if ineligible (no payload, wrong variant, or a stale fingerprint). The * adjacency-sweep pass in {@link buildOpenAIResponsesInput} is what actually enforces the * reasoning/output-item pairing constraint (Known Gotcha #1) — this function only validates the * signature/fingerprint half of eligibility. * * Under `reasoningReplay: 'summary-only'`, the returned item strips `content` * (full reasoning text) and `encrypted_content` — only `summary` is replayed. `'encrypted'` (or any * other non-`'off'` mode) returns the stored item verbatim. */ export declare const renderOpenAIResponsesReasoningItem: (input: { thought: Thought; prefixFingerprint: string; replayCompatibility: ReadonlyArray; reasoningReplay: ReasoningReplayMode; warn?: (msg: string) => void; }) => OpenAIResponsesReasoningItem | undefined; /** Default reasoning-item renderer; alias of {@link renderOpenAIResponsesReasoningItem}. */ export declare const defaultRenderOpenAIResponsesReasoningItem: (input: { thought: Thought; prefixFingerprint: string; replayCompatibility: ReadonlyArray; reasoningReplay: ReasoningReplayMode; warn?: (msg: string) => void; }) => OpenAIResponsesReasoningItem | undefined; /** * Implements {@link OpenAIResponsesHelpers.buildOpenAIResponsesInput}. * * @remarks * Algorithm (mirrors the plan's Part 2 spec exactly): * * 1. Leading `instructions` (or leading `developer`/`system`-role item, per `systemPromptChannel`) * — ALWAYS ADK-rendered via `renderChatCompletionsSystemPrompt`; there is no consumer-facing * escape hatch. * 2. Timeline: messages/thoughts/tool-calls merged and sorted by `createdAt`. A tool call renders * as two SIBLING top-level items (`function_call` then `function_call_output`), with composite * id splitting. A thought replays as a native `reasoning` item only when `reasoningReplay !== * 'off'` and a valid, prefix-matched signature exists; otherwise it renders as plain text. * 3. Reasoning-pairing enforcement pass — walks `input` left to right, dropping any `reasoning` * item not immediately followed by its paired output item, and stripping the `id` from an * output item whose paired reasoning item was just dropped. * 4. Trailing buckets (`bucketOrder` labels after `'timeline'`) render as a trailing * `{type:'message', role:'system', ...}` item. * * ## Load-bearing invariants * * These are consequences of the step ORDER above, not incidental details. Both were unwritten * once, and both were violated in ways that silently disabled reasoning replay — so they are * stated here explicitly and pinned by executable tests (see * `tests/unit/batteries/llm/openai_responses/reasoning_replay.cross.spec.ts`, "assembly * invariants"). * * 1. **The step-3 sweep only ever sees a PREFIX of the final `input`.** Step 4 appends the * trailing bucket AFTER the sweep has run, so any item added there is invisible to every * fingerprint the sweep computes. A reasoning-replay fingerprint must therefore cover only the * sweep-visible region — that is what the returned `fingerprintableLength` marks, and why * `persistThought` passes it as `throughItem`. Hashing the whole `input` instead cannot match * on the next turn whenever a trailing bucket exists, and drops every replayed item as stale. * * 2. **A reasoning item must sort STRICTLY BEFORE the output item it is paired with.** The step-2 * timeline pushes messages before thoughts and then applies a STABLE sort by `createdAt`, so on * an identical timestamp the message wins the tie and lands ahead of its own reasoning item — * leaving that item unpaired, and the step-3 sweep drops it. Callers persisting a thought and * its message from one response must therefore assign strictly increasing timestamps rather * than calling a clock twice (two `DateTime.now()` calls collide ~998 times in 1000). * * 3. **`tools` is `undefined`, never `[]`, when the registry is empty** (see the return shape). * Fingerprints on both the persist and validate side must use that same shape: * `canonicalFingerprint` serialises `undefined` and `[]` to different bytes, so coercing on one * side only makes every hash mismatch for tool-less agents. */ export declare const buildOpenAIResponsesInput: (input: Parameters[0]) => Promise>>; /** Default history assembler; alias of {@link buildOpenAIResponsesInput}. */ export declare const defaultBuildOpenAIResponsesInput: (input: Parameters[0]) => Promise>>; /** * Implements {@link OpenAIResponsesHelpers.createResponsesOutputSlotMachine}. * * @remarks * Streaming state keyed by `output_index` (one slot per output item — NOT a tool-call `index`, * unlike Chat Completions). `openSlot` deliberately opens NO slot for an unrecognized/hosted * server-side tool item type (`web_search_call`, `code_interpreter_call`, `mcp_call`, etc.) — Known * Gotcha #6 — so every subsequent event keyed to that `output_index` is silently ignored rather * than crashing on an unexpected slot kind. */ export declare const createResponsesOutputSlotMachine: () => ResponsesOutputSlotMachine; /** Default output-slot state machine factory; alias of {@link createResponsesOutputSlotMachine}. */ export declare const defaultCreateResponsesOutputSlotMachine: () => ResponsesOutputSlotMachine; /** * Normalises an ADK-authored id to the Responses item-id charset, and hashes+truncates ids over * the 64-character limit (Known Gotcha #5) rather than sending an oversized id verbatim. * * @remarks * Not part of the public {@link OpenAIResponsesHelpers} contract (no override seam is warranted for * a pure charset/length transform) — exported for the adapter and for direct unit testing. */ export declare const normalizeOpenAIResponsesItemId: (id: string, prefix?: string) => string;