@desplega.ai/agent-swarm 1.130.0 → 1.131.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/README.md +1 -1
- package/dist/{actions-73ee723n.js → actions-w53mbns9.js} +7 -7
- package/dist/{anthropic-messages-3xgme8my.js → anthropic-messages-rjmd2e7n.js} +5 -5
- package/dist/{app-hz5c2xay.js → app-m9gwqsqv.js} +4 -4
- package/dist/{assistant-08mr16yp.js → assistant-bp65m6f3.js} +9 -9
- package/dist/{azure-openai-responses-xa4sh9f6.js → azure-openai-responses-c0yjmbfd.js} +8 -5
- package/dist/{boot-reembed-5bgan3cd.js → boot-reembed-62p0at2k.js} +4 -4
- package/dist/{boot-reembed-z7ykyeq8.js → boot-reembed-xmfp6wdh.js} +5 -5
- package/dist/{boot-scrub-logs-b2rjk8mv.js → boot-scrub-logs-rr8q2dms.js} +3 -3
- package/dist/{claude-adapter-qr0k17km.js → claude-adapter-2y47mrkd.js} +6 -6
- package/dist/{cli-s21zzx4b.js → cli-0rcyq6d9.js} +447 -183
- package/dist/{cli-brebbsen.js → cli-2q72qz2s.js} +9 -6
- package/dist/{cli-e5p3es48.js → cli-3kf51j15.js} +1 -1
- package/dist/{cli-vgexpfsr.js → cli-57khth04.js} +6 -3
- package/dist/{cli-h3e66g5b.js → cli-5kx36evn.js} +1 -1
- package/dist/{cli-n9yxcnvk.js → cli-5n6e5yfn.js} +29 -7
- package/dist/{cli-tp519bjz.js → cli-5v4rw6yd.js} +2 -2
- package/dist/{cli-dakxgk70.js → cli-5ze9rv97.js} +4 -4
- package/dist/{cli-ch7t5v7n.js → cli-6map995v.js} +9 -4
- package/dist/{cli-mgerp0vs.js → cli-70xqj0z3.js} +2 -2
- package/dist/{cli-7v5dhmc8.js → cli-72q8m8gk.js} +6 -6
- package/dist/{cli-yj7z99vp.js → cli-78xwvkhj.js} +2 -2
- package/dist/{cli-t5tsq67k.js → cli-9dr5qzc0.js} +39 -4
- package/dist/{cli-73bvdyxr.js → cli-a40rnf71.js} +6 -6
- package/dist/{cli-s18zzkfp.js → cli-a4jzdpa3.js} +44 -7
- package/dist/{cli-w10fkrf1.js → cli-akdvwc9v.js} +354 -115
- package/dist/{cli-hntzdchd.js → cli-bqw0mt1n.js} +1 -1
- package/dist/{cli-mhr8tq3v.js → cli-cdeg08wx.js} +1 -1
- package/dist/{cli-qcsjdppx.js → cli-cvgjwnh1.js} +7 -7
- package/dist/{cli-z9q7pb38.js → cli-d2tfkcga.js} +4 -4
- package/dist/{cli-r62me33w.js → cli-d3x3x1c9.js} +2 -2
- package/dist/{cli-6nh2qxvh.js → cli-darcwax3.js} +1 -1
- package/dist/{cli-326083vb.js → cli-e7ckcg4v.js} +12 -12
- package/dist/{cli-fs153s3x.js → cli-efxfnq6q.js} +3 -3
- package/dist/{cli-xg2eezyv.js → cli-f1am9szv.js} +1 -1
- package/dist/{cli-2pa5w9b6.js → cli-fywms69x.js} +1 -1
- package/dist/{cli-1mj7yz1r.js → cli-g6b8x1h8.js} +1 -1
- package/dist/{cli-epsvdckx.js → cli-gm9cbhbq.js} +20 -10
- package/dist/{cli-tfw3c0y1.js → cli-nm5gtp1x.js} +2 -2
- package/dist/{cli-q8zwffx0.js → cli-pm6xbh84.js} +128 -35
- package/dist/{cli-m8fs5ttq.js → cli-psh3c4va.js} +1 -1
- package/dist/{cli-1px8pn1s.js → cli-qv61hp37.js} +2 -2
- package/dist/{cli-mf009e0j.js → cli-rzbpmxdr.js} +1 -1
- package/dist/{cli-vr2f46zj.js → cli-t1xze5qh.js} +1 -1
- package/dist/{cli-qbb2ten8.js → cli-t55yw3a1.js} +2 -2
- package/dist/{cli-haanc4qd.js → cli-t76vw5db.js} +22 -22
- package/dist/{cli-4n4mdv21.js → cli-xpxde7rm.js} +7 -3
- package/dist/{cli-xdrmxvd4.js → cli-yw8vghx2.js} +46 -10
- package/dist/cli.js +22 -22
- package/dist/{codex-adapter-6pc50x9q.js → codex-adapter-d24q75wk.js} +4 -4
- package/dist/{codex-hook-amndawyb.js → codex-hook-hq54t9ft.js} +2 -2
- package/dist/{codex-session-runner-tpg8hyj7.js → codex-session-runner-g50vcsex.js} +4 -4
- package/dist/{commands-yfpwt11g.js → commands-c3nrc5hg.js} +3 -3
- package/dist/{db-jn499nmp.js → db-nt7fg3tw.js} +3 -3
- package/dist/{google-generative-ai-txgg2q58.js → google-generative-ai-hysrd8qx.js} +7 -5
- package/dist/{google-vertex-t663s1c3.js → google-vertex-p5arrga0.js} +7 -5
- package/dist/{handlers-eev4nng0.js → handlers-fkr6swat.js} +9 -9
- package/dist/{hook-sn7pj75w.js → hook-77wpvnjc.js} +4 -4
- package/dist/{hook-9fkwkf3m.js → hook-g019yehy.js} +5 -5
- package/dist/{http-xdbbtmhx.js → http-jewj0syk.js} +202 -79
- package/dist/{index-c0qedjh6.js → index-6gm5he8h.js} +9 -9
- package/dist/{index-5fdsemf0.js → index-7n2xpbd4.js} +155 -37
- package/dist/{index-9vnkprkh.js → index-hfvggd0z.js} +10 -10
- package/dist/{index-fnack5vh.js → index-hp1kdbjj.js} +11 -11
- package/dist/{keepalive-fc7a2hw9.js → keepalive-j0fjvqfd.js} +5 -5
- package/dist/{lead-1fva34y9.js → lead-1n8x30xe.js} +28 -28
- package/dist/{maintenance-0rdb12vf.js → maintenance-gvev11sa.js} +5 -5
- package/dist/{mistral-conversations-4swenhmf.js → mistral-conversations-81fwav0j.js} +2 -2
- package/dist/{oauth-refresh-sweep-f9rptqbt.js → oauth-refresh-sweep-s9ecsvh0.js} +4 -4
- package/dist/{onboard-9zn0fx2m.js → onboard-pvm5b38w.js} +2 -2
- package/dist/{openai-codex-responses-xetseqvt.js → openai-codex-responses-5g27pd6f.js} +43 -23
- package/dist/{openai-completions-5r2g5axa.js → openai-completions-3vkmfz2a.js} +55 -11
- package/dist/{openai-responses-ce67hd23.js → openai-responses-dg4t7j7a.js} +8 -5
- package/dist/{openrouter-images-pzfp22jb.js → openrouter-images-g3gm8931.js} +1 -1
- package/dist/{otel-impl-thnttmbn.js → otel-impl-p6jvgd9c.js} +1 -1
- package/dist/{pi-mono-adapter-bpfrn6d7.js → pi-mono-adapter-t091s27g.js} +17114 -7514
- package/dist/{pricing-refresh-n0ve0kh6.js → pricing-refresh-k49rcyed.js} +5 -5
- package/dist/{rbac-roles-bbwb4wrm.js → rbac-roles-30pspvy7.js} +5 -5
- package/dist/{rbac-roles-w45bhahg.js → rbac-roles-cr80g96t.js} +4 -4
- package/dist/{render-v2-d675cp9r.js → render-v2-xm457ng1.js} +4 -4
- package/dist/{seed-pricing-47q77nw4.js → seed-pricing-yhpv35fr.js} +4 -4
- package/dist/{setup-3vnv2m43.js → setup-nytnmwwr.js} +2 -2
- package/dist/{worker-7shsr8hk.js → worker-msytr2cw.js} +28 -28
- package/openapi.json +342 -10
- package/package.json +6 -6
- package/src/apps/store.ts +13 -4
- package/src/assets/key.ts +8 -1
- package/src/be/asset-key-audit.ts +3 -1
- package/src/be/db-queries/mcp-oauth.ts +26 -0
- package/src/be/db.ts +42 -1
- package/src/be/migrations/129_asset_keys_apps_scripts.sql +161 -0
- package/src/be/scripts/db.ts +5 -2
- package/src/be/seed-skills/index.ts +7 -0
- package/src/http/assets.ts +120 -29
- package/src/http/mcp-oauth.ts +69 -1
- package/src/http/users.ts +45 -0
- package/src/oauth/ensure-mcp-token.ts +7 -1
- package/src/oauth/mcp-wrapper.ts +247 -24
- package/src/providers/pi-mono-adapter.ts +1 -1
- package/src/tests/asset-key-api.test.ts +48 -2
- package/src/tests/asset-key-invariants.test.ts +92 -0
- package/src/tests/asset-key-migration.test.ts +174 -8
- package/src/tests/asset-key-normalization.test.ts +2 -0
- package/src/tests/mcp-oauth-dcr-reuse.test.ts +95 -0
- package/src/tests/mcp-oauth-ensure-token.test.ts +23 -0
- package/src/tests/mcp-oauth-manual-client.test.ts +135 -2
- package/src/tests/mcp-oauth-wrapper.test.ts +380 -0
- package/src/tests/rbac-admission-e2e.test.ts +68 -0
- package/src/tests/script-connections.test.ts +3 -0
- package/src/tests/whoami-route.test.ts +127 -0
- package/src/tests/workflow-engine-v2.test.ts +80 -7
- package/src/tests/workflow-swarm-script.test.ts +104 -0
- package/src/tools/workflows/create-workflow.ts +4 -1
- package/src/types.ts +18 -5
- package/src/utils/template.ts +8 -1
- package/src/workflows/engine.ts +27 -4
- package/src/workflows/executors/swarm-script.ts +28 -0
- package/src/workflows/retry-poller.ts +19 -2
- package/templates/skills/asset-namespaces/content.md +2 -2
- package/templates/skills/scheduled-task-resilience/SKILL.md +102 -0
- package/templates/skills/scheduled-task-resilience/config.json +14 -0
- package/templates/skills/scheduled-task-resilience/content.md +97 -0
|
@@ -42,11 +42,28 @@ function lazyStream(model, setup) {
|
|
|
42
42
|
});
|
|
43
43
|
return outer;
|
|
44
44
|
}
|
|
45
|
-
function lazyApi(load) {
|
|
46
|
-
|
|
45
|
+
function lazyApi(load, capabilities) {
|
|
46
|
+
const api = {
|
|
47
47
|
stream: (model, context, options) => lazyStream(model, async () => (await load()).stream(model, context, options)),
|
|
48
48
|
streamSimple: (model, context, options) => lazyStream(model, async () => (await load()).streamSimple(model, context, options))
|
|
49
49
|
};
|
|
50
|
+
if (capabilities?.fetchDeferred) {
|
|
51
|
+
api.fetchDeferred = (model, handle, options) => lazyStream(model, async () => {
|
|
52
|
+
const implementation = await load();
|
|
53
|
+
if (!implementation.fetchDeferred)
|
|
54
|
+
throw new Error("API does not support deferred responses");
|
|
55
|
+
return implementation.fetchDeferred(model, handle, options);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (capabilities?.cancelDeferred) {
|
|
59
|
+
api.cancelDeferred = async (model, handle, options) => {
|
|
60
|
+
const implementation = await load();
|
|
61
|
+
if (!implementation.cancelDeferred)
|
|
62
|
+
throw new Error("API cannot cancel deferred responses");
|
|
63
|
+
await implementation.cancelDeferred(model, handle, options);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return api;
|
|
50
67
|
}
|
|
51
68
|
|
|
52
69
|
// node_modules/@earendil-works/pi-ai/dist/auth/context.js
|
|
@@ -86,38 +103,93 @@ function defaultProviderAuthContext() {
|
|
|
86
103
|
};
|
|
87
104
|
}
|
|
88
105
|
|
|
106
|
+
// node_modules/@earendil-works/pi-ai/dist/utils/abort.js
|
|
107
|
+
function abortReason(signal) {
|
|
108
|
+
if (signal.reason !== undefined)
|
|
109
|
+
return signal.reason;
|
|
110
|
+
const error = new Error("The operation was aborted");
|
|
111
|
+
error.name = "AbortError";
|
|
112
|
+
return error;
|
|
113
|
+
}
|
|
114
|
+
function operationSignal(signal) {
|
|
115
|
+
return signal ?? new AbortController().signal;
|
|
116
|
+
}
|
|
117
|
+
function raceWithAbortSignal(operation, signal) {
|
|
118
|
+
if (signal.aborted) {
|
|
119
|
+
operation.catch(() => {});
|
|
120
|
+
return Promise.reject(abortReason(signal));
|
|
121
|
+
}
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
let settled = false;
|
|
124
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
125
|
+
const onAbort = () => {
|
|
126
|
+
if (settled)
|
|
127
|
+
return;
|
|
128
|
+
settled = true;
|
|
129
|
+
cleanup();
|
|
130
|
+
reject(abortReason(signal));
|
|
131
|
+
};
|
|
132
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
133
|
+
operation.then((value) => {
|
|
134
|
+
if (settled)
|
|
135
|
+
return;
|
|
136
|
+
settled = true;
|
|
137
|
+
cleanup();
|
|
138
|
+
resolve(value);
|
|
139
|
+
}, (error) => {
|
|
140
|
+
if (settled)
|
|
141
|
+
return;
|
|
142
|
+
settled = true;
|
|
143
|
+
cleanup();
|
|
144
|
+
reject(error);
|
|
145
|
+
});
|
|
146
|
+
if (signal.aborted)
|
|
147
|
+
onAbort();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
89
151
|
// node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js
|
|
90
152
|
class InMemoryCredentialStore {
|
|
91
153
|
credentials = new Map;
|
|
92
154
|
chains = new Map;
|
|
93
|
-
enqueue(providerId, task) {
|
|
155
|
+
enqueue(providerId, task, options) {
|
|
156
|
+
const signal = operationSignal(options?.signal);
|
|
94
157
|
const previous = this.chains.get(providerId) ?? Promise.resolve();
|
|
95
|
-
const
|
|
158
|
+
const queued = (async () => {
|
|
96
159
|
await previous.catch(() => {});
|
|
160
|
+
signal.throwIfAborted();
|
|
97
161
|
return task();
|
|
98
162
|
})();
|
|
99
|
-
|
|
100
|
-
|
|
163
|
+
const tail = queued.catch(() => {});
|
|
164
|
+
this.chains.set(providerId, tail);
|
|
165
|
+
tail.then(() => {
|
|
166
|
+
if (this.chains.get(providerId) === tail)
|
|
167
|
+
this.chains.delete(providerId);
|
|
168
|
+
});
|
|
169
|
+
return raceWithAbortSignal(queued, signal);
|
|
101
170
|
}
|
|
102
|
-
async read(providerId) {
|
|
171
|
+
async read(providerId, options) {
|
|
172
|
+
options?.signal?.throwIfAborted();
|
|
103
173
|
return this.credentials.get(providerId);
|
|
104
174
|
}
|
|
105
|
-
async list() {
|
|
175
|
+
async list(options) {
|
|
176
|
+
options?.signal?.throwIfAborted();
|
|
106
177
|
return [...this.credentials].map(([providerId, credential]) => ({ providerId, type: credential.type }));
|
|
107
178
|
}
|
|
108
|
-
modify(providerId, fn) {
|
|
179
|
+
modify(providerId, fn, options) {
|
|
109
180
|
return this.enqueue(providerId, async () => {
|
|
110
181
|
const current = this.credentials.get(providerId);
|
|
111
182
|
const next = await fn(current);
|
|
183
|
+
options?.signal?.throwIfAborted();
|
|
112
184
|
if (next !== undefined)
|
|
113
185
|
this.credentials.set(providerId, next);
|
|
114
186
|
return next ?? current;
|
|
115
|
-
});
|
|
187
|
+
}, options);
|
|
116
188
|
}
|
|
117
|
-
delete(providerId) {
|
|
189
|
+
delete(providerId, options) {
|
|
118
190
|
return this.enqueue(providerId, async () => {
|
|
119
191
|
this.credentials.delete(providerId);
|
|
120
|
-
});
|
|
192
|
+
}, options);
|
|
121
193
|
}
|
|
122
194
|
}
|
|
123
195
|
|
|
@@ -138,27 +210,32 @@ function withCauseDetail(message, cause) {
|
|
|
138
210
|
return message;
|
|
139
211
|
return `${message}: ${detail}`;
|
|
140
212
|
}
|
|
141
|
-
|
|
213
|
+
function resolveProviderAuth(provider, credentials, authContext, overrides) {
|
|
214
|
+
const signal = operationSignal(overrides?.signal);
|
|
215
|
+
return raceWithAbortSignal(resolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal), signal);
|
|
216
|
+
}
|
|
217
|
+
async function resolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal) {
|
|
218
|
+
signal.throwIfAborted();
|
|
142
219
|
const requestAuthContext = overrides?.env ? overlayEnvAuthContext(authContext, overrides.env) : authContext;
|
|
143
220
|
if (overrides?.apiKey !== undefined && provider.auth.apiKey) {
|
|
144
221
|
return resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, {
|
|
145
222
|
type: "api_key",
|
|
146
223
|
key: overrides.apiKey,
|
|
147
224
|
env: overrides.env
|
|
148
|
-
});
|
|
225
|
+
}, signal);
|
|
149
226
|
}
|
|
150
|
-
const stored = await readCredential(credentials, provider.id);
|
|
227
|
+
const stored = await readCredential(credentials, provider.id, signal);
|
|
151
228
|
if (stored) {
|
|
152
229
|
if (stored.type === "oauth" && provider.auth.oauth) {
|
|
153
|
-
return resolveStoredOAuth(credentials, provider.id, provider.auth.oauth, stored, overrides?.minOAuthValidityMs);
|
|
230
|
+
return resolveStoredOAuth(credentials, provider.id, provider.auth.oauth, stored, signal, overrides?.minOAuthValidityMs);
|
|
154
231
|
}
|
|
155
232
|
if (stored.type === "api_key" && provider.auth.apiKey) {
|
|
156
233
|
const credential = overrides?.env ? { ...stored, env: { ...stored.env, ...overrides.env } } : stored;
|
|
157
|
-
return resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, credential);
|
|
234
|
+
return resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, credential, signal);
|
|
158
235
|
}
|
|
159
236
|
return;
|
|
160
237
|
}
|
|
161
|
-
return provider.auth.apiKey ? resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, undefined) : undefined;
|
|
238
|
+
return provider.auth.apiKey ? resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, undefined, signal) : undefined;
|
|
162
239
|
}
|
|
163
240
|
function overlayEnvAuthContext(base, env) {
|
|
164
241
|
return {
|
|
@@ -167,7 +244,8 @@ function overlayEnvAuthContext(base, env) {
|
|
|
167
244
|
};
|
|
168
245
|
}
|
|
169
246
|
var DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 5 * 60 * 1000;
|
|
170
|
-
|
|
247
|
+
var DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15000;
|
|
248
|
+
async function resolveStoredOAuth(credentials, providerId, oauth, stored, signal, minOAuthValidityMs) {
|
|
171
249
|
const minimumValidityMs = Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS, minOAuthValidityMs ?? 0);
|
|
172
250
|
const expiresSoon = (credential2) => Date.now() + minimumValidityMs >= credential2.expires;
|
|
173
251
|
let credential = stored;
|
|
@@ -180,11 +258,15 @@ async function resolveStoredOAuth(credentials, providerId, oauth, stored, minOAu
|
|
|
180
258
|
if (!expiresSoon(current))
|
|
181
259
|
return;
|
|
182
260
|
try {
|
|
183
|
-
|
|
261
|
+
const refreshSignal = AbortSignal.any([
|
|
262
|
+
signal,
|
|
263
|
+
AbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS)
|
|
264
|
+
]);
|
|
265
|
+
return await oauth.refresh(current, refreshSignal);
|
|
184
266
|
} catch (error) {
|
|
185
267
|
throw new ModelsError("oauth", `OAuth refresh failed for ${providerId}`, { cause: error });
|
|
186
268
|
}
|
|
187
|
-
});
|
|
269
|
+
}, { signal });
|
|
188
270
|
} catch (error) {
|
|
189
271
|
if (error instanceof ModelsError)
|
|
190
272
|
throw error;
|
|
@@ -203,16 +285,16 @@ async function resolveStoredOAuth(credentials, providerId, oauth, stored, minOAu
|
|
|
203
285
|
throw new ModelsError("oauth", `OAuth auth derivation failed for ${providerId}`, { cause: error });
|
|
204
286
|
}
|
|
205
287
|
}
|
|
206
|
-
async function resolveApiKey(authContext, apiKey, providerId, credential) {
|
|
288
|
+
async function resolveApiKey(authContext, apiKey, providerId, credential, signal) {
|
|
207
289
|
try {
|
|
208
|
-
return await apiKey.resolve({ ctx: authContext, credential });
|
|
290
|
+
return await apiKey.resolve({ ctx: authContext, credential, signal });
|
|
209
291
|
} catch (error) {
|
|
210
292
|
throw new ModelsError("auth", `API key auth failed for provider ${providerId}`, { cause: error });
|
|
211
293
|
}
|
|
212
294
|
}
|
|
213
|
-
async function readCredential(credentials, providerId) {
|
|
295
|
+
async function readCredential(credentials, providerId, signal) {
|
|
214
296
|
try {
|
|
215
|
-
return await credentials.read(providerId);
|
|
297
|
+
return await credentials.read(providerId, { signal });
|
|
216
298
|
} catch (error) {
|
|
217
299
|
throw new ModelsError("auth", `Credential store read failed for ${providerId}`, { cause: error });
|
|
218
300
|
}
|
|
@@ -221,14 +303,17 @@ async function readCredential(credentials, providerId) {
|
|
|
221
303
|
// node_modules/@earendil-works/pi-ai/dist/models-store.js
|
|
222
304
|
class InMemoryModelsStore {
|
|
223
305
|
entries = new Map;
|
|
224
|
-
async read(providerId) {
|
|
306
|
+
async read(providerId, options) {
|
|
307
|
+
options?.signal?.throwIfAborted();
|
|
225
308
|
const entry = this.entries.get(providerId);
|
|
226
309
|
return entry ? structuredClone(entry) : undefined;
|
|
227
310
|
}
|
|
228
|
-
async write(providerId, entry) {
|
|
311
|
+
async write(providerId, entry, options) {
|
|
312
|
+
options?.signal?.throwIfAborted();
|
|
229
313
|
this.entries.set(providerId, structuredClone(entry));
|
|
230
314
|
}
|
|
231
|
-
async delete(providerId) {
|
|
315
|
+
async delete(providerId, options) {
|
|
316
|
+
options?.signal?.throwIfAborted();
|
|
232
317
|
this.entries.delete(providerId);
|
|
233
318
|
}
|
|
234
319
|
}
|
|
@@ -254,18 +339,26 @@ class ModelsImpl {
|
|
|
254
339
|
credentials;
|
|
255
340
|
modelsStore;
|
|
256
341
|
authContext;
|
|
342
|
+
refreshGenerations = new Map;
|
|
343
|
+
refreshControllers = new Map;
|
|
344
|
+
publicationChains = new Map;
|
|
257
345
|
constructor(options) {
|
|
258
346
|
this.credentials = options?.credentials ?? new InMemoryCredentialStore;
|
|
259
347
|
this.modelsStore = options?.modelsStore ?? new InMemoryModelsStore;
|
|
260
348
|
this.authContext = options?.authContext ?? defaultProviderAuthContext();
|
|
261
349
|
}
|
|
262
350
|
setProvider(provider) {
|
|
351
|
+
this.supersedeProviderRefresh(provider.id);
|
|
263
352
|
this.providers.set(provider.id, provider);
|
|
264
353
|
}
|
|
265
354
|
deleteProvider(id) {
|
|
355
|
+
this.supersedeProviderRefresh(id);
|
|
266
356
|
this.providers.delete(id);
|
|
267
357
|
}
|
|
268
358
|
clearProviders() {
|
|
359
|
+
for (const id of new Set([...this.providers.keys(), ...this.refreshControllers.keys()])) {
|
|
360
|
+
this.supersedeProviderRefresh(id);
|
|
361
|
+
}
|
|
269
362
|
this.providers.clear();
|
|
270
363
|
}
|
|
271
364
|
getProviders() {
|
|
@@ -296,80 +389,139 @@ class ModelsImpl {
|
|
|
296
389
|
getModel(provider, id) {
|
|
297
390
|
return this.getModels(provider).find((model) => model.id === id);
|
|
298
391
|
}
|
|
392
|
+
supersedeProviderRefresh(providerId) {
|
|
393
|
+
const generation = (this.refreshGenerations.get(providerId) ?? 0) + 1;
|
|
394
|
+
this.refreshGenerations.set(providerId, generation);
|
|
395
|
+
const previous = this.refreshControllers.get(providerId);
|
|
396
|
+
if (previous) {
|
|
397
|
+
this.refreshControllers.delete(providerId);
|
|
398
|
+
previous.abort();
|
|
399
|
+
}
|
|
400
|
+
return generation;
|
|
401
|
+
}
|
|
402
|
+
beginProviderRefresh(providerId) {
|
|
403
|
+
const generation = this.supersedeProviderRefresh(providerId);
|
|
404
|
+
const controller = new AbortController;
|
|
405
|
+
this.refreshControllers.set(providerId, controller);
|
|
406
|
+
return { generation, controller };
|
|
407
|
+
}
|
|
408
|
+
publishProviderModels(providerId, generation, signal, publication) {
|
|
409
|
+
const previous = this.publicationChains.get(providerId) ?? Promise.resolve();
|
|
410
|
+
const queued = (async () => {
|
|
411
|
+
await previous.catch(() => {});
|
|
412
|
+
if (signal.aborted || this.refreshGenerations.get(providerId) !== generation)
|
|
413
|
+
return false;
|
|
414
|
+
if (publication.persist === null) {
|
|
415
|
+
await this.modelsStore.delete(providerId, { signal });
|
|
416
|
+
} else if (publication.persist !== undefined) {
|
|
417
|
+
await this.modelsStore.write(providerId, structuredClone(publication.persist), { signal });
|
|
418
|
+
}
|
|
419
|
+
if (signal.aborted || this.refreshGenerations.get(providerId) !== generation)
|
|
420
|
+
return false;
|
|
421
|
+
publication.update?.();
|
|
422
|
+
return true;
|
|
423
|
+
})();
|
|
424
|
+
const tail = queued.catch(() => {});
|
|
425
|
+
this.publicationChains.set(providerId, tail);
|
|
426
|
+
tail.then(() => {
|
|
427
|
+
if (this.publicationChains.get(providerId) === tail)
|
|
428
|
+
this.publicationChains.delete(providerId);
|
|
429
|
+
});
|
|
430
|
+
return raceWithAbortSignal(queued, signal);
|
|
431
|
+
}
|
|
432
|
+
async runProviderRefreshPhase(provider, credential, allowNetwork, force, generation, signal) {
|
|
433
|
+
const stored = await this.modelsStore.read(provider.id, { signal });
|
|
434
|
+
await provider.refreshModels({
|
|
435
|
+
credential,
|
|
436
|
+
stored: stored ? structuredClone(stored) : undefined,
|
|
437
|
+
publish: (publication) => this.publishProviderModels(provider.id, generation, signal, publication),
|
|
438
|
+
allowNetwork,
|
|
439
|
+
force: allowNetwork ? force : undefined,
|
|
440
|
+
signal
|
|
441
|
+
});
|
|
442
|
+
}
|
|
299
443
|
async refresh(options = {}) {
|
|
300
444
|
const allowNetwork = options.allowNetwork ?? true;
|
|
445
|
+
const callerSignal = operationSignal(options.signal);
|
|
301
446
|
const errors = new Map;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
447
|
+
if (callerSignal.aborted)
|
|
448
|
+
return { aborted: true, errors };
|
|
449
|
+
const selected = options.providers ? new Set(options.providers) : undefined;
|
|
450
|
+
const refreshable = Array.from(this.providers.values()).filter((provider) => provider.refreshModels !== undefined && (!selected || selected.has(provider.id)));
|
|
451
|
+
const refresh = Promise.all(refreshable.map(async (provider) => {
|
|
452
|
+
const { generation, controller } = this.beginProviderRefresh(provider.id);
|
|
453
|
+
const signal = AbortSignal.any([callerSignal, controller.signal]);
|
|
454
|
+
const operation = (async () => {
|
|
455
|
+
let storedCredential;
|
|
456
|
+
let credentialError;
|
|
457
|
+
try {
|
|
458
|
+
storedCredential = await this.readCredential(provider.id, signal);
|
|
459
|
+
} catch (error) {
|
|
460
|
+
credentialError = error;
|
|
461
|
+
}
|
|
462
|
+
await this.runProviderRefreshPhase(provider, storedCredential, false, undefined, generation, signal);
|
|
463
|
+
if (credentialError !== undefined)
|
|
464
|
+
throw credentialError;
|
|
465
|
+
if (!allowNetwork || signal.aborted)
|
|
466
|
+
return;
|
|
467
|
+
const credential = await this.resolveRefreshCredential(provider, storedCredential, signal);
|
|
315
468
|
if (!credential)
|
|
316
469
|
return;
|
|
317
|
-
await provider.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
force: options.force,
|
|
322
|
-
signal: options.signal
|
|
323
|
-
});
|
|
470
|
+
await this.runProviderRefreshPhase(provider, credential, true, options.force, generation, signal);
|
|
471
|
+
})();
|
|
472
|
+
try {
|
|
473
|
+
await raceWithAbortSignal(operation, signal);
|
|
324
474
|
} catch (error) {
|
|
325
|
-
if (!
|
|
475
|
+
if (!signal.aborted) {
|
|
326
476
|
errors.set(provider.id, error instanceof Error ? error : new ModelsError("model_source", `Model refresh failed for ${provider.id}`, { cause: error }));
|
|
327
477
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
allowNetwork: false,
|
|
333
|
-
signal: options.signal
|
|
334
|
-
});
|
|
335
|
-
} catch {}
|
|
478
|
+
} finally {
|
|
479
|
+
if (this.refreshControllers.get(provider.id) === controller) {
|
|
480
|
+
this.refreshControllers.delete(provider.id);
|
|
481
|
+
}
|
|
336
482
|
}
|
|
337
483
|
}));
|
|
338
|
-
|
|
484
|
+
try {
|
|
485
|
+
await raceWithAbortSignal(refresh, callerSignal);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if (!callerSignal.aborted)
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
return { aborted: callerSignal.aborted, errors: new Map(errors) };
|
|
339
491
|
}
|
|
340
|
-
async resolveRefreshCredential(provider, stored,
|
|
492
|
+
async resolveRefreshCredential(provider, stored, signal) {
|
|
341
493
|
if (stored?.type === "oauth") {
|
|
342
494
|
const oauth = provider.auth.oauth;
|
|
343
495
|
if (!oauth)
|
|
344
496
|
return;
|
|
345
|
-
if (
|
|
497
|
+
if (Date.now() < stored.expires)
|
|
346
498
|
return stored;
|
|
347
|
-
if (signal
|
|
499
|
+
if (signal.aborted)
|
|
348
500
|
return;
|
|
349
501
|
const post = await this.credentials.modify(provider.id, async (current) => {
|
|
350
502
|
if (current?.type !== "oauth" || Date.now() < current.expires)
|
|
351
503
|
return;
|
|
352
504
|
return oauth.refresh(current, signal);
|
|
353
|
-
});
|
|
505
|
+
}, { signal });
|
|
354
506
|
return post?.type === "oauth" ? post : undefined;
|
|
355
507
|
}
|
|
356
508
|
const apiKey = provider.auth.apiKey;
|
|
357
509
|
if (!apiKey)
|
|
358
510
|
return;
|
|
359
511
|
const credential = stored?.type === "api_key" ? stored : undefined;
|
|
360
|
-
const result = await apiKey.resolve({ ctx: this.authContext, credential });
|
|
512
|
+
const result = await apiKey.resolve({ ctx: this.authContext, credential, signal });
|
|
361
513
|
if (!result)
|
|
362
514
|
return;
|
|
363
515
|
return { type: "api_key", key: result.auth.apiKey, env: result.env };
|
|
364
516
|
}
|
|
365
|
-
async readCredential(providerId) {
|
|
517
|
+
async readCredential(providerId, signal) {
|
|
366
518
|
try {
|
|
367
|
-
return await this.credentials.read(providerId);
|
|
519
|
+
return await this.credentials.read(providerId, { signal });
|
|
368
520
|
} catch (error) {
|
|
369
521
|
throw new ModelsError("auth", `Credential store read failed for ${providerId}`, { cause: error });
|
|
370
522
|
}
|
|
371
523
|
}
|
|
372
|
-
async checkProviderAuth(provider, credential) {
|
|
524
|
+
async checkProviderAuth(provider, credential, signal) {
|
|
373
525
|
if (credential?.type === "oauth") {
|
|
374
526
|
return provider.auth.oauth ? { source: "OAuth", type: "oauth" } : undefined;
|
|
375
527
|
}
|
|
@@ -380,40 +532,52 @@ class ModelsImpl {
|
|
|
380
532
|
try {
|
|
381
533
|
return await apiKey.check({
|
|
382
534
|
ctx: this.authContext,
|
|
383
|
-
credential: credential?.type === "api_key" ? credential : undefined
|
|
535
|
+
credential: credential?.type === "api_key" ? credential : undefined,
|
|
536
|
+
signal
|
|
384
537
|
});
|
|
385
538
|
} catch (error) {
|
|
386
539
|
throw new ModelsError("auth", `API key auth check failed for provider ${provider.id}`, { cause: error });
|
|
387
540
|
}
|
|
388
541
|
}
|
|
389
|
-
const resolution = await resolveProviderAuth(provider, this.credentials, this.authContext);
|
|
542
|
+
const resolution = await resolveProviderAuth(provider, this.credentials, this.authContext, { signal });
|
|
390
543
|
return resolution ? { source: resolution.source, type: "api_key" } : undefined;
|
|
391
544
|
}
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
545
|
+
checkAuth(providerId, options) {
|
|
546
|
+
const signal = operationSignal(options?.signal);
|
|
547
|
+
const check = (async () => {
|
|
548
|
+
signal.throwIfAborted();
|
|
549
|
+
const provider = this.providers.get(providerId);
|
|
550
|
+
if (!provider)
|
|
551
|
+
return;
|
|
552
|
+
return this.checkProviderAuth(provider, await this.readCredential(providerId, signal), signal);
|
|
553
|
+
})();
|
|
554
|
+
return raceWithAbortSignal(check, signal);
|
|
555
|
+
}
|
|
556
|
+
getAvailable(providerId, options) {
|
|
557
|
+
const signal = operationSignal(options?.signal);
|
|
558
|
+
const available = (async () => {
|
|
559
|
+
signal.throwIfAborted();
|
|
560
|
+
const providers = providerId ? [this.providers.get(providerId)].filter((entry) => entry !== undefined) : this.getProviders();
|
|
561
|
+
const checks = await Promise.all(providers.map(async (provider) => {
|
|
562
|
+
const credential = await this.readCredential(provider.id, signal);
|
|
563
|
+
return { provider, credential, auth: await this.checkProviderAuth(provider, credential, signal) };
|
|
564
|
+
}));
|
|
565
|
+
return checks.flatMap(({ provider, credential, auth }) => {
|
|
566
|
+
if (!auth)
|
|
567
|
+
return [];
|
|
568
|
+
const models = provider.getModels();
|
|
569
|
+
return provider.filterModels?.(models, credential) ?? models;
|
|
570
|
+
});
|
|
571
|
+
})();
|
|
572
|
+
return raceWithAbortSignal(available, signal);
|
|
410
573
|
}
|
|
411
574
|
async getAuth(providerOrModel, overrides) {
|
|
575
|
+
const signal = operationSignal(overrides?.signal);
|
|
412
576
|
const providerId = typeof providerOrModel === "string" ? providerOrModel : providerOrModel.provider;
|
|
413
577
|
const provider = this.providers.get(providerId);
|
|
414
578
|
if (!provider)
|
|
415
579
|
return;
|
|
416
|
-
const result = await resolveProviderAuth(provider, this.credentials, this.authContext, overrides);
|
|
580
|
+
const result = await resolveProviderAuth(provider, this.credentials, this.authContext, { ...overrides, signal });
|
|
417
581
|
if (!result || typeof providerOrModel === "string" || !providerOrModel.headers)
|
|
418
582
|
return result;
|
|
419
583
|
return {
|
|
@@ -425,6 +589,8 @@ class ModelsImpl {
|
|
|
425
589
|
};
|
|
426
590
|
}
|
|
427
591
|
async login(providerId, type, interaction) {
|
|
592
|
+
const signal = operationSignal(interaction.signal);
|
|
593
|
+
signal.throwIfAborted();
|
|
428
594
|
const provider = this.providers.get(providerId);
|
|
429
595
|
if (!provider)
|
|
430
596
|
throw new ModelsError("provider", `Unknown provider: ${providerId}`);
|
|
@@ -432,18 +598,50 @@ class ModelsImpl {
|
|
|
432
598
|
if (!method?.login) {
|
|
433
599
|
throw new ModelsError("auth", `${provider.name} does not support ${type} login`);
|
|
434
600
|
}
|
|
435
|
-
const
|
|
601
|
+
const loginOperation = method.login({ ...interaction, signal });
|
|
602
|
+
const credential = await raceWithAbortSignal(loginOperation, signal);
|
|
603
|
+
let mutationStarted = false;
|
|
604
|
+
let markMutationStarted;
|
|
605
|
+
const started = new Promise((resolve) => {
|
|
606
|
+
markMutationStarted = resolve;
|
|
607
|
+
});
|
|
608
|
+
const mutation = this.credentials.modify(providerId, async () => {
|
|
609
|
+
mutationStarted = true;
|
|
610
|
+
markMutationStarted?.();
|
|
611
|
+
return credential;
|
|
612
|
+
}, { signal });
|
|
613
|
+
mutation.catch(() => {});
|
|
436
614
|
try {
|
|
437
|
-
await
|
|
615
|
+
await new Promise((resolve, reject) => {
|
|
616
|
+
const onAbort = () => {
|
|
617
|
+
if (!mutationStarted)
|
|
618
|
+
reject(signal.reason);
|
|
619
|
+
};
|
|
620
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
621
|
+
Promise.race([started, mutation]).then(() => {
|
|
622
|
+
signal.removeEventListener("abort", onAbort);
|
|
623
|
+
resolve();
|
|
624
|
+
}, (error) => {
|
|
625
|
+
signal.removeEventListener("abort", onAbort);
|
|
626
|
+
reject(error);
|
|
627
|
+
});
|
|
628
|
+
if (signal.aborted)
|
|
629
|
+
onAbort();
|
|
630
|
+
});
|
|
631
|
+
await mutation;
|
|
438
632
|
} catch (error) {
|
|
633
|
+
signal.throwIfAborted();
|
|
439
634
|
throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error });
|
|
440
635
|
}
|
|
441
636
|
return credential;
|
|
442
637
|
}
|
|
443
|
-
async logout(providerId) {
|
|
638
|
+
async logout(providerId, options) {
|
|
639
|
+
const signal = operationSignal(options?.signal);
|
|
640
|
+
signal.throwIfAborted();
|
|
444
641
|
try {
|
|
445
|
-
await this.credentials.delete(providerId);
|
|
642
|
+
await this.credentials.delete(providerId, { signal });
|
|
446
643
|
} catch (error) {
|
|
644
|
+
signal.throwIfAborted();
|
|
447
645
|
throw new ModelsError("auth", `Credential store delete failed for ${providerId}`, { cause: error });
|
|
448
646
|
}
|
|
449
647
|
}
|
|
@@ -458,7 +656,8 @@ class ModelsImpl {
|
|
|
458
656
|
this.requireProvider(model);
|
|
459
657
|
const resolution = await this.getAuth(model, {
|
|
460
658
|
apiKey: options?.apiKey,
|
|
461
|
-
env: options?.env
|
|
659
|
+
env: options?.env,
|
|
660
|
+
signal: options?.signal
|
|
462
661
|
});
|
|
463
662
|
if (!resolution) {
|
|
464
663
|
throw new ModelsError("auth", `Provider is not configured: ${model.provider}`);
|
|
@@ -494,6 +693,24 @@ class ModelsImpl {
|
|
|
494
693
|
async completeSimple(model, context, options) {
|
|
495
694
|
return this.streamSimple(model, context, options).result();
|
|
496
695
|
}
|
|
696
|
+
async fetchDeferred(model, handle, options) {
|
|
697
|
+
return lazyStream(model, async () => {
|
|
698
|
+
const provider = this.requireProvider(model);
|
|
699
|
+
if (!provider.fetchDeferred) {
|
|
700
|
+
throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
|
|
701
|
+
}
|
|
702
|
+
const { requestModel, requestOptions } = await this.applyAuth(model, options);
|
|
703
|
+
return provider.fetchDeferred(requestModel, handle, requestOptions);
|
|
704
|
+
}).result();
|
|
705
|
+
}
|
|
706
|
+
async cancelDeferred(model, handle, options) {
|
|
707
|
+
const provider = this.requireProvider(model);
|
|
708
|
+
if (!provider.cancelDeferred) {
|
|
709
|
+
throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
|
|
710
|
+
}
|
|
711
|
+
const { requestModel, requestOptions } = await this.applyAuth(model, options);
|
|
712
|
+
await provider.cancelDeferred(requestModel, handle, requestOptions);
|
|
713
|
+
}
|
|
497
714
|
}
|
|
498
715
|
function createModels(options) {
|
|
499
716
|
return new ModelsImpl(options);
|
|
@@ -501,7 +718,6 @@ function createModels(options) {
|
|
|
501
718
|
function createProvider(input) {
|
|
502
719
|
const baselineModels = input.models;
|
|
503
720
|
let dynamicModels = [];
|
|
504
|
-
let inflightRefresh;
|
|
505
721
|
const fetchModels = input.fetchModels;
|
|
506
722
|
const currentModels = () => {
|
|
507
723
|
const merged = [...baselineModels];
|
|
@@ -518,45 +734,68 @@ function createProvider(input) {
|
|
|
518
734
|
const byApi = single ? undefined : input.api;
|
|
519
735
|
const apiFor = (model) => single ?? byApi?.[model.api];
|
|
520
736
|
const dispatch = (model, run) => {
|
|
521
|
-
const
|
|
522
|
-
if (!
|
|
737
|
+
const streams2 = apiFor(model);
|
|
738
|
+
if (!streams2) {
|
|
523
739
|
return lazyStream(model, async () => {
|
|
524
740
|
throw new ModelsError("stream", `Provider ${input.id} has no API implementation for "${model.api}"`);
|
|
525
741
|
});
|
|
526
742
|
}
|
|
527
|
-
return run(
|
|
743
|
+
return run(streams2);
|
|
528
744
|
};
|
|
529
|
-
|
|
745
|
+
const provider = {
|
|
530
746
|
id: input.id,
|
|
531
747
|
name: input.name ?? input.id,
|
|
532
748
|
baseUrl: input.baseUrl,
|
|
533
749
|
headers: input.headers,
|
|
534
750
|
auth: input.auth,
|
|
535
751
|
getModels: currentModels,
|
|
536
|
-
refreshModels: fetchModels ? (context) => {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
dynamicModels =
|
|
752
|
+
refreshModels: fetchModels ? async (context) => {
|
|
753
|
+
if (context.stored) {
|
|
754
|
+
const restored = context.stored.models.filter((model) => model.provider === input.id).map((model) => model);
|
|
755
|
+
if (!await context.publish({
|
|
756
|
+
update: () => {
|
|
757
|
+
dynamicModels = restored;
|
|
542
758
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
759
|
+
})) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (!context.allowNetwork || context.signal.aborted)
|
|
764
|
+
return;
|
|
765
|
+
const refreshed = await fetchModels(context);
|
|
766
|
+
if (context.signal.aborted)
|
|
767
|
+
return;
|
|
768
|
+
await context.publish({
|
|
769
|
+
persist: { models: refreshed, checkedAt: Date.now() },
|
|
770
|
+
update: () => {
|
|
548
771
|
dynamicModels = refreshed;
|
|
549
|
-
await context.store.write({ models: refreshed, checkedAt: Date.now() });
|
|
550
|
-
} finally {
|
|
551
|
-
inflightRefresh = undefined;
|
|
552
772
|
}
|
|
553
|
-
})
|
|
554
|
-
return inflightRefresh;
|
|
773
|
+
});
|
|
555
774
|
} : undefined,
|
|
556
775
|
filterModels: input.filterModels,
|
|
557
|
-
stream: (model, context, options) => dispatch(model, (
|
|
558
|
-
streamSimple: (model, context, options) => dispatch(model, (
|
|
776
|
+
stream: (model, context, options) => dispatch(model, (streams2) => streams2.stream(model, context, options)),
|
|
777
|
+
streamSimple: (model, context, options) => dispatch(model, (streams2) => streams2.streamSimple(model, context, options))
|
|
559
778
|
};
|
|
779
|
+
const streams = single ? [single] : Object.values(byApi ?? {}).filter((entry) => entry !== undefined);
|
|
780
|
+
if (streams.some((entry) => entry.fetchDeferred !== undefined)) {
|
|
781
|
+
provider.fetchDeferred = (model, handle, options) => lazyStream(model, async () => {
|
|
782
|
+
const implementation = apiFor(model);
|
|
783
|
+
if (!implementation?.fetchDeferred) {
|
|
784
|
+
throw new ModelsError("provider", `Provider ${input.id} does not support deferred responses for "${model.api}"`);
|
|
785
|
+
}
|
|
786
|
+
return implementation.fetchDeferred(model, handle, options);
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
if (streams.some((entry) => entry.cancelDeferred !== undefined)) {
|
|
790
|
+
provider.cancelDeferred = async (model, handle, options) => {
|
|
791
|
+
const implementation = apiFor(model);
|
|
792
|
+
if (!implementation?.cancelDeferred) {
|
|
793
|
+
throw new ModelsError("provider", `Provider ${input.id} cannot cancel deferred responses for "${model.api}"`);
|
|
794
|
+
}
|
|
795
|
+
await implementation.cancelDeferred(model, handle, options);
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
return provider;
|
|
560
799
|
}
|
|
561
800
|
function hasApi(model, api) {
|
|
562
801
|
return model.api === api;
|