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:
| Surface | Contract |
|---|---|
| Finish reason | CruxFinishReason (stop / length / tool-calls / content-filter / refusal / error / aborted / unknown) |
| Errors | CruxAdapterError carrying CruxProviderError (kind / code / retryable / optional redacted message) |
| Tool calls | Completed name + arguments only: no progressive argument-delta streaming on any adapter |
| Stream completion | Usage / model / finish / completed tool calls after settle; failures reject instead of silent missing metadata |
| Abort / timeout | Classified as aborted / timeout; retryable is classification only (SDK owns network retries) |
Per-dimension behavior:
| Dimension | Behavior |
|---|---|
| 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. |
| Usage | Honest omission when the provider omits counts, never invented zeros. |
| Completed tool calls | Fully assembled name + arguments (and results on the generate path) appear on the completed step. |
| Errors | Timeouts, aborts, rate limits, invalid requests, refusals/safety, and provider failures become CruxProviderError on a thrown CruxAdapterError. |
| Abort / timeout | Caller AbortSignal and Crux budget timeouts are classified as aborted and timeout. |
| Retry | Adapters 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
| Package | Ownership | Stream completion | Tool calls | Errors |
|---|---|---|---|---|
@use-crux/openai | single-turn | Single-pass capture with stream_options.include_usage; completion metadata + completed tool calls | Completed only | HTTP status + SDK abort/timeout classes |
@use-crux/anthropic | single-turn | finalMessage() rejection surfaces as CruxAdapterError (not silent undefined) | Completed tool uses from the final message | Same closed taxonomy |
@use-crux/google | single-turn | Single-pass capture of usage/finish/model + completed function calls | Completed only | Status / client-error classes + safety block folding |
@use-crux/ai | loop-owned (AI SDK) | Stream iteration/completion failures normalize at the runtime boundary | SDK-assembled completed calls only | Shared 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 URL | yes | yes | yes, byte data as data URLs | yes |
| PDF input | yes | yes, document blocks | file data yes; URL degrades | yes |
| Audio input | model-dependent | degrades | wav/mp3 file data | yes |
| Assistant media decode | yes | yes | yes | yes |
| System-role media | degrades | degrades | degrades | degrades |
| Tool-result media | SDK parts where supported | image/PDF native blocks | deterministic text references | function-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:
| Adapter | generateImage binding |
|---|---|
| ai-sdk | native |
| anthropic | absent |
| convex | exact AI SDK re-export |
| native | |
| openai | native |
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… | Ownership | Mechanics | Example |
|---|---|---|---|
| Exposes text/structured/stream chat calls and leaves tool execution to Crux | single-turn | bundle | @use-crux/openai, @use-crux/anthropic, @use-crux/google |
Runs its own multi-step tool loop (generateText with stopWhen, native tool execution) | loop-owned | loop | @use-crux/ai |
| Needs a bespoke non-chat boundary | n/a | core IR | custom 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 family | generate() | stream() | Notes |
|---|---|---|---|
@use-crux/ai | router(), 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 only | Raw model id only | Native 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() / transport | Same as the managed method that owns the loop | Same as the managed method that owns the loop | transport receives raw provider params after routing resolution. |
toParams() / fromResponse() | Raw model only | Raw model only | These 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, awaitingobserver.onStepEnd(step)after every step and applying the returnedStepDirectivebefore the next one:continue,stop, oramend(swap system prompt/active tools mid-loop, optionallyrefundStepso bookkeeping steps likeLoadSkilldon't consume budget).runStructuredAttempt()makes exactly ONE structured-output attempt and returnsinvalidas a value instead of throwing: core owns the corrective-retry loop.stream()returns the SDK's stream result untouched plus a typedcompletion(). Whenrequest.safetyis set (aSafetyStream, text streams with streaming guardrails or constraints), the spec must drive it: feed every outgoing text delta, forwardemitcontent, swallowholds, surface a thrownGuardrailBlockedErroras a stream error, and callfinish()at end-of-stream, emitting the seal'spendingtail. With the AI SDK this is oneexperimental_transform.- Tool-approval needs surface as the
suspendedoutcome; 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.
| Parameter | Type | Description |
|---|---|---|
spec | AdapterSpec<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 Parameter | Description |
|---|---|
TClient | The provider's SDK client type |
TRawResponse | The provider's raw API response type |
TRawStream | The provider's raw stream type |
TExtra | Provider-specific options (e.g., tool_choice for Anthropic) |
| Method | Signature | Description |
|---|---|---|
providerId | string | Provider 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 / Method | Type | Description |
|---|---|---|
providerId | string | Provider 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 |
parallel | function | Run multiple agents concurrently |
pipeline | function | Chain agents sequentially |
consensus | function | Run agents and pick a winner via voting |
swarm | function | Run 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().
| Field | Type | Description |
|---|---|---|
model | string | Model identifier |
system | string | undefined | System message. Absent in messages mode because the final system text is folded into messages. |
systemBlocks | readonly SystemBlock[] | undefined | System message blocks with provider-level caching hints. Joining block.text with \n\n produces the resolved system text, including provider adaptation blocks. |
messages | Message[] | Conversation messages |
settings | Record<string, unknown> | Mapped generation settings |
schema | ZodType | undefined | Output schema (for structured output) |
schemaParams | Record<string, unknown> | undefined | Provider-native schema params |
tools | Array<{ name, description, parameters, execute }> | undefined | Tool definitions |
extra | TExtra | Provider-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.
| Field | Type | Description |
|---|---|---|
text | string | Extracted text |
toolCalls | Array<{ id, name, args }> | undefined | Tool call requests |
usage | TokenUsage | undefined | Token usage with inputTokenDetails and outputTokenDetails. Omit usage when the provider omits token counts; do not fabricate zeroes. |
finishReason | string | undefined | Why generation stopped |
responseId | string | undefined | Provider response ID |
actualModelId | string | undefined | Actual model used (may differ from requested) |
StreamHandle<TRawStream>
Internal stream handle returned by AdapterSpec.stream(). Public
CruxAdapter.stream() wraps this as StreamResult<TRawStream>.
| Field | Type | Description |
|---|---|---|
raw | TRawStream? | Original provider stream handle when distinct from the wrapped iterable |
rawStream | TRawStream & AsyncIterable<unknown> | Provider stream iterable, possibly wrapped for safety/observability |
extractTextDelta | (chunk: unknown) => string | undefined | Extract 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.
| Field | Type | Description |
|---|---|---|
model | string | Model identifier |
input | Record<string, unknown>? | Input for the prompt |
provider | string? | Provider identifier for adaptation matching |
tokenBudget | number? | Token budget for system message |
maxSteps | number? | Maximum tool loop iterations. Default: 10. |
settings | GenerationSettings? | Additional generation settings (highest precedence) |
extra | TExtra? | Provider-specific options |
messages | Message[]? | Additional messages to prepend (e.g., conversation history) |
validationRetry | ValidationRetryOptions? | Retry structured output on Zod validation failure |
GenerateResult<TRawResponse>
AdapterGenerateResult<TRawResponse> is the native adapter alias for this
canonical envelope.
| Field | Type | Description |
|---|---|---|
text | string | Assistant-visible text accumulated across provider-call steps |
object | TOutput? | Parsed structured output when the prompt declares an output schema |
usage | TokenUsage? | Accumulated usage, present only when every provider-call step reported usage |
cost | GenerationMeta['cost']? | Provider-reported cost when available |
steps | number | Provider-call steps represented in this envelope |
finalStep | FinalStepInfo | Final provider-call text, optional usage, finish reason, response id, model id |
messages | Message[] | Provider-neutral Crux transcript |
pendingApprovals | readonly ApprovalRequestInfo[]? | Pending approval requests when execution suspended |
raw | TRawResponse | The raw SDK response |
_meta | GenerateResultMeta | Provider/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>
| Field | Type | Description |
|---|---|---|
textStream | AsyncIterable<string> | Provider-neutral plain-string text deltas |
raw | TRawStream | Raw provider stream handle |
completion | Promise<StreamCompletion<TOutput>> | Completion facts with the same operation pair as the immediate handle |
_meta | OperationResultMeta | Exact 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.
| Field | Type | Description |
|---|---|---|
toolCallId | string | The tool call ID this result corresponds to |
name | string | Tool name |
content | string | Serialized result content |
isError | boolean? | 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.
| Parameter | Type | Description |
|---|---|---|
port | LoopRuntimePort<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.
| Method | Description |
|---|---|
id | Runtime 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";Related
- Guide: Building custom adapters
- Reference: Prompts