@adia-ai/llm 0.6.0 → 0.6.1

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/index.js CHANGED
@@ -1,23 +1,14 @@
1
1
  /**
2
- * @adia-ai/llm — provider-agnostic LLM client.
2
+ * @adia-ai/llm — provider-agnostic LLM client for AdiaUI.
3
3
  *
4
- * Re-exports the adapters facade so `@adia-ai/llm` is the single entry
5
- * point for chat-shell, the a2ui generation pipeline, and any other
6
- * consumer that needs to talk to anthropic / openai / gemini.
7
- *
8
- * import { chat, streamChat, createClient } from '@adia-ai/llm';
9
- * import { MODELS, DEFAULT_MODEL } from '@adia-ai/llm/models';
10
- * import { createAdapter } from '@adia-ai/llm/bridge';
11
- * import { StubLLMAdapter } from '@adia-ai/llm/stub';
4
+ * Re-exports the public API surface:
5
+ * - chat, streamChat, createClient — standalone functions + factory
6
+ * - MODELS, DEFAULT_MODEL — model catalog
7
+ * - StubLLMAdapter — deterministic test stub
8
+ * - createAdapter — AdiaUI pipeline bridge
12
9
  */
13
-
14
- export {
15
- chat,
16
- streamChat,
17
- createClient,
18
- } from './adapters/index.js';
19
-
20
- export {
21
- MODELS,
22
- DEFAULT_MODEL,
23
- } from './models.js';
10
+ export { chat, streamChat, createClient } from './adapters/index.js';
11
+ export { MODELS, DEFAULT_MODEL } from './models.js';
12
+ export { StubLLMAdapter } from './llm-stub.js';
13
+ export { createAdapter } from './llm-bridge.js';
14
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * LLM Bridge — Wraps AdiaUI's llm module into the AdiaUI createAdapter() API.
3
+ *
4
+ * This is the single integration point between the AdiaUI pipeline and the
5
+ * LLM module. It handles:
6
+ * - Env var reading (VITE_* in browser, process.env in Node)
7
+ * - CORS proxy routing in browser (Vite dev server at /api/llm/*)
8
+ * - API translation (AdiaUI's simple { messages, systemPrompt } → llm module's interface)
9
+ *
10
+ * Consumers call createAdapter() and get an object with .complete() and .stream()
11
+ * matching the AdiaUI pipeline interface.
12
+ */
13
+ import { StubLLMAdapter } from './llm-stub.js';
14
+ import type { LLMClient } from './adapters/index.js';
15
+ /**
16
+ * Create an LLM adapter for the AdiaUI pipeline.
17
+ *
18
+ * Auto-detects provider from env vars. Returns an object with .complete()
19
+ * and .stream() that match the AdiaUI interface (simple messages + systemPrompt).
20
+ *
21
+ * @param opts
22
+ * @param opts.provider — 'anthropic' | 'openai' | 'google' | 'stub'
23
+ * @param opts.apiKey — explicit API key (overrides env)
24
+ * @param opts.model — model override
25
+ * @returns {StubLLMAdapter | AdiaUILLMBridge}
26
+ */
27
+ export declare function createAdapter(opts?: {
28
+ provider?: string;
29
+ apiKey?: string;
30
+ model?: string;
31
+ }): Promise<StubLLMAdapter | AdiaUILLMBridge>;
32
+ /**
33
+ * Wraps the AdiaUI llm client to match the AdiaUI pipeline's simpler interface.
34
+ *
35
+ * AdiaUI calls: adapter.complete({ messages, systemPrompt })
36
+ * LLM module expects: client.chat({ model, messages, system, ... })
37
+ */
38
+ declare class AdiaUILLMBridge {
39
+ #private;
40
+ constructor(client: LLMClient, model: string, provider: string);
41
+ /**
42
+ * Non-streaming completion. Matches AdiaUI interface.
43
+ *
44
+ * 32k max_tokens: A2UI JSON for moderately complex UIs (kanban, dashboard,
45
+ * pricing table) routinely exceeds 8k. Truncation produced silent fallbacks
46
+ * that the validator rubber-stamped at ~89/100 — see diagnosis report
47
+ * 2026-04-19. Modern Claude/GPT/Gemini all support ≥32k output cleanly.
48
+ *
49
+ * @param opts
50
+ * @param opts.messages
51
+ * @param opts.systemPrompt
52
+ */
53
+ complete({ messages, systemPrompt }: {
54
+ messages: Array<{
55
+ role: string;
56
+ content: string;
57
+ }>;
58
+ systemPrompt?: string;
59
+ }): Promise<{
60
+ content: string;
61
+ stopReason: string;
62
+ usage: {
63
+ inputTokens: number;
64
+ outputTokens: number;
65
+ cacheCreationTokens: number;
66
+ cacheReadTokens: number;
67
+ };
68
+ }>;
69
+ /**
70
+ * Streaming completion. Matches AdiaUI interface.
71
+ *
72
+ * @param opts
73
+ * @param opts.messages
74
+ * @param opts.systemPrompt
75
+ * @yields {{ type: 'text', content: string } | { type: 'done', stopReason: string, usage: { inputTokens: number, outputTokens: number, cacheCreationTokens: number, cacheReadTokens: number } }}
76
+ */
77
+ stream({ messages, systemPrompt }: {
78
+ messages: Array<{
79
+ role: string;
80
+ content: string;
81
+ }>;
82
+ systemPrompt?: string;
83
+ }): AsyncGenerator<{
84
+ type: 'text';
85
+ content: string;
86
+ } | {
87
+ type: 'done';
88
+ stopReason: string;
89
+ usage: {
90
+ inputTokens: number;
91
+ outputTokens: number;
92
+ cacheCreationTokens: number;
93
+ cacheReadTokens: number;
94
+ };
95
+ }>;
96
+ /** Expose the underlying client for advanced use. */
97
+ get adapter(): LLMClient;
98
+ /** Expose provider name for detection. */
99
+ get provider(): string;
100
+ }
101
+ export {};
102
+ //# sourceMappingURL=llm-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-bridge.d.ts","sourceRoot":"","sources":["src/llm-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,qBAAqB,CAAC;AA2F/D;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,cAAc,GAAG,eAAe,CAAC,CA+DhJ;AAkBD;;;;;GAKG;AACH,cAAM,eAAe;;gBAKP,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAM9D;;;;;;;;;;;OAWG;IACG,QAAQ,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE;QAAE,QAAQ,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,mBAAmB,EAAE,MAAM,CAAC;YAAC,eAAe,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAsBvR;;;;;;;OAOG;IACI,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE;QAAE,QAAQ,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,cAAc,CACpI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GACjC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,mBAAmB,EAAE,MAAM,CAAC;YAAC,eAAe,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CACnJ;IA8BD,qDAAqD;IACrD,IAAI,OAAO,cAA2B;IAEtC,0CAA0C;IAC1C,IAAI,QAAQ,WAA6B;CAC1C"}
package/llm-bridge.js CHANGED
@@ -10,51 +10,48 @@
10
10
  * Consumers call createAdapter() and get an object with .complete() and .stream()
11
11
  * matching the AdiaUI pipeline interface.
12
12
  */
13
-
14
13
  import { StubLLMAdapter } from './llm-stub.js';
15
-
16
14
  // Lazy-loaded — ../llm/index.js uses Vite aliases that don't resolve in Node
17
15
  let _createClient = null;
18
16
  async function getCreateClient() {
19
- if (!_createClient) {
20
- try {
21
- const mod = await import('./adapters/index.js');
22
- _createClient = mod.createClient;
23
- } catch {
24
- _createClient = null;
17
+ if (!_createClient) {
18
+ try {
19
+ const mod = await import('./adapters/index.js');
20
+ _createClient = mod.createClient;
21
+ }
22
+ catch {
23
+ _createClient = null;
24
+ }
25
25
  }
26
- }
27
- return _createClient;
26
+ return _createClient;
28
27
  }
29
-
30
28
  // ── Environment ──────────────────────────────────────────────────────────
31
-
32
29
  function getEnv(key) {
33
- try {
34
- const env = import.meta.env;
35
- if (env) {
36
- const val = env[`VITE_${key}`] || env[key];
37
- if (val) return val;
30
+ try {
31
+ const env = import.meta.env;
32
+ if (env) {
33
+ const val = env[`VITE_${key}`] || env[key];
34
+ if (val)
35
+ return val;
36
+ }
38
37
  }
39
- } catch {}
40
- if (typeof process !== 'undefined' && process.env) {
41
- return process.env[key] || '';
42
- }
43
- return '';
38
+ catch { }
39
+ if (typeof process !== 'undefined' && process.env) {
40
+ return process.env[key] || '';
41
+ }
42
+ return '';
44
43
  }
45
-
46
44
  const IS_BROWSER = typeof window !== 'undefined';
47
-
48
45
  function resolveBaseUrl(provider) {
49
- if (!IS_BROWSER) return undefined; // Let the module use its defaults
50
- const proxyMap = {
51
- anthropic: '/api/llm/anthropic/v1/messages',
52
- openai: '/api/llm/openai/v1/chat/completions',
53
- google: '/api/llm/google',
54
- };
55
- return proxyMap[provider];
46
+ if (!IS_BROWSER)
47
+ return undefined; // Let the module use its defaults
48
+ const proxyMap = {
49
+ anthropic: '/api/llm/anthropic/v1/messages',
50
+ openai: '/api/llm/openai/v1/chat/completions',
51
+ google: '/api/llm/google',
52
+ };
53
+ return proxyMap[provider];
56
54
  }
57
-
58
55
  /**
59
56
  * §181 (v0.5.5) — Is the browser running on a production host (not a
60
57
  * local Vite dev server)? Used by createAdapter() to decide whether
@@ -62,17 +59,23 @@ function resolveBaseUrl(provider) {
62
59
  * even when no API key is visible to the browser.
63
60
  */
64
61
  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;
62
+ if (!IS_BROWSER)
63
+ return false;
64
+ const host = window.location?.hostname || '';
65
+ if (!host)
66
+ return false;
67
+ if (host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0')
68
+ return false;
69
+ if (host.endsWith('.local'))
70
+ return false;
71
+ if (/^10\./.test(host))
72
+ return false;
73
+ if (/^192\.168\./.test(host))
74
+ return false;
75
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(host))
76
+ return false;
77
+ return true;
74
78
  }
75
-
76
79
  /**
77
80
  * §181 (v0.5.5) — Build a bridge for the production-browser case
78
81
  * where there's no client-side API key but the page can reach a
@@ -84,117 +87,102 @@ function isProductionHost() {
84
87
  * reaches the upstream provider — the proxy discards it.
85
88
  */
86
89
  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);
90
+ const createClient = await getCreateClient();
91
+ if (!createClient) {
92
+ console.warn('LLM Bridge: LLM module not available. Using stub adapter.');
93
+ return new StubLLMAdapter();
94
+ }
95
+ const model = modelOpt || DEFAULT_MODELS[provider] || 'claude-haiku-4-5-20251001';
96
+ const proxyUrl = resolveBaseUrl(provider);
97
+ const client = createClient({
98
+ provider,
99
+ apiKey: 'browser-uses-server-side-proxy-key', // sentinel; proxy ignores it
100
+ model,
101
+ ...(proxyUrl ? { proxyUrl } : {}),
102
+ });
103
+ return new AdiaUILLMBridge(client, model, provider);
101
104
  }
