@prestyj/core 5.8.0 → 5.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.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- export { ContextWindowOptions, DEFAULT_MAX_VIDEO_BYTES, MODELS, ModelInfo, getAuthStorageKey, getAuthStorageKeys, getContextWindow, getDefaultModel, getFastModel, getMaxThinkingLevel, getModel, getModelsForProvider, getSummaryModel, getToolResultCharLimit, getVideoByteLimit, usesOpenAICodexTransport } from './model-registry.cjs';
1
+ import { ModelInfo } from './model-registry.cjs';
2
+ export { ContextWindowOptions, DEFAULT_MAX_VIDEO_BYTES, MODELS, clearRuntimeModels, getAllModels, getAuthStorageKey, getAuthStorageKeys, getContextWindow, getDefaultModel, getDefaultThinkingLevel, getFastModel, getMaxThinkingLevel, getModel, getModelsForProvider, getSummaryModel, getToolResultCharLimit, getVideoByteLimit, registerRuntimeModels, usesOpenAICodexTransport } from './model-registry.cjs';
2
3
  import { Provider, ThinkingLevel } from '@prestyj/ai';
3
4
  export { AppPaths, getAppPaths } from './paths.cjs';
4
5
 
@@ -6,6 +7,120 @@ declare function getSupportedThinkingLevels(provider: Provider, model: string):
6
7
  declare function isThinkingLevelSupported(provider: Provider, model: string, level: ThinkingLevel): boolean;
7
8
  declare function getNextThinkingLevel(provider: Provider, model: string, current: ThinkingLevel | undefined): ThinkingLevel | undefined;
8
9
 
