Pure BASIL REST and incremental Server-Sent Events (SSE), using BASIL’s existing HTTP/TLS, JSON, files, tasks, and SHA-256 library. No Python process, curl process, or new C bridge runs in an application.
Install as a source dependency
In your application’s basic.toml:
[dependencies]
OpenAI = {path = "../basic/libraries/openai"}
Then IMPORT OpenAI and run basic build in the application directory. The path
can be relative to the application’s manifest. Neither Python nor code generation
is needed to build or run an application.
Packaging limitation: use the source dependency, rather than building this
library into a .blib. The current compiler’s precompiled-library ABI rejects the
public Stream type because it owns a TCPConnection. Source imports work and
preserve ownership/cleanup. The compiler and runtime have not been changed.
Chat completions
IMPORT OpenAI
client = OpenAI.NewClient("YOUR_API_KEY", "https://api.openai.com/v1")
request = OpenAI.ChatRequest("YOUR_MODEL", [OpenAI.User("Hello!")])
request = OpenAI.WithField(request, "max_completion_tokens", JsonNumber(256))
reply = OpenAI.ChatCreate(client, request)
PRINT OpenAI.OutputText(reply)Library functions expose FAILS OpenAI.APIError. Application functions should
catch or declare it. APIError has Kind, Status, Code, Message,
Parameter, RequestID, Body (bounded error text), and ResponseHeaders.
The base URL includes the API prefix. For Fireworks, use
https://api.fireworks.ai/inference/v1 and a Fireworks model identifier.
Providers implement different subsets of the OpenAI API and may require different
options; e.g. Fireworks chat uses max_tokens. The library does not silently
translate one provider’s model parameters into another provider’s parameters.
Stream reasoning, answers, and tool calls
stream = OpenAI.ChatStream(client, request)
completion = OpenAI.NewCompletion()
WHILE NOT OpenAI.IsFinished(stream)
IF LET event = OpenAI.NextEvent(BYREF stream) THEN
OpenAI.Accumulate(BYREF completion, event)
thinking = OpenAI.ReasoningDelta(event)
answer = OpenAI.TextDelta(event)
' Consume each nonempty delta immediately.
' event.Data retains the complete JSON event, including provider fields.
END IF
WEND
assistantMessage = OpenAI.CompletionMessage(completion)Accumulate retains multiple choices, fragmented tool names/IDs/arguments,
reasoning, refusals, finish reasons, and usage. Enable stream_options.include_usage
in your request to ask for final usage. CompletionMessage(completion, 0, TRUE)
includes Fireworks-style reasoning_content when continuing a conversation;
leave the last argument false for providers that do not accept that field.
Tool calls are returned to your application, which decides whether and how to
execute them and sends ToolResult(id, content) messages. This client does not
execute model-requested tools automatically.
ReasoningDelta reads provider-supplied reasoning_content / reasoning and
Responses reasoning text/summary events. Some models expose no reasoning at all;
the client cannot reveal reasoning the server does not supply.
Use CloseStream(BYREF stream) when stopping early. Streams own their connection
and normal BASIL ownership cleanup also releases it on return/error/task
cancellation. NextSSE exposes unparsed SSE messages; NextEvent decodes JSON.
SSE framing handles split UTF-8, BOM, comments, CR/LF/CRLF, multiline data, IDs,
and retry metadata. Chat/Completions streams require [DONE]; Responses also
recognizes its completed/failed/incomplete terminal events. A missing terminal
marker is an error. Inspect response.failed / response.incomplete event data;
these terminal Responses events are returned to the application.
The client retries opening a stream, never a partially consumed response. SSE
retry is metadata, not an instruction to reconnect/replay an inference request.
Responses
request = OpenAI.ResponseRequest("YOUR_MODEL", "Hello!")
reply = OpenAI.ResponsesCreate(client, request)
PRINT OpenAI.OutputText(reply)
stream = OpenAI.ResponseStream(client, request)
WHILE NOT OpenAI.IsFinished(stream)
IF LET event = OpenAI.NextEvent(BYREF stream) THEN
' TextDelta / ReasoningDelta also understand Responses events.
' Other events, including tool calls, remain available in event.Data.
END IF
WENDComplete REST access
ENDPOINTS.md lists 354 named HTTP operations from the pinned 2026-11-09 official reference snapshot. Coverage includes Chat Completions, Responses, embeddings, moderation, images, audio, files/uploads, batches, fine-tuning, vector stores, assistants/threads/runs, containers, videos, conversations, evals, administration, and additional resources in the snapshot. The catalogue records the method, path, source link, and source hash. Named wrappers preserve unrestricted JSON request/response bodies instead of duplicating all Python SDK schema classes in BASIL.
Each named method accepts escaped string path identifiers, a JSON body where
applicable, and a query dictionary. Convenience overloads omit the query. JSON
methods have a Raw companion preserving status, headers, and bytes; its signature
includes the body (if applicable) and query explicitly. Binary methods already
return HTTPResponse. Multipart operations accept LIST OF OpenAI.Part and
also offer MultipartRaw. Documented SSE operations offer a Stream companion;
its signature includes the query dictionary.
query = OpenAI.Parameters()
Put(BYREF query, "limit", "20")
models = OpenAI.ModelsList(client, query)
model = OpenAI.ModelsRetrieve(client, "some-model-id")
' Provider extensions or newly introduced endpoints need no SDK update:
result = OpenAI.RequestJSON(client, "POST", "/some/path", OpenAI.Object())Generic transport functions are RequestJSON, RequestJSONRaw, RequestRaw,
OpenStream (POST JSON with stream=true), OpenEventStream (explicit method,
body and query), and OpenBodyStream + ReadChunk (incremental binary).
Pass API paths beginning with /, without a query string or host; use a query
dictionary separately. Named wrappers escape IDs automatically; generic paths
can use Segment(id) for an individual identifier.
ListPage / NextPage implement ordinary data / has_more / after cursor
pagination. Resource-specific pagination formats and asynchronous job polling
remain accessible through ordinary requests; callers control their loops.
Files, images, audio and binary responses
parts = [OpenAI.TextPart("purpose", "assistants"), OpenAI.ReadFilePart("file", "notes.txt", "text/plain")]
uploaded = OpenAI.FilesCreate(client, parts)
response = OpenAI.FilesContentRetrieve(client, "file-id")
' response.Body is LIST OF BYTE; never decoded as UTF-8.FilePart(name, filename, bytes, contentType) builds a part from memory.
ReadFilePart reads a stable file snapshot and reports changes during reading.
Repeated part names support array fields. Multipart boundaries are checked
against binary contents, and names/filenames are quoted and validated.
RequestMultipartJSON parses JSON; RequestMultipart preserves arbitrary bytes,
including plain-text transcription formats. OpenMultipartStream consumes SSE
responses to multipart requests; include a text part stream=true explicitly.
Download(client, path, file, query) writes to an already-open BASIL File
incrementally. On failure a partial output file may remain; callers can use a
temporary file and rename after success. Base64Encode / Base64Decode help
with data URLs and JSON-encoded media. TextPart(text) and ImagePart(url, detail)
construct multimodal chat content parts. Use JSON directly for other media types.
REST audio uploads/generation and Realtime session/control HTTP endpoints are included. Bidirectional audio and WebSocket/WebRTC transports are outside this first iteration.
Configuration and reliability
Client values are immutable-style configuration: each With... function returns
a new client. Available helpers:
WithRetries(client, count): 0–10 retries, default 2; connection failures and HTTP 408/409/429/5xx. Numericretry-after/retry-after-msis honored up to 60 seconds; otherwise exponential 500ms–8s delay. HTTP-date retry hints are not interpreted. Consider disabling retries for operations where replay is unsafe, or supply a server-supported idempotency key withWithHeader.WithTimeout(client, duration): header/body/write deadlines; default 30s header and 120s body/write. Body timeout is the underlying exchange deadline, not a per-token idle timer. Increase it for long inference streams.WithLimits(client, requestBytes, responseBytes): default 64MiB request and 256MiB response. SSE has a 1MiB per-line/event limit. Multipart uploads are assembled in memory; use bounded upload parts for very large files.WithHeader(client, name, value): project, organization, beta, idempotency and custom headers. Authentication/framing headers are reserved.WithTLS(client, options): existing BASIL TLS options (e.g. a custom trust store).
TLS certificate/hostname verification uses the existing BASIL HTTP/TLS defaults.
The SDK never follows redirects with authentication. Error bodies are bounded to
16KiB of text in APIError (the underlying response still obeys its configured
response byte limit). Raw response helpers preserve all successful bytes/headers.
Webhooks
VerifyWebhook(payloadBytes, headers, secret) validates Standard Webhooks v1
HMAC-SHA256 signatures over the original body bytes, supporting whsec_
base64 secrets, multiple signatures during rotation, and a default ±300s timestamp
tolerance. UnwrapWebhook additionally parses the verified JSON. Optional
nowSeconds and toleranceSeconds support deterministic tests. The signed ID
still needs application-level deduplication if processing must happen once.
Asymmetric v1a webhook signatures are not implemented.
Tests and maintenance
python3 tests/run.py
Python is only a local mock HTTP server/test runner. Tests compile and exercise
real BASIL code, use no external service, and incur no inference charges. Set
BASIL=/path/to/basic to select another compiler. The runner otherwise uses this
repository’s build/selfhost/stage3.
The companion TUI lives in ~/src/bt/ai-chat. Its PTY tests cover actual input,
streaming redraw, history, cancellation, resizing, exit and terminal restoration.
Live Fireworks checks covered authentication/model listing and bounded streamed
Chat Completions, including reasoning and answer deltas. Other endpoint families
are bindings based on the pinned reference, not claims of live Fireworks support
or live verification against every OpenAI resource.
Edit src/*.bas.in, then run python3 generate.py to update checked-in source.
python3 spec/fetch.py intentionally refreshes the official reference catalog
using the network; review its changes, then regenerate. Ordinary builds never
fetch documentation or run either generator.
References: official API reference , Responses streaming , webhooks , Fireworks reasoning .
Portable packaging
The source package is self-contained, including a generated local copy of the
standard BASIL SHA-256 module used for HMAC/webhooks. generate.py refreshes that
copy from stdlib/sha256.bas; runtime use requires no generator. With the compiler’s
native-resource .blib support, basic build --manifest basic.toml -o build/openai.blib
produces a portable dependency, including the HTTP/stream resource interface.