102
-
103
105
  // ── Factory ──────────────────────────────────────────────────────────────
104
-
105
106
  /**
106
107
  * Create an LLM adapter for the AdiaUI pipeline.
107
108
  *
108
109
  * Auto-detects provider from env vars. Returns an object with .complete()
109
110
  * and .stream() that match the AdiaUI interface (simple messages + systemPrompt).
110
111
  *
111
- * @param {object} [opts]
112
- * @param {string} [opts.provider] — 'anthropic' | 'openai' | 'google' | 'stub'
113
- * @param {string} [opts.apiKey] — explicit API key (overrides env)
114
- * @param {string} [opts.model] — model override
112
+ * @param opts
113
+ * @param opts.provider — 'anthropic' | 'openai' | 'google' | 'stub'
114
+ * @param opts.apiKey — explicit API key (overrides env)
115
+ * @param opts.model — model override
115
116
  * @returns {StubLLMAdapter | AdiaUILLMBridge}
116
117
  */
117
118
  export async function createAdapter(opts = {}) {
118
- const provider = opts.provider || getEnv('LLM_PROVIDER') || detectProvider();
119
- const model = opts.model || getEnv('LLM_MODEL') || undefined;
120
-
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);
119
+ const provider = opts.provider || getEnv('LLM_PROVIDER') || detectProvider();
120
+ const model = opts.model || getEnv('LLM_MODEL') || undefined;
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
+ }
135
+ // Resolve API key for the detected provider
136
+ const apiKey = opts.apiKey || getEnv(`${provider.toUpperCase()}_API_KEY`) || getEnv('ANTHROPIC_API_KEY') || getEnv('OPENAI_API_KEY') || getEnv('GOOGLE_API_KEY');
137
+ // Browser-only safety reminder: when running through the dev-server's
138
+ // passthrough proxy, the API key is sent verbatim in `x-api-key` /
139
+ // `Authorization` headers from the browser. Anyone with DevTools open
140
+ // can read it from the Network panel — fine for local dev, **never**
141
+ // deploy this shape. We log once per session (deduplicated via a
142
+ // window-scoped flag) when a key is present and we're in the browser.
143
+ // Journal entry 2026-05-11 §22 documents the root cause.
144
+ if (IS_BROWSER && apiKey && typeof window !== 'undefined' && !window.__adia_llm_key_warning_shown) {
145
+ window.__adia_llm_key_warning_shown = true;
146
+ const masked = apiKey.length > 12 ? `${apiKey.slice(0, 8)}…${apiKey.slice(-4)}` : '••••';
147
+ console.warn(`%c[@adia-ai/llm] %cAPI key in browser:%c ${provider} key (${masked}) will be sent in request headers via the Vite passthrough proxy. ` +
148
+ `Anyone with DevTools access on this dev server can read it. Local-dev only — never deploy this shape. ` +
149
+ `Rotate at the provider console if this transcript leaves your machine.`, 'color: #a78bfa; font-weight: bold;', 'color: #f59e0b; font-weight: bold;', 'color: inherit;');
132
150
  }