10
+ /**
11
+ * Local model discovery — Ollama, LM Studio, llama.cpp (`llama-server`), vLLM,
12
+ * and any other OpenAI-compatible server the user points us at.
13
+ *
14
+ * Everything rides the OpenAI-compatible `/v1` transport (see the `local`
15
+ * provider in gg-ai's stream.ts); the only per-server difference is where the
16
+ * *capabilities* come from, because `GET /v1/models` reports nothing useful:
17
+ *
18
+ * - Ollama → `POST /api/show` → `capabilities[]` + `model_info["<arch>.context_length"]`
19
+ * - LM Studio → `GET /api/v0/models` → `type`, `state`, `max_context_length`
20
+ * - llama.cpp → `GET /props` → `default_generation_settings.n_ctx`
21
+ * - vLLM/other → nothing; `max_model_len` sometimes rides the model object.
22
+ *
23
+ * Probing never throws: an unreachable server is a normal state (the user just
24
+ * doesn't have it running), not an error to surface.
25
+ */
26
+
27
+ /** Which capability API a local endpoint speaks, beyond plain `/v1/models`. */
28
+ type LocalEndpointKind = "ollama" | "lmstudio" | "llamacpp" | "vllm" | "custom";
29
+ interface LocalEndpoint {
30
+ /** Stable slug, used in model ids (`local/<id>/<rawId>`) and auth keys (`local:<id>`). */
31
+ id: string;
32
+ label: string;
33
+ /** OpenAI-compatible base URL, including the `/v1` suffix. */
34
+ baseUrl: string;
35
+ kind: LocalEndpointKind;
36
+ /** Optional bearer token (LM Studio 0.4+ can require one). */
37
+ apiKey?: string;
38
+ /** True for endpoints the user added by hand (removable). */
39
+ custom?: boolean;
40
+ }
41
+ /** One model as reported (and enriched) by a local server. */
42
+ interface LocalModel {
43
+ /** Model id on the wire, exactly as the server names it (e.g. `qwen3-coder:30b`). */
44
+ rawId: string;
45
+ endpointId: string;
46
+ contextWindow: number;
47
+ /** True when the server told us the real window; false means we guessed. */
48
+ contextWindowKnown: boolean;
49
+ supportsTools: boolean;
50
+ supportsImages: boolean;
51
+ supportsThinking: boolean;
52
+ /** LM Studio only: whether the model is currently resident in memory. */
53
+ loaded?: boolean;
54
+ }
55
+ interface LocalEndpointProbe {
56
+ endpoint: LocalEndpoint;
57
+ reachable: boolean;
58
+ /** Human-readable reason when `reachable` is false (never a raw stack). */
59
+ reason?: string;
60
+ models: LocalModel[];
61
+ }
62
+ /**
63
+ * The servers we look for without being asked. Ports are each project's
64
+ * documented default; users who moved a port add a custom endpoint instead.
65
+ */
66
+ declare const DEFAULT_LOCAL_ENDPOINTS: readonly LocalEndpoint[];
67
+ /**
68
+ * Context window assumed when a server tells us nothing. Deliberately
69
+ * conservative: over-guessing means the provider 400s mid-run at a point
70
+ * auto-compaction already sailed past, while under-guessing only compacts early.
71
+ */
72
+ declare const FALLBACK_CONTEXT_WINDOW = 8192;
73
+ /** Placeholder token for endpoints with no key — these servers ignore it. */
74
+ declare const LOCAL_API_KEY_PLACEHOLDER = "local";
75
+ /**
76
+ * `local/<endpointId>/<rawModelId>`. The raw id can itself contain slashes
77
+ * (`hf.co/user/repo:q4`), so only the first two segments are structural.
78
+ */
79
+ declare function formatLocalModelId(endpointId: string, rawId: string): string;
80
+ declare function parseLocalModelId(id: string): {
81
+ endpointId: string;
82
+ rawId: string;
83
+ } | undefined;
84
+ declare function isLocalModelId(id: string): boolean;
85
+ /** Auth-storage key holding the credential (and baseUrl) for one local endpoint. */
86
+ declare function localAuthStorageKey(endpointId: string): string;
87
+ /** Base URL with any trailing `/v1` (and trailing slashes) removed — the server root. */
88
+ declare function endpointRoot(baseUrl: string): string;
89
+ interface ProbeOptions {
90
+ timeoutMs?: number;
91
+ signal?: AbortSignal;
92
+ }
93
+ /**
94
+ * Ask one endpoint what it serves. Never throws — an unreachable server yields
95
+ * `{ reachable: false, reason }` so the UI can say "not running" without an
96
+ * error toast.
97
+ */
98
+ declare function probeEndpoint(endpoint: LocalEndpoint, { timeoutMs, signal }?: ProbeOptions): Promise<LocalEndpointProbe>;
99
+ /** Convert a probed local model into the registry shape the whole app speaks. */
100
+ declare function toModelInfo(model: LocalModel, endpoint: LocalEndpoint): ModelInfo;
101
+ interface DiscoveryResult {
102
+ probes: LocalEndpointProbe[];
103
+ models: ModelInfo[];
104
+ }
105
+ interface DiscoverOptions extends ProbeOptions {
106
+ /** Skip the 30s cache (the UI's "Scan" button). */
107
+ force?: boolean;
108
+ }
109
+ /**
110
+ * Probe every endpoint in parallel and return both the per-endpoint status (for
111
+ * the UI) and the `ModelInfo[]` ready for `registerRuntimeModels()`. Results are
112
+ * cached for 30s per endpoint set so repeated `GET /models` calls don't re-probe
113
+ * four servers; `force` bypasses it after an `ollama pull`.
114
+ */
115
+ declare function discoverLocalModels(endpoints?: readonly LocalEndpoint[], options?: DiscoverOptions): Promise<DiscoveryResult>;
116
+ /** Drop the discovery cache (used by tests and after an endpoint is added/removed). */
117
+ declare function clearLocalDiscoveryCache(): void;
118
+ /** Look up the probed capabilities of a discovered model, by full local id. */
119
+ declare function findProbedModel(probes: readonly LocalEndpointProbe[], modelId: string): {
120
+ model: LocalModel;
121
+ endpoint: LocalEndpoint;
122
+ } | undefined;
123
+
9
124
  type LogLevel = "INFO" | "ERROR" | "WARN" | "DEBUG";
10
125
  /**
11
126
  * Open the debug log in append mode, tagging this process with a session id and
@@ -55,6 +170,10 @@ interface OAuthCredentials {
55
170
  accessToken: string;
56
171
  refreshToken: string;
57
172
  expiresAt: number;
173
+ /** Original token lifetime in seconds (the provider's `expires_in`). Used to
174
+ * scale the proactive-refresh threshold: short-lived tokens (e.g. Kimi's
175
+ * 15-min access token) must refresh well before expiry, not 60s prior. */
176
+ expiresIn?: number;
58
177
  accountId?: string;
59
178
  projectId?: string;
60
179
  baseUrl?: string;
