Repository navigation
Expand file tree
/
Copy pathsandboxed-coding-clients.yaml
More file actions
189 lines (170 loc) · 7 KB
/
Copy pathsandboxed-coding-clients.yaml
File metadata and controls
189 lines (170 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# OpenShell Sandboxed Coding Clients -> Native vLLM
# Requires `--features openai-agentic-loop,store-sqlite` because the Codex chain's filters are opt-in.
#
# One host-side gateway for Codex, Claude Code, OpenCode, and pi running inside
# NVIDIA OpenShell sandboxes, each over its native wire API, with Codex
# client-tool compatibility applied to its tool payloads.
#
# Claude Code -> :8080 POST /v1/messages -> vLLM (native Anthropic Messages)
# OpenCode -> :8080 POST /v1/chat/completions -> vLLM (native Chat Completions)
# pi -> :8080 POST /v1/chat/completions -> vLLM (native Chat Completions)
# Codex -> :8081 POST /v1/responses -> vLLM (native Responses)
#
# The 8080 chain is `anthropic/messages-native-vllm.yaml`
# and the 8081 chain is `openai/responses/client-tool-compat.yaml`, each behind
# the same `basic_auth` gate. See
# `docs/developing/openshell-cli-vllm-through-praxis.md` for the sandbox side.
#
# Why both listeners bind 0.0.0.0: OpenShell never authorizes sandbox egress to
# loopback, so sandboxes reach this gateway at `host.openshell.internal`, which
# resolves to the host's container-bridge address. A `127.0.0.1` listener is
# unreachable from there. Firewall 8080 and 8081 from external networks, or
# bind the bridge address instead of 0.0.0.0.
#
# Credential isolation (three credentials, each at its own boundary):
# - The sandbox never holds the gateway password. An OpenShell provider owns
# `GATEWAY_AUTH_PASSWORD`, the harness sees only a placeholder, and the
# OpenShell proxy substitutes the real value inside the `Authorization:
# Basic` header on its way to this gateway.
# - `basic_auth` verifies that credential and strips it on both listeners.
# - `credential_injection` adds the server-owned `VLLM_API_KEY` Bearer
# toward vLLM. That key never enters a sandbox.
#
# Set `GATEWAY_AUTH_PASSWORD` and `VLLM_API_KEY` in the proxy's environment;
# both are resolved at pipeline build time.
listeners:
# Claude Code (/v1/messages), OpenCode and pi (/v1/chat/completions).
- name: messages-chat-gateway
address: "0.0.0.0:8080"
filter_chains: [sandboxed-messages-chat]
# Codex (/v1/responses). Its client tools need the iterative router, which
# would buffer the other clients' streams, so it gets a listener of its own.
- name: responses-gateway
address: "0.0.0.0:8081"
filter_chains: [sandboxed-codex-responses]
filter_chains:
- name: sandboxed-messages-chat
filters:
# Authenticate the sandboxed client before anything else and strip the
# gateway `Authorization` so it never reaches vLLM.
- filter: basic_auth
realm: "praxis-openshell-gateway"
strip_authorization: true
credentials:
- username: gateway
env_var: GATEWAY_AUTH_PASSWORD
# Classify from the request head so each protocol gets the error
# envelope its SDK parses, including OpenCode and pi Chat Completions.
- filter: ai_operation
# Validate the create-message envelope; bodyless probes such as
# `GET /v1/models` are released untouched.
- filter: anthropic_messages_request
on_invalid: reject
# `anthropic-version` belongs on the Anthropic surface only.
- filter: anthropic_messages_protocol
default_version: "2023-06-01"
conditions:
- when:
path_prefix: "/v1/messages"
# A caller's Anthropic key never reaches vLLM.
- filter: headers
request_remove:
- x-api-key
- filter: router
routes:
- path: "/v1/messages"
cluster: "vllm-backend"
- path: "/v1/messages/count_tokens"
cluster: "vllm-backend"
- path: "/v1/models"
cluster: "vllm-backend"
- path: "/v1/chat/completions"
cluster: "vllm-backend"
- path_prefix: "/"
cluster: "vllm-backend"
- filter: credential_injection
clusters:
- name: vllm-backend
header: Authorization
env_var: VLLM_API_KEY
header_prefix: "Bearer "
strip_client_credential: true
- filter: load_balancer
clusters:
- name: "vllm-backend"
endpoints:
- "127.0.0.1:8000"
- name: sandboxed-codex-responses
filters:
# The same gateway credential gates the Codex listener.
- filter: basic_auth
realm: "praxis-openshell-gateway"
strip_authorization: true
credentials:
- username: gateway
env_var: GATEWAY_AUTH_PASSWORD
# Publishes the typed operation consumed by openai_responses_request.
- filter: ai_operation
- filter: openai_responses_request
initialize_state: false
on_invalid: reject
headers:
format: x-praxis-ai-format
model: x-praxis-ai-model
stream: x-praxis-ai-stream
- filter: openai_responses_request
on_invalid: reject
headers:
format: ~
model: ~
stream: ~
mode: ~
# Single-tenant demo owner for persisted Responses state. A shared
# gateway would use `mode: trusted_headers` instead.
- filter: state_owner
mode: single_tenant
tenant_id: default
# From the container image, use an absolute path under the writable
# `/var/lib/praxis` state directory instead.
- filter: openai_responses_store
backend: sqlite
database_url: "sqlite://responses.db?mode=rwc"
responses_table: openai_responses
conversations_table: openai_conversations
- filter: openai_responses_rehydrate
- filter: iterative_request_router
initial_step: inference
max_iterations: 4
steps:
- name: inference
filters:
- filter: openai_stream_events
- filter: openai_agentic_loop
max_infer_iters: 4
- filter: openai_client_tool_compat
max_rewritten_body_bytes: 67108864
max_client_tools: 512
- filter: openai_responses_proxy
- filter: router
routes:
- path_prefix: "/"
cluster: "vllm-responses-backend"
# `basic_auth` stripped the caller's `Authorization`, so the
# sub-request reaches keyed vLLM unauthenticated without this.
- filter: credential_injection
clusters:
- name: vllm-responses-backend
header: Authorization
env_var: VLLM_API_KEY
header_prefix: "Bearer "
strip_client_credential: true
- filter: load_balancer
clusters:
- name: "vllm-responses-backend"
endpoints:
- "127.0.0.1:8000"
on_result:
- default: true
done: true
insecure_options:
allow_private_endpoints: true # gateway proxies to a local vLLM backend