Crux
API ReferenceAdapters

Adapter Interface

Provider adapter abstraction for building AI provider integrations.

import {
  defineProviderRuntime,
  defineSingleTurnProviderBundle,
} from "@use-crux/core/adapter";
import type {
  CruxAdapter,
  AdapterGenerateOptions,
  AdapterStreamOptions,
  AdapterGenerateResult,
  GenerateResult,
  StreamResult,
  FinalStepInfo,
  AdapterResponse,
  CallArgs,
  StreamHandle,
  ToolResultEntry,
  SingleTurnRuntimeContract,
  LoopOwnedRuntimeContract,
  ProviderOwnership,
  ProviderRuntimeConformanceHarness,
  NativeTranscriptCodec,
  SingleTurnProviderBundleSpec,
} from "@use-crux/core/adapter";
import {
  providerRuntimeConformance,
  transcriptCodecConformance,
} from "@use-crux/core/adapter/testing";
import { describeCruxAdapterConformance } from "@use-crux/core/adapter/testing/vitest";

Overview

The adapter package provides the public authoring surface for AI provider adapters. defineSingleTurnProviderBundle() is the preferred helper for raw chat SDKs where Crux owns the provider step loop. defineProviderRuntime() remains the lower-level runtime compiler: ownership: 'single-turn' with turn compiles the same core-owned loop mechanics directly, and ownership: 'loop-owned' with loop covers SDKs that own the loop while Crux steers policy around it. Both branches compile into the same execution engine and share the same policy modules (validation retry, tool instrumentation, approvals) plus the same per-call Safety session, so behavior does not diverge between runtimes. Provider runtimes compile into a private execution facade that wires prompt resolution, tool lifecycle, safety policy, retries, stream safety, metadata stamping, and memory capture around the SDK-specific call. Every adapter performs the same orchestration: resolve the prompt into system/messages/schema/settings, convert to the SDK-specific format, wrap with middleware, timing, and hooks, call the SDK, and normalize metadata for Devtools, production runs, and Evals; only the conversion and the SDK call are provider-specific.

Built-in adapters are built on this abstraction: @use-crux/openai, @use-crux/anthropic, and @use-crux/google export single-turn provider runtimes, while @use-crux/ai exports a loop-owned provider runtime for the Vercel AI SDK. Native provider packages own a NativeTranscriptCodec for provider message conversion, assistant text/tool-call extraction, and provider-specific tool-round appends. Core injects transcript-produced providerMessages into request builders and composes transcript.readAssistant(raw) with response-level metadata. Core owns the canonical Message[], CallArgs, AdapterResponse, ToolResultEntry, and small tool-output helpers those codecs share.

Normalized outcomes

All first-party adapters map completed calls into one closed taxonomy from @use-crux/core/adapter:

SurfaceContract
Finish reasonCruxFinishReason (stop / length / tool-calls / content-filter / refusal / error / aborted / unknown)
ErrorsCruxAdapterError carrying CruxProviderError (kind / code / retryable / optional redacted message)
Tool callsCompleted name + arguments only: no progressive argument-delta streaming on any adapter
Stream completionUsage / model / finish / completed tool calls after settle; failures reject instead of silent missing metadata
Abort / timeoutClassified as aborted / timeout; retryable is classification only (SDK owns network retries)

Per-dimension behavior:

DimensionBehavior
generate()Provider finish reasons map into CruxFinishReason. Usage, model, and response identity are captured when the provider returns them.
stream()Stream completion carries the same finish/usage/model fields as generate after the stream settles. Iteration and completion failures reject with CruxAdapterError instead of silent missing metadata.
UsageHonest omission when the provider omits counts, never invented zeros.
Completed tool callsFully assembled name + arguments (and results on the generate path) appear on the completed step.
ErrorsTimeouts, aborts, rate limits, invalid requests, refusals/safety, and provider failures become CruxProviderError on a thrown CruxAdapterError.
Abort / timeoutCaller AbortSignal and Crux budget timeouts are classified as aborted and timeout.
RetryAdapters classify retryability (retryable: true | false). They do not own a second network retry loop; SDK clients keep their own maxRetries.

Explicit non-goal: progressive tool-call deltas

No adapter exposes progressive tool-call argument streaming. There is no toolCallDelta hook and no public partial-argument fragment surface. Streams may still yield text deltas; tool calls appear only after the call is fully assembled (generate completion or stream completion). This is intentional: partial JSON tool arguments are not a reliable product surface, and Crux keeps one completed-call contract for both generate() and stream().

