adonis-ai-kit 0.0.0-stage → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +21 -0
  3. package/README.md +397 -2
  4. package/dist/commands/ai_test.d.ts +12 -0
  5. package/dist/commands/ai_test.d.ts.map +1 -0
  6. package/dist/commands/ai_test.js +98 -0
  7. package/dist/commands/ai_test.js.map +1 -0
  8. package/dist/commands/main.d.ts +14 -0
  9. package/dist/commands/main.d.ts.map +1 -0
  10. package/dist/commands/main.js +31 -0
  11. package/dist/commands/main.js.map +1 -0
  12. package/dist/commands/make_agent.d.ts +13 -0
  13. package/dist/commands/make_agent.d.ts.map +1 -0
  14. package/dist/commands/make_agent.js +116 -0
  15. package/dist/commands/make_agent.js.map +1 -0
  16. package/dist/providers/ai_provider.d.ts +23 -0
  17. package/dist/providers/ai_provider.d.ts.map +1 -0
  18. package/dist/providers/ai_provider.js +34 -0
  19. package/dist/providers/ai_provider.js.map +1 -0
  20. package/dist/services/main.d.ts +4 -0
  21. package/dist/services/main.d.ts.map +1 -0
  22. package/dist/services/main.js +7 -0
  23. package/dist/services/main.js.map +1 -0
  24. package/dist/src/acp/acp_agents.d.ts +89 -0
  25. package/dist/src/acp/acp_agents.d.ts.map +1 -0
  26. package/dist/src/acp/acp_agents.js +367 -0
  27. package/dist/src/acp/acp_agents.js.map +1 -0
  28. package/dist/src/acp/index.d.ts +4 -0
  29. package/dist/src/acp/index.d.ts.map +1 -0
  30. package/dist/src/acp/index.js +4 -0
  31. package/dist/src/acp/index.js.map +1 -0
  32. package/dist/src/acp/provider.d.ts +74 -0
  33. package/dist/src/acp/provider.d.ts.map +1 -0
  34. package/dist/src/acp/provider.js +276 -0
  35. package/dist/src/acp/provider.js.map +1 -0
  36. package/dist/src/acp/types.d.ts +128 -0
  37. package/dist/src/acp/types.d.ts.map +1 -0
  38. package/dist/src/acp/types.js +2 -0
  39. package/dist/src/acp/types.js.map +1 -0
  40. package/dist/src/agents/agent_runner.d.ts +60 -0
  41. package/dist/src/agents/agent_runner.d.ts.map +1 -0
  42. package/dist/src/agents/agent_runner.js +134 -0
  43. package/dist/src/agents/agent_runner.js.map +1 -0
  44. package/dist/src/agents/index.d.ts +4 -0
  45. package/dist/src/agents/index.d.ts.map +1 -0
  46. package/dist/src/agents/index.js +4 -0
  47. package/dist/src/agents/index.js.map +1 -0
  48. package/dist/src/agents/loader.d.ts +56 -0
  49. package/dist/src/agents/loader.d.ts.map +1 -0
  50. package/dist/src/agents/loader.js +184 -0
  51. package/dist/src/agents/loader.js.map +1 -0
  52. package/dist/src/agents/manifest.d.ts +61 -0
  53. package/dist/src/agents/manifest.d.ts.map +1 -0
  54. package/dist/src/agents/manifest.js +35 -0
  55. package/dist/src/agents/manifest.js.map +1 -0
  56. package/dist/src/ai_manager.d.ts +140 -0
  57. package/dist/src/ai_manager.d.ts.map +1 -0
  58. package/dist/src/ai_manager.js +814 -0
  59. package/dist/src/ai_manager.js.map +1 -0
  60. package/dist/src/budget/budget_allocator.d.ts +37 -0
  61. package/dist/src/budget/budget_allocator.d.ts.map +1 -0
  62. package/dist/src/budget/budget_allocator.js +98 -0
  63. package/dist/src/budget/budget_allocator.js.map +1 -0
  64. package/dist/src/budget/budget_enforcer.d.ts +70 -0
  65. package/dist/src/budget/budget_enforcer.d.ts.map +1 -0
  66. package/dist/src/budget/budget_enforcer.js +195 -0
  67. package/dist/src/budget/budget_enforcer.js.map +1 -0
  68. package/dist/src/budget/cost_tracker.d.ts +71 -0
  69. package/dist/src/budget/cost_tracker.d.ts.map +1 -0
  70. package/dist/src/budget/cost_tracker.js +199 -0
  71. package/dist/src/budget/cost_tracker.js.map +1 -0
  72. package/dist/src/budget/index.d.ts +6 -0
  73. package/dist/src/budget/index.d.ts.map +1 -0
  74. package/dist/src/budget/index.js +6 -0
  75. package/dist/src/budget/index.js.map +1 -0
  76. package/dist/src/budget/pricing.d.ts +35 -0
  77. package/dist/src/budget/pricing.d.ts.map +1 -0
  78. package/dist/src/budget/pricing.js +129 -0
  79. package/dist/src/budget/pricing.js.map +1 -0
  80. package/dist/src/budget/types.d.ts +86 -0
  81. package/dist/src/budget/types.d.ts.map +1 -0
  82. package/dist/src/budget/types.js +2 -0
  83. package/dist/src/budget/types.js.map +1 -0
  84. package/dist/src/catalog/index.d.ts +3 -0
  85. package/dist/src/catalog/index.d.ts.map +1 -0
  86. package/dist/src/catalog/index.js +3 -0
  87. package/dist/src/catalog/index.js.map +1 -0
  88. package/dist/src/catalog/models_dev.d.ts +87 -0
  89. package/dist/src/catalog/models_dev.d.ts.map +1 -0
  90. package/dist/src/catalog/models_dev.js +38 -0
  91. package/dist/src/catalog/models_dev.js.map +1 -0
  92. package/dist/src/catalog/source.d.ts +67 -0
  93. package/dist/src/catalog/source.d.ts.map +1 -0
  94. package/dist/src/catalog/source.js +678 -0
  95. package/dist/src/catalog/source.js.map +1 -0
  96. package/dist/src/circuit_breaker.d.ts +61 -0
  97. package/dist/src/circuit_breaker.d.ts.map +1 -0
  98. package/dist/src/circuit_breaker.js +217 -0
  99. package/dist/src/circuit_breaker.js.map +1 -0
  100. package/dist/src/define_config.d.ts +11 -0
  101. package/dist/src/define_config.d.ts.map +1 -0
  102. package/dist/src/define_config.js +48 -0
  103. package/dist/src/define_config.js.map +1 -0
  104. package/dist/src/error_classifier.d.ts +50 -0
  105. package/dist/src/error_classifier.d.ts.map +1 -0
  106. package/dist/src/error_classifier.js +206 -0
  107. package/dist/src/error_classifier.js.map +1 -0
  108. package/dist/src/errors.d.ts +52 -0
  109. package/dist/src/errors.d.ts.map +1 -0
  110. package/dist/src/errors.js +70 -0
  111. package/dist/src/errors.js.map +1 -0
  112. package/dist/src/guard/confirmation.d.ts +67 -0
  113. package/dist/src/guard/confirmation.d.ts.map +1 -0
  114. package/dist/src/guard/confirmation.js +180 -0
  115. package/dist/src/guard/confirmation.js.map +1 -0
  116. package/dist/src/guard/errors.d.ts +10 -0
  117. package/dist/src/guard/errors.d.ts.map +1 -0
  118. package/dist/src/guard/errors.js +14 -0
  119. package/dist/src/guard/errors.js.map +1 -0
  120. package/dist/src/guard/guard.d.ts +47 -0
  121. package/dist/src/guard/guard.d.ts.map +1 -0
  122. package/dist/src/guard/guard.js +133 -0
  123. package/dist/src/guard/guard.js.map +1 -0
  124. package/dist/src/guard/index.d.ts +7 -0
  125. package/dist/src/guard/index.d.ts.map +1 -0
  126. package/dist/src/guard/index.js +7 -0
  127. package/dist/src/guard/index.js.map +1 -0
  128. package/dist/src/guard/registry.d.ts +72 -0
  129. package/dist/src/guard/registry.d.ts.map +1 -0
  130. package/dist/src/guard/registry.js +192 -0
  131. package/dist/src/guard/registry.js.map +1 -0
  132. package/dist/src/guard/schema_registry.d.ts +37 -0
  133. package/dist/src/guard/schema_registry.d.ts.map +1 -0
  134. package/dist/src/guard/schema_registry.js +57 -0
  135. package/dist/src/guard/schema_registry.js.map +1 -0
  136. package/dist/src/guard/types.d.ts +63 -0
  137. package/dist/src/guard/types.d.ts.map +1 -0
  138. package/dist/src/guard/types.js +2 -0
  139. package/dist/src/guard/types.js.map +1 -0
  140. package/dist/src/index.d.ts +16 -0
  141. package/dist/src/index.d.ts.map +1 -0
  142. package/dist/src/index.js +14 -0
  143. package/dist/src/index.js.map +1 -0
  144. package/dist/src/logger.d.ts +7 -0
  145. package/dist/src/logger.d.ts.map +1 -0
  146. package/dist/src/logger.js +2 -0
  147. package/dist/src/logger.js.map +1 -0
  148. package/dist/src/models/catalog_resolver.d.ts +24 -0
  149. package/dist/src/models/catalog_resolver.d.ts.map +1 -0
  150. package/dist/src/models/catalog_resolver.js +66 -0
  151. package/dist/src/models/catalog_resolver.js.map +1 -0
  152. package/dist/src/models/index.d.ts +14 -0
  153. package/dist/src/models/index.d.ts.map +1 -0
  154. package/dist/src/models/index.js +8 -0
  155. package/dist/src/models/index.js.map +1 -0
  156. package/dist/src/models/keys.d.ts +9 -0
  157. package/dist/src/models/keys.d.ts.map +1 -0
  158. package/dist/src/models/keys.js +12 -0
  159. package/dist/src/models/keys.js.map +1 -0
  160. package/dist/src/models/language_model_factory.d.ts +44 -0
  161. package/dist/src/models/language_model_factory.d.ts.map +1 -0
  162. package/dist/src/models/language_model_factory.js +193 -0
  163. package/dist/src/models/language_model_factory.js.map +1 -0
  164. package/dist/src/models/model_chain.d.ts +58 -0
  165. package/dist/src/models/model_chain.d.ts.map +1 -0
  166. package/dist/src/models/model_chain.js +144 -0
  167. package/dist/src/models/model_chain.js.map +1 -0
  168. package/dist/src/models/model_ref.d.ts +12 -0
  169. package/dist/src/models/model_ref.d.ts.map +1 -0
  170. package/dist/src/models/model_ref.js +33 -0
  171. package/dist/src/models/model_ref.js.map +1 -0
  172. package/dist/src/models/protocols.d.ts +6 -0
  173. package/dist/src/models/protocols.d.ts.map +1 -0
  174. package/dist/src/models/protocols.js +33 -0
  175. package/dist/src/models/protocols.js.map +1 -0
  176. package/dist/src/models/provider_factories.d.ts +19 -0
  177. package/dist/src/models/provider_factories.d.ts.map +1 -0
  178. package/dist/src/models/provider_factories.js +70 -0
  179. package/dist/src/models/provider_factories.js.map +1 -0
  180. package/dist/src/models/types.d.ts +17 -0
  181. package/dist/src/models/types.d.ts.map +1 -0
  182. package/dist/src/models/types.js +2 -0
  183. package/dist/src/models/types.js.map +1 -0
  184. package/dist/src/response_macro.d.ts +16 -0
  185. package/dist/src/response_macro.d.ts.map +1 -0
  186. package/dist/src/response_macro.js +151 -0
  187. package/dist/src/response_macro.js.map +1 -0
  188. package/dist/src/types.d.ts +340 -0
  189. package/dist/src/types.d.ts.map +1 -0
  190. package/dist/src/types.js +2 -0
  191. package/dist/src/types.js.map +1 -0
  192. package/dist/src/usage.d.ts +13 -0
  193. package/dist/src/usage.d.ts.map +1 -0
  194. package/dist/src/usage.js +61 -0
  195. package/dist/src/usage.js.map +1 -0
  196. package/package.json +208 -4