@@ -85,6 +204,22 @@ declare const MOONSHOT_OAUTH_KEY = "moonshot-oauth";
85
204
  * order, is decided per-model via `getAuthStorageKeys()` in model-registry.ts.
86
205
  */
87
206
  declare const XIAOMI_CREDITS_KEY = "xiaomi-credits";
207
+ /**
208
+ * Prefix for local-endpoint credentials (`local:ollama`, `local:lmstudio`, …).
209
+ * One entry per endpoint, each carrying that endpoint's `baseUrl`, so the
210
+ * existing `resolveCredentials({ storageKeys })` override resolves a local model
211
+ * with no new code path. Kept in sync with `localAuthStorageKey()` in
212
+ * local-models.ts.
213
+ */
214
+ declare const LOCAL_AUTH_KEY_PREFIX = "local:";
215
+ /**
216
+ * Synchronous baseUrl read straight from the auth file, for boot paths that
217
+ * need the active endpoint before an AuthStorage instance exists (e.g. the
218
+ * CLI's sync main()). Missing/corrupt files yield undefined — callers treat
219
+ * that as the provider's public endpoint. Read-only: safe without the file
220
+ * lock (a torn mid-write read just falls back to undefined).
221
+ */
222
+ declare function readStoredBaseUrlSync(authFile: string, provider: string): string | undefined;
88
223
  declare class AuthStorage {
89
224
  private data;
90
225
  private filePath;
@@ -112,22 +247,47 @@ declare class AuthStorage {
112
247
  * Moonshot API key.
113
248
  */
114
249
  hasProviderAuth(provider: string): Promise<boolean>;
250
+ /** Endpoint ids that currently have a `local:<id>` credential stored. */
251
+ listLocalEndpointIds(): Promise<string[]>;
252
+ /**
253
+ * Write (or refresh) the credential for one local endpoint. The `baseUrl` is
254
+ * what `effectiveBaseUrl` later picks up, and `accessToken` is the endpoint's
255
+ * key — a placeholder for the servers that ignore it.
256
+ */
257
+ setLocalEndpoint(endpointId: string, baseUrl: string, apiKey?: string): Promise<void>;
258
+ /** Remove one local endpoint's credential. No-op when it isn't stored. */
259
+ removeLocalEndpoint(endpointId: string): Promise<void>;
115
260
  /**
116
261
  * True if the active credential for `provider` is a static API key with no
117
262
  * refresh mechanism. For `moonshot` this is only true when the Kimi OAuth
118
263
  * credential is absent (a present OAuth credential is refreshable).
119
264
  */
120
265
  isStaticApiKey(provider: string): Promise<boolean>;
266
+ /**
267
+ * The base URL on the credential that is active right now, if any.
268
+ * Synchronous — call only after load()/resolveCredentials() populated the
269
+ * snapshot. For `moonshot` this is the Kimi For Coding URL whenever the
270
+ * OAuth entry is the one resolveCredentials would serve (i.e. not currently
271
+ * usage-exhausted with an API key configured).
272
+ */
273
+ getStoredBaseUrl(provider: string): string | undefined;
121
274
  load(): Promise<void>;
122
275
  private ensureLoaded;
123
276
  /**
124
277
  * Force a re-read from disk, discarding the in-memory cache. Needed when
125
278
  * another process mutates the auth file out-of-band — e.g. the desktop app
126
- * writes API keys natively (Rust → ~/.ezcoder/auth.json) without going through
127
- * this instance, so a long-lived daemon's cache would otherwise stay stale and
128
- * never see a newly added provider key.
279
+ * writes API keys natively without going through this instance.
129
280
  */
130
281
  reload(): Promise<void>;
282
+ /**
283
+ * Apply one provider-scoped mutation to the latest on-disk snapshot.
284
+ * AuthStorage instances live in every app session/process, so writing this
285
+ * instance's cached snapshot can erase credentials another instance just
286
+ * added. The file lock only serializes writers; the re-read prevents stale
287
+ * full-file overwrites.
288
+ */
289
+ private mutateLatest;
290
+ private reloadLatest;
131
291
  getCredentials(provider: string): Promise<OAuthCredentials | undefined>;
132
292
  setCredentials(provider: string, creds: OAuthCredentials): Promise<void>;
133
293
  clearCredentials(provider: string): Promise<void>;
@@ -159,14 +319,13 @@ declare class AuthStorage {
159
319
  * Throws if not logged in.
160
320
  */
161
321
  resolveToken(provider: string): Promise<string>;
162
- private save;
163
322
  }
164
323
  declare class NotLoggedInError extends Error {
165
324
  provider: string;
166
325
  constructor(provider: string);
167
326
  }
168
327
 
169
- type SubscriptionUsageProvider = "anthropic" | "openai";
328
+ type SubscriptionUsageProvider = "anthropic" | "openai" | "moonshot";
170
329
  interface SubscriptionUsageWindow {
171
330
  kind: "current" | "weekly";
172
331
  label: string;
@@ -182,7 +341,8 @@ interface SubscriptionUsageSnapshot {
182
341
  }
183
342
  declare class SubscriptionUsageError extends Error {
184
343
  readonly status?: number | undefined;
185
- constructor(message: string, status?: number | undefined);
344
+ readonly retryAfterMs?: number | undefined;
345
+ constructor(message: string, status?: number | undefined, retryAfterMs?: number | undefined);
186
346
  }
187
347
  type FetchFn = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
188
348
  interface FetchSubscriptionUsageOptions {
@@ -430,4 +590,4 @@ interface AutoUpdater {
430
590
  }
431
591
  declare function createAutoUpdater(config: AutoUpdateConfig): AutoUpdater;
432
592
 
433
- export { AuthStorage, type AutoUpdateConfig, type AutoUpdater, type InlineButton, type LogLevel, MOONSHOT_OAUTH_KEY, NotLoggedInError, type OAuthCredentials, type OAuthLoginCallbacks, type ProgressCallback, SubscriptionUsageError, type SubscriptionUsageProvider, type SubscriptionUsageSnapshot, type SubscriptionUsageWindow, TelegramBot, type TelegramConfig, type TelegramMessage, type TelegramUpdate, type TelegramVoiceMessage, XIAOMI_CREDITS_KEY, closeLogger, createAutoUpdater, decodeOggOpus, downmixToMono, fetchSubscriptionUsage, generatePKCE, getClaudeCliUserAgent, getClaudeCodeVersion, getNextThinkingLevel, getSessionId, getSupportedThinkingLevels, isKimiCodingEndpoint, isLoggerOpen, isModelLoaded, isThinkingLevelSupported, kimiCodeBaseUrl, kimiCodingHeaders, log, loginAnthropic, loginGemini, loginKimi, loginOpenAI, openLog, refreshAnthropicToken, refreshGeminiToken, refreshKimiToken, refreshOpenAIToken, registerLogCleanup, resample, setProgressCallback, transcribeVoice, withFileLock };
593
+ export { AuthStorage, type AutoUpdateConfig, type AutoUpdater, DEFAULT_LOCAL_ENDPOINTS, type DiscoverOptions, type DiscoveryResult, FALLBACK_CONTEXT_WINDOW, type InlineButton, LOCAL_API_KEY_PLACEHOLDER, LOCAL_AUTH_KEY_PREFIX, type LocalEndpoint, type LocalEndpointKind, type LocalEndpointProbe, type LocalModel, type LogLevel, MOONSHOT_OAUTH_KEY, ModelInfo, NotLoggedInError, type OAuthCredentials, type OAuthLoginCallbacks, type ProbeOptions, type ProgressCallback, SubscriptionUsageError, type SubscriptionUsageProvider, type SubscriptionUsageSnapshot, type SubscriptionUsageWindow, TelegramBot, type TelegramConfig, type TelegramMessage, type TelegramUpdate, type TelegramVoiceMessage, XIAOMI_CREDITS_KEY, clearLocalDiscoveryCache, closeLogger, createAutoUpdater, decodeOggOpus, discoverLocalModels, downmixToMono, endpointRoot, fetchSubscriptionUsage, findProbedModel, formatLocalModelId, generatePKCE, getClaudeCliUserAgent, getClaudeCodeVersion, getNextThinkingLevel, getSessionId, getSupportedThinkingLevels, isKimiCodingEndpoint, isLocalModelId, isLoggerOpen, isModelLoaded, isThinkingLevelSupported, kimiCodeBaseUrl, kimiCodingHeaders, localAuthStorageKey, log, loginAnthropic, loginGemini, loginKimi, loginOpenAI, openLog, parseLocalModelId, probeEndpoint, readStoredBaseUrlSync, refreshAnthropicToken, refreshGeminiToken, refreshKimiToken, refreshOpenAIToken, registerLogCleanup, resample, setProgressCallback, toModelInfo, transcribeVoice, withFileLock };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { ContextWindowOptions, DEFAULT_MAX_VIDEO_BYTES, MODELS, ModelInfo, getAuthStorageKey, getAuthStorageKeys, getContextWindow, getDefaultModel, getFastModel, getMaxThinkingLevel, getModel, getModelsForProvider, getSummaryModel, getToolResultCharLimit, getVideoByteLimit, usesOpenAICodexTransport } from './model-registry.js';
1
+ import { ModelInfo } from './model-registry.js';
2
+ export { ContextWindowOptions, DEFAULT_MAX_VIDEO_BYTES, MODELS, clearRuntimeModels, getAllModels, getAuthStorageKey, getAuthStorageKeys, getContextWindow, getDefaultModel, getDefaultThinkingLevel, getFastModel, getMaxThinkingLevel, getModel, getModelsForProvider, getSummaryModel, getToolResultCharLimit, getVideoByteLimit, registerRuntimeModels, usesOpenAICodexTransport } from './model-registry.js';
2
3
  import { Provider, ThinkingLevel } from '@prestyj/ai';
3
4
  export { AppPaths, getAppPaths } from './paths.js';
4
5
 
@@ -6,6 +7,120 @@ declare function getSupportedThinkingLevels(provider: Provider, model: string):
6
7
  declare function isThinkingLevelSupported(provider: Provider, model: string, level: ThinkingLevel): boolean;
7
8
  declare function getNextThinkingLevel(provider: Provider, model: string, current: ThinkingLevel | undefined): ThinkingLevel | undefined;
8
9
 
10
+ /**
11
+ * Local model discovery — Ollama, LM Studio, llama.cpp (`llama-server`), vLLM,
12
+ * and any other OpenAI-compatible server the user points us at.
13
+ *
14
+ * Everything rides the OpenAI-compatible `/v1` transport (see the `local`
15
+ * provider in gg-ai's stream.ts); the only per-server difference is where the
16
+ * *capabilities* come from, because `GET /v1/models` reports nothing useful:
17
+ *
18
+ * - Ollama → `POST /api/show` → `capabilities[]` + `model_info["<arch>.context_length"]`
19
+ * - LM Studio → `GET /api/v0/models` → `type`, `state`, `max_context_length`
20
+ * - llama.cpp → `GET /props` → `default_generation_settings.n_ctx`
21
+ * - vLLM/other → nothing; `max_model_len` sometimes rides the model object.
22
+ *
23
+ * Probing never throws: an unreachable server is a normal state (the user just
24
+ * doesn't have it running), not an error to surface.
25
+ */
26
+
27
+ /** Which capability API a local endpoint speaks, beyond plain `/v1/models`. */
28
+ type LocalEndpointKind = "ollama" | "lmstudio" | "llamacpp" | "vllm" | "custom";
29
+ interface LocalEndpoint {
30
+ /** Stable slug, used in model ids (`local/<id>/<rawId>`) and auth keys (`local:<id>`). */
31
+ id: string;
32
+ label: string;
33
+ /** OpenAI-compatible base URL, including the `/v1` suffix. */
34
+ baseUrl: string;
35
+ kind: LocalEndpointKind;
36
+ /** Optional bearer token (LM Studio 0.4+ can require one). */
37
+ apiKey?: string;
38
+ /** True for endpoints the user added by hand (removable). */
39
+ custom?: boolean;
40
+ }
41
+ /** One model as reported (and enriched) by a local server. */
42
+ interface LocalModel {
43
+ /** Model id on the wire, exactly as the server names it (e.g. `qwen3-coder:30b`). */
44
+ rawId: string;
45
+ endpointId: string;
46
+ contextWindow: number;
47
+ /** True when the server told us the real window; false means we guessed. */
48
+ contextWindowKnown: boolean;
49
+ supportsTools: boolean;
50
+ supportsImages: boolean;
51
+ supportsThinking: boolean;
52
+ /** LM Studio only: whether the model is currently resident in memory. */
53
+ loaded?: boolean;
54
+ }
55
+ interface LocalEndpointProbe {
56
+ endpoint: LocalEndpoint;
57
+ reachable: boolean;
58
+ /** Human-readable reason when `reachable` is false (never a raw stack). */
59
+ reason?: string;
60
+ models: LocalModel[];
61
+ }
62
+ /**
63
+ * The servers we look for without being asked. Ports are each project's
64
+ * documented default; users who moved a port add a custom endpoint instead.
65
+ */
66
+ declare const DEFAULT_LOCAL_ENDPOINTS: readonly LocalEndpoint[];
67
+ /**
68
+ * Context window assumed when a server tells us nothing. Deliberately
69
+ * conservative: over-guessing means the provider 400s mid-run at a point
70
+ * auto-compaction already sailed past, while under-guessing only compacts early.
71
+ */
72
+ declare const FALLBACK_CONTEXT_WINDOW = 8192;
73
+ /** Placeholder token for endpoints with no key — these servers ignore it. */
74
+ declare const LOCAL_API_KEY_PLACEHOLDER = "local";
75
+ /**
76
+ * `local/<endpointId>/<rawModelId>`. The raw id can itself contain slashes
77
+ * (`hf.co/user/repo:q4`), so only the first two segments are structural.
78
+ */
79
+ declare function formatLocalModelId(endpointId: string, rawId: string): string;
80
+ declare function parseLocalModelId(id: string): {
81
+ endpointId: string;
82
+ rawId: string;
83
+ } | undefined;
84
+ declare function isLocalModelId(id: string): boolean;
85
+ /** Auth-storage key holding the credential (and baseUrl) for one local endpoint. */
86
+ declare function localAuthStorageKey(endpointId: string): string;
87
+ /** Base URL with any trailing `/v1` (and trailing slashes) removed — the server root. */
88
+ declare function endpointRoot(baseUrl: string): string;
89
+ interface ProbeOptions {
90
+ timeoutMs?: number;
91
+ signal?: AbortSignal;
92
+ }
93
+ /**
94
+ * Ask one endpoint what it serves. Never throws — an unreachable server yields
95
+ * `{ reachable: false, reason }` so the UI can say "not running" without an
96
+ * error toast.
97
+ */
98
+ declare function probeEndpoint(endpoint: LocalEndpoint, { timeoutMs, signal }?: ProbeOptions): Promise<LocalEndpointProbe>;
99
+ /** Convert a probed local model into the registry shape the whole app speaks. */
100
+ declare function toModelInfo(model: LocalModel, endpoint: LocalEndpoint): ModelInfo;
101
+ interface DiscoveryResult {
102
+ probes: LocalEndpointProbe[];
103
+ models: ModelInfo[];
104
+ }
105
+ interface DiscoverOptions extends ProbeOptions {
106
+ /** Skip the 30s cache (the UI's "Scan" button). */
107
+ force?: boolean;
108
+ }
109
+ /**
110
+ * Probe every endpoint in parallel and return both the per-endpoint status (for
111
+ * the UI) and the `ModelInfo[]` ready for `registerRuntimeModels()`. Results are
112
+ * cached for 30s per endpoint set so repeated `GET /models` calls don't re-probe
113
+ * four servers; `force` bypasses it after an `ollama pull`.
114
+ */
115
+ declare function discoverLocalModels(endpoints?: readonly LocalEndpoint[], options?: DiscoverOptions): Promise<DiscoveryResult>;
116
+ /** Drop the discovery cache (used by tests and after an endpoint is added/removed). */
117
+ declare function clearLocalDiscoveryCache(): void;
118
+ /** Look up the probed capabilities of a discovered model, by full local id. */
119
+ declare function findProbedModel(probes: readonly LocalEndpointProbe[], modelId: string): {
120
+ model: LocalModel;
121
+ endpoint: LocalEndpoint;
122
+ } | undefined;
123
+
9
124
  type LogLevel = "INFO" | "ERROR" | "WARN" | "DEBUG";
10
125
  /**
11
126
  * Open the debug log in append mode, tagging this process with a session id and
@@ -55,6 +170,10 @@ interface OAuthCredentials {
55
170
  accessToken: string;
56
171
  refreshToken: string;
57
172
  expiresAt: number;
173
+ /** Original token lifetime in seconds (the provider's `expires_in`). Used to
174
+ * scale the proactive-refresh threshold: short-lived tokens (e.g. Kimi's
175
+ * 15-min access token) must refresh well before expiry, not 60s prior. */
176
+ expiresIn?: number;
58
177
  accountId?: string;
59
178
  projectId?: string;
60
179
  baseUrl?: string;
@@ -85,6 +204,22 @@ declare const MOONSHOT_OAUTH_KEY = "moonshot-oauth";
85
204
  * order, is decided per-model via `getAuthStorageKeys()` in model-registry.ts.
86
205
  */
87
206
  declare const XIAOMI_CREDITS_KEY = "xiaomi-credits";
207
+ /**
208
+ * Prefix for local-endpoint credentials (`local:ollama`, `local:lmstudio`, …).
209
+ * One entry per endpoint, each carrying that endpoint's `baseUrl`, so the
210
+ * existing `resolveCredentials({ storageKeys })` override resolves a local model
211
+ * with no new code path. Kept in sync with `localAuthStorageKey()` in
212
+ * local-models.ts.
213
+ */
214
+ declare const LOCAL_AUTH_KEY_PREFIX = "local:";
215
+ /**
216
+ * Synchronous baseUrl read straight from the auth file, for boot paths that
217
+ * need the active endpoint before an AuthStorage instance exists (e.g. the
218
+ * CLI's sync main()). Missing/corrupt files yield undefined — callers treat
219
+ * that as the provider's public endpoint. Read-only: safe without the file
220
+ * lock (a torn mid-write read just falls back to undefined).
221
+ */
222
+ declare function readStoredBaseUrlSync(authFile: string, provider: string): string | undefined;
88
223
  declare class AuthStorage {
89
224
  private data;
90
225
  private filePath;
@@ -112,22 +247,47 @@ declare class AuthStorage {
112
247
  * Moonshot API key.
113
248
  */
114
249
  hasProviderAuth(provider: string): Promise<boolean>;
250
+ /** Endpoint ids that currently have a `local:<id>` credential stored. */
251
+ listLocalEndpointIds(): Promise<string[]>;
252
+ /**
253
+ * Write (or refresh) the credential for one local endpoint. The `baseUrl` is
254
+ * what `effectiveBaseUrl` later picks up, and `accessToken` is the endpoint's
255
+ * key — a placeholder for the servers that ignore it.
256
+ */
257
+ setLocalEndpoint(endpointId: string, baseUrl: string, apiKey?: string): Promise<void>;
258
+ /** Remove one local endpoint's credential. No-op when it isn't stored. */
259
+ removeLocalEndpoint(endpointId: string): Promise<void>;
115
260
  /**
116
261
  * True if the active credential for `provider` is a static API key with no
117
262
  * refresh mechanism. For `moonshot` this is only true when the Kimi OAuth
118
263
  * credential is absent (a present OAuth credential is refreshable).
119
264
  */
120
265
  isStaticApiKey(provider: string): Promise<boolean>;
266
+ /**
267
+ * The base URL on the credential that is active right now, if any.
268
+ * Synchronous — call only after load()/resolveCredentials() populated the
269
+ * snapshot. For `moonshot` this is the Kimi For Coding URL whenever the
270
+ * OAuth entry is the one resolveCredentials would serve (i.e. not currently
271
+ * usage-exhausted with an API key configured).
272
+ */
273
+ getStoredBaseUrl(provider: string): string | undefined;
121
274
  load(): Promise<void>;
122
275
  private ensureLoaded;
123
276
  /**
124
277
  * Force a re-read from disk, discarding the in-memory cache. Needed when
125
278
  * another process mutates the auth file out-of-band — e.g. the desktop app
126
- * writes API keys natively (Rust → ~/.ezcoder/auth.json) without going through
127
- * this instance, so a long-lived daemon's cache would otherwise stay stale and
128
- * never see a newly added provider key.
279
+ * writes API keys natively without going through this instance.
129
280
  */
130
281
  reload(): Promise<void>;
282
+ /**
283
+ * Apply one provider-scoped mutation to the latest on-disk snapshot.
284
+ * AuthStorage instances live in every app session/process, so writing this
285
+ * instance's cached snapshot can erase credentials another instance just
286
+ * added. The file lock only serializes writers; the re-read prevents stale
287
+ * full-file overwrites.
288
+ */
289
+ private mutateLatest;
290
+ private reloadLatest;
131
291
  getCredentials(provider: string): Promise<OAuthCredentials | undefined>;
132
292
  setCredentials(provider: string, creds: OAuthCredentials): Promise<void>;
133
293
  clearCredentials(provider: string): Promise<void>;
@@ -159,14 +319,13 @@ declare class AuthStorage {
159
319
  * Throws if not logged in.
160
320
  */
161
321
  resolveToken(provider: string): Promise<string>;
162
- private save;
163
322
  }
164
323
  declare class NotLoggedInError extends Error {
165
324
  provider: string;
166
325
  constructor(provider: string);
167
326
  }
168
327
 
169
- type SubscriptionUsageProvider = "anthropic" | "openai";
328
+ type SubscriptionUsageProvider = "anthropic" | "openai" | "moonshot";
170
329
  interface SubscriptionUsageWindow {
171
330
  kind: "current" | "weekly";
172
331
  label: string;
@@ -182,7 +341,8 @@ interface SubscriptionUsageSnapshot {
182
341
  }
183
342
  declare class SubscriptionUsageError extends Error {
184
343
  readonly status?: number | undefined;
185
- constructor(message: string, status?: number | undefined);
344
+ readonly retryAfterMs?: number | undefined;
345
+ constructor(message: string, status?: number | undefined, retryAfterMs?: number | undefined);
186
346
  }
187
347
  type FetchFn = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
188
348
  interface FetchSubscriptionUsageOptions {
@@ -430,4 +590,4 @@ interface AutoUpdater {
430
590
  }
431
591
  declare function createAutoUpdater(config: AutoUpdateConfig): AutoUpdater;
432
592
 
433
- export { AuthStorage, type AutoUpdateConfig, type AutoUpdater, type InlineButton, type LogLevel, MOONSHOT_OAUTH_KEY, NotLoggedInError, type OAuthCredentials, type OAuthLoginCallbacks, type ProgressCallback, SubscriptionUsageError, type SubscriptionUsageProvider, type SubscriptionUsageSnapshot, type SubscriptionUsageWindow, TelegramBot, type TelegramConfig, type TelegramMessage, type TelegramUpdate, type TelegramVoiceMessage, XIAOMI_CREDITS_KEY, closeLogger, createAutoUpdater, decodeOggOpus, downmixToMono, fetchSubscriptionUsage, generatePKCE, getClaudeCliUserAgent, getClaudeCodeVersion, getNextThinkingLevel, getSessionId, getSupportedThinkingLevels, isKimiCodingEndpoint, isLoggerOpen, isModelLoaded, isThinkingLevelSupported, kimiCodeBaseUrl, kimiCodingHeaders, log, loginAnthropic, loginGemini, loginKimi, loginOpenAI, openLog, refreshAnthropicToken, refreshGeminiToken, refreshKimiToken, refreshOpenAIToken, registerLogCleanup, resample, setProgressCallback, transcribeVoice, withFileLock };
593
+ export { AuthStorage, type AutoUpdateConfig, type AutoUpdater, DEFAULT_LOCAL_ENDPOINTS, type DiscoverOptions, type DiscoveryResult, FALLBACK_CONTEXT_WINDOW, type InlineButton, LOCAL_API_KEY_PLACEHOLDER, LOCAL_AUTH_KEY_PREFIX, type LocalEndpoint, type LocalEndpointKind, type LocalEndpointProbe, type LocalModel, type LogLevel, MOONSHOT_OAUTH_KEY, ModelInfo, NotLoggedInError, type OAuthCredentials, type OAuthLoginCallbacks, type ProbeOptions, type ProgressCallback, SubscriptionUsageError, type SubscriptionUsageProvider, type SubscriptionUsageSnapshot, type SubscriptionUsageWindow, TelegramBot, type TelegramConfig, type TelegramMessage, type TelegramUpdate, type TelegramVoiceMessage, XIAOMI_CREDITS_KEY, clearLocalDiscoveryCache, closeLogger, createAutoUpdater, decodeOggOpus, discoverLocalModels, downmixToMono, endpointRoot, fetchSubscriptionUsage, findProbedModel, formatLocalModelId, generatePKCE, getClaudeCliUserAgent, getClaudeCodeVersion, getNextThinkingLevel, getSessionId, getSupportedThinkingLevels, isKimiCodingEndpoint, isLocalModelId, isLoggerOpen, isModelLoaded, isThinkingLevelSupported, kimiCodeBaseUrl, kimiCodingHeaders, localAuthStorageKey, log, loginAnthropic, loginGemini, loginKimi, loginOpenAI, openLog, parseLocalModelId, probeEndpoint, readStoredBaseUrlSync, refreshAnthropicToken, refreshGeminiToken, refreshKimiToken, refreshOpenAIToken, registerLogCleanup, resample, setProgressCallback, toModelInfo, transcribeVoice, withFileLock };