@volter/twin-openrouter 0.1.0

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.
Files changed (56) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +131 -0
  3. package/client/openrouter-mirror.css +131 -0
  4. package/client/openrouter-mirror.tsx +221 -0
  5. package/dist/client/openrouter-mirror.bundle.js +235 -0
  6. package/dist/client/openrouter-mirror.css +131 -0
  7. package/dist/client/openrouter-mirror.d.ts +1 -0
  8. package/dist/client/openrouter-mirror.js +95 -0
  9. package/dist/client/openrouter-mirror.tsx +221 -0
  10. package/dist/src/cli.d.ts +2 -0
  11. package/dist/src/cli.js +31 -0
  12. package/dist/src/index.d.ts +15 -0
  13. package/dist/src/index.js +66 -0
  14. package/dist/src/openrouter-budget.d.ts +58 -0
  15. package/dist/src/openrouter-budget.js +133 -0
  16. package/dist/src/openrouter-capabilities.d.ts +3 -0
  17. package/dist/src/openrouter-capabilities.js +720 -0
  18. package/dist/src/openrouter-conformance.d.ts +11 -0
  19. package/dist/src/openrouter-conformance.js +53 -0
  20. package/dist/src/openrouter-connector.d.ts +90 -0
  21. package/dist/src/openrouter-connector.js +245 -0
  22. package/dist/src/openrouter-local-generation.d.ts +17 -0
  23. package/dist/src/openrouter-local-generation.js +271 -0
  24. package/dist/src/openrouter-mirror-ui.d.ts +12 -0
  25. package/dist/src/openrouter-mirror-ui.js +78 -0
  26. package/dist/src/openrouter-models.d.ts +39 -0
  27. package/dist/src/openrouter-models.js +78 -0
  28. package/dist/src/openrouter-scenario.d.ts +36 -0
  29. package/dist/src/openrouter-scenario.js +145 -0
  30. package/dist/src/openrouter-server.d.ts +20 -0
  31. package/dist/src/openrouter-server.js +144 -0
  32. package/dist/src/openrouter-stub.d.ts +8 -0
  33. package/dist/src/openrouter-stub.js +58 -0
  34. package/dist/src/openrouter-twin.d.ts +4 -0
  35. package/dist/src/openrouter-twin.js +1506 -0
  36. package/dist/src/openrouter-types.d.ts +50 -0
  37. package/dist/src/openrouter-types.js +1 -0
  38. package/dist/test-fixtures/openrouter-openapi-operations.SOURCE.md +16 -0
  39. package/dist/test-fixtures/openrouter-openapi-operations.json +1041 -0
  40. package/package.json +71 -0
  41. package/src/cli.ts +29 -0
  42. package/src/index.ts +108 -0
  43. package/src/openrouter-budget.ts +159 -0
  44. package/src/openrouter-capabilities.ts +861 -0
  45. package/src/openrouter-conformance.ts +60 -0
  46. package/src/openrouter-connector.ts +264 -0
  47. package/src/openrouter-local-generation.ts +207 -0
  48. package/src/openrouter-mirror-ui.ts +84 -0
  49. package/src/openrouter-models.ts +118 -0
  50. package/src/openrouter-scenario.ts +156 -0
  51. package/src/openrouter-server.ts +158 -0
  52. package/src/openrouter-stub.ts +60 -0
  53. package/src/openrouter-twin.ts +1441 -0
  54. package/src/openrouter-types.ts +49 -0
  55. package/test-fixtures/openrouter-openapi-operations.SOURCE.md +16 -0
  56. package/test-fixtures/openrouter-openapi-operations.json +1041 -0
