@preventive/triage 1.0.0-alpha.17 → 1.0.0-alpha.19

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 (44) hide show
  1. package/api/reap.ts +13 -75
  2. package/common/server-info.ts +6 -4
  3. package/out/client-managed-import.js +81 -0
  4. package/out/client-managed.js +68 -41
  5. package/out/client-sync.js +14 -14
  6. package/out/graph.js +10 -10
  7. package/out/index.html +1 -0
  8. package/out/view.css +1 -1
  9. package/out/view.js +179 -142
  10. package/package.json +16 -6
  11. package/report/index.js +2 -1
  12. package/report/src/finding.js +12 -0
  13. package/report/src/security.js +63 -0
  14. package/server-common/database-config.ts +16 -0
  15. package/server-common/initialize.ts +18 -0
  16. package/server-common/npm-advisories.ts +101 -0
  17. package/server-common/reap.ts +48 -0
  18. package/server-common/standalone.ts +29 -0
  19. package/server-common/storage-log.ts +34 -0
  20. package/server-common/vercel-blob.ts +110 -0
  21. package/server-e2e/app.ts +485 -0
  22. package/server-e2e/cli.js +7 -2
  23. package/server-e2e/config.ts +36 -40
  24. package/server-e2e/db-neon.ts +1 -1
  25. package/server-e2e/db.ts +1 -1
  26. package/server-e2e/http.ts +12 -2
  27. package/server-e2e/index.ts +12 -481
  28. package/server-e2e/lifecycle.ts +59 -172
  29. package/server-e2e/neon-driver.ts +2 -2
  30. package/server-e2e/npm-proxy.ts +10 -143
  31. package/server-e2e/objstore/blob-vercel.ts +4 -108
  32. package/server-e2e/objstore/init.ts +15 -23
  33. package/server-e2e/objstore/store-neon.ts +5 -9
  34. package/server-e2e/static.ts +11 -17
  35. package/out/provider-icons/claude.svg +0 -1
  36. package/out/provider-icons/deepseek.svg +0 -1
  37. package/out/provider-icons/google.svg +0 -1
  38. package/out/provider-icons/grok.svg +0 -1
  39. package/out/provider-icons/moonshot.svg +0 -1
  40. package/out/provider-icons/nvidia.svg +0 -1
  41. package/out/provider-icons/openai.svg +0 -1
  42. package/out/provider-icons/openrouter.svg +0 -1
  43. package/out/provider-icons/qwen.svg +0 -1
  44. package/out/provider-icons/zai.svg +0 -1
