@code-yeongyu/senpi-ai 2026.8.12 → 2026.8.13
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/README.md +26 -8
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/azure-openai-responses.js +5 -1
- package/dist/api/azure-openai-responses.js.map +1 -1
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/bedrock-converse-stream.lazy.js.map +1 -1
- package/dist/api/google-generative-ai.d.ts.map +1 -1
- package/dist/api/google-generative-ai.js +2 -2
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-shared.d.ts +15 -1
- package/dist/api/google-shared.d.ts.map +1 -1
- package/dist/api/google-shared.js +103 -4
- package/dist/api/google-shared.js.map +1 -1
- package/dist/api/google-vertex.d.ts.map +1 -1
- package/dist/api/google-vertex.js +2 -2
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/lazy.d.ts +5 -1
- package/dist/api/lazy.d.ts.map +1 -1
- package/dist/api/lazy.js +19 -7
- package/dist/api/lazy.js.map +1 -1
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts +3 -1
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +47 -7
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +22 -9
- package/dist/api/openai-responses-shared.js.map +1 -1
- package/dist/api/openai-responses.js +5 -1
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/pi-messages.d.ts.map +1 -1
- package/dist/api/pi-messages.js.map +1 -1
- package/dist/api/simple-options.d.ts +2 -0
- package/dist/api/simple-options.d.ts.map +1 -1
- package/dist/api/simple-options.js +8 -2
- package/dist/api/simple-options.js.map +1 -1
- package/dist/api/transform-messages.js.map +1 -1
- package/dist/api/warm-prompt-cache.js.map +1 -1
- package/dist/auth/credential-store.d.ts +6 -6
- package/dist/auth/credential-store.d.ts.map +1 -1
- package/dist/auth/credential-store.js +22 -11
- package/dist/auth/credential-store.js.map +1 -1
- package/dist/auth/helpers.d.ts +1 -0
- package/dist/auth/helpers.d.ts.map +1 -1
- package/dist/auth/helpers.js +7 -2
- package/dist/auth/helpers.js.map +1 -1
- package/dist/auth/oauth/anthropic.d.ts.map +1 -1
- package/dist/auth/oauth/anthropic.js +14 -8
- package/dist/auth/oauth/anthropic.js.map +1 -1
- package/dist/auth/oauth/device-code.d.ts +1 -1
- package/dist/auth/oauth/device-code.d.ts.map +1 -1
- package/dist/auth/oauth/device-code.js +4 -4
- package/dist/auth/oauth/device-code.js.map +1 -1
- package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/dist/auth/oauth/github-copilot.js +43 -30
- package/dist/auth/oauth/github-copilot.js.map +1 -1
- package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
- package/dist/auth/oauth/kimi-coding.js +17 -5
- package/dist/auth/oauth/kimi-coding.js.map +1 -1
- package/dist/auth/oauth/load.d.ts.map +1 -1
- package/dist/auth/oauth/load.js.map +1 -1
- package/dist/auth/oauth/openai-codex.d.ts.map +1 -1
- package/dist/auth/oauth/openai-codex.js +12 -5
- package/dist/auth/oauth/openai-codex.js.map +1 -1
- package/dist/auth/oauth/openrouter.d.ts.map +1 -1
- package/dist/auth/oauth/openrouter.js +10 -10
- package/dist/auth/oauth/openrouter.js.map +1 -1
- package/dist/auth/oauth/radius.d.ts.map +1 -1
- package/dist/auth/oauth/radius.js +8 -7
- package/dist/auth/oauth/radius.js.map +1 -1
- package/dist/auth/oauth/xai.d.ts.map +1 -1
- package/dist/auth/oauth/xai.js +3 -2
- package/dist/auth/oauth/xai.js.map +1 -1
- package/dist/auth/resolve.d.ts +1 -0
- package/dist/auth/resolve.d.ts.map +1 -1
- package/dist/auth/resolve.js +24 -13
- package/dist/auth/resolve.js.map +1 -1
- package/dist/auth/types.d.ts +21 -8
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/compat.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +3 -0
- package/dist/env-api-keys.js.map +1 -1
- package/dist/image-models.generated.d.ts +69 -54
- package/dist/image-models.generated.d.ts.map +1 -1
- package/dist/image-models.generated.js +15 -0
- package/dist/image-models.generated.js.map +1 -1
- package/dist/images-models.d.ts.map +1 -1
- package/dist/images-models.js +1 -0
- package/dist/images-models.js.map +1 -1
- package/dist/model.d.ts +2 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js.map +1 -1
- package/dist/models-store.d.ts +9 -12
- package/dist/models-store.d.ts.map +1 -1
- package/dist/models-store.js +6 -3
- package/dist/models-store.js.map +1 -1
- package/dist/models.d.ts +40 -18
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +6 -0
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +6 -0
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +247 -91
- package/dist/models.js.map +1 -1
- package/dist/node/provider-scope.d.ts.map +1 -1
- package/dist/providers/all.d.ts.map +1 -1
- package/dist/providers/all.js +6 -0
- package/dist/providers/all.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +15 -7
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +15 -6
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/baseten.d.ts +3 -0
- package/dist/providers/baseten.d.ts.map +1 -0
- package/dist/providers/baseten.js +15 -0
- package/dist/providers/baseten.js.map +1 -0
- package/dist/providers/baseten.models.d.ts +4 -0
- package/dist/providers/baseten.models.d.ts.map +1 -0
- package/dist/providers/baseten.models.js +6 -0
- package/dist/providers/baseten.models.js.map +1 -0
- package/dist/providers/cloudflare-auth.d.ts.map +1 -1
- package/dist/providers/cloudflare-auth.js +15 -10
- package/dist/providers/cloudflare-auth.js.map +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/baseten.json +1 -0
- package/dist/providers/data/fireworks.json +1 -1
- package/dist/providers/data/github-copilot.json +1 -1
- package/dist/providers/data/opencode-go.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/opengateway.json +1 -0
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/qwen-token-plan-cn.json +1 -1
- package/dist/providers/data/qwen-token-plan-individual.json +1 -0
- package/dist/providers/data/qwen-token-plan.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/providers/data/xai.json +1 -1
- package/dist/providers/faux.d.ts +18 -13
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +95 -5
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/github-copilot.js +1 -1
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +15 -7
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/kimi-coding.d.ts.map +1 -1
- package/dist/providers/kimi-coding.js +1 -0
- package/dist/providers/kimi-coding.js.map +1 -1
- package/dist/providers/ollama.d.ts.map +1 -1
- package/dist/providers/ollama.js +1 -2
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai-codex.d.ts.map +1 -1
- package/dist/providers/openai-codex.js +5 -1
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/providers/opengateway.d.ts +3 -0
- package/dist/providers/opengateway.d.ts.map +1 -0
- package/dist/providers/opengateway.js +15 -0
- package/dist/providers/opengateway.js.map +1 -0
- package/dist/providers/opengateway.models.d.ts +4 -0
- package/dist/providers/opengateway.models.d.ts.map +1 -0
- package/dist/providers/opengateway.models.js +6 -0
- package/dist/providers/opengateway.models.js.map +1 -0
- package/dist/providers/qwen-token-plan-individual.d.ts +3 -0
- package/dist/providers/qwen-token-plan-individual.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan-individual.js +15 -0
- package/dist/providers/qwen-token-plan-individual.js.map +1 -0
- package/dist/providers/qwen-token-plan-individual.models.d.ts +4 -0
- package/dist/providers/qwen-token-plan-individual.models.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan-individual.models.js +6 -0
- package/dist/providers/qwen-token-plan-individual.models.js.map +1 -0
- package/dist/providers/radius.d.ts.map +1 -1
- package/dist/providers/radius.js +36 -26
- package/dist/providers/radius.js.map +1 -1
- package/dist/providers/xai.d.ts.map +1 -1
- package/dist/providers/xai.js +1 -0
- package/dist/providers/xai.js.map +1 -1
- package/dist/tool-call-middleware/context-transformer.js.map +1 -1
- package/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.d.ts.map +1 -1
- package/dist/tool-call-middleware/protocols/kimi-xtml/parse.js.map +1 -1
- package/dist/tool-call-middleware/recovery-content-lifecycle.d.ts.map +1 -1
- package/dist/tool-call-middleware/recovery-event-stream.d.ts.map +1 -1
- package/dist/tool-call-middleware/recovery-native-projection.d.ts.map +1 -1
- package/dist/tool-call-middleware/recovery-native-projection.js.map +1 -1
- package/dist/tool-call-middleware/recovery-stream-terminal.d.ts.map +1 -1
- package/dist/tool-call-middleware/recovery-stream-wrapper.d.ts.map +1 -1
- package/dist/tool-call-middleware/recovery-stream-wrapper.js +5 -0
- package/dist/tool-call-middleware/recovery-stream-wrapper.js.map +1 -1
- package/dist/tool-call-middleware/recovery-text-projection.d.ts.map +1 -1
- package/dist/tool-call-middleware/recovery-text-projection.js.map +1 -1
- package/dist/tool-call-middleware/stream-thinking-projection.d.ts.map +1 -1
- package/dist/tool-call-middleware/stream-wrapper-shared.d.ts.map +1 -1
- package/dist/tool-call-middleware/stream-wrapper-shared.js.map +1 -1
- package/dist/tool-call-middleware/stream-wrapper.js.map +1 -1
- package/dist/types.d.ts +94 -87
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/abort.d.ts +8 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +49 -0
- package/dist/utils/abort.js.map +1 -0
- package/dist/utils/deferred-tools.js.map +1 -1
- package/dist/utils/event-stream.d.ts.map +1 -1
- package/dist/utils/overflow.d.ts +14 -1
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +18 -1
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/prompt-cache-ttl.d.ts +3 -1
- package/dist/utils/prompt-cache-ttl.d.ts.map +1 -1
- package/dist/utils/prompt-cache-ttl.js +2 -0
- package/dist/utils/prompt-cache-ttl.js.map +1 -1
- package/dist/utils/provider-retry.js.map +1 -1
- package/dist/utils/retry.d.ts +25 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +64 -0
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/tool-schema-compat.d.ts.map +1 -1
- package/dist/utils/tool-schema-compat.js +1 -0
- package/dist/utils/tool-schema-compat.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +6 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +2 -1
package/dist/models.js
CHANGED
|
@@ -3,6 +3,7 @@ import { defaultProviderAuthContext as defaultAuthContext } from "./auth/context
|
|
|
3
3
|
import { InMemoryCredentialStore } from "./auth/credential-store.js";
|
|
4
4
|
import { ModelsError, resolveProviderAuth } from "./auth/resolve.js";
|
|
5
5
|
import { InMemoryModelsStore } from "./models-store.js";
|
|
6
|
+
import { operationSignal, raceWithAbortSignal } from "./utils/abort.js";
|
|
6
7
|
export { ModelsError } from "./auth/resolve.js";
|
|
7
8
|
function mergeHeaders(base, override) {
|
|
8
9
|
if (!base && !override)
|
|
@@ -21,17 +22,25 @@ function mergeHeaders(base, override) {
|
|
|
21
22
|
class ModelsImpl {
|
|
22
23
|
constructor(options) {
|
|
23
24
|
this.providers = new Map();
|
|
25
|
+
this.refreshGenerations = new Map();
|
|
26
|
+
this.refreshControllers = new Map();
|
|
27
|
+
this.publicationChains = new Map();
|
|
24
28
|
this.credentials = options?.credentials ?? new InMemoryCredentialStore();
|
|
25
29
|
this.modelsStore = options?.modelsStore ?? new InMemoryModelsStore();
|
|
26
30
|
this.authContext = options?.authContext ?? defaultAuthContext();
|
|
27
31
|
}
|
|
28
32
|
setProvider(provider) {
|
|
33
|
+
this.supersedeProviderRefresh(provider.id);
|
|
29
34
|
this.providers.set(provider.id, provider);
|
|
30
35
|
}
|
|
31
36
|
deleteProvider(id) {
|
|
37
|
+
this.supersedeProviderRefresh(id);
|
|
32
38
|
this.providers.delete(id);
|
|
33
39
|
}
|
|
34
40
|
clearProviders() {
|
|
41
|
+
for (const id of new Set([...this.providers.keys(), ...this.refreshControllers.keys()])) {
|
|
42
|
+
this.supersedeProviderRefresh(id);
|
|
43
|
+
}
|
|
35
44
|
this.providers.clear();
|
|
36
45
|
}
|
|
37
46
|
getProviders() {
|
|
@@ -66,87 +75,148 @@ class ModelsImpl {
|
|
|
66
75
|
getModel(provider, id) {
|
|
67
76
|
return this.getModels(provider).find((model) => model.id === id);
|
|
68
77
|
}
|
|
78
|
+
supersedeProviderRefresh(providerId) {
|
|
79
|
+
const generation = (this.refreshGenerations.get(providerId) ?? 0) + 1;
|
|
80
|
+
this.refreshGenerations.set(providerId, generation);
|
|
81
|
+
const previous = this.refreshControllers.get(providerId);
|
|
82
|
+
if (previous) {
|
|
83
|
+
this.refreshControllers.delete(providerId);
|
|
84
|
+
previous.abort();
|
|
85
|
+
}
|
|
86
|
+
return generation;
|
|
87
|
+
}
|
|
88
|
+
beginProviderRefresh(providerId) {
|
|
89
|
+
const generation = this.supersedeProviderRefresh(providerId);
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
this.refreshControllers.set(providerId, controller);
|
|
92
|
+
return { generation, controller };
|
|
93
|
+
}
|
|
94
|
+
publishProviderModels(providerId, generation, signal, publication) {
|
|
95
|
+
const previous = this.publicationChains.get(providerId) ?? Promise.resolve();
|
|
96
|
+
const queued = (async () => {
|
|
97
|
+
await previous.catch(() => { });
|
|
98
|
+
if (signal.aborted || this.refreshGenerations.get(providerId) !== generation)
|
|
99
|
+
return false;
|
|
100
|
+
if (publication.persist === null) {
|
|
101
|
+
await this.modelsStore.delete(providerId, { signal });
|
|
102
|
+
}
|
|
103
|
+
else if (publication.persist !== undefined) {
|
|
104
|
+
await this.modelsStore.write(providerId, structuredClone(publication.persist), { signal });
|
|
105
|
+
}
|
|
106
|
+
if (signal.aborted || this.refreshGenerations.get(providerId) !== generation)
|
|
107
|
+
return false;
|
|
108
|
+
publication.update?.();
|
|
109
|
+
return true;
|
|
110
|
+
})();
|
|
111
|
+
const tail = queued.catch(() => { });
|
|
112
|
+
this.publicationChains.set(providerId, tail);
|
|
113
|
+
void tail.then(() => {
|
|
114
|
+
if (this.publicationChains.get(providerId) === tail)
|
|
115
|
+
this.publicationChains.delete(providerId);
|
|
116
|
+
});
|
|
117
|
+
return raceWithAbortSignal(queued, signal);
|
|
118
|
+
}
|
|
119
|
+
async runProviderRefreshPhase(provider, credential, allowNetwork, force, generation, signal) {
|
|
120
|
+
const stored = await this.modelsStore.read(provider.id, { signal });
|
|
121
|
+
await provider.refreshModels({
|
|
122
|
+
credential,
|
|
123
|
+
stored: stored ? structuredClone(stored) : undefined,
|
|
124
|
+
publish: (publication) => this.publishProviderModels(provider.id, generation, signal, publication),
|
|
125
|
+
allowNetwork,
|
|
126
|
+
force: allowNetwork ? force : undefined,
|
|
127
|
+
signal,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
69
130
|
async refresh(options = {}) {
|
|
70
131
|
const allowNetwork = options.allowNetwork ?? true;
|
|
132
|
+
const callerSignal = operationSignal(options.signal);
|
|
71
133
|
const errors = new Map();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
134
|
+
if (callerSignal.aborted)
|
|
135
|
+
return { aborted: true, errors };
|
|
136
|
+
const selected = options.providers ? new Set(options.providers) : undefined;
|
|
137
|
+
const refreshable = Array.from(this.providers.values()).filter((provider) => provider.refreshModels !== undefined && (!selected || selected.has(provider.id)));
|
|
138
|
+
const refresh = Promise.all(refreshable.map(async (provider) => {
|
|
139
|
+
const { generation, controller } = this.beginProviderRefresh(provider.id);
|
|
140
|
+
const signal = AbortSignal.any([callerSignal, controller.signal]);
|
|
141
|
+
const operation = (async () => {
|
|
142
|
+
let storedCredential;
|
|
143
|
+
let credentialError;
|
|
144
|
+
try {
|
|
145
|
+
storedCredential = await this.readCredential(provider.id, signal);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
credentialError = error;
|
|
149
|
+
}
|
|
150
|
+
// Restore cached provider state before auth resolution or network access.
|
|
151
|
+
await this.runProviderRefreshPhase(provider, storedCredential, false, undefined, generation, signal);
|
|
152
|
+
if (credentialError !== undefined)
|
|
153
|
+
throw credentialError;
|
|
154
|
+
if (!allowNetwork || signal.aborted)
|
|
155
|
+
return;
|
|
156
|
+
const credential = await this.resolveRefreshCredential(provider, storedCredential, signal);
|
|
85
157
|
if (!credential)
|
|
86
158
|
return;
|
|
87
|
-
await provider.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
force: options.force,
|
|
92
|
-
signal: options.signal,
|
|
93
|
-
});
|
|
159
|
+
await this.runProviderRefreshPhase(provider, credential, true, options.force, generation, signal);
|
|
160
|
+
})();
|
|
161
|
+
try {
|
|
162
|
+
await raceWithAbortSignal(operation, signal);
|
|
94
163
|
}
|
|
95
164
|
catch (error) {
|
|
96
|
-
if (!
|
|
165
|
+
if (!signal.aborted) {
|
|
97
166
|
errors.set(provider.id, error instanceof Error
|
|
98
167
|
? error
|
|
99
168
|
: new ModelsError("model_source", `Model refresh failed for ${provider.id}`, { cause: error }));
|
|
100
169
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
allowNetwork: false,
|
|
106
|
-
signal: options.signal,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
// Preserve the original auth/network error; cache restoration is best-effort here.
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
if (this.refreshControllers.get(provider.id) === controller) {
|
|
173
|
+
this.refreshControllers.delete(provider.id);
|
|
111
174
|
}
|
|
112
175
|
}
|
|
113
176
|
}));
|
|
114
|
-
|
|
177
|
+
try {
|
|
178
|
+
await raceWithAbortSignal(refresh, callerSignal);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (!callerSignal.aborted)
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
return { aborted: callerSignal.aborted, errors: new Map(errors) };
|
|
115
185
|
}
|
|
116
|
-
async resolveRefreshCredential(provider, stored,
|
|
186
|
+
async resolveRefreshCredential(provider, stored, signal) {
|
|
117
187
|
if (stored?.type === "oauth") {
|
|
118
188
|
const oauth = provider.auth.oauth;
|
|
119
189
|
if (!oauth)
|
|
120
190
|
return undefined;
|
|
121
|
-
if (
|
|
191
|
+
if (Date.now() < stored.expires)
|
|
122
192
|
return stored;
|
|
123
|
-
if (signal
|
|
193
|
+
if (signal.aborted)
|
|
124
194
|
return undefined;
|
|
125
195
|
const post = await this.credentials.modify(provider.id, async (current) => {
|
|
126
196
|
if (current?.type !== "oauth" || Date.now() < current.expires)
|
|
127
197
|
return undefined;
|
|
128
198
|
return oauth.refresh(current, signal);
|
|
129
|
-
});
|
|
199
|
+
}, { signal });
|
|
130
200
|
return post?.type === "oauth" ? post : undefined;
|
|
131
201
|
}
|
|
132
202
|
const apiKey = provider.auth.apiKey;
|
|
133
203
|
if (!apiKey)
|
|
134
204
|
return undefined;
|
|
135
205
|
const credential = stored?.type === "api_key" ? stored : undefined;
|
|
136
|
-
const result = await apiKey.resolve({ ctx: this.authContext, credential });
|
|
206
|
+
const result = await apiKey.resolve({ ctx: this.authContext, credential, signal });
|
|
137
207
|
if (!result)
|
|
138
208
|
return undefined;
|
|
139
209
|
return { type: "api_key", key: result.auth.apiKey, env: result.env };
|
|
140
210
|
}
|
|
141
|
-
async readCredential(providerId) {
|
|
211
|
+
async readCredential(providerId, signal) {
|
|
142
212
|
try {
|
|
143
|
-
return await this.credentials.read(providerId);
|
|
213
|
+
return await this.credentials.read(providerId, { signal });
|
|
144
214
|
}
|
|
145
215
|
catch (error) {
|
|
146
216
|
throw new ModelsError("auth", `Credential store read failed for ${providerId}`, { cause: error });
|
|
147
217
|
}
|
|
148
218
|
}
|
|
149
|
-
async checkProviderAuth(provider, credential) {
|
|
219
|
+
async checkProviderAuth(provider, credential, signal) {
|
|
150
220
|
const oauth = provider.auth.oauth;
|
|
151
221
|
if (credential?.type === "oauth") {
|
|
152
222
|
if (!oauth)
|
|
@@ -154,7 +224,7 @@ class ModelsImpl {
|
|
|
154
224
|
if (!oauth.check)
|
|
155
225
|
return { source: "OAuth", type: "oauth" };
|
|
156
226
|
try {
|
|
157
|
-
return await oauth.check({ ctx: this.authContext, credential });
|
|
227
|
+
return await oauth.check({ ctx: this.authContext, credential, signal });
|
|
158
228
|
}
|
|
159
229
|
catch (error) {
|
|
160
230
|
throw new ModelsError("auth", `OAuth auth check failed for provider ${provider.id}`, { cause: error });
|
|
@@ -167,6 +237,7 @@ class ModelsImpl {
|
|
|
167
237
|
const result = await apiKey.check({
|
|
168
238
|
ctx: this.authContext,
|
|
169
239
|
credential: credential?.type === "api_key" ? credential : undefined,
|
|
240
|
+
signal,
|
|
170
241
|
});
|
|
171
242
|
if (result)
|
|
172
243
|
return result;
|
|
@@ -176,7 +247,7 @@ class ModelsImpl {
|
|
|
176
247
|
}
|
|
177
248
|
}
|
|
178
249
|
else {
|
|
179
|
-
const resolution = await resolveProviderAuth(provider, this.credentials, this.authContext);
|
|
250
|
+
const resolution = await resolveProviderAuth(provider, this.credentials, this.authContext, { signal });
|
|
180
251
|
if (resolution)
|
|
181
252
|
return { source: resolution.source, type: "api_key" };
|
|
182
253
|
}
|
|
@@ -184,39 +255,50 @@ class ModelsImpl {
|
|
|
184
255
|
if (!oauth?.check)
|
|
185
256
|
return undefined;
|
|
186
257
|
try {
|
|
187
|
-
return await oauth.check({ ctx: this.authContext });
|
|
258
|
+
return await oauth.check({ ctx: this.authContext, signal });
|
|
188
259
|
}
|
|
189
260
|
catch (error) {
|
|
190
261
|
throw new ModelsError("auth", `OAuth auth check failed for provider ${provider.id}`, { cause: error });
|
|
191
262
|
}
|
|
192
263
|
}
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
264
|
+
checkAuth(providerId, options) {
|
|
265
|
+
const signal = operationSignal(options?.signal);
|
|
266
|
+
const check = (async () => {
|
|
267
|
+
signal.throwIfAborted();
|
|
268
|
+
const provider = this.providers.get(providerId);
|
|
269
|
+
if (!provider)
|
|
270
|
+
return undefined;
|
|
271
|
+
return this.checkProviderAuth(provider, await this.readCredential(providerId, signal), signal);
|
|
272
|
+
})();
|
|
273
|
+
return raceWithAbortSignal(check, signal);
|
|
274
|
+
}
|
|
275
|
+
getAvailable(providerId, options) {
|
|
276
|
+
const signal = operationSignal(options?.signal);
|
|
277
|
+
const available = (async () => {
|
|
278
|
+
signal.throwIfAborted();
|
|
279
|
+
const providers = providerId
|
|
280
|
+
? [this.providers.get(providerId)].filter((entry) => entry !== undefined)
|
|
281
|
+
: this.getProviders();
|
|
282
|
+
const checks = await Promise.all(providers.map(async (provider) => {
|
|
283
|
+
const credential = await this.readCredential(provider.id, signal);
|
|
284
|
+
return { provider, credential, auth: await this.checkProviderAuth(provider, credential, signal) };
|
|
285
|
+
}));
|
|
286
|
+
return checks.flatMap(({ provider, credential, auth }) => {
|
|
287
|
+
if (!auth)
|
|
288
|
+
return [];
|
|
289
|
+
const models = provider.getModels();
|
|
290
|
+
return provider.filterModels?.(models, credential) ?? models;
|
|
291
|
+
});
|
|
292
|
+
})();
|
|
293
|
+
return raceWithAbortSignal(available, signal);
|
|
213
294
|
}
|
|
214
295
|
async getAuth(providerOrModel, overrides) {
|
|
296
|
+
const signal = operationSignal(overrides?.signal);
|
|
215
297
|
const providerId = typeof providerOrModel === "string" ? providerOrModel : providerOrModel.provider;
|
|
216
298
|
const provider = this.providers.get(providerId);
|
|
217
299
|
if (!provider)
|
|
218
300
|
return undefined;
|
|
219
|
-
const result = await resolveProviderAuth(provider, this.credentials, this.authContext, overrides);
|
|
301
|
+
const result = await resolveProviderAuth(provider, this.credentials, this.authContext, { ...overrides, signal });
|
|
220
302
|
if (!result || typeof providerOrModel === "string" || !providerOrModel.headers)
|
|
221
303
|
return result;
|
|
222
304
|
return {
|
|
@@ -228,6 +310,8 @@ class ModelsImpl {
|
|
|
228
310
|
};
|
|
229
311
|
}
|
|
230
312
|
async login(providerId, type, interaction) {
|
|
313
|
+
const signal = operationSignal(interaction.signal);
|
|
314
|
+
signal.throwIfAborted();
|
|
231
315
|
const provider = this.providers.get(providerId);
|
|
232
316
|
if (!provider)
|
|
233
317
|
throw new ModelsError("provider", `Unknown provider: ${providerId}`);
|
|
@@ -235,20 +319,52 @@ class ModelsImpl {
|
|
|
235
319
|
if (!method?.login) {
|
|
236
320
|
throw new ModelsError("auth", `${provider.name} does not support ${type} login`);
|
|
237
321
|
}
|
|
238
|
-
const
|
|
322
|
+
const loginOperation = method.login({ ...interaction, signal });
|
|
323
|
+
const credential = await raceWithAbortSignal(loginOperation, signal);
|
|
324
|
+
let mutationStarted = false;
|
|
325
|
+
let markMutationStarted;
|
|
326
|
+
const started = new Promise((resolve) => {
|
|
327
|
+
markMutationStarted = resolve;
|
|
328
|
+
});
|
|
329
|
+
const mutation = this.credentials.modify(providerId, async () => {
|
|
330
|
+
mutationStarted = true;
|
|
331
|
+
markMutationStarted?.();
|
|
332
|
+
return credential;
|
|
333
|
+
}, { signal });
|
|
334
|
+
void mutation.catch(() => { });
|
|
239
335
|
try {
|
|
240
|
-
await
|
|
336
|
+
await new Promise((resolve, reject) => {
|
|
337
|
+
const onAbort = () => {
|
|
338
|
+
if (!mutationStarted)
|
|
339
|
+
reject(signal.reason);
|
|
340
|
+
};
|
|
341
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
342
|
+
void Promise.race([started, mutation]).then(() => {
|
|
343
|
+
signal.removeEventListener("abort", onAbort);
|
|
344
|
+
resolve();
|
|
345
|
+
}, (error) => {
|
|
346
|
+
signal.removeEventListener("abort", onAbort);
|
|
347
|
+
reject(error);
|
|
348
|
+
});
|
|
349
|
+
if (signal.aborted)
|
|
350
|
+
onAbort();
|
|
351
|
+
});
|
|
352
|
+
await mutation;
|
|
241
353
|
}
|
|
242
354
|
catch (error) {
|
|
355
|
+
signal.throwIfAborted();
|
|
243
356
|
throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error });
|
|
244
357
|
}
|
|
245
358
|
return credential;
|
|
246
359
|
}
|
|
247
|
-
async logout(providerId) {
|
|
360
|
+
async logout(providerId, options) {
|
|
361
|
+
const signal = operationSignal(options?.signal);
|
|
362
|
+
signal.throwIfAborted();
|
|
248
363
|
try {
|
|
249
|
-
await this.credentials.delete(providerId);
|
|
364
|
+
await this.credentials.delete(providerId, { signal });
|
|
250
365
|
}
|
|
251
366
|
catch (error) {
|
|
367
|
+
signal.throwIfAborted();
|
|
252
368
|
throw new ModelsError("auth", `Credential store delete failed for ${providerId}`, { cause: error });
|
|
253
369
|
}
|
|
254
370
|
}
|
|
@@ -264,6 +380,7 @@ class ModelsImpl {
|
|
|
264
380
|
const resolution = await this.getAuth(model, {
|
|
265
381
|
apiKey: options?.apiKey,
|
|
266
382
|
env: options?.env,
|
|
383
|
+
signal: options?.signal,
|
|
267
384
|
});
|
|
268
385
|
if (!resolution) {
|
|
269
386
|
throw new ModelsError("auth", `Provider is not configured: ${model.provider}`);
|
|
@@ -300,6 +417,24 @@ class ModelsImpl {
|
|
|
300
417
|
async completeSimple(model, context, options) {
|
|
301
418
|
return this.streamSimple(model, context, options).result();
|
|
302
419
|
}
|
|
420
|
+
async fetchDeferred(model, handle, options) {
|
|
421
|
+
return lazyStream(model, async () => {
|
|
422
|
+
const provider = this.requireProvider(model);
|
|
423
|
+
if (!provider.fetchDeferred) {
|
|
424
|
+
throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
|
|
425
|
+
}
|
|
426
|
+
const { requestModel, requestOptions } = await this.applyAuth(model, options);
|
|
427
|
+
return provider.fetchDeferred(requestModel, handle, requestOptions);
|
|
428
|
+
}).result();
|
|
429
|
+
}
|
|
430
|
+
async cancelDeferred(model, handle, options) {
|
|
431
|
+
const provider = this.requireProvider(model);
|
|
432
|
+
if (!provider.cancelDeferred) {
|
|
433
|
+
throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
|
|
434
|
+
}
|
|
435
|
+
const { requestModel, requestOptions } = await this.applyAuth(model, options);
|
|
436
|
+
await provider.cancelDeferred(requestModel, handle, requestOptions);
|
|
437
|
+
}
|
|
303
438
|
}
|
|
304
439
|
export function createModels(options) {
|
|
305
440
|
return new ModelsImpl(options);
|
|
@@ -313,7 +448,6 @@ export function createModels(options) {
|
|
|
313
448
|
export function createProvider(input) {
|
|
314
449
|
const baselineModels = input.models;
|
|
315
450
|
let dynamicModels = [];
|
|
316
|
-
let inflightRefresh;
|
|
317
451
|
const fetchModels = input.fetchModels;
|
|
318
452
|
const currentModels = () => {
|
|
319
453
|
const merged = [...baselineModels];
|
|
@@ -338,7 +472,7 @@ export function createProvider(input) {
|
|
|
338
472
|
}
|
|
339
473
|
return run(streams);
|
|
340
474
|
};
|
|
341
|
-
|
|
475
|
+
const provider = {
|
|
342
476
|
id: input.id,
|
|
343
477
|
name: input.name ?? input.id,
|
|
344
478
|
baseUrl: input.baseUrl,
|
|
@@ -346,34 +480,56 @@ export function createProvider(input) {
|
|
|
346
480
|
auth: input.auth,
|
|
347
481
|
getModels: currentModels,
|
|
348
482
|
refreshModels: fetchModels
|
|
349
|
-
? (context) => {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const refreshed = await fetchModels(context);
|
|
361
|
-
if (context.signal?.aborted || refreshed.length === 0)
|
|
362
|
-
return;
|
|
363
|
-
dynamicModels = refreshed;
|
|
364
|
-
await context.store.write({ models: refreshed, checkedAt: Date.now() });
|
|
483
|
+
? async (context) => {
|
|
484
|
+
if (context.stored) {
|
|
485
|
+
const restored = context.stored.models
|
|
486
|
+
.filter((model) => model.provider === input.id)
|
|
487
|
+
.map((model) => model);
|
|
488
|
+
if (!(await context.publish({
|
|
489
|
+
update: () => {
|
|
490
|
+
dynamicModels = restored;
|
|
491
|
+
},
|
|
492
|
+
}))) {
|
|
493
|
+
return;
|
|
365
494
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
495
|
+
}
|
|
496
|
+
if (!context.allowNetwork || context.signal.aborted)
|
|
497
|
+
return;
|
|
498
|
+
const refreshed = await fetchModels(context);
|
|
499
|
+
if (context.signal.aborted || refreshed.length === 0)
|
|
500
|
+
return;
|
|
501
|
+
await context.publish({
|
|
502
|
+
persist: { models: refreshed, checkedAt: Date.now() },
|
|
503
|
+
update: () => {
|
|
504
|
+
dynamicModels = refreshed;
|
|
505
|
+
},
|
|
506
|
+
});
|
|
371
507
|
}
|
|
372
508
|
: undefined,
|
|
373
509
|
filterModels: input.filterModels,
|
|
374
510
|
stream: (model, context, options) => dispatch(model, (streams) => streams.stream(model, context, options)),
|
|
375
511
|
streamSimple: (model, context, options) => dispatch(model, (streams) => streams.streamSimple(model, context, options)),
|
|
376
512
|
};
|
|
513
|
+
const streams = single ? [single] : Object.values(byApi ?? {}).filter((entry) => entry !== undefined);
|
|
514
|
+
if (streams.some((entry) => entry.fetchDeferred !== undefined)) {
|
|
515
|
+
provider.fetchDeferred = (model, handle, options) => lazyStream(model, async () => {
|
|
516
|
+
const implementation = apiFor(model);
|
|
517
|
+
if (!implementation?.fetchDeferred) {
|
|
518
|
+
throw new ModelsError("provider", `Provider ${input.id} does not support deferred responses for "${model.api}"`);
|
|
519
|
+
}
|
|
520
|
+
return implementation.fetchDeferred(model, handle, options);
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
if (streams.some((entry) => entry.cancelDeferred !== undefined)) {
|
|
524
|
+
provider.cancelDeferred = async (model, handle, options) => {
|
|
525
|
+
const implementation = apiFor(model);
|
|
526
|
+
if (!implementation?.cancelDeferred) {
|
|
527
|
+
throw new ModelsError("provider", `Provider ${input.id} cannot cancel deferred responses for "${model.api}"`);
|
|
528
|
+
}
|
|
529
|
+
await implementation.cancelDeferred(model, handle, options);
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return provider;
|
|
377
533
|
}
|
|
378
534
|
/**
|
|
379
535
|
* Runtime-checked narrowing for dynamically looked-up models:
|