@@ -0,0 +1,221 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+
4
+ // WHERE THIS MIRROR LIVES. Served at a vendor root the base is '' (fetches are root-relative, as
5
+ // before); served under a path prefix with a <base> tag every read and write resolves inside that
6
+ // prefix instead of escaping it.
7
+ const WIRE_BASE = typeof document === 'undefined' || document.querySelector('base[href]') === null ? '' : new URL('.', document.baseURI).pathname.replace(/\/$/, '');
8
+
9
+ type Model = { id: string; name: string; context_length: number; architecture?: { modality?: string }; pricing?: { prompt?: string; completion?: string } };
10
+ type ModelEndpoints = Model & { endpoints?: Array<{ name: string; provider_name: string; context_length: number }> };
11
+ type Generation = { id: string; model: string; created_at: string; total_cost: number; tokens_prompt: number; tokens_completion: number; streamed: boolean; provider_name?: string; finish_reason?: string };
12
+
13
+ async function json<T>(path: string, init?: RequestInit): Promise<T> {
14
+ const response = await fetch(`${WIRE_BASE}${path}`, init);
15
+ const body = await response.json();
16
+ if (!response.ok) throw new Error(body?.error?.message ?? `request failed: ${response.status}`);
17
+ return body as T;
18
+ }
19
+
20
+ function ModelDetail({ model, onClose }: { model: string; onClose: () => void }) {
21
+ const [detail, setDetail] = useState<ModelEndpoints | null>(null);
22
+ const [error, setError] = useState('');
23
+ useEffect(() => {
24
+ void json<{ data: ModelEndpoints }>(`/api/v1/models/${model}/endpoints`)
25
+ .then((body) => setDetail(body.data))
26
+ .catch((err) => setError(err instanceof Error ? err.message : String(err)));
27
+ }, [model]);
28
+ return (
29
+ <div className="detail model-detail">
30
+ <div className="detail-head">
31
+ <h3>Model detail</h3>
32
+ <button onClick={onClose}>Close</button>
33
+ </div>
34
+ {error ? <div className="error">{error}</div> : null}
35
+ {detail ? (
36
+ <div className="detail-body">
37
+ <div className="mono">{detail.id}</div>
38
+ <div className="kv"><span>Context</span><span className="mono">{detail.context_length.toLocaleString()}</span></div>
39
+ <div className="kv"><span>Prompt $/tok</span><span className="mono">{detail.pricing?.prompt ?? '-'}</span></div>
40
+ <div className="kv"><span>Completion $/tok</span><span className="mono">{detail.pricing?.completion ?? '-'}</span></div>
41
+ <h4>Endpoints</h4>
42
+ {(detail.endpoints ?? []).map((endpoint) => (
43
+ <div className="row endpoint-row" key={endpoint.provider_name}>
44
+ <span>{endpoint.name}</span>
45
+ <span className="mono">{endpoint.provider_name}</span>
46
+ <span className="mono">{endpoint.context_length.toLocaleString()} ctx</span>
47
+ </div>
48
+ ))}
49
+ </div>
50
+ ) : (error ? null : <div className="empty">Loading…</div>)}
51
+ </div>
52
+ );
53
+ }
54
+
55
+ function Models({ models, onSelect }: { models: Model[]; onSelect: (id: string) => void }) {
56
+ return (
57
+ <section className="panel models-panel">
58
+ <h2>Models</h2>
59
+ <div className="rows">
60
+ {models.map((model) => (
61
+ <button className="row model-row" key={model.id} onClick={() => onSelect(model.id)}>
62
+ <div>
63
+ <strong>{model.name}</strong>
64
+ <span className="mono">{model.id}</span>
65
+ </div>
66
+ <span>{model.architecture?.modality ?? 'text->text'}</span>
67
+ <span className="mono">{model.context_length.toLocaleString()} ctx</span>
68
+ </button>
69
+ ))}
70
+ </div>
71
+ </section>
72
+ );
73
+ }
74
+
75
+ function GenerationDetail({ id, onClose }: { id: string; onClose: () => void }) {
76
+ const [detail, setDetail] = useState<Generation | null>(null);
77
+ const [error, setError] = useState('');
78
+ useEffect(() => {
79
+ void json<{ data: Generation }>(`/api/v1/generation?id=${encodeURIComponent(id)}`)
80
+ .then((body) => setDetail(body.data))
81
+ .catch((err) => setError(err instanceof Error ? err.message : String(err)));
82
+ }, [id]);
83
+ return (
84
+ <div className="detail generation-detail">
85
+ <div className="detail-head">
86
+ <h3>Generation detail</h3>
87
+ <button onClick={onClose}>Close</button>
88
+ </div>
89
+ {error ? <div className="error">{error}</div> : null}
90
+ {detail ? (
91
+ <div className="detail-body">
92
+ <div className="mono">{detail.id}</div>
93
+ <div className="kv"><span>Model</span><span className="mono">{detail.model}</span></div>
94
+ <div className="kv"><span>Provider</span><span className="mono">{detail.provider_name ?? 'twin'}</span></div>
95
+ <div className="kv"><span>Finish</span><span className="mono">{detail.finish_reason ?? '-'}</span></div>
96
+ <div className="kv"><span>Prompt tokens</span><span className="mono">{detail.tokens_prompt}</span></div>
97
+ <div className="kv"><span>Completion tokens</span><span className="mono">{detail.tokens_completion}</span></div>
98
+ <div className="kv"><span>Total cost</span><span className="mono">${detail.total_cost.toFixed(8)}</span></div>
99
+ </div>
100
+ ) : (error ? null : <div className="empty">Loading…</div>)}
101
+ </div>
102
+ );
103
+ }
104
+
105
+ function Generations({ generations, onSelect }: { generations: Generation[]; onSelect: (id: string) => void }) {
106
+ return (
107
+ <section className="panel generations-panel">
108
+ <h2>Generations</h2>
109
+ <div className="rows">
110
+ {generations.length === 0 ? <div className="empty">No generations yet.</div> : null}
111
+ {generations.map((generation) => (
112
+ <button className="row generation-row" key={generation.id} onClick={() => onSelect(generation.id)}>
113
+ <div>
114
+ <strong>{generation.model}</strong>
115
+ <span className="mono">{generation.id}</span>
116
+ </div>
117
+ <span>{generation.streamed ? 'streamed' : 'standard'}</span>
118
+ <span className="mono">${generation.total_cost.toFixed(8)}</span>
119
+ </button>
120
+ ))}
121
+ </div>
122
+ </section>
123
+ );
124
+ }
125
+
126
+ function Playground({ onComplete }: { onComplete: () => void }) {
127
+ const [model, setModel] = useState('google/gemini-3-flash-preview');
128
+ const [prompt, setPrompt] = useState('Summarize the OpenRouter twin.');
129
+ const [result, setResult] = useState('');
130
+ const [error, setError] = useState('');
131
+ const [busy, setBusy] = useState(false);
132
+
133
+ async function send() {
134
+ setBusy(true);
135
+ setError('');
136
+ setResult('');
137
+ try {
138
+ const body = await json<any>('/api/v1/chat/completions', {
139
+ method: 'POST',
140
+ headers: { 'content-type': 'application/json' },
141
+ body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }] }),
142
+ });
143
+ setResult(body.choices?.[0]?.message?.content ?? '');
144
+ onComplete();
145
+ } catch (err) {
146
+ setError(err instanceof Error ? err.message : String(err));
147
+ } finally {
148
+ setBusy(false);
149
+ }
150
+ }
151
+
152
+ return (
153
+ <section className="panel playground-panel">
154
+ <h2>Playground</h2>
155
+ <label>
156
+ <span>Model</span>
157
+ <input value={model} onChange={(event) => setModel(event.target.value)} />
158
+ </label>
159
+ <label>
160
+ <span>Prompt</span>
161
+ <textarea rows={4} value={prompt} onChange={(event) => setPrompt(event.target.value)} />
162
+ </label>
163
+ <button onClick={send} disabled={busy}>{busy ? 'Sending...' : 'Send'}</button>
164
+ {error ? <div className="error">{error}</div> : null}
165
+ {result ? (
166
+ <div className="stub-output">
167
+ <div className="stub-banner">Stub completion: deterministic twin output, no provider model was run.</div>
168
+ <pre>{result}</pre>
169
+ </div>
170
+ ) : null}
171
+ </section>
172
+ );
173
+ }
174
+
175
+ function App() {
176
+ const [models, setModels] = useState<Model[]>([]);
177
+ const [generations, setGenerations] = useState<Generation[]>([]);
178
+ const [loadError, setLoadError] = useState('');
179
+ const [selectedModel, setSelectedModel] = useState('');
180
+ const [selectedGeneration, setSelectedGeneration] = useState('');
181
+
182
+ async function load() {
183
+ try {
184
+ const [modelsBody, generationBody] = await Promise.all([
185
+ json<{ data: Model[] }>('/api/v1/models'),
186
+ json<{ data: Generation[] }>('/api/v1/generations'),
187
+ ]);
188
+ setModels(modelsBody.data);
189
+ setGenerations(generationBody.data);
190
+ } catch (err) {
191
+ setLoadError(err instanceof Error ? err.message : String(err));
192
+ }
193
+ }
194
+
195
+ useEffect(() => {
196
+ void load();
197
+ }, []);
198
+
199
+ return (
200
+ <main className="app openrouter-mirror">
201
+ <header>
202
+ <div>
203
+ <h1>OpenRouter Twin</h1>
204
+ <p>OpenAI-compatible API simulation with deterministic stub output.</p>
205
+ </div>
206
+ <button onClick={() => void load()}>Refresh</button>
207
+ </header>
208
+ {loadError ? <div className="error">{loadError}</div> : null}
209
+ <div className="grid">
210
+ <Playground onComplete={() => void load()} />
211
+ <Models models={models} onSelect={setSelectedModel} />
212
+ <Generations generations={generations} onSelect={setSelectedGeneration} />
213
+ </div>
214
+ {selectedModel ? <ModelDetail model={selectedModel} onClose={() => setSelectedModel('')} /> : null}
215
+ {selectedGeneration ? <GenerationDetail id={selectedGeneration} onClose={() => setSelectedGeneration('')} /> : null}
216
+ </main>
217
+ );
218
+ }
219
+
220
+ const root = document.getElementById('root');
221
+ if (root) createRoot(root).render(<App />);