@@ -18,9 +18,9 @@ export type ObjstoreInitDeps = {
18
18
  // the workspace_revision handle in server-e2e/db.ts.
19
19
  handle: Handle
20
20
  reapIntervalMs: number
21
- // Hard off-switch (OBJSTORE_REAP_DISABLED). When true, NEITHER the boot
21
+ // Automatic-sweep switch (OBJSTORE_REAP_DISABLED). When true, NEITHER the boot
22
22
  // sweep nor the periodic timer runs: `startupReap` resolves immediately
23
- // and `stopReaper` is a no-op. Orphaned/superseded blobs and stale
23
+ // and no timer is installed. Orphaned/superseded blobs and stale
24
24
  // staging rows then accumulate unbounded — only safe if an external job
25
25
  // handles GC. Omitted/false → reaper runs (the default). See config.ts.
26
26
  reapDisabled?: boolean
@@ -59,6 +59,7 @@ export type ObjstoreInitDeps = {
59
59
  export type ObjstoreInit = {
60
60
  handlers: ObjstoreHandlers
61
61
  restDeps: ObjstoreRestDeps
62
+ reap: () => Promise<void>
62
63
  startupReap: Promise<void>
63
64
  // Cancels the periodic timer AND resolves only after any
64
65
  // currently-in-flight sweep has finished, so shutdown can safely
@@ -102,31 +103,22 @@ export function initObjstore(deps: ObjstoreInitDeps): ObjstoreInit {
102
103
  let inFlight: Promise<void> | null = null
103
104
  function enqueueSweep(): Promise<void> {
104
105
  if (inFlight) return inFlight
105
- // Log reaper failures unconditionally (not gated on `debug`) — a
106
- // failed sweep means stranded files / staging rows that never get
107
- // cleaned, and operators need to see it. Inner catch makes the
108
- // promise resolve so callers (startupReap awaiter + setInterval)
109
- // don't have to handle rejection.
110
- const p = reapOrphans(handle).catch((err) => {
111
- console.warn('objstore reaper error:', errStack(err))
112
- }).finally(() => { if (inFlight === p) inFlight = null })
106
+ // Keep failures visible to explicit callers; automatic sweeps log them.
107
+ const p = reapOrphans(handle).finally(() => { if (inFlight === p) inFlight = null })
113
108
  inFlight = p
114
109
  return p
115
110
  }
116
- // Hard off-switch (OBJSTORE_REAP_DISABLED). Skip the boot sweep AND the
117
- // periodic timer entirely: `startupReap` resolves immediately so the
118
- // index.ts `await startupReap` gate is a no-op, and `stopReaper` has
119
- // nothing to clear or drain. Loud, unconditional warning — with GC off,
120
- // orphaned/superseded blobs and stale staging rows are never reclaimed
121
- // (they accumulate until an external job, if any, collects them).
111
+ const drain = async () => { if (inFlight) await inFlight.catch(() => {}) }
112
+ const automaticSweep = () => enqueueSweep().catch(err => console.warn('objstore reaper error:', errStack(err)))
113
+ // Explicit cleanup remains available when automatic sweeps are disabled.
122
114
  if (deps.reapDisabled) {
123
- console.warn('objstore: reaper DISABLED (OBJSTORE_REAP_DISABLED) — orphaned/superseded blobs and stale staging will NOT be reclaimed')
124
- return { handlers, restDeps, startupReap: Promise.resolve(), stopReaper: async () => {} }
115
+ console.warn('objstore: automatic reaper DISABLED (OBJSTORE_REAP_DISABLED) — use /api/reap or an external cleanup job')
116
+ return { handlers, restDeps, reap: enqueueSweep, startupReap: Promise.resolve(), stopReaper: drain }
125
117
  }
126
118
  // Caller awaits this before accepting traffic so a fresh boot
127
119
  // can't hand out list / fetch / put-begin against a tag whose
128
120
  // on-disk state still has stranded files from a prior crash.
129
- const startupReap = enqueueSweep()
121
+ const startupReap = automaticSweep()
130
122
  // Jittered start of the periodic timer. Multi-replica deploys
131
123
  // (Neon + Vercel Blob) commonly boot N replicas in tight lock-step
132
124
  // (deploy rollout, cluster restart), which would otherwise sync every
@@ -139,20 +131,20 @@ export function initObjstore(deps: ObjstoreInitDeps): ObjstoreInit {
139
131
  let reapTimer: ReturnType<typeof setInterval> | null = null
140
132
  const jitterMs = Math.floor(Math.random() * deps.reapIntervalMs)
141
133
  const firstTimer = setTimeout(() => {
142
- enqueueSweep()
143
- reapTimer = setInterval(enqueueSweep, deps.reapIntervalMs)
134
+ void automaticSweep()
135
+ reapTimer = setInterval(automaticSweep, deps.reapIntervalMs)
144
136
  reapTimer.unref?.()
145
137
  }, deps.reapIntervalMs + jitterMs)
146
138
  firstTimer.unref?.()
147
139
  return {
148
- handlers, restDeps, startupReap,
140
+ handlers, restDeps, reap: enqueueSweep, startupReap,
149
141
  stopReaper: async () => {
150
142
  clearTimeout(firstTimer)
151
143
  if (reapTimer) clearInterval(reapTimer)
152
144
  // Drain whichever sweep is currently running (startup or
153
145
  // periodic) — either could be mid-readdir/unlink at SIGTERM
154
146
  // time and would otherwise outlive `handle.close()`.
155
- if (inFlight) await inFlight.catch(() => {})
147
+ await drain()
156
148
  },
157
149
  }
158
150
  }
@@ -4,7 +4,7 @@
4
4
  // dialect on the wire.
5
5
  //
6
6
  // `@neondatabase/serverless` is an OPTIONAL peer dep — selected by
7
- // the `DATABASE_URL` branch in `server-e2e/index.ts`. The peer dep
7
+ // the configured database URL branch in `server-e2e/index.ts`. The peer dep
8
8
  // itself is loaded lazily inside `openNeonObjstore` below. A single
9
9
  // Neon project / database holds both `workspace_revision` and the
10
10
  // two objstore tables; each plane opens its own stateless
@@ -13,14 +13,10 @@
13
13
  // release.
14
14
  //
15
15
  // Byte plane: passed in as a `BlobBackend` (./blob.ts). The
16
- // supported pairings selected by server-e2e/index.ts are:
17
- // - Neon + Vercel Blob Private Storage (multi-replica, the only
18
- // pairing that survives a replica restart without local-disk
19
- // coordination). Activated by setting BLOB_READ_WRITE_TOKEN
20
- // alongside DATABASE_URL.
21
- // - Neon + local FS (development / operator escape hatch — bytes
22
- // still need to live on a shared filesystem if you run more
23
- // than one replica).
16
+ // supported Neon pairing selected by server-e2e/index.ts is Vercel
17
+ // Blob Private Storage. DATABASE_URL or E2E_DATABASE_URL plus BLOB_READ_WRITE_TOKEN
18
+ // is required; the launcher never selects Neon + local FS. Tests
19
+ // can inject a local-FS backend directly through this adapter.
24
20
 
25
21
  import type { BlobBackend } from './blob.ts'
26
22
  import type { AllStmt, GetStmt, RunStmt } from '../db-stmt.ts'
@@ -7,8 +7,7 @@
7
7
  // request URL is only used as a Map key, never joined with the
8
8
  // filesystem — so the handler has no path-traversal surface at all:
9
9
  // `..`, percent-encoded slashes and absolute-form URIs all just
10
- // produce a key that isn't in the map and 404. Provider SVGs are
11
- // the one explicitly allowed asset subdirectory.
10
+ // produce a key that isn't in the map and 404.
12
11
  //
13
12
  // Compression: every entry whose extension is in COMPRESSIBLE gets
14
13
  // pre-computed brotli + gzip at boot. The handler picks brotli over
@@ -104,8 +103,10 @@ export type StaticHandler = (req: HttpRequest, res: ServerResponse) => boolean
104
103
  // caller falls through to its next route. Missing `staticDir`
105
104
  // (pre-build case) logs a warning and returns a handler that always
106
105
  // answers false; the API/WS planes are unaffected.
107
- export function loadStatic(staticDir: string, deepviewScanServer: string | null = null): StaticHandler {
108
- const files = readStaticFiles(staticDir, deepviewScanServer)
106
+ type StaticOptions = { transformIndex?: (html: string) => string, indexOnly?: boolean }
107
+
108
+ export function loadStatic(staticDir: string, deepviewScanServer: string | null = null, options: StaticOptions = {}): StaticHandler {
109
+ const files = readStaticFiles(staticDir, deepviewScanServer, options)
109
110
  return function handleStatic(req: HttpRequest, res: ServerResponse): boolean {
110
111
  if (req.method !== 'GET' && req.method !== 'HEAD') return false
111
112
  if (typeof req.url !== 'string') return false
@@ -153,7 +154,7 @@ export function loadStatic(staticDir: string, deepviewScanServer: string | null
153
154
  }
154
155
  }
155
156
 
156
- function readStaticFiles(staticDir: string, deepviewScanServer: string | null): ReadonlyMap<string, StaticEntry> {
157
+ function readStaticFiles(staticDir: string, deepviewScanServer: string | null, options: StaticOptions): ReadonlyMap<string, StaticEntry> {
157
158
  const files = new Map<string, StaticEntry>()
158
159
  let entries
159
160
  try { entries = readdirSync(staticDir, { withFileTypes: true }) } catch (err) {
@@ -167,24 +168,17 @@ function readStaticFiles(staticDir: string, deepviewScanServer: string | null):
167
168
  throw err
168
169
  }
169
170
  for (const entry of entries) {
170
- if (entry.isDirectory() && entry.name === 'provider-icons') {
171
- for (const icon of readdirSync(join(staticDir, entry.name), { withFileTypes: true })) {
172
- if (!icon.isFile() || extname(icon.name) !== '.svg') continue
173
- const name = `${entry.name}/${icon.name}`
174
- files.set(name, buildEntry(staticDir, name))
175
- }
176
- }
177
- // Exclude other subdirectories, symlinks and non-file entries.
178
- if (!entry.isFile()) continue
179
- files.set(entry.name, buildEntry(staticDir, entry.name, deepviewScanServer))
171
+ // Exclude subdirectories, symlinks and non-file entries.
172
+ if (!entry.isFile() || (options.indexOnly && entry.name !== 'index.html')) continue
173
+ files.set(entry.name, buildEntry(staticDir, entry.name, deepviewScanServer, options.transformIndex))
180
174
  }
181
175
  return files
182
176
  }
183
177
 
184
- function buildEntry(staticDir: string, name: string, deepviewScanServer: string | null = null): StaticEntry {
178
+ function buildEntry(staticDir: string, name: string, deepviewScanServer: string | null = null, transformIndex: (html: string) => string = html => html): StaticEntry {
185
179
  const ext = extname(name)
186
180
  const source = readFileSync(join(staticDir, name))
187
- const raw = name === 'index.html' ? Buffer.from(scanServerHtml(source.toString('utf8'), deepviewScanServer)) : source
181
+ const raw = name === 'index.html' ? Buffer.from(transformIndex(scanServerHtml(source.toString('utf8'), deepviewScanServer))) : source
188
182
  const type = CONTENT_TYPE[ext] ?? 'application/octet-stream'
189
183
  // HTML: lift `<link rel="(module)preload" …>` into a Link header and
190
184
  // drop the tags from the served body. ETag + compression run against
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"></path></svg>
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>DeepSeek</title><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z"></path></svg>
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Google</title><path d="M23 12.245c0-.905-.075-1.565-.236-2.25h-10.54v4.083h6.186c-.124 1.014-.797 2.542-2.294 3.569l-.021.136 3.332 2.53.23.022C21.779 18.417 23 15.593 23 12.245z"></path><path d="M12.225 23c3.03 0 5.574-.978 7.433-2.665l-3.542-2.688c-.948.648-2.22 1.1-3.891 1.1a6.745 6.745 0 01-6.386-4.572l-.132.011-3.465 2.628-.045.124C4.043 20.531 7.835 23 12.225 23z"></path><path d="M5.84 14.175A6.65 6.65 0 015.463 12c0-.758.138-1.491.361-2.175l-.006-.147-3.508-2.67-.115.054A10.831 10.831 0 001 12c0 1.772.436 3.447 1.197 4.938l3.642-2.763z"></path><path d="M12.225 5.253c2.108 0 3.529.892 4.34 1.638l3.167-3.031C17.787 2.088 15.255 1 12.225 1 7.834 1 4.043 3.469 2.197 7.062l3.63 2.763a6.77 6.77 0 016.398-4.572z"></path></svg>
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>MoonshotAI</title><path d="M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z"></path></svg>
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Nvidia</title><path d="M10.212 8.976V7.62c.127-.01.256-.017.388-.021 3.596-.117 5.957 3.184 5.957 3.184s-2.548 3.647-5.282 3.647a3.227 3.227 0 01-1.063-.175v-4.109c1.4.174 1.681.812 2.523 2.258l1.873-1.627a4.905 4.905 0 00-3.67-1.846 6.594 6.594 0 00-.729.044m0-4.476v2.025c.13-.01.259-.019.388-.024 5.002-.174 8.261 4.226 8.261 4.226s-3.743 4.69-7.643 4.69c-.338 0-.675-.031-1.007-.092v1.25c.278.038.558.057.838.057 3.629 0 6.253-1.91 8.794-4.169.421.347 2.146 1.193 2.501 1.564-2.416 2.083-8.048 3.763-11.24 3.763-.308 0-.603-.02-.894-.048V19.5H24v-15H10.21zm0 9.756v1.068c-3.356-.616-4.287-4.21-4.287-4.21a7.173 7.173 0 014.287-2.138v1.172h-.005a3.182 3.182 0 00-2.502 1.178s.615 2.276 2.507 2.931m-5.961-3.3c1.436-1.935 3.604-3.148 5.961-3.336V6.523C5.81 6.887 2 10.723 2 10.723s2.158 6.427 8.21 7.015v-1.166C5.77 16 4.25 10.958 4.25 10.958h-.002z"></path></svg>
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenAI</title><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"></path></svg>
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenRouter</title><path d="M18.654 3.87a5.087 5.087 0 110 10.174L23.7 19.09c.64.641.187 1.737-.72 1.737H8.48a8.479 8.479 0 010-16.958h10.175zM8.479 7.26a5.087 5.087 0 100 10.176 5.087 5.087 0 000-10.175z"></path></svg>
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z"></path></svg>
@@ -1 +0,0 @@
1
- <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Z.ai</title><path d="M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z"></path></svg>