@hanzo/usage 0.1.1 → 0.1.2

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.
@@ -0,0 +1,36 @@
1
+ import type { ProviderDescriptor, ProviderFetchContext, ProviderFetchStrategy, ProviderMetadata, ProviderSettings } from '../provider.js';
2
+ import type { CreditsSnapshot, UsageSnapshot } from '../types.js';
3
+ import type { HttpRequest } from '../host.js';
4
+ /** Everything a provider's `map` yields except the mechanical providerId/updatedAt. */
5
+ export type ApiTokenUsage = Omit<UsageSnapshot, 'providerId' | 'updatedAt'>;
6
+ export interface ApiTokenResult {
7
+ usage: ApiTokenUsage;
8
+ credits?: Omit<CreditsSnapshot, 'updatedAt'>;
9
+ }
10
+ export interface ApiTokenProviderConfig {
11
+ id: string;
12
+ metadata: ProviderMetadata;
13
+ /** Source label surfaced to the UI (defaults to the display name). */
14
+ sourceLabel?: string;
15
+ /** Env var names to fall back to when settings.apiKey is absent. */
16
+ envKeys?: string[];
17
+ /** Env var names that supply a base URL for self-hosted gateways. */
18
+ baseUrlEnv?: string[];
19
+ /** Require a resolvable base URL (self-hosted gateways) to be available. */
20
+ requireBaseUrl?: boolean;
21
+ /** Build the HTTP request. `settings.apiKey` (and resolved `baseUrl`) are present. */
22
+ request: (settings: ProviderSettings, ctx: ProviderFetchContext) => HttpRequest;
23
+ /** Map a decoded 200 body into a usage/credits result. */
24
+ map: (json: unknown, now: Date, settings: ProviderSettings) => ApiTokenResult;
25
+ }
26
+ /** Resolve a base URL from explicit settings, then from host env fallbacks. */
27
+ export declare const resolveBaseUrl: (ctx: ProviderFetchContext, envKeys?: readonly string[]) => string | undefined;
28
+ /** Resolve an API key from explicit settings, then from host env fallbacks. */
29
+ export declare const resolveApiKey: (ctx: ProviderFetchContext, envKeys?: readonly string[]) => string | undefined;
30
+ export declare const makeApiTokenStrategy: (config: ApiTokenProviderConfig) => ProviderFetchStrategy;
31
+ /** Build a single-strategy apiToken ProviderDescriptor. */
32
+ export declare const makeApiTokenProvider: (config: ApiTokenProviderConfig) => ProviderDescriptor;
33
+ /** Numeric coercion shared by mappers; non-finite/absent → undefined. */
34
+ export declare const numberOrUndefined: (v: unknown) => number | undefined;
35
+ /** Bearer auth header helper. */
36
+ export declare const bearer: (key: string) => Record<string, string>;
@@ -0,0 +1,99 @@
1
+ // Copyright (c) 2026 Hanzo AI Inc. MIT License.
2
+ // Generic apiToken provider factory — TypeScript port of CodexBarCore's
3
+ // APITokenFetchStrategy. A provider whose usage/credits/balance is exposed
4
+ // behind a single pasteable API key becomes ~30 lines: describe how to build
5
+ // the request from settings, and how to map the JSON body into a snapshot.
6
+ //
7
+ // The factory owns everything mechanical — key resolution (settings.apiKey or
8
+ // env fallback), the HTTP call, status checking, JSON parsing, and stamping
9
+ // providerId/updatedAt — so each provider file only expresses what is unique
10
+ // to it: its endpoint, its auth header, and its response shape.
11
+ import { forMode } from '../provider.js';
12
+ /** Resolve a base URL from explicit settings, then from host env fallbacks. */
13
+ export const resolveBaseUrl = (ctx, envKeys = []) => {
14
+ const fromSettings = ctx.settings?.baseUrl;
15
+ if (typeof fromSettings === 'string' && fromSettings.length > 0)
16
+ return fromSettings;
17
+ for (const name of envKeys) {
18
+ const value = ctx.host.env(name);
19
+ if (value)
20
+ return value;
21
+ }
22
+ return undefined;
23
+ };
24
+ /** Resolve an API key from explicit settings, then from host env fallbacks. */
25
+ export const resolveApiKey = (ctx, envKeys = []) => {
26
+ const fromSettings = ctx.settings?.apiKey;
27
+ if (typeof fromSettings === 'string' && fromSettings.length > 0)
28
+ return fromSettings;
29
+ for (const name of envKeys) {
30
+ const value = ctx.host.env(name);
31
+ if (value)
32
+ return value;
33
+ }
34
+ return undefined;
35
+ };
36
+ export const makeApiTokenStrategy = (config) => {
37
+ const label = config.sourceLabel ?? config.metadata.displayName;
38
+ return {
39
+ id: `${config.id}.apiToken`,
40
+ kind: 'apiToken',
41
+ async isAvailable(ctx) {
42
+ if (!resolveApiKey(ctx, config.envKeys))
43
+ return false;
44
+ if (config.requireBaseUrl && !resolveBaseUrl(ctx, config.baseUrlEnv))
45
+ return false;
46
+ return true;
47
+ },
48
+ async fetch(ctx) {
49
+ const apiKey = resolveApiKey(ctx, config.envKeys);
50
+ if (!apiKey)
51
+ throw new Error(`${config.id}: no API key`);
52
+ const baseUrl = resolveBaseUrl(ctx, config.baseUrlEnv);
53
+ if (config.requireBaseUrl && !baseUrl)
54
+ throw new Error(`${config.id}: no base URL`);
55
+ const settings = { ...ctx.settings, apiKey, ...(baseUrl ? { baseUrl } : {}) };
56
+ const req = config.request(settings, ctx);
57
+ const res = await ctx.host.http({ method: 'GET', timeoutMs: 30_000, ...req });
58
+ if (res.status !== 200)
59
+ throw new Error(`${config.id} HTTP ${res.status}`);
60
+ let body;
61
+ try {
62
+ body = JSON.parse(res.text);
63
+ }
64
+ catch {
65
+ throw new Error(`${config.id}: invalid JSON body`);
66
+ }
67
+ const now = ctx.host.now();
68
+ const iso = now.toISOString();
69
+ const { usage, credits } = config.map(body, now, settings);
70
+ return {
71
+ usage: { ...usage, providerId: config.id, updatedAt: iso },
72
+ credits: credits ? { ...credits, updatedAt: iso } : undefined,
73
+ sourceLabel: label,
74
+ strategyId: `${config.id}.apiToken`,
75
+ strategyKind: 'apiToken',
76
+ };
77
+ },
78
+ // API-key providers have exactly one source; nothing to fall back to.
79
+ shouldFallback() {
80
+ return false;
81
+ },
82
+ };
83
+ };
84
+ /** Build a single-strategy apiToken ProviderDescriptor. */
85
+ export const makeApiTokenProvider = (config) => {
86
+ const strategy = makeApiTokenStrategy(config);
87
+ return {
88
+ id: config.id,
89
+ metadata: config.metadata,
90
+ strategies: (mode) => forMode([strategy], mode),
91
+ };
92
+ };
93
+ /** Numeric coercion shared by mappers; non-finite/absent → undefined. */
94
+ export const numberOrUndefined = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
95
+ /** Bearer auth header helper. */
96
+ export const bearer = (key) => ({
97
+ Authorization: `Bearer ${key}`,
98
+ Accept: 'application/json',
99
+ });
@@ -0,0 +1,2 @@
1
+ import type { ProviderDescriptor } from '../provider.js';
2
+ export declare const byoProvider: ProviderDescriptor;
@@ -0,0 +1,114 @@
1
+ // Copyright (c) 2026 Hanzo AI Inc. MIT License.
2
+ // BYO — bring-your-own OpenAI-compatible endpoint {baseUrl, apiKey}. This is the
3
+ // self-hosted / BYO-GPU tracking lane: point it at any gateway that speaks the
4
+ // OpenAI API (vLLM, LiteLLM, Hanzo Engine, a self-hosted cloud). It first tries a
5
+ // LiteLLM-style /key/info for real spend/budget; failing that it falls back to a
6
+ // /v1/models liveness probe and reports identity + reachability only
7
+ // (dataConfidence 'unknown').
8
+ //
9
+ // NOTE: BYO devices registered into Hanzo Cloud are metered natively by the cloud
10
+ // ledger (run-for-pay) regardless of what this local probe can read — this lane is
11
+ // for endpoints Hanzo does not itself operate.
12
+ import { forMode } from '../provider.js';
13
+ import { bearer, resolveApiKey, resolveBaseUrl } from './api-token.js';
14
+ const ENV_KEYS = ['BYO_API_KEY'];
15
+ const ENV_URL = ['BYO_BASE_URL'];
16
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
17
+ const obj = (v) => v && typeof v === 'object' ? v : {};
18
+ const stripV1 = (base) => base.replace(/\/v1\/?$/, '').replace(/\/$/, '');
19
+ const keyInfo = async (ctx, base, key, now) => {
20
+ const res = await ctx.host.http({
21
+ url: `${stripV1(base)}/key/info`,
22
+ headers: bearer(key),
23
+ timeoutMs: 15_000,
24
+ });
25
+ if (res.status !== 200)
26
+ return undefined;
27
+ let info;
28
+ try {
29
+ info = obj(obj(JSON.parse(res.text)).info);
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ if (Object.keys(info).length === 0)
35
+ return undefined;
36
+ const spend = num(info.spend);
37
+ return {
38
+ providerId: 'byo',
39
+ identity: {
40
+ providerId: 'byo',
41
+ loginMethod: 'api',
42
+ accountEmail: typeof info.user_id === 'string' ? info.user_id : undefined,
43
+ accountOrganization: typeof info.team_id === 'string' ? info.team_id : undefined,
44
+ plan: typeof info.key_name === 'string' ? info.key_name : undefined,
45
+ },
46
+ providerCost: { used: spend, currencyCode: 'USD', period: 'key', updatedAt: now.toISOString() },
47
+ subscriptionExpiresAt: typeof info.expires === 'string' ? info.expires : undefined,
48
+ dataConfidence: 'exact',
49
+ updatedAt: now.toISOString(),
50
+ };
51
+ };
52
+ const modelsLiveness = async (ctx, base, key, now) => {
53
+ const root = stripV1(base);
54
+ const res = await ctx.host.http({
55
+ url: `${root}/v1/models`,
56
+ headers: bearer(key),
57
+ timeoutMs: 15_000,
58
+ });
59
+ if (res.status !== 200)
60
+ throw new Error(`byo: /v1/models HTTP ${res.status}`);
61
+ let count;
62
+ try {
63
+ const data = obj(JSON.parse(res.text)).data;
64
+ if (Array.isArray(data))
65
+ count = data.length;
66
+ }
67
+ catch {
68
+ // liveness only — a 200 is enough even if the body is not the expected shape
69
+ }
70
+ return {
71
+ providerId: 'byo',
72
+ identity: {
73
+ providerId: 'byo',
74
+ loginMethod: 'api',
75
+ accountOrganization: count !== undefined ? `${count} models` : undefined,
76
+ },
77
+ dataConfidence: 'unknown',
78
+ updatedAt: now.toISOString(),
79
+ };
80
+ };
81
+ const byoStrategy = {
82
+ id: 'byo.apiToken',
83
+ kind: 'apiToken',
84
+ async isAvailable(ctx) {
85
+ return Boolean(resolveApiKey(ctx, ENV_KEYS) && resolveBaseUrl(ctx, ENV_URL));
86
+ },
87
+ async fetch(ctx) {
88
+ const key = resolveApiKey(ctx, ENV_KEYS);
89
+ const base = resolveBaseUrl(ctx, ENV_URL);
90
+ if (!key || !base)
91
+ throw new Error('byo: baseUrl and apiKey are required');
92
+ const now = ctx.host.now();
93
+ const info = await keyInfo(ctx, base, key, now);
94
+ if (info) {
95
+ return { usage: info, sourceLabel: 'BYO /key/info', strategyId: this.id, strategyKind: this.kind };
96
+ }
97
+ const usage = await modelsLiveness(ctx, base, key, now);
98
+ return { usage, sourceLabel: 'BYO liveness', strategyId: this.id, strategyKind: this.kind };
99
+ },
100
+ shouldFallback() {
101
+ return false;
102
+ },
103
+ };
104
+ export const byoProvider = {
105
+ id: 'byo',
106
+ metadata: {
107
+ displayName: 'BYO endpoint',
108
+ sessionLabel: 'Key spend',
109
+ weeklyLabel: 'Liveness',
110
+ dashboardUrl: 'https://docs.hanzo.ai/docs/gateway',
111
+ color: '#64748b',
112
+ },
113
+ strategies: (mode) => forMode([byoStrategy], mode),
114
+ };
@@ -0,0 +1,2 @@
1
+ import type { ProviderDescriptor } from '../provider.js';
2
+ export declare const deepgramProvider: ProviderDescriptor;
@@ -0,0 +1,83 @@
1
+ // Copyright (c) 2026 Hanzo AI Inc. MIT License.
2
+ // Deepgram provider — port of CodexBarCore/Providers/Deepgram. Deepgram exposes a
3
+ // usage-breakdown API (audio hours, tokens, requests), no balance/quota. When no
4
+ // project is configured we enumerate projects and aggregate. Auth is the Deepgram
5
+ // `Token <key>` scheme (NOT Bearer).
6
+ import { forMode } from '../provider.js';
7
+ import { resolveApiKey } from './api-token.js';
8
+ const ENV_KEYS = ['DEEPGRAM_API_KEY'];
9
+ const ENV_URL = 'DEEPGRAM_API_URL';
10
+ const ENV_PROJECT = 'DEEPGRAM_PROJECT_ID';
11
+ const DEFAULT_BASE = 'https://api.deepgram.com/v1';
12
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
13
+ const deepgramStrategy = {
14
+ id: 'deepgram.apiToken',
15
+ kind: 'apiToken',
16
+ async isAvailable(ctx) {
17
+ return Boolean(resolveApiKey(ctx, ENV_KEYS));
18
+ },
19
+ async fetch(ctx) {
20
+ const key = resolveApiKey(ctx, ENV_KEYS);
21
+ if (!key)
22
+ throw new Error('deepgram: no API key');
23
+ const base = ctx.settings?.baseUrl ?? ctx.host.env(ENV_URL) ?? DEFAULT_BASE;
24
+ const headers = { Authorization: `Token ${key}`, Accept: 'application/json' };
25
+ const get = async (path) => {
26
+ const res = await ctx.host.http({ url: `${base}${path}`, headers, timeoutMs: 30_000 });
27
+ if (res.status !== 200)
28
+ throw new Error(`deepgram ${path} HTTP ${res.status}`);
29
+ return JSON.parse(res.text);
30
+ };
31
+ const configured = (typeof ctx.settings?.projectId === 'string' ? ctx.settings.projectId : undefined) ??
32
+ ctx.host.env(ENV_PROJECT);
33
+ let projectIds;
34
+ if (configured) {
35
+ projectIds = [configured];
36
+ }
37
+ else {
38
+ const body = await get('/projects');
39
+ const projects = Array.isArray(body.projects) ? body.projects : [];
40
+ projectIds = projects
41
+ .map((p) => p.project_id)
42
+ .filter((id) => Boolean(id));
43
+ }
44
+ let hours = 0;
45
+ let tokens = 0;
46
+ let requests = 0;
47
+ for (const id of projectIds) {
48
+ const body = await get(`/projects/${id}/usage/breakdown`);
49
+ for (const r of (Array.isArray(body.results) ? body.results : [])) {
50
+ hours += num(r.total_hours);
51
+ tokens += num(r.tokens_in) + num(r.tokens_out);
52
+ requests += num(r.requests);
53
+ }
54
+ }
55
+ const now = ctx.host.now().toISOString();
56
+ const usage = {
57
+ providerId: 'deepgram',
58
+ identity: {
59
+ providerId: 'deepgram',
60
+ loginMethod: 'api',
61
+ accountOrganization: projectIds.length === 1 ? projectIds[0] : `${projectIds.length} projects`,
62
+ },
63
+ totals: { tokens, requests },
64
+ dataConfidence: tokens > 0 || requests > 0 || hours > 0 ? 'exact' : 'unknown',
65
+ updatedAt: now,
66
+ };
67
+ return { usage, sourceLabel: 'Deepgram usage', strategyId: this.id, strategyKind: this.kind };
68
+ },
69
+ shouldFallback() {
70
+ return false;
71
+ },
72
+ };
73
+ export const deepgramProvider = {
74
+ id: 'deepgram',
75
+ metadata: {
76
+ displayName: 'Deepgram',
77
+ sessionLabel: 'Usage',
78
+ weeklyLabel: 'Requests',
79
+ dashboardUrl: 'https://console.deepgram.com/usage',
80
+ color: '#13ef93',
81
+ },
82
+ strategies: (mode) => forMode([deepgramStrategy], mode),
83
+ };
@@ -0,0 +1,2 @@
1
+ import type { ProviderDescriptor } from '../provider.js';
2
+ export declare const groqProvider: ProviderDescriptor;
@@ -0,0 +1,83 @@
1
+ // Copyright (c) 2026 Hanzo AI Inc. MIT License.
2
+ // Groq provider — port of CodexBarCore/Providers/Groq. Groq exposes NO balance or
3
+ // quota; its only usage signal is an enterprise Prometheus metrics API reporting
4
+ // current throughput RATES. We faithfully report those (requests/min, tokens/min)
5
+ // as an honest throughput snapshot — not a cumulative total (Groq gives no total).
6
+ // Four parallel PromQL queries against {base}/metrics/prometheus/api/v1/query.
7
+ import { forMode } from '../provider.js';
8
+ import { bearer, resolveApiKey } from './api-token.js';
9
+ const ENV_KEYS = ['GROQ_API_KEY'];
10
+ const ENV_URL = 'GROQ_API_URL';
11
+ const DEFAULT_BASE = 'https://api.groq.com/v1';
12
+ const QUERIES = {
13
+ requests: 'sum(model_project_id_status_code:requests:rate5m)',
14
+ tokensIn: 'sum(model_project_id:tokens_in:rate5m)',
15
+ tokensOut: 'sum(model_project_id:tokens_out:rate5m)',
16
+ };
17
+ /** Sum the scalar value across every returned Prometheus series (per-second rate). */
18
+ const sumRate = (body) => {
19
+ if (body.status !== 'success')
20
+ return 0;
21
+ let total = 0;
22
+ for (const series of body.data?.result ?? []) {
23
+ const raw = series.value?.[1];
24
+ const n = typeof raw === 'number' ? raw : Number(raw);
25
+ if (Number.isFinite(n))
26
+ total += n;
27
+ }
28
+ return total;
29
+ };
30
+ const groqStrategy = {
31
+ id: 'groq.apiToken',
32
+ kind: 'apiToken',
33
+ async isAvailable(ctx) {
34
+ return Boolean(resolveApiKey(ctx, ENV_KEYS));
35
+ },
36
+ async fetch(ctx) {
37
+ const key = resolveApiKey(ctx, ENV_KEYS);
38
+ if (!key)
39
+ throw new Error('groq: no API key');
40
+ const base = ctx.settings?.baseUrl ?? ctx.host.env(ENV_URL) ?? DEFAULT_BASE;
41
+ const query = async (promql) => {
42
+ const res = await ctx.host.http({
43
+ url: `${base}/metrics/prometheus/api/v1/query?query=${encodeURIComponent(promql)}`,
44
+ headers: bearer(key),
45
+ timeoutMs: 30_000,
46
+ });
47
+ if (res.status !== 200)
48
+ throw new Error(`groq metrics HTTP ${res.status}`);
49
+ return JSON.parse(res.text);
50
+ };
51
+ const [requests, tokensIn, tokensOut] = await Promise.all([
52
+ query(QUERIES.requests),
53
+ query(QUERIES.tokensIn),
54
+ query(QUERIES.tokensOut),
55
+ ]);
56
+ // Per-second rates → per-minute throughput (Groq exposes no cumulative total).
57
+ const requestsPerMin = Math.round(sumRate(requests) * 60);
58
+ const tokensPerMin = Math.round((sumRate(tokensIn) + sumRate(tokensOut)) * 60);
59
+ const now = ctx.host.now().toISOString();
60
+ const usage = {
61
+ providerId: 'groq',
62
+ identity: { providerId: 'groq', loginMethod: 'api' },
63
+ totals: { tokens: tokensPerMin, requests: requestsPerMin },
64
+ dataConfidence: 'estimated',
65
+ updatedAt: now,
66
+ };
67
+ return { usage, sourceLabel: 'Groq metrics', strategyId: this.id, strategyKind: this.kind };
68
+ },
69
+ shouldFallback() {
70
+ return false;
71
+ },
72
+ };
73
+ export const groqProvider = {
74
+ id: 'groq',
75
+ metadata: {
76
+ displayName: 'Groq',
77
+ sessionLabel: 'Throughput (req/min)',
78
+ weeklyLabel: 'Tokens/min',
79
+ dashboardUrl: 'https://console.groq.com/metrics',
80
+ color: '#f55036',
81
+ },
82
+ strategies: (mode) => forMode([groqStrategy], mode),
83
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/usage",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Track AI provider usage, rate limits, and spend across every Hanzo surface. Headless provider engine + full provider catalog.",
5
5
  "license": "MIT",
6
6
  "author": "Hanzo AI Inc",
package/src/catalog.ts CHANGED
@@ -14,6 +14,7 @@ export interface ProviderCatalogEntry {
14
14
 
15
15
  export const providerCatalog: ProviderCatalogEntry[] = [
16
16
  { id: 'hanzo', name: 'Hanzo', color: '#ff2d55' },
17
+ { id: 'byo', name: 'BYO endpoint', color: '#64748b' },
17
18
  { id: 'abacus', name: 'Abacus AI', icon: 'providers/abacus.svg' },
18
19
  { id: 'alibaba', name: 'Alibaba', icon: 'providers/alibaba.svg' },
19
20
  { id: 'alibabatokenplan', name: 'Alibaba Token Plan' },
package/src/index.ts CHANGED
@@ -6,18 +6,81 @@ export * from './store.js'
6
6
  export { codexProvider } from './providers/codex.js'
7
7
  export { claudeProvider } from './providers/claude.js'
8
8
  export { hanzoProvider } from './providers/hanzo.js'
9
+ export { groqProvider } from './providers/groq.js'
10
+ export { deepgramProvider } from './providers/deepgram.js'
11
+ export { byoProvider } from './providers/byo.js'
12
+ export * from './providers/api-token.js'
13
+ export {
14
+ openrouterProvider,
15
+ deepseekProvider,
16
+ elevenlabsProvider,
17
+ poeProvider,
18
+ veniceProvider,
19
+ chutesProvider,
20
+ moonshotProvider,
21
+ kimiProvider,
22
+ kimik2Provider,
23
+ zaiProvider,
24
+ openaiProvider,
25
+ litellmProvider,
26
+ llmproxyProvider,
27
+ minimaxProvider,
28
+ } from './providers/api-token-providers.js'
9
29
 
10
30
  import type { ProviderDescriptor } from './provider.js'
11
31
  import { codexProvider } from './providers/codex.js'
12
32
  import { claudeProvider } from './providers/claude.js'
13
33
  import { hanzoProvider } from './providers/hanzo.js'
34
+ import { groqProvider } from './providers/groq.js'
35
+ import { deepgramProvider } from './providers/deepgram.js'
36
+ import { byoProvider } from './providers/byo.js'
37
+ import {
38
+ openrouterProvider,
39
+ deepseekProvider,
40
+ elevenlabsProvider,
41
+ poeProvider,
42
+ veniceProvider,
43
+ chutesProvider,
44
+ moonshotProvider,
45
+ kimiProvider,
46
+ kimik2Provider,
47
+ zaiProvider,
48
+ openaiProvider,
49
+ litellmProvider,
50
+ llmproxyProvider,
51
+ minimaxProvider,
52
+ } from './providers/api-token-providers.js'
14
53
 
15
- /** All built-in providers, registry-style like CodexBar's descriptor registry. */
54
+ /** All built-in providers with a live fetch pipeline, registry-style like
55
+ * CodexBar's descriptor registry. Keys are the canonical provider ids. */
16
56
  export const providerRegistry: Record<string, ProviderDescriptor> = {
17
57
  hanzo: hanzoProvider,
18
58
  codex: codexProvider,
19
59
  claude: claudeProvider,
60
+ openai: openaiProvider,
61
+ openrouter: openrouterProvider,
62
+ deepseek: deepseekProvider,
63
+ elevenlabs: elevenlabsProvider,
64
+ deepgram: deepgramProvider,
65
+ groq: groqProvider,
66
+ poe: poeProvider,
67
+ venice: veniceProvider,
68
+ chutes: chutesProvider,
69
+ moonshot: moonshotProvider,
70
+ kimi: kimiProvider,
71
+ kimik2: kimik2Provider,
72
+ zai: zaiProvider,
73
+ litellm: litellmProvider,
74
+ llmproxy: llmproxyProvider,
75
+ minimax: minimaxProvider,
76
+ byo: byoProvider,
20
77
  }
21
78
 
22
79
  export const allProviders: ProviderDescriptor[] = Object.values(providerRegistry)
80
+
81
+ /** Provider ids that have a live native pipeline — UIs badge catalog entries
82
+ * as "In-app tracked" when their id is in this set (the rest are connect-only
83
+ * or tracked only via api.hanzo.ai cloud routing). */
84
+ export const trackedProviderIds: readonly string[] = Object.keys(providerRegistry)
85
+
23
86
  export * from './catalog.js'