133
- return new StubLLMAdapter();
134
- }
135
-
136
- // Resolve API key for the detected provider
137
- const apiKey = opts.apiKey || getEnv(`${provider.toUpperCase()}_API_KEY`) || getEnv('ANTHROPIC_API_KEY') || getEnv('OPENAI_API_KEY') || getEnv('GOOGLE_API_KEY');
138
-
139
- // Browser-only safety reminder: when running through the dev-server's
140
- // passthrough proxy, the API key is sent verbatim in `x-api-key` /
141
- // `Authorization` headers from the browser. Anyone with DevTools open
142
- // can read it from the Network panel — fine for local dev, **never**
143
- // deploy this shape. We log once per session (deduplicated via a
144
- // window-scoped flag) when a key is present and we're in the browser.
145
- // Journal entry 2026-05-11 §22 documents the root cause.
146
- if (IS_BROWSER && apiKey && typeof window !== 'undefined' && !window.__adia_llm_key_warning_shown) {
147
- window.__adia_llm_key_warning_shown = true;
148
- const masked = apiKey.length > 12 ? `${apiKey.slice(0, 8)}…${apiKey.slice(-4)}` : '••••';
149
- console.warn(
150
- `%c[@adia-ai/llm] %cAPI key in browser:%c ${provider} key (${masked}) will be sent in request headers via the Vite passthrough proxy. ` +
151
- `Anyone with DevTools access on this dev server can read it. Local-dev only — never deploy this shape. ` +
152
- `Rotate at the provider console if this transcript leaves your machine.`,
153
- 'color: #a78bfa; font-weight: bold;',
154
- 'color: #f59e0b; font-weight: bold;',
155
- 'color: inherit;'
156
- );
157
- }
158
-
159
- // No key found → fall back to stub
160
- if (!apiKey) {
161
- console.warn('LLM Bridge: No API keys found. Using stub adapter.');
162
- return new StubLLMAdapter();
163
- }
164
-
165
- const createClient = await getCreateClient();
166
- if (!createClient) {
167
- console.warn('LLM Bridge: LLM module not available. Using stub adapter.');
168
- return new StubLLMAdapter();
169
- }
170
-
171
- const proxyUrl = resolveBaseUrl(provider);
172
- const client = createClient({
173
- provider,
174
- apiKey,
175
- model: model || DEFAULT_MODELS[provider] || 'claude-sonnet-4-20250514',
176
- ...(proxyUrl ? { proxyUrl } : {}),
177
- });
178
-
179
- return new AdiaUILLMBridge(client, model || DEFAULT_MODELS[provider] || 'claude-sonnet-4-20250514', provider);
151
+ // No key found → fall back to stub
152
+ if (!apiKey) {
153
+ console.warn('LLM Bridge: No API keys found. Using stub adapter.');
154
+ return new StubLLMAdapter();
155
+ }
156
+ const createClient = await getCreateClient();
157
+ if (!createClient) {
158
+ console.warn('LLM Bridge: LLM module not available. Using stub adapter.');
159
+ return new StubLLMAdapter();
160
+ }
161
+ const proxyUrl = resolveBaseUrl(provider);
162
+ const client = createClient({
163
+ provider,
164
+ apiKey,
165
+ model: model || DEFAULT_MODELS[provider] || 'claude-sonnet-4-20250514',
166
+ ...(proxyUrl ? { proxyUrl } : {}),
167
+ });
168
+ return new AdiaUILLMBridge(client, model || DEFAULT_MODELS[provider] || 'claude-sonnet-4-20250514', provider);
180
169
  }
