Calling AI APIs
Call an AI service from an application without exposing provider credentials, and read a fictional offline event stream.
Calling an AI service from an application is separate from asking an assistant to write Defjs. AI-assisted development is that assistant workflow. API and SSE event formats are specific to each provider. The stream on this page is a fictional backend, not a provider protocol.
Call an AI API from an application
Define the call with the HTTP or SSE contract you already use, then call the operation on a client the application owns. Start with HTTP requests and SSE event streams. Send JSON is the usual JSON body. Consume SSE events shows how to read and close a stream, but stops after the first event. That recipe is not a complete AI response. For a complete AI response, keep reading until the provider's completion signal or EOF, handle stream errors, and close the stream; adapt event names and payload schemas to the provider. Cancellation and timeouts is how the caller stops an HTTP call.
Keep the provider secret on a server that is allowed to spend it. Do not put that secret in a browser bundle, a public build variable, or a client shipped to the browser. Credentials belong to the application: set them on the server client, or attach them in an interceptor that runs only inside that server boundary. Client configuration and Interceptors are where that configuration goes. withCredentials only selects Fetch cookie mode. It does not hold a provider secret.
After the operation returns, the application owns the stream. For SSE, read the events, close the handle, and wait until it is closed. For HTTP, abort the call when the answer is no longer needed. An abort does not prove the service undid work it had already accepted.
Do not retry a streaming POST only because the attempt failed. Defjs does not retry HTTP on its own. SSE reconnect stays off until the application opts in, and a method such as POST is not sent again unless that policy explicitly allows it. A failed attempt can already have started work the service bills for. Sending the same POST again can start that work a second time. Read the replay rules in HTTP requests and the reconnect rules in SSE event streams before adding a retry.
Fictional offline stream
The wire format below is fictional. It is not the event format of a specific provider. withSSEHandle supplies the response, so the example needs no network and no real credentials. The body is two delta events and then a completion event. The loop keeps both pieces of text. It does not stop after the first event.
import {
createClient,
defineEventStream,
struct,
withEndpoint,
withSSEHandle,
} from "@defjs/core";
const completion = defineEventStream({
method: "POST",
path: "/complete",
maxBufferSize: 64 * 1024,
maxQueueSize: 8,
input: struct.object({ prompt: struct.string() }),
builder: (input) => ({ body: input }),
events: {
delta: struct.object({ text: struct.string() }),
done: struct.object({ reason: struct.string() }),
},
});
const wire = [
"event: delta",
'data: {"text":"Hello"}',
"",
"event: delta",
'data: {"text":" there"}',
"",
"event: done",
'data: {"reason":"stop"}',
"",
"",
].join("\n");
const client = createClient(
withEndpoint("https://fiction.example"),
withSSEHandle(
async () =>
new Response(wire, {
headers: { "content-type": "text/event-stream" },
status: 200,
}),
),
);
async function main() {
const [error, stream] = await completion(client, { prompt: "hello" });
if (error) {
console.error(error.code);
return;
}
let answer = "";
let complete = false;
try {
for await (const event of stream) {
if (event.event === "delta") answer += event.data.text;
if (event.event === "done") {
complete = true;
break;
}
}
} catch (cause) {
console.error(
"Stream failed",
cause instanceof Error ? cause.name : "unknown",
);
return;
} finally {
stream.close("consumer-finished");
await stream.closed;
}
if (!complete) {
console.error("Stream ended before the completion event");
return;
}
console.log(answer);
}
void main();In a project with @defjs/core installed, save the example as fictional-completion.ts, then run:
bun fictional-completion.tsExpected output:
Hello there