The normalized-outcome layer is also not a Crux-owned network retry engine (retryable is classification only), and not a second error taxonomy for observability graph records: span/run errors still use the graph's compact error summary plus exception events, and adapter failures that throw are captured there after normalization.

Per-adapter mapping notes

PackageOwnershipStream completionTool callsErrors
@use-crux/openaisingle-turnSingle-pass capture with stream_options.include_usage; completion metadata + completed tool callsCompleted onlyHTTP status + SDK abort/timeout classes
@use-crux/anthropicsingle-turnfinalMessage() rejection surfaces as CruxAdapterError (not silent undefined)Completed tool uses from the final messageSame closed taxonomy
@use-crux/googlesingle-turnSingle-pass capture of usage/finish/model + completed function callsCompleted onlyStatus / client-error classes + safety block folding
@use-crux/ailoop-owned (AI SDK)Stream iteration/completion failures normalize at the runtime boundarySDK-assembled completed calls onlyShared taxonomy on the runtime path; public CruxAIError remains an optional user-boundary helper

All four packages exercise the same core conformance helper so generate success, stream completion with a finished tool call, refusal/content-filter (where the provider can express it), timeout, abort, and erroring iteration stay aligned.

Guide: Adapter outcomes. Privacy of code / message: Privacy.

Multimodal Content Support

All first-party generation adapters accept canonical Message.content values as either strings or readonly ContentPart[]. Each adapter uses one provider-local content part table for managed calls, public codecs, headless handles, transport mode, and rich tool-result encoding. Model calls validate and normalize media sources before provider I/O; malformed media throws InvalidMediaSourceError, and valid media that the selected adapter or model cannot send throws UnsupportedCapabilityError.

Capability@use-crux/ai@use-crux/anthropic@use-crux/openai Chat@use-crux/google
Image input, data or URLyesyesyes, byte data as data URLsyes
PDF inputyesyes, document blocksfile data yes; URL degradesyes
Audio inputmodel-dependentdegradeswav/mp3 file datayes
Assistant media decodeyesyesyesyes
System-role mediadegradesdegradesdegradesdegrades
Tool-result mediaSDK parts where supportedimage/PDF native blocksdeterministic text referencesfunction-response parts

GenerateResult.text and StreamResult.textStream remain string-shaped. Assistant-returned media is available in result.messages.

Image generation is a separate flat operation, projected from the same internal five-adapter conformance fixture used by adapter tests:

AdaptergenerateImage binding
ai-sdknative
anthropicabsent
convexexact AI SDK re-export
googlenative
openainative

Generated images are immediately usable data assets. Persistence stays explicit: call assetStore.put(result.image) later when durable storage is needed.

Which contract do I implement?

Your SDK…OwnershipMechanicsExample
Exposes text/structured/stream chat calls and leaves tool execution to Cruxsingle-turnbundle@use-crux/openai, @use-crux/anthropic, @use-crux/google
Runs its own multi-step tool loop (generateText with stopWhen, native tool execution)loop-ownedloop@use-crux/ai
Needs a bespoke non-chat boundaryn/acore IRcustom provider

Never implement more than one generation runtime for one SDK. In either branch, core owns the policy layer: prompt resolution, router()/split()/retry()/fallback()/cascade() routing, validation retry, constraints, guardrails, the tool-approval protocol, instrumentation, timeouts, and observability. A provider runtime implements mechanics only, and should delegate to SDK-native capabilities wherever one exists.

Routing wrapper support

Routing wrappers are authored in @use-crux/core/routing and resolved by the managed adapter execution loop. Codecs such as toParams() and headless provider SDK calls are translation-only and require a raw model.

Adapter familygenerate()stream()Notes
@use-crux/airouter(), split(), retry(), fallback(), cascade()router(), split(), retry(), fallback()cascade() needs complete tier results and is generate-only.
Native single-turn adapters (@use-crux/openai, @use-crux/anthropic, @use-crux/google)Raw model id onlyRaw model id onlyNative package model options are string and type-reject routing wrappers. Use @use-crux/ai or a core loop-owned adapter for routed calls.
Headless prepare() / transportSame as the managed method that owns the loopSame as the managed method that owns the looptransport receives raw provider params after routing resolution.
toParams() / fromResponse()Raw model onlyRaw model onlyThese helpers do not run prompt policy, routing, tools, or observability.