181
-
182
170
  function detectProvider() {
183
- if (getEnv('ANTHROPIC_API_KEY')) return 'anthropic';
184
- if (getEnv('OPENAI_API_KEY')) return 'openai';
185
- if (getEnv('GOOGLE_API_KEY')) return 'google';
186
- return 'stub';
171
+ if (getEnv('ANTHROPIC_API_KEY'))
172
+ return 'anthropic';
173
+ if (getEnv('OPENAI_API_KEY'))
174
+ return 'openai';
175
+ if (getEnv('GOOGLE_API_KEY'))
176
+ return 'google';
177
+ return 'stub';
187
178
  }
188
-
189
179
  // ── Bridge class ─────────────────────────────────────────────────────────
190
-
191
180
  /** Default models per provider */
192
181
  const DEFAULT_MODELS = {
193
- anthropic: 'claude-sonnet-4-20250514',
194
- openai: 'gpt-4o',
195
- google: 'gemini-2.0-flash',
182
+ anthropic: 'claude-sonnet-4-20250514',
183
+ openai: 'gpt-4o',
184
+ google: 'gemini-2.0-flash',
196
185
  };
197
-
198
186
  /**
199
187
  * Wraps the AdiaUI llm client to match the AdiaUI pipeline's simpler interface.
200
188
  *
@@ -202,91 +190,88 @@ const DEFAULT_MODELS = {
202
190
  * LLM module expects: client.chat({ model, messages, system, ... })
203
191
  */
