@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.
Files changed (228) hide show
  1. package/README.md +26 -8
  2. package/dist/api/anthropic-messages.js.map +1 -1
  3. package/dist/api/azure-openai-responses.js +5 -1
  4. package/dist/api/azure-openai-responses.js.map +1 -1
  5. package/dist/api/bedrock-converse-stream.js.map +1 -1
  6. package/dist/api/bedrock-converse-stream.lazy.js.map +1 -1
  7. package/dist/api/google-generative-ai.d.ts.map +1 -1
  8. package/dist/api/google-generative-ai.js +2 -2
  9. package/dist/api/google-generative-ai.js.map +1 -1
  10. package/dist/api/google-shared.d.ts +15 -1
  11. package/dist/api/google-shared.d.ts.map +1 -1
  12. package/dist/api/google-shared.js +103 -4
  13. package/dist/api/google-shared.js.map +1 -1
  14. package/dist/api/google-vertex.d.ts.map +1 -1
  15. package/dist/api/google-vertex.js +2 -2
  16. package/dist/api/google-vertex.js.map +1 -1
  17. package/dist/api/lazy.d.ts +5 -1
  18. package/dist/api/lazy.d.ts.map +1 -1
  19. package/dist/api/lazy.js +19 -7
  20. package/dist/api/lazy.js.map +1 -1
  21. package/dist/api/openai-codex-responses.js.map +1 -1
  22. package/dist/api/openai-completions.d.ts +3 -1
  23. package/dist/api/openai-completions.d.ts.map +1 -1
  24. package/dist/api/openai-completions.js +47 -7
  25. package/dist/api/openai-completions.js.map +1 -1
  26. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  27. package/dist/api/openai-responses-shared.js +22 -9
  28. package/dist/api/openai-responses-shared.js.map +1 -1
  29. package/dist/api/openai-responses.js +5 -1
  30. package/dist/api/openai-responses.js.map +1 -1
  31. package/dist/api/pi-messages.d.ts.map +1 -1
  32. package/dist/api/pi-messages.js.map +1 -1
  33. package/dist/api/simple-options.d.ts +2 -0
  34. package/dist/api/simple-options.d.ts.map +1 -1
  35. package/dist/api/simple-options.js +8 -2
  36. package/dist/api/simple-options.js.map +1 -1
  37. package/dist/api/transform-messages.js.map +1 -1
  38. package/dist/api/warm-prompt-cache.js.map +1 -1
  39. package/dist/auth/credential-store.d.ts +6 -6
  40. package/dist/auth/credential-store.d.ts.map +1 -1
  41. package/dist/auth/credential-store.js +22 -11
  42. package/dist/auth/credential-store.js.map +1 -1
  43. package/dist/auth/helpers.d.ts +1 -0
  44. package/dist/auth/helpers.d.ts.map +1 -1
  45. package/dist/auth/helpers.js +7 -2
  46. package/dist/auth/helpers.js.map +1 -1
  47. package/dist/auth/oauth/anthropic.d.ts.map +1 -1
  48. package/dist/auth/oauth/anthropic.js +14 -8
  49. package/dist/auth/oauth/anthropic.js.map +1 -1
  50. package/dist/auth/oauth/device-code.d.ts +1 -1
  51. package/dist/auth/oauth/device-code.d.ts.map +1 -1
  52. package/dist/auth/oauth/device-code.js +4 -4
  53. package/dist/auth/oauth/device-code.js.map +1 -1
  54. package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
  55. package/dist/auth/oauth/github-copilot.js +43 -30
  56. package/dist/auth/oauth/github-copilot.js.map +1 -1
  57. package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
  58. package/dist/auth/oauth/kimi-coding.js +17 -5
  59. package/dist/auth/oauth/kimi-coding.js.map +1 -1
  60. package/dist/auth/oauth/load.d.ts.map +1 -1
  61. package/dist/auth/oauth/load.js.map +1 -1
  62. package/dist/auth/oauth/openai-codex.d.ts.map +1 -1
  63. package/dist/auth/oauth/openai-codex.js +12 -5
  64. package/dist/auth/oauth/openai-codex.js.map +1 -1
  65. package/dist/auth/oauth/openrouter.d.ts.map +1 -1
  66. package/dist/auth/oauth/openrouter.js +10 -10
  67. package/dist/auth/oauth/openrouter.js.map +1 -1
  68. package/dist/auth/oauth/radius.d.ts.map +1 -1
  69. package/dist/auth/oauth/radius.js +8 -7
  70. package/dist/auth/oauth/radius.js.map +1 -1
  71. package/dist/auth/oauth/xai.d.ts.map +1 -1
  72. package/dist/auth/oauth/xai.js +3 -2
  73. package/dist/auth/oauth/xai.js.map +1 -1
  74. package/dist/auth/resolve.d.ts +1 -0
  75. package/dist/auth/resolve.d.ts.map +1 -1
  76. package/dist/auth/resolve.js +24 -13
  77. package/dist/auth/resolve.js.map +1 -1
  78. package/dist/auth/types.d.ts +21 -8
  79. package/dist/auth/types.d.ts.map +1 -1
  80. package/dist/auth/types.js.map +1 -1
  81. package/dist/cli.js +1 -0
  82. package/dist/cli.js.map +1 -1
  83. package/dist/compat.js.map +1 -1
  84. package/dist/env-api-keys.d.ts.map +1 -1
  85. package/dist/env-api-keys.js +3 -0
  86. package/dist/env-api-keys.js.map +1 -1
  87. package/dist/image-models.generated.d.ts +69 -54
  88. package/dist/image-models.generated.d.ts.map +1 -1
  89. package/dist/image-models.generated.js +15 -0
  90. package/dist/image-models.generated.js.map +1 -1
  91. package/dist/images-models.d.ts.map +1 -1
  92. package/dist/images-models.js +1 -0
  93. package/dist/images-models.js.map +1 -1
  94. package/dist/model.d.ts +2 -0
  95. package/dist/model.d.ts.map +1 -1
  96. package/dist/model.js.map +1 -1
  97. package/dist/models-store.d.ts +9 -12
  98. package/dist/models-store.d.ts.map +1 -1
  99. package/dist/models-store.js +6 -3
  100. package/dist/models-store.js.map +1 -1
  101. package/dist/models.d.ts +40 -18
  102. package/dist/models.d.ts.map +1 -1
  103. package/dist/models.generated.d.ts +6 -0
  104. package/dist/models.generated.d.ts.map +1 -1
  105. package/dist/models.generated.js +6 -0
  106. package/dist/models.generated.js.map +1 -1
  107. package/dist/models.js +247 -91
  108. package/dist/models.js.map +1 -1
  109. package/dist/node/provider-scope.d.ts.map +1 -1
  110. package/dist/providers/all.d.ts.map +1 -1
  111. package/dist/providers/all.js +6 -0
  112. package/dist/providers/all.js.map +1 -1
  113. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  114. package/dist/providers/amazon-bedrock.js +15 -7
  115. package/dist/providers/amazon-bedrock.js.map +1 -1
  116. package/dist/providers/anthropic.d.ts.map +1 -1
  117. package/dist/providers/anthropic.js +15 -6
  118. package/dist/providers/anthropic.js.map +1 -1
  119. package/dist/providers/baseten.d.ts +3 -0
  120. package/dist/providers/baseten.d.ts.map +1 -0
  121. package/dist/providers/baseten.js +15 -0
  122. package/dist/providers/baseten.js.map +1 -0
  123. package/dist/providers/baseten.models.d.ts +4 -0
  124. package/dist/providers/baseten.models.d.ts.map +1 -0
  125. package/dist/providers/baseten.models.js +6 -0
  126. package/dist/providers/baseten.models.js.map +1 -0
  127. package/dist/providers/cloudflare-auth.d.ts.map +1 -1
  128. package/dist/providers/cloudflare-auth.js +15 -10
  129. package/dist/providers/cloudflare-auth.js.map +1 -1
  130. package/dist/providers/data/.manifest.json +1 -1
  131. package/dist/providers/data/baseten.json +1 -0
  132. package/dist/providers/data/fireworks.json +1 -1
  133. package/dist/providers/data/github-copilot.json +1 -1
  134. package/dist/providers/data/opencode-go.json +1 -1
  135. package/dist/providers/data/opencode.json +1 -1
  136. package/dist/providers/data/opengateway.json +1 -0
  137. package/dist/providers/data/openrouter.json +1 -1
  138. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  139. package/dist/providers/data/qwen-token-plan-individual.json +1 -0
  140. package/dist/providers/data/qwen-token-plan.json +1 -1
  141. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  142. package/dist/providers/data/xai.json +1 -1
  143. package/dist/providers/faux.d.ts +18 -13
  144. package/dist/providers/faux.d.ts.map +1 -1
  145. package/dist/providers/faux.js +95 -5
  146. package/dist/providers/faux.js.map +1 -1
  147. package/dist/providers/github-copilot.js +1 -1
  148. package/dist/providers/github-copilot.js.map +1 -1
  149. package/dist/providers/google-vertex.d.ts.map +1 -1
  150. package/dist/providers/google-vertex.js +15 -7
  151. package/dist/providers/google-vertex.js.map +1 -1
  152. package/dist/providers/kimi-coding.d.ts.map +1 -1
  153. package/dist/providers/kimi-coding.js +1 -0
  154. package/dist/providers/kimi-coding.js.map +1 -1
  155. package/dist/providers/ollama.d.ts.map +1 -1
  156. package/dist/providers/ollama.js +1 -2
  157. package/dist/providers/ollama.js.map +1 -1
  158. package/dist/providers/openai-codex.d.ts.map +1 -1
  159. package/dist/providers/openai-codex.js +5 -1
  160. package/dist/providers/openai-codex.js.map +1 -1
  161. package/dist/providers/opengateway.d.ts +3 -0
  162. package/dist/providers/opengateway.d.ts.map +1 -0
  163. package/dist/providers/opengateway.js +15 -0
  164. package/dist/providers/opengateway.js.map +1 -0
  165. package/dist/providers/opengateway.models.d.ts +4 -0
  166. package/dist/providers/opengateway.models.d.ts.map +1 -0
  167. package/dist/providers/opengateway.models.js +6 -0
  168. package/dist/providers/opengateway.models.js.map +1 -0
  169. package/dist/providers/qwen-token-plan-individual.d.ts +3 -0
  170. package/dist/providers/qwen-token-plan-individual.d.ts.map +1 -0
  171. package/dist/providers/qwen-token-plan-individual.js +15 -0
  172. package/dist/providers/qwen-token-plan-individual.js.map +1 -0
  173. package/dist/providers/qwen-token-plan-individual.models.d.ts +4 -0
  174. package/dist/providers/qwen-token-plan-individual.models.d.ts.map +1 -0
  175. package/dist/providers/qwen-token-plan-individual.models.js +6 -0
  176. package/dist/providers/qwen-token-plan-individual.models.js.map +1 -0
  177. package/dist/providers/radius.d.ts.map +1 -1
  178. package/dist/providers/radius.js +36 -26
  179. package/dist/providers/radius.js.map +1 -1
  180. package/dist/providers/xai.d.ts.map +1 -1
  181. package/dist/providers/xai.js +1 -0
  182. package/dist/providers/xai.js.map +1 -1
  183. package/dist/tool-call-middleware/context-transformer.js.map +1 -1
  184. package/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.d.ts.map +1 -1
  185. package/dist/tool-call-middleware/protocols/kimi-xtml/parse.js.map +1 -1
  186. package/dist/tool-call-middleware/recovery-content-lifecycle.d.ts.map +1 -1
  187. package/dist/tool-call-middleware/recovery-event-stream.d.ts.map +1 -1
  188. package/dist/tool-call-middleware/recovery-native-projection.d.ts.map +1 -1
  189. package/dist/tool-call-middleware/recovery-native-projection.js.map +1 -1
  190. package/dist/tool-call-middleware/recovery-stream-terminal.d.ts.map +1 -1
  191. package/dist/tool-call-middleware/recovery-stream-wrapper.d.ts.map +1 -1
  192. package/dist/tool-call-middleware/recovery-stream-wrapper.js +5 -0
  193. package/dist/tool-call-middleware/recovery-stream-wrapper.js.map +1 -1
  194. package/dist/tool-call-middleware/recovery-text-projection.d.ts.map +1 -1
  195. package/dist/tool-call-middleware/recovery-text-projection.js.map +1 -1
  196. package/dist/tool-call-middleware/stream-thinking-projection.d.ts.map +1 -1
  197. package/dist/tool-call-middleware/stream-wrapper-shared.d.ts.map +1 -1
  198. package/dist/tool-call-middleware/stream-wrapper-shared.js.map +1 -1
  199. package/dist/tool-call-middleware/stream-wrapper.js.map +1 -1
  200. package/dist/types.d.ts +94 -87
  201. package/dist/types.d.ts.map +1 -1
  202. package/dist/types.js.map +1 -1
  203. package/dist/utils/abort.d.ts +8 -0
  204. package/dist/utils/abort.d.ts.map +1 -0
  205. package/dist/utils/abort.js +49 -0
  206. package/dist/utils/abort.js.map +1 -0
  207. package/dist/utils/deferred-tools.js.map +1 -1
  208. package/dist/utils/event-stream.d.ts.map +1 -1
  209. package/dist/utils/overflow.d.ts +14 -1
  210. package/dist/utils/overflow.d.ts.map +1 -1
  211. package/dist/utils/overflow.js +18 -1
  212. package/dist/utils/overflow.js.map +1 -1
  213. package/dist/utils/prompt-cache-ttl.d.ts +3 -1
  214. package/dist/utils/prompt-cache-ttl.d.ts.map +1 -1
  215. package/dist/utils/prompt-cache-ttl.js +2 -0
  216. package/dist/utils/prompt-cache-ttl.js.map +1 -1
  217. package/dist/utils/provider-retry.js.map +1 -1
  218. package/dist/utils/retry.d.ts +25 -0
  219. package/dist/utils/retry.d.ts.map +1 -1
  220. package/dist/utils/retry.js +64 -0
  221. package/dist/utils/retry.js.map +1 -1
  222. package/dist/utils/tool-schema-compat.d.ts.map +1 -1
  223. package/dist/utils/tool-schema-compat.js +1 -0
  224. package/dist/utils/tool-schema-compat.js.map +1 -1
  225. package/dist/utils/validation.d.ts.map +1 -1
  226. package/dist/utils/validation.js +6 -0
  227. package/dist/utils/validation.js.map +1 -1
  228. 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
