Packages
opencode_sdk
0.1.62
0.1.84
0.1.83
0.1.82
0.1.81
0.1.80
0.1.79
0.1.78
0.1.77
0.1.76
0.1.75
0.1.74
0.1.73
0.1.72
0.1.71
0.1.70
0.1.69
0.1.68
0.1.67
0.1.66
0.1.65
0.1.64
0.1.63
0.1.62
0.1.61
0.1.60
0.1.59
0.1.58
0.1.57
0.1.56
0.1.55
0.1.54
0.1.53
0.1.52
0.1.51
0.1.50
0.1.49
0.1.48
0.1.47
0.1.46
0.1.45
0.1.44
0.1.43
0.1.42
0.1.41
0.1.40
0.1.39
0.1.38
0.1.37
0.1.36
0.1.35
0.1.34
0.1.33
0.1.32
0.1.31
0.1.30
0.1.29
0.1.28
0.1.27
0.1.26
0.1.25
0.1.24
0.1.23
0.1.22
0.1.21
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Unofficial OpenCode SDK for Elixir. A complete Elixir SDK for OpenCode, providing server creation, session management, messaging, file operations, and more. Mirrors the functionality of the official JavaScript SDK (@opencode-ai/sdk). Not affiliated with or built by the OpenCode team.
Current section
Files
Jump to
Current section
Files
lib/opencode/client.ex
defmodule OpenCode.Client do
@moduledoc """
HTTP client used by generated operations.
"""
@default_base_url "http://127.0.0.1:4096"
@spec request(map()) :: {:ok, any()} | {:error, any()}
def request(%{url: url, method: method} = params) do
opts = params[:opts] || []
base = Keyword.get(opts, :base_url, @default_base_url)
time = Keyword.get(opts, :timeout, :infinity)
headers = Keyword.get(opts, :headers, [])
dir = Keyword.get(opts, :directory)
req =
Req.new(url: base <> url, method: method, receive_timeout: time)
|> add_headers(headers)
|> add_directory(dir)
|> add_body(params[:body])
|> add_query(params[:query])
case sse?(params) do
true ->
req = Req.Request.put_header(req, "accept", "text/event-stream")
{:ok, %{stream: sse_stream(req)}}
false ->
case Req.request(req) do
{:ok, %{status: status, body: body}} when status in 200..299 ->
{:ok, body}
{:ok, %{status: status, body: body}} ->
{:error, {status, body}}
{:error, error} ->
{:error, error}
end
end
end
defp add_body(req, nil), do: req
defp add_body(req, body), do: Req.merge(req, json: body)
defp add_query(req, nil), do: req
defp add_query(req, query), do: Req.merge(req, params: query)
defp add_headers(req, []), do: req
defp add_headers(req, headers) when is_map(headers) do
Enum.reduce(headers, req, fn {key, val}, acc ->
Req.Request.put_header(acc, to_string(key), to_string(val))
end)
end
defp add_headers(req, headers) do
Enum.reduce(headers, req, fn {key, val}, acc ->
Req.Request.put_header(acc, to_string(key), to_string(val))
end)
end
defp add_directory(req, nil), do: req
defp add_directory(req, ""), do: req
defp add_directory(req, dir) do
val =
case String.match?(dir, ~r/[^\x00-\x7F]/) do
true -> URI.encode(dir)
false -> dir
end
Req.Request.put_header(req, "x-opencode-directory", val)
end
defp sse?(%{url: url, method: :get}) when url in ["/event", "/global/event"], do: true
defp sse?(_), do: false
defp sse_stream(req) do
Stream.resource(
fn ->
ref = make_ref()
pid = spawn_link(fn -> sse_run(req, self(), ref) end)
%{ref: ref, pid: pid, buf: ""}
end,
fn state ->
receive do
{ref, :data, data} when ref == state.ref ->
{events, buf} = parse_sse(state.buf <> data)
{events, %{state | buf: buf}}
{ref, :done} when ref == state.ref ->
{:halt, state}
{ref, :error, error} when ref == state.ref ->
{[{:error, error}], state}
end
end,
fn state ->
case Process.alive?(state.pid) do
true -> Process.exit(state.pid, :normal)
false -> :ok
end
end
)
end
defp sse_run(req, pid, ref) do
case Req.request(req,
into: fn {:data, data}, acc ->
send(pid, {ref, :data, data})
{:cont, acc}
end
) do
{:ok, _} -> send(pid, {ref, :done})
{:error, error} -> send(pid, {ref, :error, error})
end
end
defp parse_sse(data) do
parts = String.split(data, "\n\n")
count = length(parts)
case count do
0 -> {[], ""}
1 -> {[], data}
_ ->
events = parts |> Enum.drop(-1) |> Enum.flat_map(&decode_sse/1)
{events, List.last(parts)}
end
end
defp decode_sse(chunk) do
data =
chunk
|> String.split("\n")
|> Enum.filter(&String.starts_with?(&1, "data:"))
|> Enum.map(&String.replace_prefix(&1, "data:", ""))
|> Enum.join("\n")
|> String.trim()
case data do
"" -> []
_ ->
case Jason.decode(data) do
{:ok, val} -> [val]
{:error, _} -> [data]
end
end
end
end