# An unofficial OpenAI SDK for Bend, with typed requests, streaming, and tool calling. import Base # # openai-bend # # **An unofficial OpenAI SDK for Bend, with typed requests, streaming, and tool calling.** # # Write your application in Bend. Use OpenAI's official TypeScript SDK for the network connection. A small, authenticated companion process connects the two over localhost. # # [Examples](./examples) · [Source](https://github.com/gouveags/openai-bend) · [Report an issue](https://github.com/gouveags/openai-bend/issues) # # > Experimental v0.1.0. This is a community project, not an official OpenAI or Bend project. It implements the foreground Responses API. It is not a feature-complete replacement for the Python or TypeScript SDKs. # # # ## What you get # # | Capability | v0.1.0 | # | --------------------------------------------------- | ------------------------------------------------------------------ | # | Create responses | Typed request builder plus arbitrary JSON options | # | Streaming | Every semantic event, including unknown future events | # | Response helpers | Text, function calls, usage and complete raw JSON | # | Structured outputs | JSON Schema request helper; application-controlled decoding | # | Function calling | Tool definitions, typed call extraction and function-output helper | # | Images and files | References in request JSON; no upload helper | # | Cancellation | Close a stream, or cancel by ID over a separate connection | # | Error metadata | API status, request ID and Retry-After | # | Native / JavaScript | Same Bend API and companion protocol | # | Uploads, pagination, background responses, Realtime | Not implemented | # # Function execution is deliberately explicit: the library never executes a command or function just because the model named it. Refusals, failed/incomplete responses, annotations and provider-specific details remain available in the raw response or event. # # ## Requirements # # - Linux or macOS. The tested platform is Linux x86_64; macOS is not yet validated. # - [Bend 2](https://github.com/bendlang/bend), [Bun](https://bun.sh/), and Git. # - Clang 14+ and Make for native builds. # - An OpenAI API key and access to your selected model for real inference. # # The companion is a separate local process. **Installing the Bend Hub package does not install Bun, the companion, or its dependencies.** # # ## Install the companion # # ```sh # git clone https://github.com/gouveags/openai-bend.git # cd openai-bend # git checkout v0.1.0 # bun install --frozen-lockfile # ``` # # Load your API key into `OPENAI_API_KEY` using your secret manager or shell's private input facility. Do not put it in source code, commit an environment file, or paste it into a command that will be saved in shell history. # # Generate a separate local bridge token: # # ```sh # export OPENAI_BEND_TOKEN="$(openssl rand -hex 32)" # export OPENAI_BEND_PORT=42101 # export OPENAI_MODEL='your-available-model-id' # bun run bridge # ``` # # Expected startup message: # # ```text # openai-bend 0.1.0 listening on 127.0.0.1:42101 (protocol 1) # ``` # # Your Bend process needs `OPENAI_BEND_TOKEN`, `OPENAI_BEND_PORT` and your model selection. **It does not need the OpenAI API key.** When using separate terminals, share the same bridge token through your local secret-management mechanism. The bridge does not print credentials. # # ## Run an example # # From another terminal with the bridge token available: # # ```sh # make build/request.js # bun build/request.js example-1 once \ # "{\"model\":\"$OPENAI_MODEL\",\"input\":\"Say hello.\"}" # # # Stream every event: # bun build/request.js example-2 stream \ # "{\"model\":\"$OPENAI_MODEL\",\"input\":\"Explain interaction nets briefly.\"}" # ``` # # The command prints the result or each semantic event, then `finished`. Provider, protocol and transport failures exit nonzero. `finished` means the transport ended correctly: inspect the response status or terminal event to distinguish completed, failed and incomplete model output. # # For native execution: # # ```sh # make build/request # ./build/request example-3 once \ # "{\"model\":\"$OPENAI_MODEL\",\"input\":\"Say hello.\"}" # ``` # # The Makefile emits C using Bend and compiles with Clang `-O1` by default to keep development builds manageable. Override `CFLAGS` for an optimized build after validating it on your machine. This project makes no performance claim. # # ## Use the Bend library # # Within this checkout, a minimal typed request looks like this: # # ```bend # import Base # import ./openai.bend as OpenAI # # def start(token: String, model: String) -> # IO(Result<&1, &1, OpenAI.Error, OpenAI.Stream>): # OpenAI.create( # OpenAI.Client{42101, token}, # "my-unique-request-id", # OpenAI.text_request(model, "Say hello.")) # ``` # # `create` and `stream` return an owned stream handle. Call `OpenAI.next` until it returns: # # - `Event{stream, kind, data, request_id}` — process the event and use the returned stream for the next read. # - `Finished{}` — the connection has closed cleanly. # - `Failed{error}` — the connection has closed with a transport, API or protocol error. # # For nonstreaming calls, the event kind is `result`. Streaming calls first expose `metadata`, followed by provider event names such as `response.output_text.delta`. All events include their original JSON payload. `OpenAI.response(data)` extracts a typed response from a `result` payload; for a terminal streaming event, pass its nested `response` object. # # Call `OpenAI.close(stream)` if you stop consuming early. To cancel a blocked request from another Bend computation, use `OpenAI.cancel(client, request_id)`. Cancellation requests an upstream abort; it cannot guarantee that remote processing or billing has not already happened. # # See [the complete reader](./examples/request.bend), [hello](./examples/hello.bend), [structured outputs](./examples/structured.bend), [tools](./examples/tools.bend), and [cancellation](./examples/cancel.bend). All examples read `OPENAI_BEND_PORT` and `OPENAI_BEND_TOKEN`. # # ## Structured outputs and tools # # `OpenAI.Request{model, input, options}` accepts a JSON input and additional fields. Use `Json.Text` for a simple prompt, or a `Json.Array` for messages, image/file references and tool results. # # ```bend # # Add these fields to Request.options: # Json.Field{"text", OpenAI.json_schema("person", schema)} # Json.Field{"tools", Json.Array{[ # OpenAI.function_tool("weather", "Look up weather", schema) # ]}} # ``` # # The schema must meet [OpenAI's supported JSON Schema rules](https://developers.openai.com/api/docs/guides/structured-outputs). Structured output is not automatically converted into an arbitrary Bend datatype: inspect the response status/refusal, parse its text with `Json.read`, then validate your application's fields. # # For tools, inspect `Response.calls`. Each `FunctionCall` contains `id`, `call_id`, `name`, JSON-encoded `arguments`, and the complete raw item. Validate the name and arguments against an application-owned allowlist before executing anything. Return a result with `OpenAI.function_output(call_id, output)`. # # For a stored-response continuation, submit that item in the next input with `previous_response_id` set to the response ID. Repeat instructions and tool definitions as needed; instructions are not automatically inherited. For stateless/manual replay, follow the [official continuation guidance](https://developers.openai.com/api/docs/guides/function-calling), including reasoning items. There is no automatic tool loop in this release. # # ## JSON and forward compatibility # # `json.bend` supplies `Null`, `Boolean`, `Number`, `Text`, `Array` and `Object`, with `Field` entries for objects. `Json.get` returns `None` for an absent property and `Some{Null{}}` for explicit null. Omit an option by leaving it out of the field list. # # Numbers are stored as decimal strings and do not pass through Bend's `F32`. The companion uses JavaScript JSON and the official SDK, so its normal IEEE-754 numeric limitations still apply. Do not send integers beyond JavaScript's safe range expecting lossless end-to-end arithmetic. # # Use valid JSON number strings in `Json.Number`. Unknown response fields and event types are preserved. `OpenAI.open_raw` accepts an arbitrary JSON request object for newly added fields, but supports only the allowlisted Responses create operation. Request options cannot change the companion's URL, credentials or SDK method. # # ## Architecture and security # # ```text # Bend application ── authenticated localhost TCP ── companion # │ # official openai SDK # │ HTTPS # OpenAI API # ``` # # - The companion binds only `127.0.0.1`. It is not a public server and must not be exposed through a proxy or tunnel. # - A token of at least 32 characters is required. Use a randomly generated token, not a memorable password. API credentials remain in the companion process. # - One request per connection; cancellation uses a separate connection and request ID. Use unique IDs for simultaneous requests. # - JSON decoding is limited to 64 nesting levels and 65536 tokens. Requests are capped at 1 MiB; output frames at 4 MiB; concurrent connections at 32. Partial requests expire after 10 seconds. # - The upstream deadline defaults to 120 seconds and can be configured with `OPENAI_BEND_DEADLINE_MS`, up to 600000. Socket inactivity is also bounded. # - The official SDK owns retries (two retries configured in the CLI). The bridge does not replay requests after disconnect or restart. Do not automatically retry a partly received inference result without considering duplicate execution/cost. # - Frames are newline-delimited JSON, with non-ASCII characters escaped. The Bend reader handles fragmented/coalesced frames and Unicode surrogate pairs. # - The CLI fixes the upstream URL to `https://api.openai.com/v1`. Alternate upstream clients exist only as programmatic configuration for tests or an operator-controlled integration. # - No telemetry or prompt logging is implemented by this project. Provider error messages are returned to the authenticated caller and may contain submitted content. Example programs intentionally print responses. # # The bridge token is sent over local TCP without TLS. This design assumes a trusted local machine and companion. It does not protect against a compromised local account, privileged process, malicious replacement bridge, or someone who can read your process environment. Bend's termination/type checks do not prove the socket runtime, provider behavior or the entire SDK secure. The source marks the few externally driven or structurally unrecognized recursive functions `@unsafe` explicitly. # # ## Development and validation # # ```sh # bun install --frozen-lockfile # make test # bun audit # ``` # # Tests use the actual OpenAI TypeScript SDK against a local HTTP mock, plus real TCP connections and compiled Bend clients. They require no real API key and make no inference requests to OpenAI. A successful local mock run proves protocol and integration behavior, not model behavior or account access. # # The dependency lockfile is committed. Formatting and TypeScript checks are part of `make test`. See `compatibility.json` for the pinned compiler, companion protocol and SDK versions. # # ## Troubleshooting # # | Symptom | Check | # | ------------------------------------- | ------------------------------------------------------------------------------- | # | Connection refused | Start the companion and check the port. | # | Unauthorized / request-ID mismatch | Use the same bridge token in both processes. | # | API 401/403 | Check the companion's API key and project/model access. | # | API 429 | Inspect error metadata; avoid adding another unbounded retry loop. | # | Truncated stream | Treat the partial output as incomplete; do not silently call it success. | # | Native build consumes too much memory | Use the tested compiler and default Makefile flags; report a reproducible case. | # # ## License # # MIT. The OpenAI TypeScript SDK is a separately licensed dependency. Bend is a separate project. Contributions and reproducible bug reports are welcome at [GitHub](https://github.com/gouveags/openai-bend/issues). # def repository() -> String: "https://github.com/gouveags/openai-bend" def version() -> String: "0.1.0"