agent-accelerator 0.1.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/LICENSE +21 -0
- package/README.md +1220 -0
- package/SYSTEM_PROMPT.md +14 -0
- package/SYSTEM_PROMPT_AGENT.md +34 -0
- package/SYSTEM_PROMPT_TOOLS.md +9 -0
- package/bunfig.toml +2 -0
- package/package.json +59 -0
- package/src/agent/agent.ts +615 -0
- package/src/agent/context.ts +161 -0
- package/src/agent/delegation.ts +481 -0
- package/src/agent/loop.ts +569 -0
- package/src/agent/subagent.ts +83 -0
- package/src/ai-sdk/converters.ts +342 -0
- package/src/ai-sdk/errors.ts +122 -0
- package/src/ai-sdk/executor.ts +454 -0
- package/src/ai-sdk/index.ts +55 -0
- package/src/ai-sdk/model-provider.ts +303 -0
- package/src/ai-sdk/options.ts +306 -0
- package/src/ai-sdk/provider.ts +415 -0
- package/src/ai-sdk/registry.ts +416 -0
- package/src/data/README.md +84 -0
- package/src/index.ts +190 -0
- package/src/models/catalog-cache.ts +273 -0
- package/src/models/catalog.ts +503 -0
- package/src/streaming/event-stream.ts +211 -0
- package/src/streaming/sse-parser.ts +97 -0
- package/src/tokens/counter.ts +136 -0
- package/src/tools/executor.ts +365 -0
- package/src/tools/schema.ts +221 -0
- package/src/tools/tool.ts +101 -0
- package/src/types/agent.ts +87 -0
- package/src/types/core.ts +86 -0
- package/src/types/message.ts +115 -0
- package/src/types/model.ts +212 -0
- package/src/types/provider-payloads.ts +434 -0
- package/src/types/response.ts +158 -0
- package/src/types/tool.ts +61 -0
- package/src/utils/base64.ts +27 -0
- package/src/utils/cache.ts +146 -0
- package/src/utils/env.ts +78 -0
- package/src/utils/headers.ts +110 -0
- package/src/utils/media.ts +137 -0
- package/src/utils/serialization.ts +91 -0
- package/src/utils/session.ts +26 -0
- package/src/utils/thought-signature.ts +27 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
|
|
4
|
+
/** Default TTL: 12 Hours (in milliseconds). */
|
|
5
|
+
export const DEFAULT_CATALOG_TTL_MS = 12 * 60 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
let globalCatalogTtlMs = DEFAULT_CATALOG_TTL_MS;
|
|
8
|
+
|
|
9
|
+
/** Sets the global model catalog cache TTL in milliseconds. */
|
|
10
|
+
export function setCatalogTTL(ttlMs: number): void {
|
|
11
|
+
if (Number.isFinite(ttlMs) && ttlMs > 0) {
|
|
12
|
+
globalCatalogTtlMs = ttlMs;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Gets the currently configured global catalog TTL in milliseconds. */
|
|
17
|
+
export function getCatalogTTL(): number {
|
|
18
|
+
return globalCatalogTtlMs;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CatalogCacheFile {
|
|
22
|
+
fetchedAt: number;
|
|
23
|
+
ttlMs: number;
|
|
24
|
+
source: string;
|
|
25
|
+
version: string;
|
|
26
|
+
data: Record<string, any>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CatalogStatus {
|
|
30
|
+
loaded: boolean;
|
|
31
|
+
fromCache: boolean;
|
|
32
|
+
isExpired: boolean;
|
|
33
|
+
fetchedAt?: number;
|
|
34
|
+
ttlMs: number;
|
|
35
|
+
expiresAt?: number;
|
|
36
|
+
cachePath: string;
|
|
37
|
+
providerCount: number;
|
|
38
|
+
modelCount: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RefreshCatalogOptions {
|
|
42
|
+
/** Force re-download even if current cache is still within TTL. Default: false */
|
|
43
|
+
force?: boolean;
|
|
44
|
+
/** Custom TTL in milliseconds for this cache entry. Default: 12 hours */
|
|
45
|
+
ttlMs?: number;
|
|
46
|
+
/** Custom models endpoint URL. Default: "https://models.dev/api.json" */
|
|
47
|
+
apiUrl?: string;
|
|
48
|
+
/** Fetch timeout in milliseconds. Default: 30000 */
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type CatalogUpdateListener = () => void;
|
|
53
|
+
const updateListeners: Set<CatalogUpdateListener> = new Set();
|
|
54
|
+
|
|
55
|
+
export function registerCatalogUpdateListener(listener: CatalogUpdateListener): () => void {
|
|
56
|
+
updateListeners.add(listener);
|
|
57
|
+
return () => updateListeners.delete(listener);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function notifyUpdateListeners(): void {
|
|
61
|
+
for (const listener of updateListeners) {
|
|
62
|
+
try {
|
|
63
|
+
listener();
|
|
64
|
+
} catch {}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Non-configurable cache directory: src/data */
|
|
69
|
+
export function getCacheDir(): string {
|
|
70
|
+
return path.resolve(process.cwd(), "src/data");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Non-configurable cache file path: src/data/models-cache.json */
|
|
74
|
+
export function getCacheFilePath(): string {
|
|
75
|
+
return path.resolve(process.cwd(), "src/data/models-cache.json");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readJsonFileSync(filePath: string): any {
|
|
79
|
+
try {
|
|
80
|
+
if (!fs.existsSync(filePath)) return null;
|
|
81
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
82
|
+
return JSON.parse(content);
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeJsonFileSync(filePath: string, data: any): void {
|
|
89
|
+
const dir = path.dirname(filePath);
|
|
90
|
+
if (!fs.existsSync(dir)) {
|
|
91
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
fs.writeFileSync(filePath, JSON.stringify(data), "utf-8");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// In-memory catalog state
|
|
97
|
+
let activeCatalog: Record<string, any> = {};
|
|
98
|
+
let activeFetchedAt: number | undefined = undefined;
|
|
99
|
+
let activeTtlMs: number = globalCatalogTtlMs;
|
|
100
|
+
let activeFromCache = false;
|
|
101
|
+
|
|
102
|
+
// Synchronous bootstrap: load from src/data/models-cache.json if present
|
|
103
|
+
function initializeCatalogSync(): void {
|
|
104
|
+
const cachePath = getCacheFilePath();
|
|
105
|
+
const cached = readJsonFileSync(cachePath);
|
|
106
|
+
if (cached && typeof cached === "object") {
|
|
107
|
+
if (cached.data && typeof cached.data === "object") {
|
|
108
|
+
activeCatalog = cached.data;
|
|
109
|
+
activeFetchedAt = cached.fetchedAt;
|
|
110
|
+
activeTtlMs = cached.ttlMs || globalCatalogTtlMs;
|
|
111
|
+
activeFromCache = true;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (cached.google || cached.openai) {
|
|
115
|
+
activeCatalog = cached;
|
|
116
|
+
activeFetchedAt = fs.statSync(cachePath).mtimeMs || Date.now();
|
|
117
|
+
activeTtlMs = globalCatalogTtlMs;
|
|
118
|
+
activeFromCache = true;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
activeCatalog = {};
|
|
124
|
+
activeFetchedAt = undefined;
|
|
125
|
+
activeTtlMs = globalCatalogTtlMs;
|
|
126
|
+
activeFromCache = false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
initializeCatalogSync();
|
|
130
|
+
|
|
131
|
+
/** Returns the active in-memory catalog data. */
|
|
132
|
+
export function getActiveCatalog(): Record<string, any> {
|
|
133
|
+
return activeCatalog;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Checks whether the active model catalog has expired based on its TTL. */
|
|
137
|
+
export function isCatalogExpired(): boolean {
|
|
138
|
+
if (!activeFetchedAt) return true;
|
|
139
|
+
const age = Date.now() - activeFetchedAt;
|
|
140
|
+
return age < 0 || age >= activeTtlMs;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Returns diagnostic metadata about the current model catalog status. */
|
|
144
|
+
export function getCatalogStatus(): CatalogStatus {
|
|
145
|
+
const providerCount = Object.keys(activeCatalog).length;
|
|
146
|
+
let modelCount = 0;
|
|
147
|
+
for (const prov of Object.values(activeCatalog)) {
|
|
148
|
+
if (prov && typeof prov === "object" && prov.models) {
|
|
149
|
+
modelCount += Object.keys(prov.models).length;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const isExpired = !activeFetchedAt || Date.now() - activeFetchedAt >= activeTtlMs;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
loaded: providerCount > 0,
|
|
157
|
+
fromCache: activeFromCache,
|
|
158
|
+
isExpired,
|
|
159
|
+
fetchedAt: activeFetchedAt,
|
|
160
|
+
ttlMs: activeTtlMs,
|
|
161
|
+
expiresAt: activeFetchedAt ? activeFetchedAt + activeTtlMs : undefined,
|
|
162
|
+
cachePath: getCacheFilePath(),
|
|
163
|
+
providerCount,
|
|
164
|
+
modelCount,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Refreshes the model catalog by downloading from models.dev
|
|
170
|
+
* directly into src/data/models-cache.json with a 12-hour TTL.
|
|
171
|
+
*/
|
|
172
|
+
export async function refreshModelCatalog(options: RefreshCatalogOptions = {}): Promise<CatalogStatus> {
|
|
173
|
+
const effectiveTtl = options.ttlMs ?? globalCatalogTtlMs;
|
|
174
|
+
const apiUrl = options.apiUrl || "https://models.dev/api.json";
|
|
175
|
+
const timeoutMs = options.timeoutMs ?? 30000;
|
|
176
|
+
const cachePath = getCacheFilePath();
|
|
177
|
+
|
|
178
|
+
// Return existing cache if still within TTL and not forcing
|
|
179
|
+
if (!options.force) {
|
|
180
|
+
const cached = readJsonFileSync(cachePath);
|
|
181
|
+
if (cached && typeof cached === "object" && cached.data) {
|
|
182
|
+
const age = Date.now() - (cached.fetchedAt || 0);
|
|
183
|
+
const fileTtl = cached.ttlMs || effectiveTtl;
|
|
184
|
+
if (age >= 0 && age < fileTtl) {
|
|
185
|
+
activeCatalog = cached.data;
|
|
186
|
+
activeFetchedAt = cached.fetchedAt;
|
|
187
|
+
activeTtlMs = fileTtl;
|
|
188
|
+
activeFromCache = true;
|
|
189
|
+
notifyUpdateListeners();
|
|
190
|
+
return getCatalogStatus();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Fetch from models.dev
|
|
196
|
+
const controller = new AbortController();
|
|
197
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
198
|
+
let freshData: Record<string, any>;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const res = await fetch(apiUrl, {
|
|
202
|
+
headers: {
|
|
203
|
+
Accept: "application/json",
|
|
204
|
+
"User-Agent": "agent-accelerator/1.0",
|
|
205
|
+
},
|
|
206
|
+
signal: controller.signal,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (!res.ok) {
|
|
210
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
freshData = (await res.json()) as Record<string, any>;
|
|
214
|
+
} catch (err: any) {
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
// If download fails, retain disk cache if available
|
|
217
|
+
const existing = readJsonFileSync(cachePath);
|
|
218
|
+
if (existing && existing.data) {
|
|
219
|
+
activeCatalog = existing.data;
|
|
220
|
+
activeFetchedAt = existing.fetchedAt;
|
|
221
|
+
activeTtlMs = existing.ttlMs || effectiveTtl;
|
|
222
|
+
activeFromCache = true;
|
|
223
|
+
notifyUpdateListeners();
|
|
224
|
+
}
|
|
225
|
+
return getCatalogStatus();
|
|
226
|
+
} finally {
|
|
227
|
+
clearTimeout(timer);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const fetchedAt = Date.now();
|
|
231
|
+
const cachePayload: CatalogCacheFile = {
|
|
232
|
+
fetchedAt,
|
|
233
|
+
ttlMs: effectiveTtl,
|
|
234
|
+
source: apiUrl,
|
|
235
|
+
version: "1.0",
|
|
236
|
+
data: freshData,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Save to src/data/models-cache.json
|
|
240
|
+
writeJsonFileSync(cachePath, cachePayload);
|
|
241
|
+
|
|
242
|
+
// Update in-memory state
|
|
243
|
+
activeCatalog = freshData;
|
|
244
|
+
activeFetchedAt = fetchedAt;
|
|
245
|
+
activeTtlMs = effectiveTtl;
|
|
246
|
+
activeFromCache = true;
|
|
247
|
+
|
|
248
|
+
notifyUpdateListeners();
|
|
249
|
+
return getCatalogStatus();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let inFlightRefresh: Promise<CatalogStatus> | null = null;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Ensures that the model catalog is loaded and fresh.
|
|
256
|
+
* Called automatically before Agent runs. If the cache is expired, triggers a refresh.
|
|
257
|
+
*/
|
|
258
|
+
export async function ensureModelCatalogFresh(options: RefreshCatalogOptions = {}): Promise<CatalogStatus> {
|
|
259
|
+
const current = getCatalogStatus();
|
|
260
|
+
if (!current.isExpired && current.loaded && !options.force) {
|
|
261
|
+
return current;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (inFlightRefresh) {
|
|
265
|
+
return inFlightRefresh;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
inFlightRefresh = refreshModelCatalog(options).finally(() => {
|
|
269
|
+
inFlightRefresh = null;
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return inFlightRefresh;
|
|
273
|
+
}
|