laiya 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/bin/laiya +20 -0
- data/context/chatgpt-codex.md +43 -0
- data/context/getting-started.md +49 -0
- data/context/http-api.md +49 -0
- data/context/index.yaml +26 -0
- data/context/providers-and-models.md +104 -0
- data/examples/chatgpt/authentication.rb +45 -0
- data/examples/chatgpt/readme.md +107 -0
- data/examples/chatgpt/service.rb +34 -0
- data/guides/chatgpt-codex/readme.md +43 -0
- data/guides/getting-started/readme.md +49 -0
- data/guides/http-api/readme.md +49 -0
- data/guides/index.md +14 -0
- data/guides/links.yaml +8 -0
- data/guides/providers-and-models/readme.md +104 -0
- data/lib/laiya/configuration/builder.rb +40 -0
- data/lib/laiya/configuration.rb +152 -0
- data/lib/laiya/environment/application.rb +53 -0
- data/lib/laiya/models/catalog.rb +108 -0
- data/lib/laiya/models/discover.rb +110 -0
- data/lib/laiya/provider/codex/authentication.rb +185 -0
- data/lib/laiya/provider/codex/request.rb +136 -0
- data/lib/laiya/provider/codex/response.rb +117 -0
- data/lib/laiya/provider/codex/responses.rb +93 -0
- data/lib/laiya/provider/codex/server_sent_events.rb +78 -0
- data/lib/laiya/provider/codex.rb +298 -0
- data/lib/laiya/provider/interface.rb +19 -0
- data/lib/laiya/provider/ollama.rb +22 -0
- data/lib/laiya/provider/openai.rb +92 -0
- data/lib/laiya/provider.rb +10 -0
- data/lib/laiya/router.rb +85 -0
- data/lib/laiya/service/application.rb +50 -0
- data/lib/laiya/version.rb +8 -0
- data/lib/laiya/web/application.rb +48 -0
- data/lib/laiya/web.rb +6 -0
- data/lib/laiya.rb +19 -0
- data/license.md +21 -0
- data/readme.md +68 -0
- data/releases.md +7 -0
- data.tar.gz.sig +0 -0
- metadata +162 -0
- metadata.gz.sig +3 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 665e132b315d14e0d867411d06e49c015ed8bfe18f50e3ee15623eee73a90e6e
|
|
4
|
+
data.tar.gz: 693d030853123c19d1dda07fda06fac7d6c6b8a8d4b9db7a488cbda3e8ef2add
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6bb1a3cc19e46eaf6908343327edeb7c4af1867b07016962b4a6a70c22d7f31e44aa3bdf644f6a26ad2ed4d152dfe041f3d416a912aea00005076f988f60c198
|
|
7
|
+
data.tar.gz: '015801fda685fe3fc0e3fe1d792784fdd4aaa6255e04d6e1d0ea29dc72b5c4360d513834e5681d4ca3b4092067231548e43bf87773b0066930ab4677bdaeac34'
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
data/bin/laiya
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Released under the MIT License.
|
|
5
|
+
# Copyright, 2026, by Samuel Williams.
|
|
6
|
+
|
|
7
|
+
require "async/service"
|
|
8
|
+
require_relative "../lib/laiya/environment/application"
|
|
9
|
+
|
|
10
|
+
ARGV.each do |path|
|
|
11
|
+
require(path)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
configuration = Async::Service::Configuration.build do
|
|
15
|
+
service "laiya" do
|
|
16
|
+
include Laiya::Environment::Application
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Async::Service::Controller.run(configuration)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# ChatGPT Codex Provider
|
|
2
|
+
|
|
3
|
+
This guide explains how to run the experimental Codex provider with a ChatGPT login while keeping tool execution on the client.
|
|
4
|
+
|
|
5
|
+
## Authentication and Trust
|
|
6
|
+
|
|
7
|
+
The Codex provider reads ChatGPT credentials from
|
|
8
|
+
`CODEX_HOME/auth.json` (default `~/.codex/auth.json`) and refreshes tokens when
|
|
9
|
+
needed. If the Codex CLI uses an OS keyring, configure file-backed credentials
|
|
10
|
+
and run `codex login`:
|
|
11
|
+
|
|
12
|
+
```toml
|
|
13
|
+
cli_auth_credentials_store = "file"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Treat `auth.json` as a password. This provider calls the Codex-specific backend,
|
|
17
|
+
not the public OpenAI Platform API. It is experimental and intended for a
|
|
18
|
+
trusted, single-user service. Do not expose it to the public internet or an
|
|
19
|
+
untrusted multi-user environment.
|
|
20
|
+
|
|
21
|
+
## Start the Example
|
|
22
|
+
|
|
23
|
+
From the Laiya repository root:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
export LAIYA_API_KEY="$(openssl rand -hex 32)"
|
|
27
|
+
bundle exec ruby examples/chatgpt/service.rb
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The example binds to `127.0.0.1:9293`. It requires the client to send the
|
|
31
|
+
separate `LAIYA_API_KEY` as a Bearer token. For remote clients, use TLS and a
|
|
32
|
+
private access-controlled network. See the [example files](https://github.com/socketry/laiya/tree/main/examples/chatgpt).
|
|
33
|
+
|
|
34
|
+
## Client-Owned Tool Execution
|
|
35
|
+
|
|
36
|
+
Use `POST /v1/responses` for tool calling. Laiya preserves the Codex Responses
|
|
37
|
+
stream and response items. The client executes a returned function call and
|
|
38
|
+
sends its `function_call_output` back in the next request. Since Codex is used
|
|
39
|
+
statelessly, send the full input history, including the previous response's
|
|
40
|
+
reasoning and function-call items; `previous_response_id` is not supported.
|
|
41
|
+
|
|
42
|
+
The text-only `POST /v1/chat/completions` adapter rejects tool-enabled requests
|
|
43
|
+
so it cannot silently lose Codex reasoning state.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This guide explains how to install Laiya, configure a provider, and make an OpenAI-compatible request.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add Laiya to your application:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bundle add laiya
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configure a Provider
|
|
14
|
+
|
|
15
|
+
Laiya's Ruby API uses a `Laiya::Configuration` to connect model IDs to
|
|
16
|
+
providers. For an OpenAI Platform API key:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
require "laiya"
|
|
20
|
+
|
|
21
|
+
configuration = Laiya::Configuration.build do |builder|
|
|
22
|
+
builder.provider :openai, Laiya::Provider::OpenAI.new(
|
|
23
|
+
api_key: ENV.fetch("OPENAI_API_KEY")
|
|
24
|
+
)
|
|
25
|
+
builder.model "gpt-4.1-mini", provider: :openai
|
|
26
|
+
builder.default_provider :openai
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
application = Laiya::Web::Application.new(configuration: configuration)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The provider forwards `Protocol::HTTP::Request` and `Protocol::HTTP::Response`
|
|
33
|
+
objects using `Async::HTTP`. It does not parse OpenAI request or response bodies
|
|
34
|
+
in the direct proxy path.
|
|
35
|
+
|
|
36
|
+
For Ollama and multi-provider setups, see [Providers and Models](../providers-and-models/).
|
|
37
|
+
|
|
38
|
+
## Run the API Service
|
|
39
|
+
|
|
40
|
+
For a local OpenAI API proxy, set the API key and start Laiya:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
export OPENAI_API_KEY="..."
|
|
44
|
+
bundle exec bin/laiya
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The service listens on `http://localhost:9292` by default. Configure `LAIYA_URL`
|
|
48
|
+
to change the endpoint. See [HTTP API](../http-api/) for supported routes and
|
|
49
|
+
streaming behavior.
|
data/context/http-api.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# HTTP API
|
|
2
|
+
|
|
3
|
+
This guide explains how Laiya's OpenAI-compatible HTTP API handles routing, proxying, and streaming.
|
|
4
|
+
|
|
5
|
+
## Run the Async Service
|
|
6
|
+
|
|
7
|
+
Laiya runs directly on `Async::HTTP::Server` under `Async::Service`; it does not
|
|
8
|
+
use Rack:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
bundle exec bin/laiya
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The default service listens on `http://localhost:9292`. `LAIYA_URL` configures
|
|
15
|
+
the endpoint. `bin/laiya` loads optional Ruby configuration paths supplied on
|
|
16
|
+
the command line before starting the service.
|
|
17
|
+
|
|
18
|
+
## Endpoints
|
|
19
|
+
|
|
20
|
+
The API accepts OpenAI-compatible `Protocol::HTTP::Request` objects and returns
|
|
21
|
+
`Protocol::HTTP::Response` objects. Supported common routes include:
|
|
22
|
+
|
|
23
|
+
- `GET /v1/models`
|
|
24
|
+
- `POST /v1/chat/completions`
|
|
25
|
+
- `POST /v1/responses` when the configured provider supports it
|
|
26
|
+
- Other OpenAI-compatible paths are forwarded to the selected provider.
|
|
27
|
+
|
|
28
|
+
Configured model routes produce a Laiya model catalog. When a provider is the
|
|
29
|
+
default and no model catalog is configured, `GET /v1/models` is forwarded
|
|
30
|
+
upstream. See [Providers and Models](../providers-and-models/) for discovery.
|
|
31
|
+
|
|
32
|
+
## Proxy and Model Routing
|
|
33
|
+
|
|
34
|
+
The built-in OpenAI provider is a direct HTTP proxy. It forwards the method,
|
|
35
|
+
path, headers, and request body, replacing authorization with its configured
|
|
36
|
+
upstream key and filtering hop-by-hop headers. It returns the upstream response
|
|
37
|
+
without parsing its body.
|
|
38
|
+
|
|
39
|
+
When a model route or discovery source is configured, Laiya buffers and rewinds
|
|
40
|
+
request bodies for known JSON completion endpoints to read the `model` field.
|
|
41
|
+
It forwards the original body unchanged. Streaming response bodies are not
|
|
42
|
+
buffered by the router.
|
|
43
|
+
|
|
44
|
+
## Streaming
|
|
45
|
+
|
|
46
|
+
OpenAI-compatible upstream SSE responses pass through unchanged. Translating
|
|
47
|
+
providers may adapt streaming formats. The Codex Responses endpoint passes
|
|
48
|
+
native Responses SSE through, including tool-call events; Laiya does not execute
|
|
49
|
+
client tools.
|
data/context/index.yaml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
|
3
|
+
---
|
|
4
|
+
description: An OpenAI-compatible HTTP API and provider proxy
|
|
5
|
+
metadata:
|
|
6
|
+
bug_tracker_uri: https://github.com/socketry/laiya/issues
|
|
7
|
+
changelog_uri: https://github.com/socketry/laiya/blob/main/releases.md
|
|
8
|
+
documentation_uri: https://socketry.github.io/laiya/
|
|
9
|
+
source_code_uri: https://github.com/socketry/laiya.git
|
|
10
|
+
files:
|
|
11
|
+
- path: getting-started.md
|
|
12
|
+
title: Getting Started
|
|
13
|
+
description: This guide explains how to install Laiya, configure a provider, and
|
|
14
|
+
make an OpenAI-compatible request.
|
|
15
|
+
- path: providers-and-models.md
|
|
16
|
+
title: Providers and Models
|
|
17
|
+
description: This guide explains how to route model IDs to providers and when to
|
|
18
|
+
discover a provider's model catalog.
|
|
19
|
+
- path: http-api.md
|
|
20
|
+
title: HTTP API
|
|
21
|
+
description: This guide explains how Laiya's OpenAI-compatible HTTP API handles
|
|
22
|
+
routing, proxying, and streaming.
|
|
23
|
+
- path: chatgpt-codex.md
|
|
24
|
+
title: ChatGPT Codex Provider
|
|
25
|
+
description: This guide explains how to run the experimental Codex provider with
|
|
26
|
+
a ChatGPT login while keeping tool execution on the client.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Providers and Models
|
|
2
|
+
|
|
3
|
+
This guide explains how to route model IDs to providers and when to discover a provider's model catalog.
|
|
4
|
+
|
|
5
|
+
## Default Provider
|
|
6
|
+
|
|
7
|
+
Use a default provider when one upstream should receive any model name:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
configuration = Laiya::Configuration.build do |builder|
|
|
11
|
+
builder.provider :ollama, Laiya::Provider::Ollama.new
|
|
12
|
+
builder.default_provider :ollama
|
|
13
|
+
end
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
With no explicit model routes or discovery setting, Laiya forwards
|
|
17
|
+
`GET /v1/models` to the default provider and passes completion requests through
|
|
18
|
+
to it.
|
|
19
|
+
|
|
20
|
+
## Discover Models from an Upstream
|
|
21
|
+
|
|
22
|
+
Providers such as Ollama expose an OpenAI-compatible `GET /v1/models` endpoint.
|
|
23
|
+
Use `models: :discover` to query it at runtime instead of copying model names
|
|
24
|
+
into configuration:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
configuration = Laiya::Configuration.build do |builder|
|
|
28
|
+
builder.provider :ollama, Laiya::Provider::Ollama.new, models: :discover
|
|
29
|
+
builder.default_provider :ollama
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`Laiya::Models::Discover` fetches and caches the provider's catalog for 60
|
|
34
|
+
seconds. Laiya uses the discovered IDs for `GET /v1/models` and routes requests
|
|
35
|
+
for those IDs to the provider. The builder itself does not make network calls.
|
|
36
|
+
|
|
37
|
+
For multiple providers, enable discovery on each provider. Distinct model IDs
|
|
38
|
+
are routed automatically. If two providers advertise the same ID, the default
|
|
39
|
+
provider wins; an explicit `builder.model` route overrides discovery:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
configuration = Laiya::Configuration.build do |builder|
|
|
43
|
+
builder.provider :openai, Laiya::Provider::OpenAI.new(
|
|
44
|
+
api_key: ENV.fetch("OPENAI_API_KEY")
|
|
45
|
+
), models: :discover
|
|
46
|
+
builder.provider :ollama, Laiya::Provider::Ollama.new, models: :discover
|
|
47
|
+
|
|
48
|
+
builder.model "llama3.2", provider: :ollama
|
|
49
|
+
builder.default_provider :openai
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Expose Model Limits
|
|
54
|
+
|
|
55
|
+
OpenAI's standard model-list format has no context-window fields. Laiya lets
|
|
56
|
+
you attach a display name and limits to an explicit model route; it publishes
|
|
57
|
+
them under its `laiya` extension field:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
configuration = Laiya::Configuration.build do |builder|
|
|
61
|
+
builder.provider :ollama, Laiya::Provider::Ollama.new, models: :discover
|
|
62
|
+
builder.model "llama3.2",
|
|
63
|
+
provider: :ollama,
|
|
64
|
+
display_name: "Llama 3.2",
|
|
65
|
+
limits: {context: 32_768, input: 28_672, output: 4_096}
|
|
66
|
+
builder.default_provider :ollama
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The `limits` keys are `context`, `input`, and `output`, and values are positive
|
|
71
|
+
token counts. OpenCode does not currently infer these custom fields from
|
|
72
|
+
`GET /v1/models`; configure its model `limit.context`, `limit.input`, and
|
|
73
|
+
`limit.output` values on each client as well. The `context` limit should match
|
|
74
|
+
the effective upstream model configuration (for Ollama, including `num_ctx`).
|
|
75
|
+
|
|
76
|
+
## Customize Discovery
|
|
77
|
+
|
|
78
|
+
Subclass `Laiya::Models::Discover` to filter models or attach provider-specific
|
|
79
|
+
metadata. Override `include_model?` to filter entries and `normalize_model` to
|
|
80
|
+
add or adjust model fields:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
class LocalModels < Laiya::Models::Discover
|
|
84
|
+
protected
|
|
85
|
+
|
|
86
|
+
def include_model?(model)
|
|
87
|
+
super && model["id"].start_with?("llama")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def normalize_model(model)
|
|
91
|
+
super.merge("laiya" => {"limits" => {"context" => 32_768, "output" => 4_096}})
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
ollama = Laiya::Provider::Ollama.new
|
|
96
|
+
configuration = Laiya::Configuration.build do |builder|
|
|
97
|
+
builder.provider :ollama, ollama, models: LocalModels.new(ollama)
|
|
98
|
+
builder.default_provider :ollama
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The model list includes configured IDs and discovered metadata. Custom discovery
|
|
103
|
+
metadata is provider-specific and is likewise exposed under Laiya's extension
|
|
104
|
+
field; standard clients may ignore it.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "json"
|
|
7
|
+
require "openssl"
|
|
8
|
+
require "protocol/http/middleware"
|
|
9
|
+
|
|
10
|
+
module Examples
|
|
11
|
+
module ChatGPT
|
|
12
|
+
# Requires a local API key before forwarding to the personal Codex account.
|
|
13
|
+
class Authentication < Protocol::HTTP::Middleware
|
|
14
|
+
def initialize(delegate, token:)
|
|
15
|
+
super(delegate)
|
|
16
|
+
raise ArgumentError, "LAIYA_API_KEY must not be empty" if token.empty?
|
|
17
|
+
|
|
18
|
+
@token = token
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call(request)
|
|
22
|
+
scheme, supplied = request.headers["authorization"].to_s.split(" ", 2)
|
|
23
|
+
|
|
24
|
+
unless valid_token?(scheme, supplied)
|
|
25
|
+
return Protocol::HTTP::Response[
|
|
26
|
+
401,
|
|
27
|
+
{"content-type" => "application/json", "www-authenticate" => "Bearer"},
|
|
28
|
+
[JSON.dump(error: {message: "Invalid API key", type: "authentication_error"})],
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
return @delegate.call(request)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def valid_token?(scheme, supplied)
|
|
38
|
+
return false unless scheme == "Bearer" && supplied
|
|
39
|
+
return false unless supplied.bytesize == @token.bytesize
|
|
40
|
+
|
|
41
|
+
OpenSSL.fixed_length_secure_compare(supplied, @token)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Laiya with ChatGPT Codex
|
|
2
|
+
|
|
3
|
+
This example uses `Laiya::Provider::Codex` to adapt Laiya's OpenAI Chat
|
|
4
|
+
Completions API to OpenAI's Codex Responses backend. It loads the ChatGPT login
|
|
5
|
+
from the Codex CLI's file-based credentials, refreshes credentials as needed,
|
|
6
|
+
and converts Responses events—including function calls—back to Chat Completions.
|
|
7
|
+
Tools are returned to the API caller; Laiya does not execute them.
|
|
8
|
+
|
|
9
|
+
This is an experimental, single-account integration with the Codex-specific
|
|
10
|
+
backend, not the public OpenAI Platform API. Backend behavior and supported
|
|
11
|
+
models may change. OpenAI documents ChatGPT authentication for local Codex
|
|
12
|
+
workflows and trusted private automation; do not run this as a public or
|
|
13
|
+
multi-user service.
|
|
14
|
+
|
|
15
|
+
## Prepare credentials
|
|
16
|
+
|
|
17
|
+
The Codex provider reads `CODEX_HOME/auth.json` (default `~/.codex/auth.json`).
|
|
18
|
+
It requires ChatGPT login credentials with access and refresh tokens. If the
|
|
19
|
+
Codex CLI currently stores credentials in the OS keyring, configure file-based
|
|
20
|
+
storage in `~/.codex/config.toml`:
|
|
21
|
+
|
|
22
|
+
```toml
|
|
23
|
+
cli_auth_credentials_store = "file"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then run `codex login` and verify `codex login status`. Laiya never prints or
|
|
27
|
+
logs the credential values. Treat `auth.json` as a password; on a remote host,
|
|
28
|
+
transfer it only through a secret manager or another trusted secure channel and
|
|
29
|
+
restrict its permissions to the service account.
|
|
30
|
+
|
|
31
|
+
## Run locally
|
|
32
|
+
|
|
33
|
+
Create a client key and start the service from the Laiya repository root:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
export LAIYA_API_KEY="$(openssl rand -hex 32)"
|
|
37
|
+
bundle install
|
|
38
|
+
bundle exec ruby examples/chatgpt/service.rb
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
By default it binds only to `127.0.0.1:9293`. Keep it local unless you put it
|
|
42
|
+
behind TLS and an access-controlled gateway. The service requires the caller to
|
|
43
|
+
send the `LAIYA_API_KEY` as a Bearer token; this is separate from the Codex
|
|
44
|
+
credentials used upstream.
|
|
45
|
+
|
|
46
|
+
`LAIYA_CHATGPT_MODEL` selects the model exposed by `GET /v1/models` and sent to
|
|
47
|
+
the Codex backend. It defaults to `gpt-6-luna`; set it to a model available to
|
|
48
|
+
your account. Set `CODEX_HOME` if the credential file is in a non-default
|
|
49
|
+
directory. `LAIYA_URL` can change the bind endpoint; do not bind publicly
|
|
50
|
+
without TLS and network access controls.
|
|
51
|
+
|
|
52
|
+
## Try it
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
curl http://127.0.0.1:9293/v1/chat/completions \
|
|
56
|
+
-H "authorization: Bearer $LAIYA_API_KEY" \
|
|
57
|
+
-H 'content-type: application/json' \
|
|
58
|
+
-d '{"model":"gpt-6-luna","messages":[{"role":"user","content":"Say hello"}]}'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For the native Responses endpoint, send `input` as message items (this is also
|
|
62
|
+
the format used when preserving tool-call history):
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
curl -sS http://127.0.0.1:9293/v1/responses \
|
|
66
|
+
-H "authorization: Bearer $LAIYA_API_KEY" \
|
|
67
|
+
-H 'content-type: application/json' \
|
|
68
|
+
-d '{"model":"gpt-6-luna","input":[{"role":"user","content":[{"type":"input_text","text":"Reply with exactly: Laiya connected"}]}]}'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use `/v1/responses` for Codex tool calling. Laiya preserves the Responses API
|
|
72
|
+
tool-call items and streaming events, so the client executes the tools and
|
|
73
|
+
sends `function_call_output` items in the next request. The text-only
|
|
74
|
+
`/v1/chat/completions` adapter rejects tool-enabled requests because converting
|
|
75
|
+
away Codex's encrypted reasoning state would break some tool continuations.
|
|
76
|
+
Send full Responses input history on each request; `previous_response_id` is not
|
|
77
|
+
available because the Codex backend is used statelessly.
|
|
78
|
+
|
|
79
|
+
## Connect OpenCode from another computer
|
|
80
|
+
|
|
81
|
+
Put the service behind TLS and an access-controlled network gateway, then add a
|
|
82
|
+
custom Responses-capable OpenAI provider to OpenCode. Keep the Laiya API key in
|
|
83
|
+
the client machine's secret configuration rather than committing it:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"$schema": "https://opencode.ai/config.json",
|
|
88
|
+
"provider": {
|
|
89
|
+
"laiya": {
|
|
90
|
+
"npm": "@ai-sdk/openai",
|
|
91
|
+
"name": "Laiya Codex",
|
|
92
|
+
"options": {
|
|
93
|
+
"baseURL": "https://laiya.example.com/v1",
|
|
94
|
+
"apiKey": "<LAIYA_API_KEY>"
|
|
95
|
+
},
|
|
96
|
+
"models": {
|
|
97
|
+
"gpt-6-luna": {"name": "GPT-6 Luna"}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"model": "laiya/gpt-6-luna"
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
OpenCode uses `/v1/responses` for this provider. The model can return tool calls,
|
|
106
|
+
which OpenCode executes on the client computer and reports back on subsequent
|
|
107
|
+
requests; Laiya never executes client tools.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Released under the MIT License.
|
|
5
|
+
# Copyright, 2026, by Samuel Williams.
|
|
6
|
+
|
|
7
|
+
require "async/http"
|
|
8
|
+
require "async/service"
|
|
9
|
+
require "async/service/managed/environment"
|
|
10
|
+
require "laiya"
|
|
11
|
+
require "laiya/environment/application"
|
|
12
|
+
require_relative "authentication"
|
|
13
|
+
|
|
14
|
+
configuration = Async::Service::Configuration.build do
|
|
15
|
+
service "laiya-chatgpt" do
|
|
16
|
+
include Laiya::Environment::Application
|
|
17
|
+
|
|
18
|
+
configuration do
|
|
19
|
+
Laiya::Configuration.build do |builder|
|
|
20
|
+
builder.provider :codex, Laiya::Provider::Codex.new
|
|
21
|
+
builder.model ENV.fetch("LAIYA_CHATGPT_MODEL", "gpt-6-luna"), provider: :codex
|
|
22
|
+
builder.default_provider :codex
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
application do
|
|
27
|
+
Examples::ChatGPT::Authentication.new(super(),
|
|
28
|
+
token: ENV.fetch("LAIYA_API_KEY"),
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Async::Service::Controller.run(configuration)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# ChatGPT Codex Provider
|
|
2
|
+
|
|
3
|
+
This guide explains how to run the experimental Codex provider with a ChatGPT login while keeping tool execution on the client.
|
|
4
|
+
|
|
5
|
+
## Authentication and Trust
|
|
6
|
+
|
|
7
|
+
The Codex provider reads ChatGPT credentials from
|
|
8
|
+
`CODEX_HOME/auth.json` (default `~/.codex/auth.json`) and refreshes tokens when
|
|
9
|
+
needed. If the Codex CLI uses an OS keyring, configure file-backed credentials
|
|
10
|
+
and run `codex login`:
|
|
11
|
+
|
|
12
|
+
```toml
|
|
13
|
+
cli_auth_credentials_store = "file"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Treat `auth.json` as a password. This provider calls the Codex-specific backend,
|
|
17
|
+
not the public OpenAI Platform API. It is experimental and intended for a
|
|
18
|
+
trusted, single-user service. Do not expose it to the public internet or an
|
|
19
|
+
untrusted multi-user environment.
|
|
20
|
+
|
|
21
|
+
## Start the Example
|
|
22
|
+
|
|
23
|
+
From the Laiya repository root:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
export LAIYA_API_KEY="$(openssl rand -hex 32)"
|
|
27
|
+
bundle exec ruby examples/chatgpt/service.rb
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The example binds to `127.0.0.1:9293`. It requires the client to send the
|
|
31
|
+
separate `LAIYA_API_KEY` as a Bearer token. For remote clients, use TLS and a
|
|
32
|
+
private access-controlled network. See the [example files](https://github.com/socketry/laiya/tree/main/examples/chatgpt).
|
|
33
|
+
|
|
34
|
+
## Client-Owned Tool Execution
|
|
35
|
+
|
|
36
|
+
Use `POST /v1/responses` for tool calling. Laiya preserves the Codex Responses
|
|
37
|
+
stream and response items. The client executes a returned function call and
|
|
38
|
+
sends its `function_call_output` back in the next request. Since Codex is used
|
|
39
|
+
statelessly, send the full input history, including the previous response's
|
|
40
|
+
reasoning and function-call items; `previous_response_id` is not supported.
|
|
41
|
+
|
|
42
|
+
The text-only `POST /v1/chat/completions` adapter rejects tool-enabled requests
|
|
43
|
+
so it cannot silently lose Codex reasoning state.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This guide explains how to install Laiya, configure a provider, and make an OpenAI-compatible request.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add Laiya to your application:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bundle add laiya
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configure a Provider
|
|
14
|
+
|
|
15
|
+
Laiya's Ruby API uses a `Laiya::Configuration` to connect model IDs to
|
|
16
|
+
providers. For an OpenAI Platform API key:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
require "laiya"
|
|
20
|
+
|
|
21
|
+
configuration = Laiya::Configuration.build do |builder|
|
|
22
|
+
builder.provider :openai, Laiya::Provider::OpenAI.new(
|
|
23
|
+
api_key: ENV.fetch("OPENAI_API_KEY")
|
|
24
|
+
)
|
|
25
|
+
builder.model "gpt-4.1-mini", provider: :openai
|
|
26
|
+
builder.default_provider :openai
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
application = Laiya::Web::Application.new(configuration: configuration)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The provider forwards `Protocol::HTTP::Request` and `Protocol::HTTP::Response`
|
|
33
|
+
objects using `Async::HTTP`. It does not parse OpenAI request or response bodies
|
|
34
|
+
in the direct proxy path.
|
|
35
|
+
|
|
36
|
+
For Ollama and multi-provider setups, see [Providers and Models](../providers-and-models/).
|
|
37
|
+
|
|
38
|
+
## Run the API Service
|
|
39
|
+
|
|
40
|
+
For a local OpenAI API proxy, set the API key and start Laiya:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
export OPENAI_API_KEY="..."
|
|
44
|
+
bundle exec bin/laiya
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The service listens on `http://localhost:9292` by default. Configure `LAIYA_URL`
|
|
48
|
+
to change the endpoint. See [HTTP API](../http-api/) for supported routes and
|
|
49
|
+
streaming behavior.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# HTTP API
|
|
2
|
+
|
|
3
|
+
This guide explains how Laiya's OpenAI-compatible HTTP API handles routing, proxying, and streaming.
|
|
4
|
+
|
|
5
|
+
## Run the Async Service
|
|
6
|
+
|
|
7
|
+
Laiya runs directly on `Async::HTTP::Server` under `Async::Service`; it does not
|
|
8
|
+
use Rack:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
bundle exec bin/laiya
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The default service listens on `http://localhost:9292`. `LAIYA_URL` configures
|
|
15
|
+
the endpoint. `bin/laiya` loads optional Ruby configuration paths supplied on
|
|
16
|
+
the command line before starting the service.
|
|
17
|
+
|
|
18
|
+
## Endpoints
|
|
19
|
+
|
|
20
|
+
The API accepts OpenAI-compatible `Protocol::HTTP::Request` objects and returns
|
|
21
|
+
`Protocol::HTTP::Response` objects. Supported common routes include:
|
|
22
|
+
|
|
23
|
+
- `GET /v1/models`
|
|
24
|
+
- `POST /v1/chat/completions`
|
|
25
|
+
- `POST /v1/responses` when the configured provider supports it
|
|
26
|
+
- Other OpenAI-compatible paths are forwarded to the selected provider.
|
|
27
|
+
|
|
28
|
+
Configured model routes produce a Laiya model catalog. When a provider is the
|
|
29
|
+
default and no model catalog is configured, `GET /v1/models` is forwarded
|
|
30
|
+
upstream. See [Providers and Models](../providers-and-models/) for discovery.
|
|
31
|
+
|
|
32
|
+
## Proxy and Model Routing
|
|
33
|
+
|
|
34
|
+
The built-in OpenAI provider is a direct HTTP proxy. It forwards the method,
|
|
35
|
+
path, headers, and request body, replacing authorization with its configured
|
|
36
|
+
upstream key and filtering hop-by-hop headers. It returns the upstream response
|
|
37
|
+
without parsing its body.
|
|
38
|
+
|
|
39
|
+
When a model route or discovery source is configured, Laiya buffers and rewinds
|
|
40
|
+
request bodies for known JSON completion endpoints to read the `model` field.
|
|
41
|
+
It forwards the original body unchanged. Streaming response bodies are not
|
|
42
|
+
buffered by the router.
|
|
43
|
+
|
|
44
|
+
## Streaming
|
|
45
|
+
|
|
46
|
+
OpenAI-compatible upstream SSE responses pass through unchanged. Translating
|
|
47
|
+
providers may adapt streaming formats. The Codex Responses endpoint passes
|
|
48
|
+
native Responses SSE through, including tool-call events; Laiya does not execute
|
|
49
|
+
client tools.
|
data/guides/index.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Guides
|
|
2
|
+
|
|
3
|
+
Use these guides to set up Laiya, route OpenAI-compatible requests to providers,
|
|
4
|
+
and run the HTTP API.
|
|
5
|
+
|
|
6
|
+
## Getting Started
|
|
7
|
+
|
|
8
|
+
- [Getting Started](getting-started/) — Install Laiya and make your first request.
|
|
9
|
+
|
|
10
|
+
## Configuration
|
|
11
|
+
|
|
12
|
+
- [Providers and Models](providers-and-models/) — Configure provider routes and discover upstream models.
|
|
13
|
+
- [HTTP API](http-api/) — Run the async service and understand proxy and streaming behavior.
|
|
14
|
+
- [ChatGPT Codex](chatgpt-codex/) — Use the experimental Codex provider with ChatGPT login credentials.
|