agent-accelerator 0.4.5 → 0.5.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 +149 -57
- package/bunfig.toml +1 -0
- package/package.json +2 -3
- package/src/agent/agent.ts +401 -120
- package/src/agent/context.ts +11 -16
- package/src/agent/delegation.ts +111 -274
- package/src/agent/loop.ts +253 -98
- package/src/agent/subagent.ts +4 -12
- package/src/agent/worker.ts +435 -0
- package/src/index.ts +39 -5
- package/src/models/catalog-cache.ts +65 -25
- package/src/models/catalog.ts +21 -2
- package/src/providers/google.ts +282 -74
- package/src/providers/openai-compat.ts +61 -41
- package/src/providers/openai.ts +46 -31
- package/src/providers/openrouter.ts +42 -31
- package/src/providers/shared.ts +110 -16
- package/src/providers.ts +119 -6
- package/src/scheduler/scheduled-actions.md +41 -0
- package/src/scheduler/scheduler.ts +814 -0
- package/src/scheduler/store.ts +263 -0
- package/src/scheduler/time.ts +148 -0
- package/src/scheduler/tool.ts +151 -0
- package/src/session/store.ts +211 -104
- package/src/streaming/event-stream.ts +6 -4
- package/src/telemetry-file.ts +31 -0
- package/src/telemetry.ts +65 -0
- package/src/tools/executor.ts +163 -38
- package/src/tools/subagent-result.ts +60 -0
- package/src/types/agent.ts +57 -20
- package/src/types/model.ts +18 -0
- package/src/types/response.ts +2 -0
- package/src/types/scheduler.ts +81 -0
- package/src/types/tool.ts +15 -0
- package/src/utils/cache.ts +14 -5
- package/src/utils/documents.ts +112 -52
- package/src/utils/errors.ts +11 -0
- package/src/utils/headers.ts +15 -15
- package/src/utils/home.ts +30 -0
- package/src/utils/media.ts +57 -12
- package/src/utils/raw-capture.ts +76 -0
- package/src/utils/retry.ts +11 -3
- package/src/utils/safe-io.ts +252 -0
- package/src/utils/session.ts +7 -8
- package/src/data/README.md +0 -84
|
@@ -2,6 +2,8 @@ import * as path from "node:path";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { getEnv } from "../utils/env.ts";
|
|
5
|
+
import { telemetryBus } from "../telemetry.ts";
|
|
6
|
+
import { onHomeChange, resolveHome } from "../utils/home.ts";
|
|
5
7
|
|
|
6
8
|
/** Default TTL: 12 Hours (in milliseconds). */
|
|
7
9
|
export const DEFAULT_CATALOG_TTL_MS = 12 * 60 * 60 * 1000;
|
|
@@ -75,14 +77,14 @@ function notifyUpdateListeners(): void {
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
/**
|
|
78
|
-
* Catalog cache directory
|
|
79
|
-
*
|
|
80
|
-
*
|
|
80
|
+
* Catalog cache directory: `<home>/catalog` (the single Agent Accelerator home,
|
|
81
|
+
* see `resolveHome`), or `AGENT_CACHE_DIR` when set (read-only or serverless
|
|
82
|
+
* filesystems).
|
|
81
83
|
*/
|
|
82
84
|
export function getCacheDir(): string {
|
|
83
85
|
const override = getEnv("AGENT_CACHE_DIR");
|
|
84
86
|
if (override && override.trim()) return path.resolve(override.trim());
|
|
85
|
-
return path.
|
|
87
|
+
return path.join(resolveHome(), "catalog");
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
/** Cache file path: `<cacheDir>/models.dev.json`, honoring AGENT_CACHE_DIR (Q-16a/Q-34). */
|
|
@@ -121,13 +123,22 @@ export function verifyCatalogSha256(text: string): boolean {
|
|
|
121
123
|
return hex === pin;
|
|
122
124
|
}
|
|
123
125
|
|
|
126
|
+
/** Wall-clock of the last failed network refresh (0 = none); drives the retry backoff. */
|
|
127
|
+
let lastRefreshFailureAt = 0;
|
|
128
|
+
/** After a failed refresh, `ensureModelCatalogFresh` stays quiet this long (10 min). */
|
|
129
|
+
export const REFRESH_FAILURE_BACKOFF_MS = 10 * 60 * 1000;
|
|
130
|
+
/** Network timeout for a background refresh when a usable catalog is already loaded. */
|
|
131
|
+
const BACKGROUND_REFRESH_TIMEOUT_MS = 8_000;
|
|
132
|
+
|
|
133
|
+
let inFlightRefresh: Promise<CatalogStatus> | null = null;
|
|
134
|
+
|
|
124
135
|
// In-memory catalog state
|
|
125
136
|
let activeCatalog: Record<string, any> = {};
|
|
126
137
|
let activeFetchedAt: number | undefined = undefined;
|
|
127
138
|
let activeTtlMs: number = globalCatalogTtlMs;
|
|
128
139
|
let activeFromCache = false;
|
|
129
140
|
|
|
130
|
-
// Synchronous bootstrap: load
|
|
141
|
+
// Synchronous bootstrap: load <home>/catalog/models.dev.json if present
|
|
131
142
|
function initializeCatalogSync(): void {
|
|
132
143
|
const cachePath = getCacheFilePath();
|
|
133
144
|
const cached = readJsonFileSync(cachePath);
|
|
@@ -155,6 +166,11 @@ function initializeCatalogSync(): void {
|
|
|
155
166
|
}
|
|
156
167
|
|
|
157
168
|
initializeCatalogSync();
|
|
169
|
+
// `setHome()` moves the cache folder: reload from the new place.
|
|
170
|
+
onHomeChange(() => {
|
|
171
|
+
initializeCatalogSync();
|
|
172
|
+
notifyUpdateListeners();
|
|
173
|
+
});
|
|
158
174
|
|
|
159
175
|
/** Returns the active in-memory catalog data. */
|
|
160
176
|
export function getActiveCatalog(): Record<string, any> {
|
|
@@ -216,7 +232,7 @@ export function isValidCatalogPayload(data: unknown): data is Record<string, any
|
|
|
216
232
|
|
|
217
233
|
/**
|
|
218
234
|
* Refreshes the model catalog by downloading from models.dev
|
|
219
|
-
*
|
|
235
|
+
* into `<home>/catalog/models.dev.json` with a 12-hour TTL.
|
|
220
236
|
*/
|
|
221
237
|
export async function refreshModelCatalog(options: RefreshCatalogOptions = {}): Promise<CatalogStatus> {
|
|
222
238
|
const effectiveTtl = options.ttlMs ?? globalCatalogTtlMs;
|
|
@@ -247,6 +263,7 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
|
|
|
247
263
|
let freshData: Record<string, any>;
|
|
248
264
|
|
|
249
265
|
const fallbackToDiskCache = (): void => {
|
|
266
|
+
lastRefreshFailureAt = Date.now();
|
|
250
267
|
const existing = readJsonFileSync(cachePath);
|
|
251
268
|
if (existing && existing.data) {
|
|
252
269
|
activeCatalog = existing.data;
|
|
@@ -309,6 +326,7 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
|
|
|
309
326
|
clearTimeout(timer);
|
|
310
327
|
}
|
|
311
328
|
|
|
329
|
+
lastRefreshFailureAt = 0;
|
|
312
330
|
const fetchedAt = Date.now();
|
|
313
331
|
const cachePayload: CatalogCacheFile = {
|
|
314
332
|
fetchedAt,
|
|
@@ -326,42 +344,64 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
|
|
|
326
344
|
activeTtlMs = effectiveTtl;
|
|
327
345
|
activeFromCache = false;
|
|
328
346
|
|
|
329
|
-
// Save to
|
|
347
|
+
// Save to <home>/catalog/models.dev.json (or the AGENT_CACHE_DIR override)
|
|
330
348
|
try {
|
|
331
349
|
writeJsonFileSync(cachePath, cachePayload);
|
|
332
350
|
} catch (err) {
|
|
333
351
|
activeFromCache = false;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
} catch {}
|
|
352
|
+
telemetryBus.emitWarning(
|
|
353
|
+
"catalog_cache_write_failed",
|
|
354
|
+
`[Agent Accelerator] Could not write model catalog cache (${err instanceof Error ? err.message : String(err)}). Continuing with the in-memory catalog.`,
|
|
355
|
+
{ path: cachePath }
|
|
356
|
+
);
|
|
340
357
|
}
|
|
341
358
|
|
|
342
359
|
notifyUpdateListeners();
|
|
343
360
|
return getCatalogStatus();
|
|
344
361
|
}
|
|
345
362
|
|
|
346
|
-
|
|
363
|
+
|
|
364
|
+
/** True when `AGENT_CATALOG_REFRESH` is `off`/`0`/`false`: never touch the network automatically. */
|
|
365
|
+
function automaticRefreshDisabled(): boolean {
|
|
366
|
+
const v = getEnv("AGENT_CATALOG_REFRESH")?.trim().toLowerCase();
|
|
367
|
+
return v === "off" || v === "0" || v === "false";
|
|
368
|
+
}
|
|
347
369
|
|
|
348
370
|
/**
|
|
349
|
-
* Ensures that the model catalog is loaded and fresh.
|
|
350
|
-
*
|
|
371
|
+
* Ensures that the model catalog is loaded and fresh. Called automatically
|
|
372
|
+
* before Agent runs.
|
|
373
|
+
*
|
|
374
|
+
* - Fresh and loaded: returns immediately.
|
|
375
|
+
* - Expired but loaded (stale-while-revalidate): returns the stale catalog at
|
|
376
|
+
* once and refreshes in the background with a short timeout, so a run is
|
|
377
|
+
* never delayed by models.dev being slow or unreachable.
|
|
378
|
+
* - Nothing loaded yet (cold start): waits for the download.
|
|
379
|
+
* - A failed refresh is not retried automatically for 10 minutes.
|
|
380
|
+
* - `AGENT_CATALOG_REFRESH=off` disables automatic refreshes entirely
|
|
381
|
+
* (air-gapped hosts, hermetic tests); `force` and `refreshModelCatalog`
|
|
382
|
+
* still work.
|
|
351
383
|
*/
|
|
352
384
|
export async function ensureModelCatalogFresh(options: RefreshCatalogOptions = {}): Promise<CatalogStatus> {
|
|
353
385
|
const current = getCatalogStatus();
|
|
354
|
-
if (!
|
|
355
|
-
return current;
|
|
386
|
+
if (!options.force) {
|
|
387
|
+
if (!current.isExpired && current.loaded) return current;
|
|
388
|
+
if (automaticRefreshDisabled()) return current;
|
|
389
|
+
if (Date.now() - lastRefreshFailureAt < REFRESH_FAILURE_BACKOFF_MS) return current;
|
|
356
390
|
}
|
|
357
391
|
|
|
358
|
-
|
|
392
|
+
const start = (): Promise<CatalogStatus> => {
|
|
393
|
+
if (!inFlightRefresh) {
|
|
394
|
+
const timeoutMs = options.timeoutMs ?? (current.loaded ? BACKGROUND_REFRESH_TIMEOUT_MS : undefined);
|
|
395
|
+
inFlightRefresh = refreshModelCatalog({ ...options, ...(timeoutMs !== undefined ? { timeoutMs } : {}) }).finally(() => {
|
|
396
|
+
inFlightRefresh = null;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
359
399
|
return inFlightRefresh;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
inFlightRefresh = refreshModelCatalog(options).finally(() => {
|
|
363
|
-
inFlightRefresh = null;
|
|
364
|
-
});
|
|
400
|
+
};
|
|
365
401
|
|
|
366
|
-
|
|
402
|
+
if (current.loaded && !options.force) {
|
|
403
|
+
start().catch(() => {}); // background revalidation; failures are non-fatal
|
|
404
|
+
return current;
|
|
405
|
+
}
|
|
406
|
+
return start();
|
|
367
407
|
}
|
package/src/models/catalog.ts
CHANGED
|
@@ -52,6 +52,19 @@ function applyModalityOverrides(provider: string, modelId: string, base: ModelMo
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Whether a lookup may fall back to the GLOBAL model index (any provider's
|
|
57
|
+
* entry with that id). Allowed for first-class providers and for explicit
|
|
58
|
+
* bare-id searches (`getModelFromCatalog(id, id)`). Denied for custom
|
|
59
|
+
* prefixes (`ollama/gpt-oss-20b`): a self-hosted model must not inherit the
|
|
60
|
+
* price, modalities or reasoning flags of an unrelated hosted model that
|
|
61
|
+
* happens to share its id: unknown stays unknown.
|
|
62
|
+
*/
|
|
63
|
+
function allowsGlobalFallback(providerInput: string, modelIdInput: string): boolean {
|
|
64
|
+
const p = providerInput.toLowerCase();
|
|
65
|
+
return p in PROVIDER_ALIASES || p === "gemini" || p === modelIdInput.trim().toLowerCase();
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
function normalizeModelIdForLookup(modelId: string): string {
|
|
56
69
|
if (modelId.includes("/")) {
|
|
57
70
|
const parts = modelId.split("/");
|
|
@@ -280,7 +293,11 @@ export function getModelFromCatalog(providerInput: string, modelIdInput: string)
|
|
|
280
293
|
}
|
|
281
294
|
}
|
|
282
295
|
|
|
283
|
-
// Global O(1) fallback via indexed catalog
|
|
296
|
+
// Global O(1) fallback via indexed catalog (first-class providers / bare ids only)
|
|
297
|
+
if (!allowsGlobalFallback(providerInput, modelId)) {
|
|
298
|
+
setLookupCache(cacheKey, undefined);
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
284
301
|
const index = getGlobalIndex();
|
|
285
302
|
const lowerModelId = modelId.toLowerCase();
|
|
286
303
|
const strippedLower = normalizeModelIdForLookup(modelId).toLowerCase();
|
|
@@ -310,7 +327,9 @@ export interface ModelThinkingInfo {
|
|
|
310
327
|
* @example `const info = getModelThinkingInfo("google", "gemini-3.5-flash-lite");`
|
|
311
328
|
*/
|
|
312
329
|
export function getModelThinkingInfo(provider: string, modelId: string): ModelThinkingInfo {
|
|
313
|
-
const spec =
|
|
330
|
+
const spec =
|
|
331
|
+
getModelFromCatalog(provider, modelId) ||
|
|
332
|
+
(allowsGlobalFallback(provider, modelId) ? getModelFromCatalog(modelId, modelId) : undefined);
|
|
314
333
|
if (!spec) {
|
|
315
334
|
return {
|
|
316
335
|
supportsThinking: true,
|