Call the relay and report audit results.
Allowed executions receive OpenAI-compatible relay settings plus idempotent report helpers for completion, failure, and streaming callbacks.
Relay calls
Once prepared execution is allowed, the SDK exposes OpenAI-compatible fields
directly on the allowed result. AI SDK routes can use @vela/ai-sdk
to prepare execution and build a streamText-ready input with
prepared.createStreamTextInput() plus a shared
prepared.reporter. The generated callbacks include
onAbort, onError, and onFinish;
other clients can use the
direct OpenAI-compatible fields or raw fetch.
import { prepareVelaStreamTextWithResponse } from "@vela/ai-sdk";
import { streamText } from "ai";
const prepared = await prepareVelaStreamTextWithResponse(vela, {
estimatedCostUsd: 0.05,
}, {
onReportError(error) {
console.error("Failed to record Vela execution result", error);
},
});
if (prepared.outcome !== "allow") {
return prepared.response;
}
try {
const result = streamText(prepared.createStreamTextInput({
messages,
}));
return result;
} catch (error) {
await prepared.reporter.tryReportFailedWithError(error);
throw error;
}
For route tests and setup responses that should avoid loading the AI SDK
runtime on non-allow paths, prepare with core SDK first and import
@vela/ai-sdk plus ai only after allow.
If route setup can fail after allow, use prepared.reporter
in the shortest route path, or share an explicit idempotent reporter
between the setup catch block and AI SDK stream callbacks.
import { createVelaClientFromEnvironment, createVelaEnvironmentDiagnosticsResponse, createVelaIntegration, getVelaEnvironmentDiagnostics } from "@vela/sdk";
const environment = getVelaEnvironmentDiagnostics({ env: process.env });
if (!environment.ok) {
return createVelaEnvironmentDiagnosticsResponse(environment);
}
const client = createVelaClientFromEnvironment({ env: process.env });
const scopedVela = createVelaIntegration({ client, executionScope });
const prepared = await scopedVela.prepareExecutionWithResponse({
estimatedCostUsd: 0.05,
});
if (prepared.outcome !== "allow") {
return prepared.response;
}
const [{ createVelaStreamTextInput }, { streamText }] = await Promise.all([
import("@vela/ai-sdk"),
import("ai"),
]);
const reporter = prepared.createResultReporter({
onReportError(error) {
console.error("Failed to record Vela execution result", error);
},
});
const result = streamText(createVelaStreamTextInput(
prepared,
{
messages,
},
{ reporter },
));
import { createOpenAiCompatibleUsageReportInput } from "@vela/sdk";
const reporter = prepared.createResultReporter();
const response = await fetch(prepared.openAiCompatibleChatCompletionsUrl, {
method: "POST",
headers: prepared.openAiCompatibleRequestHeaders,
body: JSON.stringify({
model: prepared.openAiCompatibleModel,
messages,
}),
});
const payload = await response.json();
await reporter.tryReportCompletedWithUsage(
createOpenAiCompatibleUsageReportInput(payload.usage),
);
Audit reporting
Audit should not make the model response unreliable. For streaming and callback
heavy integrations, create an idempotent reporter and use the non-throwing
tryReport... methods. Adapter onReportError
also observes supplied reporter { ok: false } outcomes and
thrown reporter errors before app callbacks continue. AI SDK stream aborts
are reported as failed executions before the app onAbort
callback continues.
const reporter = prepared.createResultReporter({
onReportError(error) {
console.error("Failed to record Vela execution result", error);
},
});
try {
const result = await callModel({
model: prepared.openAiCompatibleModel,
...prepared.openAiCompatibleClientOptions,
});
await reporter.tryReportCompletedWithUsage(extractUsage(result));
return result;
} catch (error) {
await reporter.tryReportFailedWithError(error);
throw error;
}
Use usage when available
reportCompletedWithUsage() normalizes token counts and uses the
prepare-time estimated cost when no final cost is supplied.
Normalize unknown errors
reportFailedWithError() and getErrorMessage()
keep thrown strings, empty errors, and unknown values predictable.
Keep reports idempotent
ExecutionResultReporter settles on the first report, so callback
paths do not need a separate hasReported guard.