- const refreshable = Array.from(this.providers.values()).filter((provider) => provider.refreshModels !== undefined);
73
- await Promise.all(refreshable.map(async (provider) => {
74
- if (options.signal?.aborted)
75
- return;
76
- const store = {
77
- read: () => this.modelsStore.read(provider.id),
78
- write: (entry) => this.modelsStore.write(provider.id, entry),
79
- delete: () => this.modelsStore.delete(provider.id),
80
- };
81
- let stored;
82
- try {
83
- stored = await this.readCredential(provider.id);
84
- const credential = await this.resolveRefreshCredential(provider, stored, allowNetwork, options.signal);
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.refreshModels({
88
- credential,
89
- store,
90
- allowNetwork,
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 (!options.signal?.aborted) {
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
- try {
102
- await provider.refreshModels({
103
- credential: stored,
104
- store,
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
- return { aborted: options.signal?.aborted ?? false, errors };
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, allowNetwork, signal) {
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 (!allowNetwork || Date.now() < stored.expires)
191
+ if (Date.now() < stored.expires)
122
192
  return stored;
123
- if (signal?.aborted)
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
- async checkAuth(providerId) {
194
- const provider = this.providers.get(providerId);
195
- if (!provider)
196
- return undefined;
197
- return this.checkProviderAuth(provider, await this.readCredential(providerId));
198
- }
199
- async getAvailable(providerId) {
200
- const providers = providerId
201
- ? [this.providers.get(providerId)].filter((entry) => entry !== undefined)
202
- : this.getProviders();
203
- const checks = await Promise.all(providers.map(async (provider) => {
204
- const credential = await this.readCredential(provider.id);
205
- return { provider, credential, auth: await this.checkProviderAuth(provider, credential) };
206
- }));
207
- return checks.flatMap(({ provider, credential, auth }) => {
208
- if (!auth)
209
- return [];
210
- const models = provider.getModels();
211
- return provider.filterModels?.(models, credential) ?? models;
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 credential = await method.login(interaction);
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 this.credentials.modify(providerId, async () => credential);
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
- return {
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
- inflightRefresh ??= (async () => {
351
- try {
352
- const stored = await context.store.read();
353
- if (stored) {
354
- dynamicModels = stored.models
355
- .filter((model) => model.provider === input.id)
356
- .map((model) => model);
357
- }
358
- if (!context.allowNetwork || context.signal?.aborted)
359
- return;
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
- finally {
367
- inflightRefresh = undefined;
368
- }
369
- })();
370
- return inflightRefresh;
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: