Packages
opencode_sdk
0.1.40
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/server.ex
defmodule OpenCode.Server do
@moduledoc """
OpenCode server process management.
Mirrors the JS SDK's `createOpencodeServer()` behavior.
"""
defstruct [:port, :url]
@type t :: %__MODULE__{
port: port() | nil,
url: String.t() | nil
}
@spec create(keyword()) :: {:ok, t()} | {:error, term()}
def create(opts \\ []) do
host = Keyword.get(opts, :hostname, "127.0.0.1")
num = Keyword.get(opts, :port, 4096)
time = Keyword.get(opts, :timeout, 5000)
config = Keyword.get(opts, :config, %{})
args = build_args(host, num, config)
env = build_env(config)
port =
Port.open({:spawn_executable, find_executable("opencode")}, [
:binary,
:exit_status,
:use_stdio,
:stderr_to_stdout,
args: args,
env: env
])
case wait_for_ready(port, time, "", host, num) do
{:ok, url} ->
{:ok, %__MODULE__{port: port, url: url}}
{:error, reason} ->
if Port.info(port) != nil, do: Port.close(port)
{:error, reason}
end
end
@spec close(t()) :: :ok
def close(%__MODULE__{port: port}) when is_port(port) do
Port.close(port)
:ok
end
def close(_), do: :ok
defp build_args(host, num, config) do
args = ["serve", "--hostname=#{host}", "--port=#{num}"]
level =
case config do
%{logLevel: val} -> val
%{"logLevel" => val} -> val
_ -> nil
end
case level do
nil -> args
val -> args ++ ["--log-level=#{val}"]
end
end
defp build_env(config) do
base =
System.get_env()
|> Enum.map(fn {key, val} -> {String.to_charlist(key), String.to_charlist(val)} end)
|> Enum.reject(fn {key, _} -> key == ~c"OPENCODE_CONFIG_CONTENT" end)
[{~c"OPENCODE_CONFIG_CONTENT", String.to_charlist(Jason.encode!(config))} | base]
end
defp find_executable(name) do
case System.find_executable(name) do
nil -> String.to_charlist(name)
path -> String.to_charlist(path)
end
end
defp wait_for_ready(port, time, buf, host, num) do
deadline = System.monotonic_time(:millisecond) + time
base_url = "http://#{host}:#{num}"
do_wait(port, deadline, buf, base_url)
end
defp do_wait(port, deadline, buf, base_url) do
left = deadline - System.monotonic_time(:millisecond)
case left <= 0 do
true ->
case health_ok?(base_url) do
true -> {:ok, base_url}
false -> {:error, {:timeout, "Server did not start within timeout"}}
end
false ->
receive do
{^port, {:data, data}} ->
next = buf <> data
case parse_url(next) do
{:ok, url} -> {:ok, url}
:not_found -> do_wait(port, deadline, next, base_url)
end
{^port, {:exit_status, status}} ->
{:error, {:exit_status, status, buf}}
after
min(left, 100) ->
do_wait(port, deadline, buf, base_url)
end
end
end
defp parse_url(out) do
out
|> String.split("\n")
|> Enum.find_value(:not_found, fn line ->
case String.contains?(line, "opencode server listening") do
true ->
case Regex.run(~r/on\s+(https?:\/\/[^\s]+)/, line) do
[_, url] -> {:ok, url}
_ -> :not_found
end
false ->
:not_found
end
end)
end
defp health_ok?(url) do
case Req.get(url <> "/global/health",
connect_options: [timeout: 500],
receive_timeout: 500
) do
{:ok, %{status: status}} when status in 200..299 -> true
_ -> false
end
end
end