204
192
  class AdiaUILLMBridge {
205
- #client;
206
- #model;
207
- #provider;
208
-
209
- constructor(client, model, provider) {
210
- this.#client = client;
211
- this.#model = model;
212
- this.#provider = provider;
213
- }
214
-
215
- /**
216
- * Non-streaming completion. Matches AdiaUI interface.
217
- *
218
- * 32k max_tokens: A2UI JSON for moderately complex UIs (kanban, dashboard,
219
- * pricing table) routinely exceeds 8k. Truncation produced silent fallbacks
220
- * that the validator rubber-stamped at ~89/100 — see diagnosis report
221
- * 2026-04-19. Modern Claude/GPT/Gemini all support ≥32k output cleanly.
222
- *
223
- * @param {{ messages: { role: string, content: string }[], systemPrompt?: string }} opts
224
- * @returns {Promise<{ content: string, stopReason: string, usage: { inputTokens: number, outputTokens: number } }>}
225
- */
226
- async complete({ messages, systemPrompt }) {
227
- const response = await this.#client.chat({
228
- model: this.#model,
229
- messages,
230
- system: systemPrompt,
231
- maxTokens: 32768,
232
- // Anthropic-only: mark the system prompt as a cache breakpoint. No-op
233
- // on other providers (unknown opt silently ignored) and no-op below the
234
- // model's minimum cacheable size.
235
- cache: this.#provider === 'anthropic',
236
- });
237
- return {
238
- content: response.text,
239
- // 'max_tokens' / 'length' / 'MAX_TOKENS' (Gemini) signal truncation;
240
- // downstream parser uses this to refuse silent fallback rendering.
241
- stopReason: response.stopReason ?? 'end',
242
- usage: {
243
- inputTokens: response.usage?.input ?? 0,
244
- outputTokens: response.usage?.output ?? 0,
245
- cacheCreationTokens: response.usage?.cacheCreation ?? 0,
246
- cacheReadTokens: response.usage?.cacheRead ?? 0,
247
- },
248
- };
249
- }
250
-
251
- /**
252
- * Streaming completion. Matches AdiaUI interface.
253
- *
254
- * @param {{ messages: { role: string, content: string }[], systemPrompt?: string }} opts
255
- * @yields {{ type: 'text', content: string } | { type: 'done', stopReason: string, usage: { inputTokens: number, outputTokens: number, cacheCreationTokens: number, cacheReadTokens: number } }}
256
- */
257
- async *stream({ messages, systemPrompt }) {
258
- for await (const chunk of this.#client.stream({
259
- model: this.#model,
260
- messages,
261
- system: systemPrompt,
262
- maxTokens: 32768,
263
- cache: this.#provider === 'anthropic',
264
- })) {
265
- if (chunk.type === 'text') {
266
- yield { type: 'text', content: chunk.text };
267
- } else if (chunk.type === 'done') {
268
- // Surface the terminal stopReason + cache telemetry so the consumer
269
- // can detect max_tokens truncation and the dialog recorder can log
270
- // cache hit-rate per turn.
271
- yield {
272
- type: 'done',
273
- stopReason: chunk.stopReason ?? 'end',
274
- usage: {
275
- inputTokens: chunk.usage?.input ?? 0,
276
- outputTokens: chunk.usage?.output ?? 0,
277
- cacheCreationTokens: chunk.usage?.cacheCreation ?? 0,
278
- cacheReadTokens: chunk.usage?.cacheRead ?? 0,
279
- },
193
+ #client;
194
+ #model;
195
+ #provider;
196
+ constructor(client, model, provider) {
197
+ this.#client = client;
198
+ this.#model = model;
199
+ this.#provider = provider;
200
+ }
201
+ /**
202
+ * Non-streaming completion. Matches AdiaUI interface.
203
+ *
204
+ * 32k max_tokens: A2UI JSON for moderately complex UIs (kanban, dashboard,
205
+ * pricing table) routinely exceeds 8k. Truncation produced silent fallbacks
206
+ * that the validator rubber-stamped at ~89/100 see diagnosis report
207
+ * 2026-04-19. Modern Claude/GPT/Gemini all support ≥32k output cleanly.
208
+ *
209
+ * @param opts
210
+ * @param opts.messages
211
+ * @param opts.systemPrompt
212
+ */
213
+ async complete({ messages, systemPrompt }) {
214
+ const response = await this.#client.chat({
215
+ model: this.#model,
216
+ messages,
217
+ ...(systemPrompt != null ? { system: systemPrompt } : {}),
218
+ maxTokens: 32768,
219
+ cache: this.#provider === 'anthropic',
220
+ });
221
+ return {
222
+ content: response.text,
223
+ // 'max_tokens' / 'length' / 'MAX_TOKENS' (Gemini) signal truncation;
224
+ // downstream parser uses this to refuse silent fallback rendering.
225
+ stopReason: response.stopReason ?? 'end',
226
+ usage: {
227
+ inputTokens: response.usage?.input ?? 0,
228
+ outputTokens: response.usage?.output ?? 0,
229
+ cacheCreationTokens: response.usage?.cacheCreation ?? 0,
230
+ cacheReadTokens: response.usage?.cacheRead ?? 0,
231
+ },
280
232
  };
281
- }
282
- // Other chunk types (thinking, error) are still available on the
283
- // underlying adapter but the AdiaUI pipeline doesn't consume them yet.
284
233
  }