Under the hood, turn compiles to AdapterSpec: core calls call() once per loop turn and appendToolRound() formats results. loop.bind() compiles to a LoopRuntimePort: the SDK owns the loop and core steers each step through a StepObserver. bind(client) returns the client-dependent operations (BoundLoopRuntime), which core assembles with id, describeModel, and mapSettings(settings, model) into a gateway-closed LoopRuntimePort:

  • runTextLoop() runs the SDK loop, awaiting observer.onStepEnd(step) after every step and applying the returned StepDirective before the next one: continue, stop, or amend (swap system prompt/active tools mid-loop, optionally refundStep so bookkeeping steps like LoadSkill don't consume budget).
  • runStructuredAttempt() makes exactly ONE structured-output attempt and returns invalid as a value instead of throwing: core owns the corrective-retry loop.
  • stream() returns the SDK's stream result untouched plus a typed completion(). When request.safety is set (a SafetyStream, text streams with streaming guardrails or constraints), the spec must drive it: feed every outgoing text delta, forward emit content, swallow holds, surface a thrown GuardrailBlockedError as a stream error, and call finish() at end-of-stream, emitting the seal's pending tail. With the AI SDK this is one experimental_transform.
  • Tool-approval needs surface as the suspended outcome; core mints approval ids/tokens and owns resume.

Test provider runtimes with providerRuntimeConformance(). The suite binds the public runtime through runtime.create(...), so it covers both single-turn and loop-owned adapters without pinning provider internals. Your harness should own only the remote SDK seam: translate abstract model emissions into SDK-shaped fakes, then expose captured request bodies for provider-specific assertions.

import { providerRuntimeConformance } from "@use-crux/core/adapter/testing";

const violations = await providerRuntimeConformance(
  myProviderRuntime,
  myHarness(),
);
expect(violations).toEqual([]);

For Vitest packages, use the describe helper:

import { describeCruxAdapterConformance } from "@use-crux/core/adapter/testing/vitest";

describeCruxAdapterConformance({
  name: "my-provider",
  runtime: myProviderRuntime,
  harness: myHarness(),
  capabilities: {
    ownership: "single-turn",
    structuredOutput: true,
    streaming: true,
  },
});

Use adapterSpecConformance() and loopRuntimePortConformance() only when you are testing lower-level compiler output or core execution IR directly. fakeLoopRuntime() remains the in-memory reference implementation for policy tests that need a loop-owned runtime without loading an SDK.

defineSingleTurnProviderBundle(spec)

Compile a raw chat SDK provider bundle into a Crux adapter runtime, public create() factory, and lightweight helper factory.

import { defineSingleTurnProviderBundle } from "@use-crux/core/adapter";

const myProvider = defineSingleTurnProviderBundle({
  id: "my-provider",
  bind: (client) => ({
    call: (request) => client.chat.create(request),
    stream: (request) => client.chat.stream(request),
  }),
  profile: {
    request: buildRequest,
    transcript: myTranscript,
    response: {
      meta: responseMeta,
      text: structuredTextOverride,
    },
    stream: {
      request: (request) => ({ ...request, stream: true }),
      textDelta: (chunk) => chunk.delta?.text,
    },
    settings: mapSettings,
    outputSchema: wrapOutputSchema,
  },
});

export const myProviderRuntime = myProvider.runtime;
export const createMyProvider = myProvider.create;
export const myProviderHelpers = myProvider.helpers();

The bundle owns provider wire facts: request bodies, SDK method binding, transcript conversion, assistant-turn extraction, response metadata extraction, stream delta extraction, settings/schema mapping, and provider-specific dependencies. Single-turn bundles should use transcript: NativeTranscriptCodec plus response.meta(raw); response.text(raw, assistant) is only needed when the SDK exposes parsed structured output separately from assistant message text. Crux owns prompt resolution, tool execution, default canonical tool-round appends, validation retry, safety, memory capture, and observability.

request(args, ctx) receives the usual canonical CallArgs plus args.providerMessages, already produced by transcript.fromMessages(args.messages). Request builders should use providerMessages rather than re-running public fromMessages() wrappers.

Use deps.create and deps.helpers when your public factories should accept provider-friendly arguments instead of the raw dependency object that core threads into request builders:

const google = defineSingleTurnProviderBundle({
  id: "google",
  bind: bindGoogle,
  profile: googleProfile,
  deps: {
    create: (client: GoogleGenAI, opts?: CreateGoogleOptions) => ({
      cacheResolver: createGoogleCachedContentResolver(client, opts),
    }),
    helpers: () => ({}),
  },
});

defineProviderRuntime({ ownership: 'single-turn', turn }) is still available for lower-level compiler tests or unusual adapter packages that need to assemble the runtime object directly. Normal single-turn providers should use the bundle helper so runtime, create(), helper factories, dependency mapping, and extension checks are compiled consistently.

Rather than hand-writing a NativeTranscriptCodec, compile one from a ProviderTranscriptDialect with defineProviderTranscriptCodec(). Core owns the canonical transcript semantics (extracting neutral ProviderTranscriptUnits from Message[], grouping adjacent tool results, rendering fallback text/error flags through shared ToolResultEncodingHelpers, and appending each tool round once via appendCanonicalToolRound()), while the dialect only encodes/decodes its SDK wire format and reads the assistant turn. A dialect never interprets raw Message.metadata, so fromMessages() and runtime tool-round appends cannot drift. See Building adapters for a full dialect example; reach for a bespoke appendToolRound only when the canonical units cannot represent your wire format.

Test transcript codecs directly with transcriptCodecConformance():

import { transcriptCodecConformance } from "@use-crux/core/adapter/testing";

const violations = transcriptCodecConformance({
  name: "my-provider transcript",
  transcript: myTranscript,
  canonicalMessages,
  providerMessages,
  decodedMessages,
  rawAssistant,
  assistant: {
    text: "Checking.",
    toolCalls: [{ id: "call_1", name: "lookup", args: {} }],
  },
});

expect(violations).toEqual([]);

Provider packages may expose GenerateTextFn and GenerateObjectFn helpers when useful, but those helpers should stay explicitly smaller than adapter generate(): no prompt resolution, tool loop, safety, memory capture, or instrumentation.

Internal IR: adapter(spec)

Create a core-driven adapter from a compiled AdapterSpec. Provider packages should implement defineSingleTurnProviderBundle(); use this only when testing core adapter internals or building a bespoke runtime compiler.

ParameterTypeDescription
specAdapterSpec<TClient, TRawResponse, TRawStream, TExtra>Provider-specific adapter specification

Returns: (client: TClient) => CruxAdapter<TClient, TRawResponse, TRawStream, TExtra>

import { adapter } from "@use-crux/core/adapter";

const createMyAdapter = adapter({
  providerId: "my-provider",

  async call(client, args) {
    const response = await client.chat({
      model: args.model,
      system: args.system,
      messages: args.messages,
      ...args.settings,
    });
    return {
      raw: response,
      extracted: {
        text: response.content,
        toolCalls: undefined,
        usage: {
          inputTokens: response.usage.input,
          outputTokens: response.usage.output,
          totalTokens: response.usage.input + response.usage.output,
          inputTokenDetails: {},
          outputTokenDetails: {},
        },
        finishReason: response.stop_reason,
        responseId: response.id,
        actualModelId: response.model,
      },
    };
  },

  async stream(client, args) {
    const stream = await client.chatStream({
      model: args.model,
      messages: args.messages,
    });
    return {
      raw: stream,
      rawStream: stream,
      extractTextDelta: (chunk) => chunk.delta?.text,
      completion: async () => undefined,
    };
  },

  appendToolRound(messages, response, results) {
    return [
      ...messages,
      { role: "assistant", content: response.text },
      ...results.map((r) => ({ role: "tool" as const, content: r.content })),
    ];
  },

  mapSettings(settings) {
    return {
      temperature: settings.temperature,
      max_tokens: settings.maxTokens,
      top_p: settings.topP,
    };
  },
});

// Create an adapter instance
const adapter = createMyAdapter(myClient);
const result = await adapter.generate(myPrompt, {
  model: "my-model",
  input: { topic: "AI" },
});

AdapterSpec<TClient, TRawResponse, TRawStream, TExtra>

The core-step execution IR produced by the turn provider-runtime branch.

Type ParameterDescription
TClientThe provider's SDK client type
TRawResponseThe provider's raw API response type
TRawStreamThe provider's raw stream type
TExtraProvider-specific options (e.g., tool_choice for Anthropic)
MethodSignatureDescription
providerIdstringProvider identifier for adaptation matching (e.g., 'anthropic', 'openai')
call(client, args: CallArgs<TExtra>) => Promise<{ raw, extracted }>Execute a non-streaming API call
stream(client, args: CallArgs<TExtra>) => Promise<StreamHandle<TRawStream>>Execute a streaming API call
appendToolRound(messages, response, results) => Message[]Format tool results for the next tool loop turn
mapSettings(settings: GenerationSettings) => Record<string, unknown>Map canonical settings to provider-native field names
sanitizeToolSchema?(schema) => Record<string, unknown>Optional. Post-process JSON Schema output for this provider.
wrapOutputSchema?(schema: ZodType) => Record<string, unknown>Optional. Convert structured output schema to provider-native params.

CruxAdapter<TClient, TRawResponse, TRawStream, TExtra>

The adapter interface returned by the factory.

Property / MethodTypeDescription
providerIdstringProvider identifier from the spec
generate(prompt, opts)Promise<AdapterGenerateResult<TRawResponse>>Execute a prompt (non-streaming) with automatic tool loop
stream(prompt, opts)Promise<StreamResult<TRawStream>>Execute a prompt and return canonical text deltas plus raw stream access
prepare(prompt, opts)Promise<CallHandle<...>>Headless handle: prepare params, then feed provider or SDK responses
parallelfunctionRun multiple agents concurrently
pipelinefunctionChain agents sequentially
consensusfunctionRun agents and pick a winner via voting
swarmfunctionRun a swarm of agents with peer-to-peer routing

CallArgs<TExtra>

Canonical args assembled by the adapter from prompt resolution. Passed to spec.call() and spec.stream().

FieldTypeDescription
modelstringModel identifier
systemstring | undefinedSystem message. Absent in messages mode because the final system text is folded into messages.
systemBlocksreadonly SystemBlock[] | undefinedSystem message blocks with provider-level caching hints. Joining block.text with \n\n produces the resolved system text, including provider adaptation blocks.
messagesMessage[]Conversation messages
settingsRecord<string, unknown>Mapped generation settings
schemaZodType | undefinedOutput schema (for structured output)
schemaParamsRecord<string, unknown> | undefinedProvider-native schema params
toolsArray<{ name, description, parameters, execute }> | undefinedTool definitions
extraTExtraProvider-specific options

AdapterResponse

Canonical response extracted by the adapter from the provider's raw response. Used by the tool loop to determine if another iteration is needed.

FieldTypeDescription
textstringExtracted text
toolCallsArray<{ id, name, args }> | undefinedTool call requests
usageTokenUsage | undefinedToken usage with inputTokenDetails and outputTokenDetails. Omit usage when the provider omits token counts; do not fabricate zeroes.
finishReasonstring | undefinedWhy generation stopped
responseIdstring | undefinedProvider response ID
actualModelIdstring | undefinedActual model used (may differ from requested)

StreamHandle<TRawStream>

Internal stream handle returned by AdapterSpec.stream(). Public CruxAdapter.stream() wraps this as StreamResult<TRawStream>.

FieldTypeDescription
rawTRawStream?Original provider stream handle when distinct from the wrapped iterable
rawStreamTRawStream & AsyncIterable<unknown>Provider stream iterable, possibly wrapped for safety/observability
extractTextDelta(chunk: unknown) => string | undefinedExtract text delta from a stream chunk
completion() => Promise<StreamCompletionMetadata | undefined>Get provider/policy completion facts after the stream ends; this extends GenerationMeta with buffered output facts, while Core adds operation identity at the public boundary

AdapterGenerateOptions<TExtra>

Options for adapter generate() calls.

FieldTypeDescription
modelstringModel identifier
inputRecord<string, unknown>?Input for the prompt
providerstring?Provider identifier for adaptation matching
tokenBudgetnumber?Token budget for system message
maxStepsnumber?Maximum tool loop iterations. Default: 10.
settingsGenerationSettings?Additional generation settings (highest precedence)
extraTExtra?Provider-specific options
messagesMessage[]?Additional messages to prepend (e.g., conversation history)
validationRetryValidationRetryOptions?Retry structured output on Zod validation failure

GenerateResult<TRawResponse>

AdapterGenerateResult<TRawResponse> is the native adapter alias for this canonical envelope.

FieldTypeDescription
textstringAssistant-visible text accumulated across provider-call steps
objectTOutput?Parsed structured output when the prompt declares an output schema
usageTokenUsage?Accumulated usage, present only when every provider-call step reported usage
costGenerationMeta['cost']?Provider-reported cost when available
stepsnumberProvider-call steps represented in this envelope
finalStepFinalStepInfoFinal provider-call text, optional usage, finish reason, response id, model id
messagesMessage[]Provider-neutral Crux transcript
pendingApprovalsreadonly ApprovalRequestInfo[]?Pending approval requests when execution suspended
rawTRawResponseThe raw SDK response
_metaGenerateResultMetaProvider/policy facts plus the exact generation.call trace and span; responseId remains provider identity

usage is omitted if any provider-call step omitted usage; Crux never reports a partial token sum as a total. finalStep.usage follows the provider's final step exactly and is also omitted when the final step was unmetered.

StreamResult<TRawStream>

FieldTypeDescription
textStreamAsyncIterable<string>Provider-neutral plain-string text deltas
rawTRawStreamRaw provider stream handle
completionPromise<StreamCompletion<TOutput>>Completion facts with the same operation pair as the immediate handle
_metaOperationResultMetaExact generation.stream identity, available immediately and stable through completion

Adapter payloads and provider stream handles do not manufacture Crux IDs. Core finalizes managed results after provider normalization. See Operation Result Correlation for payload/result types, middleware timing, and streaming failure behavior.

ToolResultEntry

Tool result to feed back into the next tool loop turn.

FieldTypeDescription
toolCallIdstringThe tool call ID this result corresponds to
namestringTool name
contentstringSerialized result content
isErrorboolean?Whether this result represents an error

Internal IR: loopRuntimeAdapter(port)

Create an SDK-loop executor from a LoopRuntimePort. Provider packages should implement defineProviderRuntime({ ownership: 'loop-owned', loop }); use this only when testing runtime internals or building a bespoke runtime compiler. The port is already bound to its SDK client, so there is no separate client argument.

ParameterTypeDescription
portLoopRuntimePort<TModel, TRawResponse, TRawStream>Loop runtime port

generate() accepts ExecutorGenerateOptions: model (plain or routing wrapper), input, tools, toolMiddleware, toolApproval, messages, maxSteps, settings, tokenBudget, timeout, validationRetry, constraints, guardrails, observer, activeTools, and a port-specific extra passthrough. timeout is structured: totalMs covers the whole managed call, stepMs covers provider attempts, chunkMs covers stream inactivity, and toolMs / tools[name] cover tool execution. The result carries raw (the SDK's own result object), text, object, _meta, steps, messages, and pendingApprovals when suspended on tool approval.

LoopRuntimePort

The SDK-loop execution IR produced by the loop provider-runtime branch. The SDK client/gateway is closed over when the port is created, so each run method takes only the request.

MethodDescription
idRuntime identifier for observability (e.g. 'ai-sdk')
describeModel(model)Extract ModelInfo from an SDK model reference
mapSettings(settings, model)Map canonical settings to SDK option names
runTextLoop(request)Multi-step text + tools; SDK owns the loop, core steers via request.observer
runStructuredAttempt(request)ONE structured attempt; returns invalid as a value, never throws on schema failure
runStream(request)Streaming; returns the SDK stream result untouched + typed completion(). Must drive request.safety when present (streaming guardrails)
replayStream?(cached)Optional semantic-cache stream replay

Types

import type {
  // Factory and result types (core-driven loop)
  CruxAdapter,
  AdapterGenerateOptions,
  AdapterStreamOptions,
  AdapterGenerateResult,
  GenerateResult,
  StreamResult,
  StreamCompletion,
  FinalStepInfo,
  // Spec interfaces
  AdapterSpec,
  LoopRuntimePort,
  // Executor contract types (SDK-driven loop)
  ExecutorRequest,
  StructuredRequest,
  ExecutorStep,
  StepDirective,
  StepObserver,
  ExecutorOutcome,
  StructuredAttempt,
  ExecutorStreamHandle,
  ExecutorStreamMeta,
  CruxExecutor,
  ExecutorGenerateOptions,
  ExecutorGenerateResult,
  PendingToolApproval,
  ApprovalRequestInfo,
  ProviderOwnership,
  // Canonical types
  AdapterResponse,
  CallArgs,
  StreamHandle,
  ToolResultEntry,
  StatusDelta,
} from "@use-crux/core/adapter";

On this page