saros-proxy 0.7.4 → 0.10.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 (114) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +116 -64
  3. package/dist/cli/autostart.d.ts +0 -0
  4. package/dist/cli/autostart.js +0 -0
  5. package/dist/cli/autostart.js.map +0 -0
  6. package/dist/cli/daemon.d.ts +0 -0
  7. package/dist/cli/daemon.js +4 -0
  8. package/dist/cli/daemon.js.map +1 -1
  9. package/dist/cli/dsh-config.d.ts +32 -0
  10. package/dist/cli/dsh-config.js +122 -0
  11. package/dist/cli/dsh-config.js.map +1 -0
  12. package/dist/cli/harness-models.d.ts +26 -2
  13. package/dist/cli/harness-models.js +192 -2
  14. package/dist/cli/harness-models.js.map +1 -1
  15. package/dist/cli/harness-sync.d.ts +3 -6
  16. package/dist/cli/harness-sync.js +14 -7
  17. package/dist/cli/harness-sync.js.map +1 -1
  18. package/dist/cli/help.d.ts +0 -0
  19. package/dist/cli/help.js +2 -2
  20. package/dist/cli/help.js.map +0 -0
  21. package/dist/cli/omp-config.d.ts +0 -0
  22. package/dist/cli/omp-config.js +11 -1
  23. package/dist/cli/omp-config.js.map +1 -1
  24. package/dist/cli/opencode-config.d.ts +0 -0
  25. package/dist/cli/opencode-config.js +0 -0
  26. package/dist/cli/opencode-config.js.map +0 -0
  27. package/dist/cli/overview.d.ts +0 -0
  28. package/dist/cli/overview.js +2 -1
  29. package/dist/cli/overview.js.map +1 -1
  30. package/dist/cli/pi-config.d.ts +0 -0
  31. package/dist/cli/pi-config.js +11 -1
  32. package/dist/cli/pi-config.js.map +1 -1
  33. package/dist/cli/setup.d.ts +6 -10
  34. package/dist/cli/setup.js +100 -232
  35. package/dist/cli/setup.js.map +1 -1
  36. package/dist/cli/ui.d.ts +1 -2
  37. package/dist/cli/ui.js +0 -16
  38. package/dist/cli/ui.js.map +1 -1
  39. package/dist/cli/update-check.d.ts +0 -0
  40. package/dist/cli/update-check.js +0 -0
  41. package/dist/cli/update-check.js.map +0 -0
  42. package/dist/cli/usage.d.ts +12 -0
  43. package/dist/cli/usage.js +182 -0
  44. package/dist/cli/usage.js.map +1 -0
  45. package/dist/config.d.ts +13 -17
  46. package/dist/config.js +29 -78
  47. package/dist/config.js.map +1 -1
  48. package/dist/constants.d.ts +9 -5
  49. package/dist/constants.js +14 -5
  50. package/dist/constants.js.map +1 -1
  51. package/dist/index.d.ts +0 -0
  52. package/dist/index.js +9 -25
  53. package/dist/index.js.map +1 -1
  54. package/dist/key-encryption.d.ts +0 -0
  55. package/dist/key-encryption.js +0 -0
  56. package/dist/key-encryption.js.map +0 -0
  57. package/dist/logger.d.ts +0 -0
  58. package/dist/logger.js +0 -0
  59. package/dist/logger.js.map +0 -0
  60. package/dist/model-probe.d.ts +0 -0
  61. package/dist/model-probe.js +0 -0
  62. package/dist/model-probe.js.map +0 -0
  63. package/dist/model-stats.d.ts +20 -0
  64. package/dist/model-stats.js +135 -0
  65. package/dist/model-stats.js.map +1 -0
  66. package/dist/models-fetcher.d.ts +0 -0
  67. package/dist/models-fetcher.js +0 -0
  68. package/dist/models-fetcher.js.map +0 -0
  69. package/dist/models-sync.d.ts +0 -0
  70. package/dist/models-sync.js +0 -0
  71. package/dist/models-sync.js.map +0 -0
  72. package/dist/probe-cache.d.ts +0 -0
  73. package/dist/probe-cache.js +0 -0
  74. package/dist/probe-cache.js.map +0 -0
  75. package/dist/providers/commandcode.d.ts +17 -0
  76. package/dist/providers/commandcode.js +237 -0
  77. package/dist/providers/commandcode.js.map +1 -0
  78. package/dist/providers/index.d.ts +43 -0
  79. package/dist/providers/index.js +75 -0
  80. package/dist/providers/index.js.map +1 -0
  81. package/dist/providers/infer.d.ts +23 -0
  82. package/dist/providers/infer.js +13 -0
  83. package/dist/providers/infer.js.map +1 -0
  84. package/dist/providers/opencode-go.d.ts +4 -0
  85. package/dist/providers/opencode-go.js +190 -0
  86. package/dist/providers/opencode-go.js.map +1 -0
  87. package/dist/providers/types.d.ts +135 -0
  88. package/dist/providers/types.js +10 -0
  89. package/dist/providers/types.js.map +1 -0
  90. package/dist/proxy-logic.d.ts +61 -22
  91. package/dist/proxy-logic.js +163 -106
  92. package/dist/proxy-logic.js.map +1 -1
  93. package/dist/proxy.d.ts +43 -2
  94. package/dist/proxy.js +327 -62
  95. package/dist/proxy.js.map +1 -1
  96. package/dist/token-usage.d.ts +53 -0
  97. package/dist/token-usage.js +198 -0
  98. package/dist/token-usage.js.map +1 -0
  99. package/dist/usage-client.d.ts +62 -0
  100. package/dist/usage-client.js +155 -0
  101. package/dist/usage-client.js.map +1 -0
  102. package/dist/validation.d.ts +5 -5
  103. package/dist/validation.js +7 -9
  104. package/dist/validation.js.map +1 -1
  105. package/package.json +11 -7
  106. package/dist/dashboard-parser.d.ts +0 -47
  107. package/dist/dashboard-parser.js +0 -88
  108. package/dist/dashboard-parser.js.map +0 -1
  109. package/dist/firefox-cookies.d.ts +0 -42
  110. package/dist/firefox-cookies.js +0 -170
  111. package/dist/firefox-cookies.js.map +0 -1
  112. package/dist/scraper.d.ts +0 -70
  113. package/dist/scraper.js +0 -239
  114. package/dist/scraper.js.map +0 -1
