@adia-ai/llm 0.5.4 → 0.5.5
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.
- package/CHANGELOG.md +31 -0
- package/README.md +25 -0
- package/llm-bridge.js +59 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
_No pending changes._
|
|
10
10
|
|
|
11
|
+
## [0.5.5] - 2026-05-14
|
|
12
|
+
|
|
13
|
+
### Removed — §188 (v0.5.5) — replace `handleLlmPassthrough` with exe.dev LLM Gateway
|
|
14
|
+
|
|
15
|
+
**-155 LOC** in `packages/llm/server.js` (692 → 537 lines).
|
|
16
|
+
|
|
17
|
+
Deleted:
|
|
18
|
+
|
|
19
|
+
- `LLM_PASSTHROUGH_UPSTREAMS` const + provider-specific adapter map
|
|
20
|
+
- `MODEL_ALLOWLIST` + `MAX_TOKENS_CEILING` env-driven guardrails
|
|
21
|
+
- `handleLlmPassthrough()` function (body parsing, model gating, header scrub, fetch + SSE stream pipe)
|
|
22
|
+
- `handleLlmHealthz()` function
|
|
23
|
+
- 2 route dispatchers (`/api/llm/*` + `/api/llm/healthz`)
|
|
24
|
+
|
|
25
|
+
These existed to proxy LLM API calls from the browser to upstream providers (Anthropic/OpenAI/Google) with API key injection, model allowlist enforcement, max_tokens ceiling, origin/referer scrub. The §181 implementation predated the discovery that exe.dev (the platform hosting `ui-kit.exe.xyz`) provides this natively at `http://169.254.169.254/gateway/llm/<provider>/<rest>`.
|
|
26
|
+
|
|
27
|
+
**Production deployment** routes `/api/llm/*` via Caddy reverse_proxy to the exe.dev gateway (see `infra/caddy/llm-gateway.snippet` + `docs/guides/production-llm-proxy-deployment.md`). The browser code path (`packages/llm/llm-bridge.js`) is unchanged — relative `/api/llm/anthropic/v1/messages` resolves identically; Caddy intercepts before this server sees the request.
|
|
28
|
+
|
|
29
|
+
**Local dev (`npm run dev`) unchanged**: Vite proxy at `vite.config.js:38-80` continues to use the developer's own `ANTHROPIC_API_KEY`. The gateway is link-local (`169.254.169.254`), VM-internal only; localhost dev doesn't reach it.
|
|
30
|
+
|
|
31
|
+
**What stays in `server.js`**: `/api/chat`, `/api/generate`, `/api/convert-html` — AdiaUI-specific business logic, not generic LLM proxying.
|
|
32
|
+
|
|
33
|
+
### Lost vs §181 (intentional)
|
|
34
|
+
|
|
35
|
+
- Model allowlist enforcement (Haiku-only floor at the proxy)
|
|
36
|
+
- `max_tokens` ceiling capping at the proxy
|
|
37
|
+
|
|
38
|
+
These were defenses against per-key API billing drain when AdiaUI owned the API key. With subscription-billed exe.dev gateway, the subscription IS the cost cap. Per user direction, accepted.
|
|
39
|
+
|
|
40
|
+
Commit `656b39dd1`. See root CHANGELOG and journal §181-§188 for the full architectural arc.
|
|
41
|
+
|
|
11
42
|
## [0.5.4] - 2026-05-14
|
|
12
43
|
|
|
13
44
|
### Changed — lockstep ride-along; no source changes
|
package/README.md
CHANGED
|
@@ -133,6 +133,31 @@ Either contract works — the client auto-detects which one your proxy
|
|
|
133
133
|
implements by URL shape. Pick the one that matches your existing
|
|
134
134
|
infrastructure.
|
|
135
135
|
|
|
136
|
+
#### Platform-native gateways (no proxy code needed)
|
|
137
|
+
|
|
138
|
+
If your VM runs on a managed platform that provides an **LLM Gateway**
|
|
139
|
+
natively (e.g. **exe.dev** at `http://169.254.169.254/gateway/llm/<provider>/<rest>`
|
|
140
|
+
on every VM, included in subscription), the cleanest production deploy
|
|
141
|
+
skips both contracts above and just **reverse-proxies `/api/llm/*`
|
|
142
|
+
through Caddy/nginx directly to the platform endpoint**. No API key
|
|
143
|
+
in app config, no hand-rolled proxy code, no per-cycle maintenance —
|
|
144
|
+
the platform handles auth, billing, model selection.
|
|
145
|
+
|
|
146
|
+
The AdiaUI repo demonstrates this pattern. `infra/caddy/llm-gateway.snippet`
|
|
147
|
+
holds a drop-in Caddyfile fragment for exe.dev VMs. The §188 (v0.5.5)
|
|
148
|
+
arc deleted ~155 LOC of hand-rolled passthrough from `server.js` in
|
|
149
|
+
favor of this approach. See `docs/specs/exe-dev-platform-reference.md`
|
|
150
|
+
+ `docs/guides/production-llm-proxy-deployment.md` for the full
|
|
151
|
+
recipe and `docs/journal/2026/05/2026-05-14.md §185-§188` for the
|
|
152
|
+
architectural migration arc.
|
|
153
|
+
|
|
154
|
+
**Stage 0b recon checklist**: before hand-rolling a proxy, check whether
|
|
155
|
+
your platform provides one natively. Look for `LLM Gateway` / `AI` /
|
|
156
|
+
`Inference` in the platform's docs. Other platforms with similar
|
|
157
|
+
gateways: Cloudflare AI Gateway, AWS Bedrock proxy, Azure OpenAI
|
|
158
|
+
private endpoints. Match shape; check pricing; prefer platform-native
|
|
159
|
+
over hand-rolled when available.
|
|
160
|
+
|
|
136
161
|
## Subpath exports
|
|
137
162
|
|
|
138
163
|
| Subpath | Purpose |
|
package/llm-bridge.js
CHANGED
|
@@ -55,6 +55,51 @@ function resolveBaseUrl(provider) {
|
|
|
55
55
|
return proxyMap[provider];
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* §181 (v0.5.5) — Is the browser running on a production host (not a
|
|
60
|
+
* local Vite dev server)? Used by createAdapter() to decide whether
|
|
61
|
+
* to construct a real bridge that routes through the same-origin proxy
|
|
62
|
+
* even when no API key is visible to the browser.
|
|
63
|
+
*/
|
|
64
|
+
function isProductionHost() {
|
|
65
|
+
if (!IS_BROWSER) return false;
|
|
66
|
+
const host = window.location?.hostname || '';
|
|
67
|
+
if (!host) return false;
|
|
68
|
+
if (host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0') return false;
|
|
69
|
+
if (host.endsWith('.local')) return false;
|
|
70
|
+
if (/^10\./.test(host)) return false;
|
|
71
|
+
if (/^192\.168\./.test(host)) return false;
|
|
72
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(host)) return false;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* §181 (v0.5.5) — Build a bridge for the production-browser case
|
|
78
|
+
* where there's no client-side API key but the page can reach a
|
|
79
|
+
* same-origin proxy at /api/llm/<provider>/<rest>. The proxy strips
|
|
80
|
+
* the incoming auth header and injects its own server-side key.
|
|
81
|
+
*
|
|
82
|
+
* Uses a sentinel "browser-uses-proxy" string for the apiKey so the
|
|
83
|
+
* underlying client passes its non-empty check. The sentinel never
|
|
84
|
+
* reaches the upstream provider — the proxy discards it.
|
|
85
|
+
*/
|
|
86
|
+
async function createBrowserProxyBridge(provider, modelOpt) {
|
|
87
|
+
const createClient = await getCreateClient();
|
|
88
|
+
if (!createClient) {
|
|
89
|
+
console.warn('LLM Bridge: LLM module not available. Using stub adapter.');
|
|
90
|
+
return new StubLLMAdapter();
|
|
91
|
+
}
|
|
92
|
+
const model = modelOpt || DEFAULT_MODELS[provider] || 'claude-haiku-4-5-20251001';
|
|
93
|
+
const proxyUrl = resolveBaseUrl(provider);
|
|
94
|
+
const client = createClient({
|
|
95
|
+
provider,
|
|
96
|
+
apiKey: 'browser-uses-server-side-proxy-key', // sentinel; proxy ignores it
|
|
97
|
+
model,
|
|
98
|
+
...(proxyUrl ? { proxyUrl } : {}),
|
|
99
|
+
});
|
|
100
|
+
return new AdiaUILLMBridge(client, model, provider);
|
|
101
|
+
}
|
|
102
|
+
|
|
58
103
|
// ── Factory ──────────────────────────────────────────────────────────────
|
|
59
104
|
|
|
60
105
|
/**
|
|
@@ -73,7 +118,20 @@ export async function createAdapter(opts = {}) {
|
|
|
73
118
|
const provider = opts.provider || getEnv('LLM_PROVIDER') || detectProvider();
|
|
74
119
|
const model = opts.model || getEnv('LLM_MODEL') || undefined;
|
|
75
120
|
|
|
76
|
-
if (provider === 'stub')
|
|
121
|
+
if (provider === 'stub') {
|
|
122
|
+
// §181 (v0.5.5) — browser on a production host: even though
|
|
123
|
+
// detectProvider() returned 'stub' (no env vars in the browser),
|
|
124
|
+
// the page can still make real LLM calls via the same-origin
|
|
125
|
+
// proxy at /api/llm/<provider>/<rest>. The proxy strips the
|
|
126
|
+
// incoming x-api-key / Authorization header and re-injects its
|
|
127
|
+
// own server-side key. The sentinel below is a non-empty
|
|
128
|
+
// placeholder so the bridge passes the !apiKey gate; it never
|
|
129
|
+
// reaches the upstream provider.
|
|
130
|
+
if (IS_BROWSER && isProductionHost()) {
|
|
131
|
+
return createBrowserProxyBridge('anthropic', opts.model);
|
|
132
|
+
}
|
|
133
|
+
return new StubLLMAdapter();
|
|
134
|
+
}
|
|
77
135
|
|
|
78
136
|
// Resolve API key for the detected provider
|
|
79
137
|
const apiKey = opts.apiKey || getEnv(`${provider.toUpperCase()}_API_KEY`) || getEnv('ANTHROPIC_API_KEY') || getEnv('OPENAI_API_KEY') || getEnv('GOOGLE_API_KEY');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/llm",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Provider-agnostic LLM client
|
|
3
|
+
"version": "0.5.5",
|
|
4
|
+
"description": "Provider-agnostic LLM client \u2014 anthropic / openai / gemini adapters with a unified chat() + streamChat() facade. Used by AdiaUI's chat-shell and the A2UI generation pipeline; works in browser (with proxyUrl) and Node.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.js",
|