@adia-ai/llm 0.5.3 → 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 CHANGED
@@ -8,6 +8,49 @@ 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
+
42
+ ## [0.5.4] - 2026-05-14
43
+
44
+ ### Changed — lockstep ride-along; no source changes
45
+
46
+ `version`: `0.5.3` → `0.5.4`. Source byte-identical to `0.5.3`.
47
+ Tarball changes: none.
48
+
49
+ Tracking the v0.5.4 cycle's prop-fidelity arcs (§163 transpiler fix,
50
+ §164 chunk re-harvest, §165 validator check #19, §166a/b/c audit-script
51
+ family slots 12+13 + smoke shift-left): see root `CHANGELOG.md` for the
52
+ full narrative. This package carries no source changes this cycle.
53
+
11
54
  ## [0.5.3] - 2026-05-14
12
55
 
13
56
  _No pending 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') return new StubLLMAdapter();
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.3",
4
- "description": "Provider-agnostic LLM client 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.",
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",