285
- }
286
-
287
- /** Expose the underlying client for advanced use. */
288
- get adapter() { return this.#client; }
289
-
290
- /** Expose provider name for detection. */
291
- get provider() { return this.#provider; }
234
+ /**
235
+ * Streaming completion. Matches AdiaUI interface.
236
+ *
237
+ * @param opts
238
+ * @param opts.messages
239
+ * @param opts.systemPrompt
240
+ * @yields {{ type: 'text', content: string } | { type: 'done', stopReason: string, usage: { inputTokens: number, outputTokens: number, cacheCreationTokens: number, cacheReadTokens: number } }}
241
+ */
242
+ async *stream({ messages, systemPrompt }) {
243
+ for await (const chunk of this.#client.stream({
244
+ model: this.#model,
245
+ messages,
246
+ ...(systemPrompt != null ? { system: systemPrompt } : {}),
247
+ maxTokens: 32768,
248
+ cache: this.#provider === 'anthropic',
249
+ })) {
250
+ if (chunk.type === 'text') {
251
+ yield { type: 'text', content: chunk.text };
252
+ }
253
+ else if (chunk.type === 'done') {
254
+ // Surface the terminal stopReason + cache telemetry so the consumer
255
+ // can detect max_tokens truncation and the dialog recorder can log
256
+ // cache hit-rate per turn.
257
+ yield {
258
+ type: 'done',
259
+ stopReason: chunk.stopReason ?? 'end',
260
+ usage: {
261
+ inputTokens: chunk.usage?.input ?? 0,
262
+ outputTokens: chunk.usage?.output ?? 0,
263
+ cacheCreationTokens: chunk.usage?.cacheCreation ?? 0,
264
+ cacheReadTokens: chunk.usage?.cacheRead ?? 0,
265
+ },
266
+ };
267
+ }
268
+ // Other chunk types (thinking, error) are still available on the
269
+ // underlying adapter but the AdiaUI pipeline doesn't consume them yet.
270
+ }
271
+ }
272
+ /** Expose the underlying client for advanced use. */
273
+ get adapter() { return this.#client; }
274
+ /** Expose provider name for detection. */
275
+ get provider() { return this.#provider; }
292
276
  }
277
+ //# sourceMappingURL=llm-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-bridge.js","sourceRoot":"","sources":["src/llm-bridge.ts"],"names":[],"mappings":"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"}