@@ -0,0 +1,678 @@
1
+ import { mkdir, readFile, rename, rm, stat, utimes, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { DEFAULT_MODEL_PRICING } from '../budget/pricing.js';
4
+ import { getAllAcpProviderEntries } from '../acp/acp_agents.js';
5
+ const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
6
+ /** Built-in fallback data is retried quickly instead of being pinned in memory forever. */
7
+ const FALLBACK_TTL_MS = 60_000;
8
+ const MODELS_DEV_URL = 'https://models.dev/api.json';
9
+ /** Maps config driver / provider names to models.dev provider ids. */
10
+ const PROVIDER_ALIASES = Object.freeze({
11
+ gemini: 'google',
12
+ 'google-gemini': 'google',
13
+ 'google-generative-ai': 'google',
14
+ claude: 'anthropic',
15
+ 'openai-compatible': 'openai',
16
+ });
17
+ /**
18
+ * Resolves a provider/driver name (e.g. config driver `gemini`) to the models.dev provider id
19
+ * (`google`). Unknown names are returned unchanged.
20
+ */
21
+ export function resolveCatalogProviderId(providerId) {
22
+ return Object.hasOwn(PROVIDER_ALIASES, providerId) ? PROVIDER_ALIASES[providerId] : providerId;
23
+ }
24
+ function isPlainObject(value) {
25
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
26
+ }
27
+ /**
28
+ * Keeps only providers that look like models.dev providers (`{ models: { ... } }`).
29
+ * Returns `null` when nothing usable remains.
30
+ */
31
+ function sanitizeCatalog(json) {
32
+ if (!isPlainObject(json))
33
+ return null;
34
+ const catalog = {};
35
+ for (const [id, provider] of Object.entries(json)) {
36
+ if (!isPlainObject(provider) || !isPlainObject(provider.models))
37
+ continue;
38
+ const models = {};
39
+ for (const [modelId, model] of Object.entries(provider.models)) {
40
+ if (isPlainObject(model)) {
41
+ models[modelId] = { ...model, id: typeof model.id === 'string' ? model.id : modelId };
42
+ }
43
+ }
44
+ catalog[id] = {
45
+ ...provider,
46
+ id: typeof provider.id === 'string' ? provider.id : id,
47
+ name: typeof provider.name === 'string' ? provider.name : id,
48
+ env: Array.isArray(provider.env) ? provider.env : [],
49
+ models,
50
+ };
51
+ }
52
+ return Object.keys(catalog).length > 0 ? catalog : null;
53
+ }
54
+ function toCatalogCost(pricing) {
55
+ return {
56
+ input: pricing.input,
57
+ output: pricing.output,
58
+ cache_read: pricing.cacheReadCostPer1M,
59
+ cache_write: pricing.cacheWriteCostPer1M,
60
+ };
61
+ }
62
+ /** Fallback catalog prices come from token-budget's table so the two can never drift apart. */
63
+ function costOf(model) {
64
+ const pricing = Object.hasOwn(DEFAULT_MODEL_PRICING, model) ? DEFAULT_MODEL_PRICING[model] : undefined;
65
+ return pricing ? toCatalogCost(pricing) : undefined;
66
+ }
67
+ export class ModelsDevSource {
68
+ #cache = null;
69
+ #cacheKey = null;
70
+ #cacheAt = null;
71
+ #cacheIsFallback = false;
72
+ #inFlight = null;
73
+ #inFlightKey = null;
74
+ #logger;
75
+ #customCachePath;
76
+ #customOverridePath;
77
+ #ttlMs;
78
+ #fallback = null;
79
+ #injectFallback;
80
+ #injectAcp;
81
+ constructor(options) {
82
+ this.#logger = options?.logger;
83
+ this.#customCachePath = options?.cachePath;
84
+ this.#customOverridePath = options?.overridePath;
85
+ const system = options?.systemProviders;
86
+ const legacy = options?.injectSystemProviders ?? true;
87
+ this.#injectFallback = system ? (system.fallback ?? true) : legacy;
88
+ this.#injectAcp = system ? (system.acp ?? true) : legacy;
89
+ this.#ttlMs =
90
+ typeof options?.ttlMs === 'number' && options.ttlMs > 0 ? options.ttlMs : DEFAULT_TTL_MS;
91
+ }
92
+ setLogger(logger) {
93
+ this.#logger = logger;
94
+ }
95
+ async load(options = {}) {
96
+ const preferCache = options.preferCache ?? true;
97
+ const source = await this.#getSource(options);
98
+ if (preferCache && this.#isMemoryFresh(source.key)) {
99
+ this.#logger?.debug?.(`[ModelsDevSource] returning cached models for ${source.key}`);
100
+ return this.#cache;
101
+ }
102
+ if (this.#inFlight && this.#inFlightKey === source.key) {
103
+ this.#logger?.debug?.(`[ModelsDevSource] waiting for in-flight load of ${source.key}`);
104
+ return this.#inFlight;
105
+ }
106
+ return this.#loadSource(source, { forceRemote: false });
107
+ }
108
+ async refresh(options = {}) {
109
+ const source = await this.#getSource(options);
110
+ if (this.#inFlight && this.#inFlightKey === source.key) {
111
+ this.#logger?.debug?.(`[ModelsDevSource] deduplicating refresh for ${source.key}`);
112
+ return this.#inFlight;
113
+ }
114
+ const forceRemote = options.forceRemote ?? false;
115
+ if (forceRemote && this.#cache && this.#cacheKey === source.key && this.#cacheAt) {
116
+ const ageMs = Date.now() - this.#cacheAt;
117
+ if (!this.#cacheIsFallback && ageMs < 60_000) {
118
+ this.#logger?.info?.(`[ModelsDevSource] refresh skipped — in-memory cache is fresh (${Math.round(ageMs / 1000)}s)`);
119
+ return this.#cache;
120
+ }
121
+ }
122
+ if (forceRemote && !source.overridePath) {
123
+ try {
124
+ const fileStat = await stat(source.cachePath);
125
+ const fileAgeMs = Date.now() - fileStat.mtimeMs;
126
+ if (fileAgeMs < 120_000) {
127
+ const local = await this.#readLocalCache(source.cachePath);
128
+ if (local) {
129
+ this.#logger?.info?.(`[ModelsDevSource] refresh: file fresh (${Math.round(fileAgeMs / 1000)}s), using local cache`);
130
+ this.#store(source.key, this.#injectSystemProviders(local), false);
131
+ return this.#cache;
132
+ }
133
+ }
134
+ }
135
+ catch {
136
+ // file doesn't exist or stat failed, fall through to fetch
137
+ }
138
+ }
139
+ return this.#loadSource(source, { forceRemote });
140
+ }
141
+ resetCache() {
142
+ this.#cache = null;
143
+ this.#cacheKey = null;
144
+ this.#cacheAt = null;
145
+ this.#cacheIsFallback = false;
146
+ this.#inFlight = null;
147
+ this.#inFlightKey = null;
148
+ }
149
+ async getProvider(providerId) {
150
+ const catalog = await this.load();
151
+ return this.#provider(catalog, providerId);
152
+ }
153
+ async getModel(providerId, modelId) {
154
+ const catalog = await this.load();
155
+ const catalogId = resolveCatalogProviderId(providerId);
156
+ const provider = this.#provider(catalog, catalogId);
157
+ if (!provider)
158
+ return undefined;
159
+ const raw = Object.hasOwn(provider.models ?? {}, modelId) ? provider.models[modelId] : undefined;
160
+ if (!raw)
161
+ return undefined;
162
+ return this.#normalizeModel(catalogId, provider.name, raw);
163
+ }
164
+ async getContextLimit(providerId, modelId) {
165
+ const model = await this.getModel(providerId, modelId);
166
+ return model?.contextLimit;
167
+ }
168
+ async supportsToolCall(providerId, modelId) {
169
+ const model = await this.getModel(providerId, modelId);
170
+ return model?.toolCall ?? false;
171
+ }
172
+ async getModelPricing(providerId, modelId) {
173
+ const model = await this.getModel(providerId, modelId);
174
+ return model?.pricing;
175
+ }
176
+ async listProviders() {
177
+ const catalog = await this.load();
178
+ return Object.values(catalog);
179
+ }
180
+ async listModels(providerId) {
181
+ const catalog = await this.load();
182
+ const wanted = providerId ? resolveCatalogProviderId(providerId) : undefined;
183
+ const entries = [];
184
+ for (const [pId, provider] of Object.entries(catalog)) {
185
+ if (wanted && pId !== wanted)
186
+ continue;
187
+ for (const raw of Object.values(provider.models ?? {})) {
188
+ entries.push(this.#normalizeModel(pId, provider.name, raw));
189
+ }
190
+ }
191
+ return entries;
192
+ }
193
+ /**
194
+ * Dynamically resolves a model reference string (e.g. 'openai/gpt-4o', 'claude-3-5-sonnet', 'gpt-4o')
195
+ * into a matched provider ID, model ID, and catalog entry.
196
+ *
197
+ * Besides exact ids, an undated alias resolves to the newest dated model that extends it
198
+ * (`claude-3-5-sonnet` -> `claude-3-5-sonnet-20241022`).
199
+ */
200
+ async resolveModel(modelRef, defaultProvider) {
201
+ const catalog = await this.load();
202
+ // 1. Explicit syntax: "providerId/modelId"
203
+ if (modelRef.includes('/')) {
204
+ const [pId, ...rest] = modelRef.split('/');
205
+ const mId = rest.join('/');
206
+ const catalogId = resolveCatalogProviderId(pId);
207
+ const entry = await this.getModel(catalogId, mId);
208
+ return { providerId: catalogId, modelId: mId, entry };
209
+ }
210
+ const lookup = (providerId) => {
211
+ const provider = this.#provider(catalog, providerId);
212
+ if (!provider)
213
+ return undefined;
214
+ const models = provider.models ?? {};
215
+ if (Object.hasOwn(models, modelRef))
216
+ return { providerId, modelId: modelRef };
217
+ const alias = Object.keys(models)
218
+ .filter((id) => id.startsWith(`${modelRef}-`))
219
+ .sort()
220
+ .pop();
221
+ return alias ? { providerId, modelId: alias } : undefined;
222
+ };
223
+ const toResult = (hit) => {
224
+ const provider = this.#provider(catalog, hit.providerId);
225
+ return {
226
+ ...hit,
227
+ entry: this.#normalizeModel(hit.providerId, provider.name, provider.models[hit.modelId]),
228
+ };
229
+ };
230
+ // 2. Check default provider if specified
231
+ const defaultId = defaultProvider ? resolveCatalogProviderId(defaultProvider) : undefined;
232
+ if (defaultId) {
233
+ const hit = lookup(defaultId);
234
+ if (hit)
235
+ return toResult(hit);
236
+ }
237
+ // 3. Search catalog across all (non-ACP) providers: exact id first, then dated alias
238
+ const searchable = Object.keys(catalog).filter((id) => !id.startsWith('acp-'));
239
+ for (const pId of searchable) {
240
+ const provider = this.#provider(catalog, pId);
241
+ if (provider && Object.hasOwn(provider.models ?? {}, modelRef)) {
242
+ return toResult({ providerId: pId, modelId: modelRef });
243
+ }
244
+ }
245
+ for (const pId of searchable) {
246
+ const hit = lookup(pId);
247
+ if (hit)
248
+ return toResult(hit);
249
+ }
250
+ // 4. Fallback: return defaultProvider or 'openai' with modelRef
251
+ return { providerId: defaultId ?? 'openai', modelId: modelRef };
252
+ }
253
+ #provider(catalog, providerId) {
254
+ return Object.hasOwn(catalog, providerId) ? catalog[providerId] : undefined;
255
+ }
256
+ #isMemoryFresh(key) {
257
+ if (!this.#cache || this.#cacheKey !== key || this.#cacheAt === null)
258
+ return false;
259
+ const ttl = this.#cacheIsFallback ? FALLBACK_TTL_MS : this.#ttlMs;
260
+ return Date.now() - this.#cacheAt < ttl;
261
+ }
262
+ #store(key, catalog, isFallback) {
263
+ this.#cache = catalog;
264
+ this.#cacheKey = key;
265
+ this.#cacheAt = Date.now();
266
+ this.#cacheIsFallback = isFallback;
267
+ }
268
+ /**
269
+ * Fields models.dev does not provide are reported as "unknown" (`false` / `0`), never guessed.
270
+ */
271
+ #normalizeModel(providerId, providerName, raw) {
272
+ const pricing = raw.cost
273
+ ? {
274
+ input: raw.cost.input ?? 0,
275
+ output: raw.cost.output ?? 0,
276
+ cacheReadCostPer1M: raw.cost.cache_read,
277
+ cacheWriteCostPer1M: raw.cost.cache_write,
278
+ reasoningCostPer1M: raw.cost.reasoning,
279
+ }
280
+ : undefined;
281
+ return {
282
+ id: raw.id,
283
+ name: raw.name ?? raw.id,
284
+ providerId,
285
+ providerName,
286
+ contextLimit: raw.limit?.context ?? 0,
287
+ outputLimit: raw.limit?.output ?? 0,
288
+ toolCall: raw.tool_call ?? false,
289
+ structuredOutput: raw.structured_output ?? false,
290
+ pricing,
291
+ description: raw.description,
292
+ headers: raw.headers,
293
+ rawModel: raw,
294
+ };
295
+ }
296
+ async #getSource(options) {
297
+ const overridePath = options.overridePath ?? this.#customOverridePath ?? process.env.MODELS_DEV_PATH;
298
+ if (overridePath) {
299
+ const absolutePath = path.resolve(overridePath);
300
+ return {
301
+ key: `override:${absolutePath}`,
302
+ cachePath: absolutePath,
303
+ overridePath: absolutePath,
304
+ };
305
+ }
306
+ const cacheOverride = options.cachePath ?? this.#customCachePath ?? process.env.MODELS_DEV_CACHE_PATH;
307
+ if (cacheOverride) {
308
+ const absolutePath = path.resolve(cacheOverride);
309
+ return {
310
+ key: `cache:${absolutePath}`,
311
+ cachePath: absolutePath,
312
+ };
313
+ }
314
+ let defaultPath;
315
+ try {
316
+ const { default: app } = await import('@adonisjs/core/services/app');
317
+ defaultPath = app.makePath('storage', 'models', 'models.dev.json');
318
+ }
319
+ catch {
320
+ defaultPath = path.resolve(process.cwd(), 'storage', 'models', 'models.dev.json');
321
+ }
322
+ return {
323
+ key: `default:${defaultPath}`,
324
+ cachePath: defaultPath,
325
+ };
326
+ }
327
+ #loadSource(source, options) {
328
+ const promise = this.#readSource(source, options).then(({ catalog, isFallback }) => {
329
+ this.#store(source.key, catalog, isFallback);
330
+ return catalog;
331
+ });
332
+ this.#inFlight = promise;
333
+ this.#inFlightKey = source.key;
334
+ return promise.finally(() => {
335
+ if (this.#inFlight === promise) {
336
+ this.#inFlight = null;
337
+ this.#inFlightKey = null;
338
+ }
339
+ });
340
+ }
341
+ async #readSource(source, options) {
342
+ if (source.overridePath) {
343
+ this.#logger?.info?.(`[ModelsDevSource] loading from override: ${source.overridePath}`);
344
+ try {
345
+ const data = sanitizeCatalog(JSON.parse(await readFile(source.overridePath, 'utf-8')));
346
+ if (data)
347
+ return { catalog: this.#injectSystemProviders(data), isFallback: false };
348
+ this.#logger?.warn?.('[ModelsDevSource] override file has no valid providers');
349
+ }
350
+ catch (err) {
351
+ this.#logger?.warn?.(`[ModelsDevSource] failed to parse override file: ${err instanceof Error ? err.message : String(err)}`);
352
+ }
353
+ }
354
+ // 1. A fresh disk cache wins (unless a remote refresh is forced)
355
+ const local = await this.#readLocalCacheWithAge(source.cachePath);
356
+ if (local && !options.forceRemote && local.ageMs < this.#ttlMs) {
357
+ return { catalog: this.#injectSystemProviders(local.data), isFallback: false };
358
+ }
359
+ // 2. Otherwise (missing / expired / forced) ask models.dev
360
+ const remote = await this.#fetchRemote(source.cachePath);
361
+ if (remote) {
362
+ return { catalog: this.#injectSystemProviders(remote), isFallback: false };
363
+ }
364
+ // 3. Network failed: a stale cache is better than nothing
365
+ if (local) {
366
+ this.#logger?.warn?.(`[ModelsDevSource] refresh failed, using stale cache (${Math.round(local.ageMs / 3_600_000)}h old)`);
367
+ return { catalog: this.#injectSystemProviders(local.data), isFallback: false };
368
+ }
369
+ return { catalog: this.#injectSystemProviders(this.#getFallbackModels()), isFallback: true };
370
+ }
371
+ async #readLocalCache(cachePath) {
372
+ return (await this.#readLocalCacheWithAge(cachePath))?.data ?? null;
373
+ }
374
+ async #readLocalCacheWithAge(cachePath) {
375
+ try {
376
+ const [content, fileStat] = await Promise.all([readFile(cachePath, 'utf-8'), stat(cachePath)]);
377
+ const data = sanitizeCatalog(JSON.parse(content));
378
+ if (!data) {
379
+ this.#logger?.warn?.(`[ModelsDevSource] cache at ${cachePath} contains no valid providers`);
380
+ return null;
381
+ }
382
+ return { data, ageMs: Math.max(0, Date.now() - fileStat.mtimeMs) };
383
+ }
384
+ catch (err) {
385
+ if (err?.code !== 'ENOENT') {
386
+ const msg = err instanceof Error ? err.message : String(err);
387
+ this.#logger?.warn?.(`[ModelsDevSource] cache read error at ${cachePath}: ${msg}`);
388
+ }
389
+ return null;
390
+ }
391
+ }
392
+ /** Writes via a temp file + rename so concurrent readers never see a half-written file. */
393
+ async #writeAtomic(filePath, content) {
394
+ await mkdir(path.dirname(filePath), { recursive: true });
395
+ const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
396
+ try {
397
+ await writeFile(tmpPath, content);
398
+ await rename(tmpPath, filePath);
399
+ }
400
+ catch (err) {
401
+ await rm(tmpPath, { force: true }).catch(() => { });
402
+ throw err;
403
+ }
404
+ }
405
+ async #fetchRemote(cachePath) {
406
+ this.#logger?.info?.('[ModelsDevSource] fetching catalog from models.dev...');
407
+ const etagPath = `${cachePath}.etag`;
408
+ let etag = null;
409
+ let lastModified = null;
410
+ try {
411
+ const meta = JSON.parse(await readFile(etagPath, 'utf-8'));
412
+ etag = typeof meta.etag === 'string' ? meta.etag : null;
413
+ lastModified = typeof meta.lastModified === 'string' ? meta.lastModified : null;
414
+ }
415
+ catch {
416
+ // no previous metadata
417
+ }
418
+ let lastError;
419
+ for (let attempt = 1; attempt <= 2; attempt++) {
420
+ try {
421
+ const headers = {};
422
+ if (etag)
423
+ headers['If-None-Match'] = etag;
424
+ if (lastModified)
425
+ headers['If-Modified-Since'] = lastModified;
426
+ const response = await fetch(MODELS_DEV_URL, {
427
+ signal: AbortSignal.timeout(6_000),
428
+ headers: Object.keys(headers).length ? headers : undefined,
429
+ });
430
+ if (response.status === 304) {
431
+ this.#logger?.info?.('[ModelsDevSource] 304 Not Modified — using local cache');
432
+ const cached = await this.#readLocalCache(cachePath);
433
+ if (cached) {
434
+ // Restart the TTL clock of the unchanged file
435
+ const now = new Date();
436
+ await utimes(cachePath, now, now).catch(() => { });
437
+ return cached;
438
+ }
439
+ throw new Error('Received 304 but local cache file was missing');
440
+ }
441
+ if (!response.ok) {
442
+ throw new Error(`Failed to fetch models.dev: ${response.status} ${response.statusText}`);
443
+ }
444
+ const data = sanitizeCatalog(await response.json());
445
+ if (!data) {
446
+ throw new Error('Invalid models.dev response: no valid providers');
447
+ }
448
+ await this.#writeAtomic(cachePath, JSON.stringify(data));
449
+ const newEtag = response.headers.get('etag');
450
+ const newLastMod = response.headers.get('last-modified');
451
+ if (newEtag || newLastMod) {
452
+ await this.#writeAtomic(etagPath, JSON.stringify({ etag: newEtag, lastModified: newLastMod })).catch(() => { });
453
+ }
454
+ this.#logger?.info?.(`[ModelsDevSource] successfully updated catalog at ${cachePath}`);
455
+ return data;
456
+ }
457
+ catch (err) {
458
+ lastError = err instanceof Error ? err : new Error(String(err));
459
+ this.#logger?.warn?.(`[ModelsDevSource] fetch attempt ${attempt} failed: ${lastError.message}`);
460
+ if (attempt < 2) {
461
+ await new Promise((resolve) => setTimeout(resolve, 300 * attempt));
462
+ }
463
+ }
464
+ }
465
+ this.#logger?.warn?.(`[ModelsDevSource] all network attempts failed. Error: ${lastError?.message}`);
466
+ return null;
467
+ }
468
+ #injectSystemProviders(catalog) {
469
+ if (!this.#injectFallback && !this.#injectAcp)
470
+ return catalog;
471
+ const registry = { ...catalog };
472
+ // Inject system fallback providers if missing
473
+ if (this.#injectFallback) {
474
+ const fallback = this.#getFallbackModels();
475
+ for (const id of ['openai', 'anthropic', 'google']) {
476
+ if (!Object.hasOwn(registry, id))
477
+ registry[id] = fallback[id];
478
+ }
479
+ }
480
+ // ACP agents are exposed under `acp-<id>` providers (never auto-selected as fallbacks)
481
+ for (const entry of this.#injectAcp ? getAllAcpProviderEntries() : []) {
482
+ if (entry && !Object.hasOwn(registry, entry.id)) {
483
+ registry[entry.id] = {
484
+ id: entry.id,
485
+ name: entry.name,
486
+ env: entry.env,
487
+ npm: entry.npm,
488
+ protocol: entry.protocol,
489
+ models: Object.fromEntries(Object.entries(entry.models).map(([modelId, model]) => [
490
+ modelId,
491
+ {
492
+ ...model,
493
+ tool_call: true,
494
+ structured_output: true,
495
+ limit: { context: 200_000, output: 8_192 },
496
+ },
497
+ ])),
498
+ };
499
+ }
500
+ }
501
+ return registry;
502
+ }
503
+ #getFallbackModels() {
504
+ if (this.#fallback)
505
+ return this.#fallback;
506
+ this.#fallback = {
507
+ 'openai': {
508
+ id: 'openai',
509
+ name: 'OpenAI',
510
+ env: ['OPENAI_API_KEY'],
511
+ api: 'https://api.openai.com/v1',
512
+ protocol: 'openai',
513
+ npm: '@ai-sdk/openai',
514
+ models: {
515
+ 'gpt-4o': {
516
+ id: 'gpt-4o',
517
+ name: 'GPT-4o',
518
+ tool_call: true,
519
+ structured_output: true,
520
+ limit: { context: 128_000, output: 16_384 },
521
+ cost: costOf('gpt-4o'),
522
+ },
523
+ 'gpt-4o-mini': {
524
+ id: 'gpt-4o-mini',
525
+ name: 'GPT-4o Mini',
526
+ tool_call: true,
527
+ structured_output: true,
528
+ limit: { context: 128_000, output: 16_384 },
529
+ cost: costOf('gpt-4o-mini'),
530
+ },
531
+ 'gpt-4.1': {
532
+ id: 'gpt-4.1',
533
+ name: 'GPT-4.1',
534
+ tool_call: true,
535
+ structured_output: true,
536
+ limit: { context: 1_047_576, output: 32_768 },
537
+ cost: costOf('gpt-4.1'),
538
+ },
539
+ 'o1': {
540
+ id: 'o1',
541
+ name: 'o1',
542
+ tool_call: true,
543
+ structured_output: true,
544
+ reasoning: true,
545
+ limit: { context: 200_000, output: 100_000 },
546
+ cost: costOf('o1'),
547
+ },
548
+ 'o3-mini': {
549
+ id: 'o3-mini',
550
+ name: 'o3-mini',
551
+ tool_call: true,
552
+ structured_output: true,
553
+ reasoning: true,
554
+ limit: { context: 200_000, output: 100_000 },
555
+ cost: costOf('o3-mini'),
556
+ },
557
+ },
558
+ },
559
+ 'anthropic': {
560
+ id: 'anthropic',
561
+ name: 'Anthropic',
562
+ env: ['ANTHROPIC_API_KEY'],
563
+ api: 'https://api.anthropic.com/v1',
564
+ protocol: 'anthropic',
565
+ npm: '@ai-sdk/anthropic',
566
+ models: {
567
+ 'claude-3-7-sonnet-latest': {
568
+ id: 'claude-3-7-sonnet-latest',
569
+ name: 'Claude 3.7 Sonnet',
570
+ tool_call: true,
571
+ structured_output: true,
572
+ reasoning: true,
573
+ limit: { context: 200_000, output: 64_000 },
574
+ cost: costOf('claude-3-7-sonnet'),
575
+ },
576
+ 'claude-3-5-sonnet-20241022': {
577
+ id: 'claude-3-5-sonnet-20241022',
578
+ name: 'Claude 3.5 Sonnet',
579
+ tool_call: true,
580
+ structured_output: true,
581
+ limit: { context: 200_000, output: 8_192 },
582
+ cost: costOf('claude-3-5-sonnet'),
583
+ },
584
+ 'claude-3-5-haiku-20241022': {
585
+ id: 'claude-3-5-haiku-20241022',
586
+ name: 'Claude 3.5 Haiku',
587
+ tool_call: true,
588
+ structured_output: true,
589
+ limit: { context: 200_000, output: 8_192 },
590
+ cost: costOf('claude-3-5-haiku'),
591
+ },
592
+ },
593
+ },
594
+ 'google': {
595
+ id: 'google',
596
+ name: 'Google',
597
+ env: ['GOOGLE_GENERATIVE_AI_API_KEY', 'GEMINI_API_KEY'],
598
+ api: 'https://generativelanguage.googleapis.com/v1beta',
599
+ protocol: 'google',
600
+ npm: '@ai-sdk/google',
601
+ models: {
602
+ 'gemini-2.5-flash': {
603
+ id: 'gemini-2.5-flash',
604
+ name: 'Gemini 2.5 Flash',
605
+ tool_call: true,
606
+ structured_output: true,
607
+ reasoning: true,
608
+ limit: { context: 1_048_576, output: 65_536 },
609
+ cost: costOf('gemini-2.5-flash'),
610
+ },
611
+ 'gemini-2.5-pro': {
612
+ id: 'gemini-2.5-pro',
613
+ name: 'Gemini 2.5 Pro',
614
+ tool_call: true,
615
+ structured_output: true,
616
+ reasoning: true,
617
+ limit: { context: 1_048_576, output: 65_536 },
618
+ cost: costOf('gemini-2.5-pro'),
619
+ },
620
+ 'gemini-2.0-flash': {
621
+ id: 'gemini-2.0-flash',
622
+ name: 'Gemini 2.0 Flash',
623
+ tool_call: true,
624
+ structured_output: true,
625
+ limit: { context: 1_048_576, output: 8_192 },
626
+ cost: costOf('gemini-2.0-flash'),
627
+ },
628
+ 'gemini-1.5-pro': {
629
+ id: 'gemini-1.5-pro',
630
+ name: 'Gemini 1.5 Pro',
631
+ tool_call: true,
632
+ structured_output: true,
633
+ limit: { context: 2_000_000, output: 8_192 },
634
+ cost: costOf('gemini-1.5-pro'),
635
+ },
636
+ },
637
+ },
638
+ 'groq': {
639
+ id: 'groq',
640
+ name: 'Groq',
641
+ env: ['GROQ_API_KEY'],
642
+ api: 'https://api.groq.com/openai/v1',
643
+ protocol: 'openai',
644
+ npm: '@ai-sdk/groq',
645
+ models: {
646
+ 'llama-3.3-70b-versatile': {
647
+ id: 'llama-3.3-70b-versatile',
648
+ name: 'Llama 3.3 70B',
649
+ tool_call: true,
650
+ structured_output: true,
651
+ limit: { context: 128_000, output: 32_768 },
652
+ cost: costOf('llama-3.3-70b-versatile'),
653
+ },
654
+ },
655
+ },
656
+ 'ollama': {
657
+ id: 'ollama',
658
+ name: 'Ollama (Local)',
659
+ env: [],
660
+ api: 'http://localhost:11434/v1',
661
+ protocol: 'openai-compatible',
662
+ npm: '@ai-sdk/openai-compatible',
663
+ models: {
664
+ 'llama3.2': {
665
+ id: 'llama3.2',
666
+ name: 'Llama 3.2',
667
+ tool_call: true,
668
+ structured_output: true,
669
+ limit: { context: 128_000, output: 8_192 },
670
+ },
671
+ },
672
+ },
673
+ };
674
+ return this.#fallback;
675
+ }
676
+ }
677
+ export const modelsDevSource = new ModelsDevSource();
678
+ //# sourceMappingURL=source.js.map