import { z } from 'zod'; import Anthropic from '@anthropic-ai/sdk'; import OpenAI from 'openai'; type Provider = "anthropic" | "xiaomi" | "openai" | "gemini" | "glm" | "moonshot" | "minimax" | "deepseek" | "openrouter" | "sakana" | "xai" | "palsu" /** Hugging Face Inference Providers router (OpenAI-compatible). */ | "huggingface" /** Locally hosted OpenAI-compatible server (Ollama, LM Studio, llama.cpp, vLLM). */ | "local"; type ThinkingLevel = "low" | "medium" | "high" | "xhigh" | "max" | "ultra"; type CacheRetention = "none" | "short" | "long"; interface TextContent { type: "text"; text: string; } interface ThinkingContent { type: "thinking"; text: string; signature?: string; } interface ImageContent { type: "image"; mediaType: string; data: string; } interface VideoContent { type: "video"; mediaType: string; data: string; /** Moonshot/Kimi file id (e.g. "d4f0…") after uploading via the files API. * Moonshot rejects inline base64 video; the provider uploads the clip once * and caches the id here so later turns reference `ms://` instead of * re-sending the bytes. */ fileId?: string; } interface ToolCall { type: "tool_call"; id: string; name: string; args: Record; } type ToolResultContent = string | (TextContent | ImageContent | VideoContent)[]; interface ToolResult { type: "tool_result"; toolCallId: string; content: ToolResultContent; isError?: boolean; /** * Set when the agent loop trimmed `content` to fit a per-result or per-turn * budget. The provider (model input) and the persistent transcript both see * the trimmed `content`, but the live `tool_call_end` event carried the FULL * preview — so this marker makes that divergence explicit and reconcilable. * Internal metadata only: it is never serialized onto the provider wire. */ capped?: { /** Length of the original, untrimmed string content. */ originalChars: number; /** Length of the trimmed content actually sent to the model. */ keptChars: number; /** Which budget triggered the trim. */ scope: "per-result" | "per-turn"; }; } interface ServerToolCall { type: "server_tool_call"; id: string; name: string; input: unknown; } interface ServerToolResult { type: "server_tool_result"; toolUseId: string; resultType: string; data: unknown; } /** Opaque content block preserved for round-tripping (e.g. compaction blocks). */ interface RawContent { type: "raw"; data: Record; } type ContentPart = TextContent | ThinkingContent | ImageContent | VideoContent | ToolCall | ServerToolCall | ServerToolResult | RawContent; type MessageProvenanceSource = "human" | "agent" | "runtime"; type MessageProvenanceKind = "prompt" | "steering" | "notification" | "completion_gate" | "review_follow_up" | "continuation" | "model_switch" | "automation" | "compaction_summary" | "compaction_ack"; type MessageProvenanceVisibility = "transcript" | "hidden" | "summary"; /** Internal message metadata. `stream()` removes it before provider dispatch. */ interface MessageProvenance { source: MessageProvenanceSource; kind: MessageProvenanceKind; visibility: MessageProvenanceVisibility; } interface MessageMetadata { provenance?: MessageProvenance; } interface SystemMessage extends MessageMetadata { role: "system"; content: string; } interface UserMessage extends MessageMetadata { role: "user"; content: string | (TextContent | ImageContent | VideoContent)[]; } interface AssistantMessage extends MessageMetadata { role: "assistant"; content: string | ContentPart[]; } interface ToolResultMessage extends MessageMetadata { role: "tool"; content: ToolResult[]; } type Message = SystemMessage | UserMessage | AssistantMessage | ToolResultMessage; interface Tool { name: string; description: string; parameters: z.ZodType; /** Raw JSON Schema — bypasses zodToJsonSchema when set (used by MCP tools) */ rawInputSchema?: Record; } type ToolChoice = "auto" | "none" | "required" | { name: string; }; interface ServerToolDefinition { type: string; name: string; [key: string]: unknown; } interface TextDeltaEvent { type: "text_delta"; text: string; } interface ThinkingDeltaEvent { type: "thinking_delta"; text: string; } interface ToolCallDeltaEvent { type: "toolcall_delta"; id: string; name: string; argsJson: string; } interface ToolCallDoneEvent { type: "toolcall_done"; id: string; name: string; args: Record; } interface DoneEvent { type: "done"; stopReason: StopReason; } interface ErrorEvent { type: "error"; error: Error; } interface ServerToolCallEvent { type: "server_toolcall"; id: string; name: string; input: unknown; } interface ServerToolResultEvent { type: "server_toolresult"; toolUseId: string; resultType: string; data: unknown; } interface KeepaliveEvent { type: "keepalive"; } type StreamEvent = TextDeltaEvent | ThinkingDeltaEvent | ToolCallDeltaEvent | ToolCallDoneEvent | ServerToolCallEvent | ServerToolResultEvent | DoneEvent | ErrorEvent | KeepaliveEvent; type StopReason = "end_turn" | "tool_use" | "max_tokens" | "pause_turn" | "stop_sequence" | "refusal" | "error"; interface StreamResponse { message: AssistantMessage; stopReason: StopReason; usage: Usage; } interface Usage { inputTokens: number; /** Total billed output tokens, including reasoning tokens when the provider reports them separately. */ outputTokens: number; /** Reasoning/thinking-token subset of outputTokens. */ reasoningTokens?: number; cacheRead?: number; cacheWrite?: number; serverToolUse?: { webSearchRequests?: number; webFetchRequests?: number; }; } interface StreamOptions { provider: Provider; model: string; messages: Message[]; tools?: Tool[]; toolChoice?: ToolChoice; serverTools?: ServerToolDefinition[]; maxTokens?: number; temperature?: number; topP?: number; stop?: string[]; thinking?: ThinkingLevel; apiKey?: string; baseUrl?: string; signal?: AbortSignal; /** Prompt cache retention preference. Providers map this to their supported values. Default: "short". */ cacheRetention?: CacheRetention; /** Stable per-session cache routing key for providers that support it (OpenAI, Moonshot, Gemini Code Assist). */ promptCacheKey?: string; /** OpenAI service tier for latency-sensitive requests. Only sent to first-party OpenAI API calls. */ serviceTier?: "auto" | "default" | "flex" | "priority"; /** OpenAI ChatGPT account ID (from OAuth JWT) for codex endpoint */ accountId?: string; /** Stable conversation identity for Codex transport headers. This is distinct from * promptCacheKey: sessions with matching prefixes may share a cache key, but must * retain independent session/thread identities. */ transportSessionId?: string; /** Google Cloud/Code Assist project ID used by Gemini OAuth transport. */ projectId?: string; /** Enable provider-native web search. Each provider uses its own format: * - Anthropic: server tool `web_search_20250305` * - Moonshot: `builtin_function` `$web_search` * - GLM: web search via MCP servers (not inline — this flag is a no-op) * - OpenAI/Codex: not supported (Chat Completions / Codex APIs lack web search) */ webSearch?: boolean; /** Enable server-side compaction (Anthropic only, beta). Automatically * summarizes earlier context when approaching the context window limit. */ compaction?: boolean; /** Enable server-side clearing of old tool use/result pairs (Anthropic only, beta). * The API automatically removes older tool interactions to free context. */ clearToolUses?: boolean; /** Custom fetch implementation. Useful in non-Node environments (e.g. Expo/React Native) * where the default `globalThis.fetch` doesn't support streaming properly. * Passed directly to the underlying provider SDK. */ fetch?: typeof globalThis.fetch; /** Whether the target model supports image input. When false, image content * in user messages and tool_result messages is downgraded to a text placeholder * before being sent to the provider. Default: true. */ supportsImages?: boolean; /** Whether the target model supports video input. When false, video content * in user messages is downgraded to a text placeholder before being sent to * the provider. Default: false. */ supportsVideo?: boolean; /** Use streaming transport (default: true). When false, providers issue a * single non-streaming request and synthesize events from the full response. * The agent loop flips this to `false` as a fallback after repeated stream * stalls — broken SSE connections (transient CDN / proxy issues) often * recover when the same request is issued over a plain HTTP request/response. */ streaming?: boolean; /** Override the User-Agent sent with OAuth-authenticated Anthropic requests. * Anthropic's OAuth edge rejects requests whose claude-cli version lags too * far behind the real Claude Code release; callers that track the live * version should pass it here. Ignored for non-Anthropic providers and for * Anthropic requests using a regular API key. */ userAgent?: string; /** Extra HTTP headers attached to every model request. Used by providers * whose endpoint gates on client identity (e.g. Kimi For Coding requires a * `User-Agent: kimi-code-cli/...` and `X-Msh-*` device headers). Merged * into the underlying SDK's default headers. */ defaultHeaders?: Record; } /** * Push-based async iterable. Producers push events, consumers * iterate with `for await`. Also supports thenable so you can * `await stream(...)` directly to get the final response. */ declare class EventStream implements AsyncIterable { private queue; private resolve; private done; private error; push(event: T): void; close(): void; abort(error: Error): void; [Symbol.asyncIterator](): AsyncIterator; } /** * Pull-based stream result. Wraps an async generator that yields * StreamEvents and returns a StreamResponse. Also thenable so: * * const msg = await stream({...}) // awaits response * for await (const e of stream({...})) {} // iterates events * * The generator is pumped eagerly — events flow into an internal * buffer regardless of whether a consumer is iterating. This avoids * the push-based EventStream's stall bugs (lost wakeups, single * resolve field, iterator starvation). */ declare class StreamResult implements AsyncIterable { readonly response: Promise; private buffer; private done; private error; private resolveResponse; private rejectResponse; private resolveWait; /** * High-water mark: when the buffer exceeds this many unconsumed events, * the pump pauses until the consumer drains below the low-water mark. * Prevents unbounded memory growth when a consumer is slow. * Only active when someone IS iterating — if nobody iterates (the `then()` * path), backpressure is skipped so the pump can complete and resolve. */ private static readonly HIGH_WATER; private static readonly LOW_WATER; private iterating; private paused; private resolveDrain; constructor(generator: AsyncGenerator, signal?: AbortSignal); private pump; private _nextWithAbort; [Symbol.asyncIterator](): AsyncIterator; then(onfulfilled?: ((value: StreamResponse) => TResult1 | PromiseLike) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null): Promise; } /** * Local model ids are namespaced by endpoint (`local//`) so * the same model name served by two machines stays distinct in the registry. * The server only knows the raw id, so strip the routing prefix here — at the * one place that talks to the wire. Counterpart to gg-core's * `formatLocalModelId`/`parseLocalModelId`. */ declare function localWireModelId(id: string): string; /** * Unified streaming entry point. Returns a StreamResult that is both * an async iterable (for streaming events) and thenable (await for * the final response). * * Providers are resolved via the provider registry. Built-in providers * (anthropic, openai, glm, moonshot) are registered at module load. * Extensions can register custom providers via `providerRegistry.register()`. * * ```ts * // Stream events * for await (const event of stream({ provider: "anthropic", model: "claude-sonnet-5", messages })) { * if (event.type === "text_delta") process.stdout.write(event.text); * } * * // Or just await the final message * const response = await stream({ provider: "openai", model: "gpt-4.1", messages }); * ``` */ declare function stream(options: StreamOptions): StreamResult; /** * A provider stream function. Takes StreamOptions and returns a StreamResult. * Each provider implements this to handle its specific API format. */ type ProviderStreamFn = (options: StreamOptions) => StreamResult; /** * Registry entry for a provider. A provider can have a simple stream function * or a more complex setup with custom routing logic. */ interface ProviderEntry { /** Main stream function for this provider */ stream: ProviderStreamFn; } /** * Map-based provider registry. Built-in providers are registered at module load, * and extensions can register custom providers at runtime. */ declare class ProviderRegistryImpl { private providers; /** * Register a provider. Overwrites any existing provider with the same name. * * ```ts * import { providerRegistry } from "@kenkaiiii/gg-ai"; * * providerRegistry.register("deepseek", { * stream: (options) => streamOpenAI({ ...options, baseUrl: "https://api.deepseek.com/v1" }), * }); * ``` */ register(name: string, entry: ProviderEntry): void; /** Remove a registered provider. */ unregister(name: string): boolean; /** Get a provider entry by name. */ get(name: string): ProviderEntry | undefined; /** Check if a provider is registered. */ has(name: string): boolean; /** List all registered provider names. */ list(): string[]; } /** Global provider registry. Import this to register custom providers. */ declare const providerRegistry: ProviderRegistryImpl; /** * Error model for gg-ai and downstream consumers. * * Every error users see should answer one question: "is this me or them?" * That answer drives whether they retry, switch model, log in, or report a * GG Coder bug. The `FormattedError` shape captures it in plain English: * * ✗ OpenAI returned an error. * An error occurred while processing your request... * → This is an OpenAI issue, not GG Coder. Retry — if it persists, check status.openai.com. * * ✗ GG Coder hit an unexpected error. * Cannot read property 'foo' of undefined * → This is a GG Coder bug — please report it. */ type ErrorSource = "provider" | "ggcoder" | "network" | "auth" | "capability"; interface FormattedError { /** Plain-English headline, e.g. "OpenAI returned an error." */ headline: string; /** Machine-readable classification. */ source: ErrorSource; /** Detailed message body from the underlying error (no JSON, no tag prefix). */ message: string; /** Action line — tells the user whether to retry, switch model, log in, or report a bug. */ guidance: string; /** Provider name when source === "provider". */ provider?: string; /** HTTP status code if known. */ statusCode?: number; /** Provider request ID, kept for telemetry / debug — not shown by default. */ requestId?: string; /** Unix seconds when a usage/rate limit resets, when the provider reports it. */ resetsAt?: number; } declare class GGAIError extends Error { readonly source: ErrorSource; readonly requestId?: string; readonly hint?: string; constructor(message: string, options?: { source?: ErrorSource; requestId?: string; hint?: string; cause?: unknown; }); } declare class ProviderError extends GGAIError { readonly provider: string; readonly statusCode?: number; /** Unix seconds when a usage/rate limit resets, when the provider reports it. */ readonly resetsAt?: number; constructor(provider: string, message: string, options?: { statusCode?: number; requestId?: string; hint?: string; cause?: unknown; resetsAt?: number; }); } /** * Normalise any thrown value into a structured display object. Always returns * a non-empty `headline` and `guidance` so the UI never has to second-guess * what to show the user. */ /** * Is this a subscription/plan usage-window exhaustion error (as opposed to a * transient per-minute throttle)? These don't clear with a quick retry — the * user has to wait for the window to reset — so callers must surface them as a * hard stop, not silently retry for minutes. Detected from the canonical * "usage limit reached" message gg-ai stamps onto the ProviderError. */ declare function isUsageLimitError(err: unknown): boolean; /** * Substrings that mark a hard, non-retriable billing/quota stop on ANY provider * (credit exhaustion, balance too low, plan quota spent). Single source of truth * shared across the OpenAI-compatible and Anthropic provider boundaries and the * agent-loop retry classifier, so the lists can't drift. Matched case-insensitively. */ declare function isHardBillingMessage(message: string): boolean; declare function formatError(err: unknown): FormattedError; /** * Render a FormattedError as a multi-line string for terminal display. * * Format: * * * → */ declare function formatErrorForDisplay(err: unknown): string; /** * Inspect a raw provider error message and tag it with a clearer, actionable * prefix so a worker orchestrator can route on intent instead of regexing JSON. * Preserves the original message verbatim after the prefix — helpful for * debugging. * * Order matters: context-overflow is checked first because some providers wrap * overflow errors in HTTP 429 envelopes; we want the structural meaning, not * the transport status. Billing comes before auth/rate-limit because "402 * Payment Required" must not be mis-routed as a rate-limit retry. */ declare function classifyProviderError(message: string): string; declare const REDACTED = "[REDACTED]"; interface RedactionOptions { /** Exact secret values to remove in addition to high-confidence formats. */ secrets?: Iterable; /** Maximum recursive object depth before a stable truncation marker is emitted. */ maxDepth?: number; /** Maximum total array/object entries cloned before truncation markers are emitted. */ maxEntries?: number; /** Maximum retained string length after sanitization. */ maxStringLength?: number; } /** Collect sufficiently distinctive secrets from security-sensitive environment variables. */ declare function environmentSecrets(env: Record): string[]; /** Redact credentials from arbitrary text without mutating its source. */ declare function redactText(text: string, options?: RedactionOptions): string; /** * Recursively clone and sanitize transport/persistence payloads. * Cycles, excessive depth, and excessive collection sizes become stable markers. */ declare function redactValue(value: T, options?: RedactionOptions): T; /** True when the string contains at least one unpaired surrogate. */ declare function hasLoneSurrogate(text: string): boolean; /** Replace unpaired surrogates with U+FFFD; returns the input when already valid. */ declare function toWellFormedText(text: string): string; /** `text.slice(0, chars)` that never cuts an astral character in half. */ declare function sliceHead(text: string, chars: number): string; /** `text.slice(-chars)` that never cuts an astral character in half. */ declare function sliceTail(text: string, chars: number): string; /** * Strip unpaired surrogates from everything headed for the wire. Returns the * same array (and same message objects) when the history is already valid, so * the clean path stays allocation-free. */ declare function sanitizeMessagesForWire(messages: Message[]): Message[]; /** * Provider-level diagnostic hook. Mirrors the pattern used by gg-agent's * setStreamDiagnostic — the host app wires a callback (typically writing to * a debug log) and providers call `providerDiag(...)` to record interesting * lifecycle events (e.g. raw SSE event types and timings). */ type ProviderDiagnosticFn = (phase: string, data?: Record) => void; /** Register a diagnostic callback for provider-level tracing. */ declare function setProviderDiagnostic(fn: ProviderDiagnosticFn | null): void; /** * Converts a Zod schema to a JSON Schema object suitable for provider tool * parameter definitions. * * Anthropic's `input_schema` validator is strict in two ways: * * 1. The root must be `type: "object"`. Returns 400 with * `tools.N.custom.input_schema.type: Field required` otherwise. * * 2. The root must NOT contain `oneOf`, `anyOf`, or `allOf`. Returns 400 with * `input_schema does not support oneOf, allOf, or anyOf at the top level`. * * Both rules trip whenever a tool's parameters are defined via * `z.discriminatedUnion(...)` or `z.union(...)` — Zod 4's * `z.toJSONSchema` emits `{oneOf: [...]}` at the root with no `type`. * * The fix is to collapse the union into a single flat object schema: * * - properties = union of all branch properties (later branches win on * conflict; that's fine because the model only uses these for hints — * Zod's actual `tool.parameters.parse(args)` is the real validator) * - required = intersection of branch `required` arrays (a field is only * required if EVERY branch requires it) * - if the union has a discriminator field (every branch has the same * property as a `const`), we replace the discriminator's per-branch * `const` with an `enum` listing every literal — the model gets a clear * hint of the valid action values without needing oneOf * * The flattening is lossy for *schema-level* constraints (e.g. "if action=X, * then field Y is required") — Zod still enforces those at parse time. For * the model's purposes this is identical to a single object with optional * fields and a discriminator enum, which is exactly how Anthropic-supported * tools are typically authored anyway. */ type JsonSchema = Record; /** * Resolve a tool's JSON Schema for provider tool definitions: prefer the * tool's pre-built `rawInputSchema`, otherwise convert its Zod `parameters`. */ declare function resolveToolSchema(tool: Tool): JsonSchema; /** * Cap historical images before provider dispatch, removing the oldest first. * The persisted/live conversation is never mutated; only modified messages and * tool results are cloned for the outgoing request. */ declare function clampProviderContextImages(messages: Message[], provider: Provider, supportsImages: boolean | undefined): Message[]; declare function toAnthropicMessages(messages: Message[], cacheControl?: { type: "ephemeral"; ttl?: "1h"; }): { system: Anthropic.TextBlockParam[] | undefined; messages: Anthropic.MessageParam[]; }; declare function toOpenAIMessages(messages: Message[], options?: { provider?: string; thinking?: boolean; supportsImages?: boolean; /** Wire name for reasoning on assistant messages. Defaults to `reasoning_content`. */ reasoningField?: string; }): OpenAI.ChatCompletionMessageParam[]; /** * Fire a minimal `max_tokens: 1` request that populates the Anthropic prompt * cache with the system prompt + tools prefix, so the first real user turn is * a cache read instead of a cold cache write. Best-effort: any error is * swallowed so a failed pre-warm never blocks the session. * * Called by AgentSession when speedProfile is "optimized", before the first * real agent-loop turn. The cache TTL follows the `cacheRetention` option — * pass "long" (1 h) so the pre-warm survives until the user's first message. */ declare function prewarmAnthropicCache(options: { apiKey: string; model: string; system: string; tools?: StreamOptions["tools"]; serverTools?: StreamOptions["serverTools"]; baseUrl?: string; userAgent?: string; cacheRetention?: StreamOptions["cacheRetention"]; signal?: AbortSignal; }): Promise; interface PalsuProviderState { callCount: number; } type PalsuResponseFactory = (messages: Message[], options: StreamOptions, state: PalsuProviderState) => AssistantMessage | Promise; type PalsuResponse = AssistantMessage | PalsuResponseFactory; /** Create an assistant message with a single text block. */ declare function palsuText(text: string): AssistantMessage; /** Create an assistant message with a thinking block and optional text reply. */ declare function palsuThinking(thinking: string, text?: string): AssistantMessage; /** Create an assistant message with a single tool call. */ declare function palsuToolCall(name: string, args: Record, id?: string): AssistantMessage; /** Create an assistant message from content parts with optional stop reason. */ declare function palsuAssistantMessage(content: ContentPart[], options?: { stopReason?: StopReason; }): AssistantMessage & { _stopReason?: StopReason; }; interface PalsuModelConfig { /** Default response for this model when its queue is empty. */ defaultResponse?: PalsuResponse; } interface PalsuModelHandle { /** Replace this model's response queue. */ setResponses(responses: PalsuResponse[]): void; /** Append responses to this model's queue. */ appendResponses(...responses: PalsuResponse[]): void; /** Number of unconsumed responses in this model's queue. */ getPendingResponseCount(): number; } interface PalsuProviderHandle { /** Replace the shared response queue entirely. */ setResponses(responses: PalsuResponse[]): void; /** Append responses to the shared queue. */ appendResponses(...responses: PalsuResponse[]): void; /** Number of unconsumed responses in the shared queue. */ getPendingResponseCount(): number; /** Mutable state — tracks call count. */ state: PalsuProviderState; /** Get a handle for a model-specific response queue. */ getModel(name: string): PalsuModelHandle; /** Remove this provider from the registry. */ unregister(): void; } interface PalsuProviderConfig { /** Provider name to register under. Default: "palsu". */ name?: string; /** Response returned when all queues are empty. Default: empty text message. */ defaultResponse?: PalsuResponse; /** Enable prompt cache simulation. Tracks common message prefixes across calls. */ promptCache?: boolean; /** Model-specific configurations with per-model response queues. */ models?: Record; } /** * Register a palsu (mock) LLM provider for testing. * Returns a handle to control responses and inspect state. * * ```ts * const palsu = registerPalsuProvider(); * palsu.appendResponses(palsuText("Hello!")); * * const result = await stream({ provider: "palsu", model: "test", messages }); * console.log(result.message); // { role: "assistant", content: [{ type: "text", text: "Hello!" }] } * * palsu.unregister(); // cleanup * ``` */ declare function registerPalsuProvider(config?: PalsuProviderConfig): PalsuProviderHandle; export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, GGAIError, type ImageContent, type Message, type MessageProvenance, type MessageProvenanceKind, type MessageProvenanceSource, type MessageProvenanceVisibility, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderDiagnosticFn, type ProviderEntry, ProviderError, type ProviderStreamFn, REDACTED as REDACTION_MARKER, type RawContent, type RedactionOptions, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, type VideoContent, clampProviderContextImages, classifyProviderError, environmentSecrets, formatError, formatErrorForDisplay, hasLoneSurrogate, isHardBillingMessage, isUsageLimitError, localWireModelId, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, prewarmAnthropicCache, providerRegistry, redactText, redactValue, registerPalsuProvider, resolveToolSchema, sanitizeMessagesForWire, setProviderDiagnostic, sliceHead, sliceTail, stream, toAnthropicMessages, toOpenAIMessages, toWellFormedText };