@@ -0,0 +1,43 @@
1
+ import type { KeyProvider, ProviderId, SmokeResult } from './types.js';
2
+ export * from './types.js';
3
+ export { inferProvider, type KeyEntry } from './infer.js';
4
+ export { opencodeGoProvider, parseOpencodeUsage } from './opencode-go.js';
5
+ export { commandcodeProvider, commandcodeClientHeaders, resolveClientVersion, parsePlanId, COMMANDCODE_BASE_URL, COMMANDCODE_BILLING_PATH, } from './commandcode.js';
6
+ /** Get a provider by id. */
7
+ export declare function getProvider(id: ProviderId): KeyProvider | undefined;
8
+ /** All registered providers. */
9
+ export declare function allProviders(): readonly KeyProvider[];
10
+ /** Structural validity: at least one provider accepts the format. */
11
+ export declare function isPlausibleKey(key: string): boolean;
12
+ export interface KeyIdentification {
13
+ /** Provider the key belongs to, or null when unknown/invalid. */
14
+ provider: ProviderId | null;
15
+ /** 'unverified' when only the prefix matched (no definitive ping). */
16
+ confidence: 'verified' | 'unverified' | 'invalid' | 'unknown';
17
+ /** Per-provider smoke results (diagnostics for the TUI). */
18
+ attempts: SmokeResult[];
19
+ }
20
+ /**
21
+ * Identify which provider a key belongs to.
22
+ *
23
+ * Decision tree:
24
+ * 1. Providers whose prefix says 'no' are skipped entirely.
25
+ * 2. Remaining candidates are smoke-tested in parallel.
26
+ * 3. First 'valid' wins (definitive positive).
27
+ * 4. All candidates 'invalid' → the key is rejected.
28
+ * 5. Only inconclusive 'error' results → unknown (network problem).
29
+ * 6. No candidates at all → prefix not recognized by any provider.
30
+ *
31
+ * When `verify: false`, skips the network and returns the prefix-level
32
+ * answer ('unverified') — used by config validation where offline tolerance
33
+ * is required.
34
+ */
35
+ export declare function identifyKey(key: string, options?: {
36
+ verify?: boolean;
37
+ timeoutMs?: number;
38
+ }): Promise<KeyIdentification>;
39
+ /**
40
+ * Extract candidate API keys from arbitrary pasted text (TUI drop-a-key
41
+ * flow). Matches oc_sk_, user_ and sk- tokens of plausible length; dedupes.
42
+ */
43
+ export declare function extractKeys(text: string): string[];
@@ -0,0 +1,75 @@
1
+ /**
2
+ * index.ts — Provider registry and key identification.
3
+ *
4
+ * identifyKey() classifies a pasted key by pinging candidate providers in
5
+ * parallel: a 200 is a definitive positive, a 401/403 a definitive negative,
6
+ * network errors are inconclusive. Handles the sk- collision between
7
+ * opencode-go and commandcode empirically.
8
+ */
9
+ import { opencodeGoProvider } from './opencode-go.js';
10
+ import { commandcodeProvider } from './commandcode.js';
11
+ export * from './types.js';
12
+ export { inferProvider } from './infer.js';
13
+ export { opencodeGoProvider, parseOpencodeUsage } from './opencode-go.js';
14
+ export { commandcodeProvider, commandcodeClientHeaders, resolveClientVersion, parsePlanId, COMMANDCODE_BASE_URL, COMMANDCODE_BILLING_PATH, } from './commandcode.js';
15
+ const ALL_PROVIDERS = [opencodeGoProvider, commandcodeProvider];
16
+ /** Get a provider by id. */
17
+ export function getProvider(id) {
18
+ return ALL_PROVIDERS.find((p) => p.id === id);
19
+ }
20
+ /** All registered providers. */
21
+ export function allProviders() {
22
+ return ALL_PROVIDERS;
23
+ }
24
+ /** Structural validity: at least one provider accepts the format. */
25
+ export function isPlausibleKey(key) {
26
+ return ALL_PROVIDERS.some((p) => p.quickMatch(key) !== 'no');
27
+ }
28
+ /** Providers whose quickMatch does not rule the key out. */
29
+ function candidates(key) {
30
+ return ALL_PROVIDERS.filter((p) => p.quickMatch(key) !== 'no');
31
+ }
32
+ /**
33
+ * Identify which provider a key belongs to.
34
+ *
35
+ * Decision tree:
36
+ * 1. Providers whose prefix says 'no' are skipped entirely.
37
+ * 2. Remaining candidates are smoke-tested in parallel.
38
+ * 3. First 'valid' wins (definitive positive).
39
+ * 4. All candidates 'invalid' → the key is rejected.
40
+ * 5. Only inconclusive 'error' results → unknown (network problem).
41
+ * 6. No candidates at all → prefix not recognized by any provider.
42
+ *
43
+ * When `verify: false`, skips the network and returns the prefix-level
44
+ * answer ('unverified') — used by config validation where offline tolerance
45
+ * is required.
46
+ */
47
+ export async function identifyKey(key, options) {
48
+ const cands = candidates(key);
49
+ if (cands.length === 0) {
50
+ return { provider: null, confidence: 'unknown', attempts: [] };
51
+ }
52
+ if (options?.verify === false) {
53
+ const unique = new Set(cands.map((c) => c.id));
54
+ const provider = unique.size === 1 ? cands[0].id : null;
55
+ return { provider, confidence: 'unverified', attempts: [] };
56
+ }
57
+ const attempts = await Promise.all(cands.map((c) => c.smokeTest(key, options?.timeoutMs)));
58
+ const valid = attempts.find((a) => a.status === 'valid');
59
+ if (valid) {
60
+ return { provider: valid.provider, confidence: 'verified', attempts };
61
+ }
62
+ if (attempts.every((a) => a.status === 'invalid')) {
63
+ return { provider: null, confidence: 'invalid', attempts };
64
+ }
65
+ return { provider: null, confidence: 'unknown', attempts };
66
+ }
67
+ /**
68
+ * Extract candidate API keys from arbitrary pasted text (TUI drop-a-key
69
+ * flow). Matches oc_sk_, user_ and sk- tokens of plausible length; dedupes.
70
+ */
71
+ export function extractKeys(text) {
72
+ const matches = text.match(/(?:oc_sk_|user_|sk-)[A-Za-z0-9_-]{16,}/g) ?? [];
73
+ return [...new Set(matches)];
74
+ }
75
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * infer.ts — Synchronous provider inference for config-time decisions.
3
+ *
4
+ * Config loading must never block on network I/O, so the provider for each
5
+ * key is inferred structurally here ('user_' → commandcode; 'oc_sk_' and
6
+ * 'sk-' → opencode-go, the legacy default). The authoritative, empirical
7
+ * answer comes from identifyKey() (async, network) — the setup wizard uses it
8
+ * and persists the result as an explicit `provider` field in config.yaml.
9
+ */
10
+ import type { ProviderId } from './types.js';
11
+ export interface KeyEntry {
12
+ label: string;
13
+ key: string;
14
+ /** Explicit provider from config.yaml; wins over inference when present. */
15
+ provider?: ProviderId;
16
+ }
17
+ /**
18
+ * Infer the provider for a key entry.
19
+ * Explicit `provider` field wins; 'user_' keys are commandcode; everything
20
+ * else plausible defaults to opencode-go (back-compat with every existing
21
+ * config.yaml).
22
+ */
23
+ export declare function inferProvider(entry: KeyEntry): ProviderId;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Infer the provider for a key entry.
3
+ * Explicit `provider` field wins; 'user_' keys are commandcode; everything
4
+ * else plausible defaults to opencode-go (back-compat with every existing
5
+ * config.yaml).
6
+ */
7
+ export function inferProvider(entry) {
8
+ if (entry.provider === 'commandcode' || entry.provider === 'opencode-go') {
9
+ return entry.provider;
10
+ }
11
+ return entry.key.startsWith('user_') ? 'commandcode' : 'opencode-go';
12
+ }
13
+ //# sourceMappingURL=infer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infer.js","sourceRoot":"","sources":["../../src/providers/infer.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAe;IAC3C,IAAI,KAAK,CAAC,QAAQ,KAAK,aAAa,IAAI,KAAK,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;QACzE,OAAO,KAAK,CAAC,QAAQ,CAAC;IACxB,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC;AACvE,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { KeyProvider, KeyUsage } from './types.js';
2
+ /** Parse opencode-go usage JSON (moved from usage-client.ts). */
3
+ export declare function parseOpencodeUsage(body: string): KeyUsage | null;
4
+ export declare const opencodeGoProvider: KeyProvider;
@@ -0,0 +1,190 @@
1
+ /**
2
+ * opencode-go.ts — KeyProvider adapter for the OpenCode-Go API (opencode.ai).
3
+ *
4
+ * Keys: `sk-` prefix (shared with commandcode → 'maybe'), ≥ 20 chars.
5
+ * Usage: GET /zen/go/v1/usage with Bearer = the key itself.
6
+ */
7
+ import { API_KEY_PREFIX, MIN_KEY_LENGTH, OPENCODE_GO_KEY_PREFIX, USAGE_PATH, } from '../constants.js';
8
+ /** Bare-name index of the live opencode-go catalog. */
9
+ let catalogCache = null;
10
+ /** Ensures the background refresh fires at most once per process. */
11
+ let refreshStarted = false;
12
+ /**
13
+ * Fetch the public opencode-go model list and index it. The endpoint needs
14
+ * no auth (verified live); failures keep the previous cache.
15
+ */
16
+ async function refreshCatalog(baseUrl, timeoutMs) {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
19
+ try {
20
+ const res = await fetch(`${baseUrl.replace(/\/$/, '')}${USAGE_PATH.replace(/\/usage$/, '/models')}`, {
21
+ signal: controller.signal,
22
+ });
23
+ if (!res.ok)
24
+ return false;
25
+ const body = (await res.json());
26
+ if (!Array.isArray(body.data))
27
+ return false;
28
+ const bareNames = new Set();
29
+ for (const entry of body.data) {
30
+ if (!entry || typeof entry !== 'object')
31
+ continue;
32
+ const id = entry.id;
33
+ if (typeof id !== 'string' || !id)
34
+ continue;
35
+ bareNames.add(id.toLowerCase());
36
+ const last = id.split('/').pop() ?? id;
37
+ if (last)
38
+ bareNames.add(last.toLowerCase());
39
+ }
40
+ catalogCache = { bareNames };
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ finally {
47
+ clearTimeout(timer);
48
+ }
49
+ }
50
+ function parsePercent(window) {
51
+ const percent = window?.percent;
52
+ if (typeof percent !== 'number' || !Number.isFinite(percent))
53
+ return 0;
54
+ return Math.min(100, Math.max(0, percent));
55
+ }
56
+ /** Parse opencode-go usage JSON (moved from usage-client.ts). */
57
+ export function parseOpencodeUsage(body) {
58
+ let parsed;
59
+ try {
60
+ parsed = JSON.parse(body);
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ const usage = parsed?.usage;
66
+ if (!usage || typeof usage !== 'object')
67
+ return null;
68
+ const rolling = parsePercent(usage.rolling);
69
+ const weekly = parsePercent(usage.weekly);
70
+ const monthly = parsePercent(usage.monthly);
71
+ return {
72
+ rollingPercent: rolling,
73
+ weeklyPercent: weekly,
74
+ monthlyPercent: monthly,
75
+ usedPercent: Math.max(rolling, weekly, monthly),
76
+ rollingResetsAt: typeof usage.rolling?.resetsAt === 'string' ? usage.rolling.resetsAt : undefined,
77
+ weeklyResetsAt: typeof usage.weekly?.resetsAt === 'string' ? usage.weekly.resetsAt : undefined,
78
+ monthlyResetsAt: typeof usage.monthly?.resetsAt === 'string' ? usage.monthly.resetsAt : undefined,
79
+ };
80
+ }
81
+ export const opencodeGoProvider = {
82
+ id: 'opencode-go',
83
+ displayName: 'OpenCode Go',
84
+ baseUrl: 'https://opencode.ai',
85
+ chatBasePath: '/zen/go/v1',
86
+ quickMatch(key) {
87
+ if (key.length < MIN_KEY_LENGTH)
88
+ return 'no';
89
+ // oc_sk_ is minted only by OpenCode → definitive.
90
+ if (key.startsWith(OPENCODE_GO_KEY_PREFIX))
91
+ return 'yes';
92
+ // sk- is shared with commandcode → never a definitive 'yes'.
93
+ return key.startsWith(API_KEY_PREFIX) ? 'maybe' : 'no';
94
+ },
95
+ async smokeTest(key, timeoutMs = 10_000) {
96
+ const controller = new AbortController();
97
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
98
+ try {
99
+ const res = await fetch(`${this.baseUrl}${USAGE_PATH}`, {
100
+ headers: { Authorization: `Bearer ${key}` },
101
+ signal: controller.signal,
102
+ });
103
+ if (res.ok) {
104
+ return { provider: this.id, status: 'valid', httpStatus: res.status };
105
+ }
106
+ // 401/403 = definitive rejection; 5xx/429 = inconclusive
107
+ const definitive = res.status === 401 || res.status === 403;
108
+ return {
109
+ provider: this.id,
110
+ status: definitive ? 'invalid' : 'error',
111
+ httpStatus: res.status,
112
+ detail: definitive ? 'upstream rejected the key' : 'upstream unavailable',
113
+ };
114
+ }
115
+ catch (err) {
116
+ return {
117
+ provider: this.id,
118
+ status: 'error',
119
+ detail: err instanceof Error ? err.message : String(err),
120
+ };
121
+ }
122
+ finally {
123
+ clearTimeout(timer);
124
+ }
125
+ },
126
+ usagePath() {
127
+ return USAGE_PATH;
128
+ },
129
+ parseUsageResponse(body) {
130
+ return parseOpencodeUsage(body);
131
+ },
132
+ // Verified live (2026-09-20): the zen endpoint accepts
133
+ // stream_options.include_usage and every chunk carries a `usage` key
134
+ // (null until the final chunk, which reports the real counts).
135
+ supportsStreamUsage: true,
136
+ // Required by opencode.ai/docs/go ("Send a stable session ID in
137
+ // x-opencode-session for each conversation"); enforced live — chat
138
+ // requests without it fail with 400 MissingSessionID.
139
+ sessionHeader: 'x-opencode-session',
140
+ /**
141
+ * Affinity via the live catalog when loaded; 'maybe' before the first
142
+ * fetch resolves (non-committal, keeps the key as rotation fallback).
143
+ */
144
+ modelAffinity(modelId) {
145
+ if (!catalogCache) {
146
+ if (!refreshStarted) {
147
+ refreshStarted = true;
148
+ void refreshCatalog(this.baseUrl, 5_000);
149
+ }
150
+ return 'maybe';
151
+ }
152
+ const bare = (modelId.split('/').pop() ?? modelId).toLowerCase();
153
+ return catalogCache.bareNames.has(bare) ? 'yes' : 'no';
154
+ },
155
+ /** Same verdict as modelAffinity — the catalog index is already bare-name based. */
156
+ modelAffinityByName(bareName) {
157
+ if (!catalogCache) {
158
+ if (!refreshStarted) {
159
+ refreshStarted = true;
160
+ void refreshCatalog(this.baseUrl, 5_000);
161
+ }
162
+ return 'maybe';
163
+ }
164
+ return catalogCache.bareNames.has(bareName.toLowerCase()) ? 'yes' : 'no';
165
+ },
166
+ /** opencode-go ids are already bare (1:1 naming) — the input passes through. */
167
+ resolveNativeId(bareName) {
168
+ return catalogCache?.bareNames.has(bareName.toLowerCase()) ? bareName : null;
169
+ },
170
+ /** Public catalog endpoint (no auth required, verified live). */
171
+ async fetchCatalog(timeoutMs = 5_000) {
172
+ const ok = await refreshCatalog(this.baseUrl, timeoutMs);
173
+ if (!ok)
174
+ return null;
175
+ // Re-fetch and return the raw list for catalog consumers (harness sync).
176
+ try {
177
+ const res = await fetch(`${this.baseUrl.replace(/\/$/, '')}${USAGE_PATH.replace(/\/usage$/, '/models')}`);
178
+ if (!res.ok)
179
+ return null;
180
+ const body = (await res.json());
181
+ return Array.isArray(body.data)
182
+ ? body.data
183
+ : null;
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ },
189
+ };
190
+ //# sourceMappingURL=opencode-go.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-go.js","sourceRoot":"","sources":["../../src/providers/opencode-go.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * types.ts — Provider abstraction for multi-provider API key support.
3
+ *
4
+ * A KeyProvider encapsulates everything saros needs to know about one
5
+ * upstream API platform: how to recognize its keys structurally, how to
6
+ * verify them with a cheap network ping (smoke test), where its usage
7
+ * endpoint lives, and how to parse its usage response shape.
8
+ */
9
+ export type ProviderId = 'opencode-go' | 'commandcode';
10
+ /** Result of a provider smoke test (key verification ping). */
11
+ export interface SmokeResult {
12
+ /** Provider that ran the test. */
13
+ provider: ProviderId;
14
+ /**
15
+ * - 'valid' — key authenticated successfully (HTTP 200)
16
+ * - 'invalid' — upstream definitively rejected the key (401/403 auth)
17
+ * - 'error' — network/timeout/5xx; inconclusive, may retry
18
+ */
19
+ status: 'valid' | 'invalid' | 'error';
20
+ /** HTTP status code when available. */
21
+ httpStatus?: number;
22
+ /** Human-readable detail (safe to show in the TUI — never the key). */
23
+ detail?: string;
24
+ /** Provider-specific extras (e.g. commandcode planId). */
25
+ meta?: Record<string, string>;
26
+ }
27
+ /** Structural prefix check result. */
28
+ export type PrefixMatch = 'yes' | 'no' | 'maybe';
29
+ /**
30
+ * Common interface every key provider implements.
31
+ * All network methods must never log or echo the key itself.
32
+ */
33
+ export interface KeyProvider {
34
+ readonly id: ProviderId;
35
+ /** Human-readable name for TUI output. */
36
+ readonly displayName: string;
37
+ /** Upstream base URL (HTTPS enforced by callers). */
38
+ readonly baseUrl: string;
39
+ /**
40
+ * Canonical chat route prefix saros accepts from clients. Clients always
41
+ * speak opencode-go shapes (/zen/go/v1/...); providers without a matching
42
+ * upstream path get their requests remapped (see remapChatPath).
43
+ */
44
+ readonly chatBasePath: string;
45
+ /**
46
+ * Cheap structural check. 'yes' = prefix uniquely identifies this
47
+ * provider, 'maybe' = prefix shared with another provider (empirical
48
+ * smoke test required), 'no' = definitely not this provider's format.
49
+ */
50
+ quickMatch(key: string): PrefixMatch;
51
+ /**
52
+ * Verify a key with a cheap authenticated GET.
53
+ * Implementations must use a read-only endpoint (no token spend).
54
+ */
55
+ smokeTest(key: string, timeoutMs?: number): Promise<SmokeResult>;
56
+ /**
57
+ * Usage/quota endpoint path for this provider (empty string when the
58
+ * provider has no per-key usage API saros can query).
59
+ */
60
+ usagePath(): string;
61
+ /**
62
+ * Parse a usage response body into the shared KeyUsage shape.
63
+ * Returns null when the body doesn't match this provider's contract.
64
+ */
65
+ parseUsageResponse(body: string): KeyUsage | null;
66
+ /**
67
+ * True when the provider accepts OpenAI's `stream_options.include_usage`
68
+ * and reports token counts for streamed responses. The proxy then asks
69
+ * for usage explicitly on streaming requests (unless the client set the
70
+ * option itself), so per-key token accounting stays accurate.
71
+ * Providers that reject unknown body fields must leave this unset.
72
+ */
73
+ readonly supportsStreamUsage?: boolean;
74
+ /**
75
+ * Header this provider uses to identify one conversation (upstream
76
+ * routing + prompt-cache optimization). saros forwards a client-supplied
77
+ * value untouched and derives a stable one from the conversation when the
78
+ * client sends none — some harnesses only send it on a subset of their
79
+ * request paths, and the upstream rejects the rest outright
80
+ * (OpenCode Go: 400 `MissingSessionID`).
81
+ */
82
+ readonly sessionHeader?: string;
83
+ /**
84
+ * Extra headers this provider requires on upstream requests
85
+ * (e.g. CommandCode CLI-identity headers). Called per request;
86
+ * must not include Authorization (added by the proxy).
87
+ */
88
+ extraUpstreamHeaders?(): Record<string, string>;
89
+ /**
90
+ * Fetch this provider's live model catalog (OpenAI list shape).
91
+ * Returns null when unavailable — callers fall back to bundled data.
92
+ * Implementations must not require a valid key (catalogs are public).
93
+ */
94
+ fetchCatalog?(timeoutMs?: number): Promise<Array<Record<string, unknown>> | null>;
95
+ /**
96
+ * Structural model-id affinity check. Consulted BEFORE dispatching so a
97
+ * request naming a commandcode-only model (e.g. 'claude-*') is not sent
98
+ * with an opencode-go key (and vice versa).
99
+ */
100
+ modelAffinity(modelId: string): ModelAffinity;
101
+ /**
102
+ * Affinity by bare model name (without vendor prefix), e.g. 'ds-v4-flash'
103
+ * for 'deepseek/ds-v4-flash'. Providers whose catalogs use vendor-prefixed
104
+ * ids match the bare name against their catalog's last path segment;
105
+ * others return the same verdict as modelAffinity(id).
106
+ * Enables cross-provider aliasing so shared models rotate across all keys.
107
+ */
108
+ modelAffinityByName?(bareName: string): ModelAffinity;
109
+ /**
110
+ * Map a bare model name to this provider's native catalog id
111
+ * ('deepseek-v4-flash' → 'deepseek/deepseek-v4-flash'), or null when the
112
+ * catalog doesn't serve it. Backed by the provider's live catalog;
113
+ * providers with 1:1 naming may return the input unchanged.
114
+ */
115
+ resolveNativeId?(bareName: string): string | null;
116
+ }
117
+ /**
118
+ * Model → provider affinity.
119
+ *
120
+ * 'yes' = the model id is (almost certainly) served by this provider,
121
+ * 'no' = definitely not, 'maybe' = cannot be decided structurally — the
122
+ * proxy tries matching providers first and falls back to any available key.
123
+ * Used for request routing in mixed-provider key pools.
124
+ */
125
+ export type ModelAffinity = 'yes' | 'no' | 'maybe';
126
+ /** Shared usage shape used across providers (mirrors usage-client KeyUsage). */
127
+ export interface KeyUsage {
128
+ usedPercent: number;
129
+ rollingPercent: number;
130
+ weeklyPercent: number;
131
+ monthlyPercent: number;
132
+ rollingResetsAt?: string;
133
+ weeklyResetsAt?: string;
134
+ monthlyResetsAt?: string;
135
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * types.ts — Provider abstraction for multi-provider API key support.
3
+ *
4
+ * A KeyProvider encapsulates everything saros needs to know about one
5
+ * upstream API platform: how to recognize its keys structurally, how to
6
+ * verify them with a cheap network ping (smoke test), where its usage
7
+ * endpoint lives, and how to parse its usage response shape.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}