@funkai/agents 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.
Files changed (153) hide show
  1. package/.generated/req.txt +1 -0
  2. package/.turbo/turbo-build.log +21 -0
  3. package/.turbo/turbo-test$colon$coverage.log +109 -0
  4. package/.turbo/turbo-test.log +141 -0
  5. package/.turbo/turbo-typecheck.log +4 -0
  6. package/CHANGELOG.md +16 -0
  7. package/ISSUES.md +540 -0
  8. package/LICENSE +21 -0
  9. package/README.md +128 -0
  10. package/banner.svg +97 -0
  11. package/coverage/lcov-report/base.css +224 -0
  12. package/coverage/lcov-report/block-navigation.js +87 -0
  13. package/coverage/lcov-report/core/agents/base/agent.ts.html +1705 -0
  14. package/coverage/lcov-report/core/agents/base/index.html +146 -0
  15. package/coverage/lcov-report/core/agents/base/output.ts.html +256 -0
  16. package/coverage/lcov-report/core/agents/base/utils.ts.html +694 -0
  17. package/coverage/lcov-report/core/agents/flow/engine.ts.html +928 -0
  18. package/coverage/lcov-report/core/agents/flow/flow-agent.ts.html +1462 -0
  19. package/coverage/lcov-report/core/agents/flow/index.html +146 -0
  20. package/coverage/lcov-report/core/agents/flow/messages.ts.html +508 -0
  21. package/coverage/lcov-report/core/agents/flow/steps/factory.ts.html +1975 -0
  22. package/coverage/lcov-report/core/agents/flow/steps/index.html +116 -0
  23. package/coverage/lcov-report/core/index.html +131 -0
  24. package/coverage/lcov-report/core/logger.ts.html +541 -0
  25. package/coverage/lcov-report/core/models/providers/index.html +116 -0
  26. package/coverage/lcov-report/core/models/providers/openai.ts.html +337 -0
  27. package/coverage/lcov-report/core/provider/index.html +131 -0
  28. package/coverage/lcov-report/core/provider/provider.ts.html +346 -0
  29. package/coverage/lcov-report/core/provider/usage.ts.html +376 -0
  30. package/coverage/lcov-report/core/tool.ts.html +577 -0
  31. package/coverage/lcov-report/favicon.png +0 -0
  32. package/coverage/lcov-report/index.html +221 -0
  33. package/coverage/lcov-report/lib/hooks.ts.html +262 -0
  34. package/coverage/lcov-report/lib/index.html +161 -0
  35. package/coverage/lcov-report/lib/middleware.ts.html +274 -0
  36. package/coverage/lcov-report/lib/runnable.ts.html +151 -0
  37. package/coverage/lcov-report/lib/trace.ts.html +520 -0
  38. package/coverage/lcov-report/prettify.css +1 -0
  39. package/coverage/lcov-report/prettify.js +2 -0
  40. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  41. package/coverage/lcov-report/sorter.js +210 -0
  42. package/coverage/lcov-report/utils/attempt.ts.html +199 -0
  43. package/coverage/lcov-report/utils/error.ts.html +421 -0
  44. package/coverage/lcov-report/utils/index.html +176 -0
  45. package/coverage/lcov-report/utils/resolve.ts.html +208 -0
  46. package/coverage/lcov-report/utils/result.ts.html +538 -0
  47. package/coverage/lcov-report/utils/zod.ts.html +178 -0
  48. package/coverage/lcov.info +1566 -0
  49. package/dist/index.d.mts +2883 -0
  50. package/dist/index.d.mts.map +1 -0
  51. package/dist/index.mjs +2312 -0
  52. package/dist/index.mjs.map +1 -0
  53. package/docs/core/agent.md +231 -0
  54. package/docs/core/hooks.md +95 -0
  55. package/docs/core/overview.md +87 -0
  56. package/docs/core/step.md +279 -0
  57. package/docs/core/tools.md +98 -0
  58. package/docs/core/workflow.md +235 -0
  59. package/docs/guides/create-agent.md +224 -0
  60. package/docs/guides/create-tool.md +137 -0
  61. package/docs/guides/create-workflow.md +374 -0
  62. package/docs/overview.md +244 -0
  63. package/docs/provider/models.md +55 -0
  64. package/docs/provider/overview.md +106 -0
  65. package/docs/provider/usage.md +100 -0
  66. package/docs/research/experimental-context.md +167 -0
  67. package/docs/research/gap-analysis.md +86 -0
  68. package/docs/research/prepare-step-and-active-tools.md +138 -0
  69. package/docs/research/sub-agent-model.md +249 -0
  70. package/docs/troubleshooting.md +60 -0
  71. package/logo.svg +17 -0
  72. package/models.config.json +18 -0
  73. package/package.json +60 -0
  74. package/scripts/generate-models.ts +324 -0
  75. package/src/core/agents/base/agent.test.ts +1522 -0
  76. package/src/core/agents/base/agent.ts +547 -0
  77. package/src/core/agents/base/output.test.ts +93 -0
  78. package/src/core/agents/base/output.ts +57 -0
  79. package/src/core/agents/base/types.test-d.ts +69 -0
  80. package/src/core/agents/base/types.ts +503 -0
  81. package/src/core/agents/base/utils.test.ts +397 -0
  82. package/src/core/agents/base/utils.ts +197 -0
  83. package/src/core/agents/flow/engine.test.ts +452 -0
  84. package/src/core/agents/flow/engine.ts +281 -0
  85. package/src/core/agents/flow/flow-agent.test.ts +1027 -0
  86. package/src/core/agents/flow/flow-agent.ts +473 -0
  87. package/src/core/agents/flow/messages.test.ts +198 -0
  88. package/src/core/agents/flow/messages.ts +141 -0
  89. package/src/core/agents/flow/steps/agent.test.ts +280 -0
  90. package/src/core/agents/flow/steps/agent.ts +87 -0
  91. package/src/core/agents/flow/steps/all.test.ts +300 -0
  92. package/src/core/agents/flow/steps/all.ts +73 -0
  93. package/src/core/agents/flow/steps/builder.ts +124 -0
  94. package/src/core/agents/flow/steps/each.test.ts +257 -0
  95. package/src/core/agents/flow/steps/each.ts +61 -0
  96. package/src/core/agents/flow/steps/factory.test-d.ts +50 -0
  97. package/src/core/agents/flow/steps/factory.test.ts +1025 -0
  98. package/src/core/agents/flow/steps/factory.ts +645 -0
  99. package/src/core/agents/flow/steps/map.test.ts +273 -0
  100. package/src/core/agents/flow/steps/map.ts +75 -0
  101. package/src/core/agents/flow/steps/race.test.ts +290 -0
  102. package/src/core/agents/flow/steps/race.ts +59 -0
  103. package/src/core/agents/flow/steps/reduce.test.ts +310 -0
  104. package/src/core/agents/flow/steps/reduce.ts +73 -0
  105. package/src/core/agents/flow/steps/result.ts +27 -0
  106. package/src/core/agents/flow/steps/step.test.ts +402 -0
  107. package/src/core/agents/flow/steps/step.ts +51 -0
  108. package/src/core/agents/flow/steps/while.test.ts +283 -0
  109. package/src/core/agents/flow/steps/while.ts +75 -0
  110. package/src/core/agents/flow/types.ts +348 -0
  111. package/src/core/logger.test.ts +163 -0
  112. package/src/core/logger.ts +152 -0
  113. package/src/core/models/index.test.ts +137 -0
  114. package/src/core/models/index.ts +152 -0
  115. package/src/core/models/providers/openai.ts +84 -0
  116. package/src/core/provider/provider.test.ts +128 -0
  117. package/src/core/provider/provider.ts +99 -0
  118. package/src/core/provider/types.ts +98 -0
  119. package/src/core/provider/usage.test.ts +304 -0
  120. package/src/core/provider/usage.ts +97 -0
  121. package/src/core/tool.test.ts +65 -0
  122. package/src/core/tool.ts +164 -0
  123. package/src/core/types.ts +66 -0
  124. package/src/index.ts +95 -0
  125. package/src/lib/context.test.ts +86 -0
  126. package/src/lib/context.ts +49 -0
  127. package/src/lib/hooks.test.ts +102 -0
  128. package/src/lib/hooks.ts +59 -0
  129. package/src/lib/middleware.test.ts +122 -0
  130. package/src/lib/middleware.ts +63 -0
  131. package/src/lib/runnable.test.ts +41 -0
  132. package/src/lib/runnable.ts +22 -0
  133. package/src/lib/trace.test.ts +291 -0
  134. package/src/lib/trace.ts +145 -0
  135. package/src/models/index.ts +123 -0
  136. package/src/models/providers/index.ts +15 -0
  137. package/src/models/providers/openai.ts +84 -0
  138. package/src/testing/context.ts +32 -0
  139. package/src/testing/index.ts +2 -0
  140. package/src/testing/logger.ts +19 -0
  141. package/src/utils/attempt.test.ts +127 -0
  142. package/src/utils/attempt.ts +38 -0
  143. package/src/utils/error.test.ts +179 -0
  144. package/src/utils/error.ts +112 -0
  145. package/src/utils/resolve.test.ts +38 -0
  146. package/src/utils/resolve.ts +41 -0
  147. package/src/utils/result.test.ts +79 -0
  148. package/src/utils/result.ts +151 -0
  149. package/src/utils/zod.test.ts +69 -0
  150. package/src/utils/zod.ts +31 -0
  151. package/tsconfig.json +25 -0
  152. package/tsdown.config.ts +15 -0
  153. package/vitest.config.ts +46 -0
@@ -0,0 +1,324 @@
1
+ /* eslint-disable security/detect-non-literal-fs-filename */
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { OpenRouter } from "@openrouter/sdk";
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const PACKAGE_ROOT = join(__dirname, "..");
10
+ const CONFIG_PATH = join(PACKAGE_ROOT, "models.config.json");
11
+ const MODELS_DIR = join(PACKAGE_ROOT, "src", "models");
12
+ const PROVIDERS_DIR = join(MODELS_DIR, "providers");
13
+ const GENERATED_DIR = join(PACKAGE_ROOT, ".generated");
14
+ const REQ_PATH = join(GENERATED_DIR, "req.txt");
15
+
16
+ const STALE_MS = 24 * 60 * 60 * 1000;
17
+
18
+ /**
19
+ * Pricing fields we extract from the OpenRouter API.
20
+ * Maps API field name → our camelCase field name.
21
+ */
22
+ const PRICING_FIELDS: Record<string, string> = {
23
+ prompt: "prompt",
24
+ completion: "completion",
25
+ inputCacheRead: "inputCacheRead",
26
+ inputCacheWrite: "inputCacheWrite",
27
+ webSearch: "webSearch",
28
+ internalReasoning: "internalReasoning",
29
+ image: "image",
30
+ audio: "audio",
31
+ audioOutput: "audioOutput",
32
+ };
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Banner
36
+ // ---------------------------------------------------------------------------
37
+
38
+ const BANNER = `// ──────────────────────────────────────────────────────────────
39
+ // █████╗ ██████╗ ███████╗███╗ ██╗████████╗███████╗
40
+ // ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝██╔════╝
41
+ // ███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ███████╗
42
+ // ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
43
+ // ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ███████║
44
+ // ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
45
+ //
46
+ // AUTO-GENERATED — DO NOT EDIT
47
+ // Update: pnpm --filter=@pkg/agent-sdk generate:models
48
+ // ──────────────────────────────────────────────────────────────`;
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Types
52
+ // ---------------------------------------------------------------------------
53
+
54
+ interface ConfigEntry {
55
+ id: string;
56
+ category: string;
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Helpers
61
+ // ---------------------------------------------------------------------------
62
+
63
+ /**
64
+ * Convert a provider key to a constant name.
65
+ * e.g. "openai" → "OPENAI_MODELS"
66
+ */
67
+ function toConstName(provider: string): string {
68
+ return `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_MODELS`;
69
+ }
70
+
71
+ /**
72
+ * Build the pricing object string for a model, including only non-zero fields.
73
+ */
74
+ function buildPricing(apiPricing: Record<string, string | undefined>): string {
75
+ const parts: string[] = [];
76
+ for (const [apiKey, ourKey] of Object.entries(PRICING_FIELDS)) {
77
+ // eslint-disable-next-line security/detect-object-injection -- Key from Object.entries iteration over a static config object
78
+ const raw = apiPricing[apiKey];
79
+ if (!raw) {
80
+ continue;
81
+ }
82
+ const value = parseFloat(raw);
83
+ if (value === 0) {
84
+ continue;
85
+ }
86
+ parts.push(`${ourKey}: ${value}`);
87
+ }
88
+ return `{ ${parts.join(", ")} }`;
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Staleness check
93
+ // ---------------------------------------------------------------------------
94
+
95
+ function isFresh(): boolean {
96
+ if (!existsSync(REQ_PATH)) {
97
+ return false;
98
+ }
99
+ try {
100
+ const timestamp = readFileSync(REQ_PATH, "utf-8").trim();
101
+ const lastRun = new Date(timestamp).getTime();
102
+ return Date.now() - lastRun < STALE_MS;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Templates
110
+ // ---------------------------------------------------------------------------
111
+
112
+ /**
113
+ * Root index.ts — types, helpers, re-exports.
114
+ */
115
+ function rootIndex(): string {
116
+ return `${BANNER}
117
+
118
+ import { MODELS as GENERATED_MODELS } from './providers/index.js'
119
+
120
+ /**
121
+ * Supported OpenRouter model identifiers, derived from the generated {@link MODELS} array.
122
+ */
123
+ export type OpenRouterLanguageModelId = (typeof GENERATED_MODELS)[number]['id']
124
+
125
+ /**
126
+ * Model category for classification and filtering.
127
+ */
128
+ export type ModelCategory = 'chat' | 'coding' | 'reasoning'
129
+
130
+ /**
131
+ * Per-model pricing in USD per token.
132
+ *
133
+ * Field names match the OpenRouter API convention. All values are
134
+ * per-token (or per-unit) rates as numbers. Optional fields are
135
+ * omitted when the provider does not support them.
136
+ */
137
+ export interface ModelPricing {
138
+ /** Cost per input (prompt) token. */
139
+ prompt: number
140
+
141
+ /** Cost per output (completion) token. */
142
+ completion: number
143
+
144
+ /** Cost per cached input token (read). */
145
+ inputCacheRead?: number
146
+
147
+ /** Cost per cached input token (write). */
148
+ inputCacheWrite?: number
149
+
150
+ /** Cost per web search request. */
151
+ webSearch?: number
152
+
153
+ /** Cost per internal reasoning token. */
154
+ internalReasoning?: number
155
+
156
+ /** Cost per image input token. */
157
+ image?: number
158
+
159
+ /** Cost per audio input second. */
160
+ audio?: number
161
+
162
+ /** Cost per audio output second. */
163
+ audioOutput?: number
164
+ }
165
+
166
+ /**
167
+ * Model definition with metadata and pricing.
168
+ */
169
+ export interface ModelDefinition {
170
+ /** OpenRouter model identifier (e.g. \`"openai/gpt-5.2-codex"\`). */
171
+ id: string
172
+
173
+ /** Model category for classification. */
174
+ category: ModelCategory
175
+
176
+ /** Token pricing rates. */
177
+ pricing: ModelPricing
178
+ }
179
+
180
+ /**
181
+ * Supported OpenRouter models with pricing data.
182
+ */
183
+ export const MODELS = GENERATED_MODELS satisfies readonly ModelDefinition[]
184
+
185
+ /**
186
+ * Look up a model definition by its identifier.
187
+ *
188
+ * @param id - The model identifier to look up.
189
+ * @returns The matching model definition.
190
+ * @throws {Error} If no model matches the given ID.
191
+ *
192
+ * @example
193
+ * \`\`\`typescript
194
+ * const m = model('openai/gpt-5.2-codex')
195
+ * console.log(m.pricing.prompt) // 0.00000175
196
+ * console.log(m.category) // 'coding'
197
+ * \`\`\`
198
+ */
199
+ export function model(id: OpenRouterLanguageModelId): ModelDefinition {
200
+ const found = MODELS.find((m) => m.id === id)
201
+ if (!found) {
202
+ throw new Error(\`Unknown model: \${id}\`)
203
+ }
204
+ return found
205
+ }
206
+
207
+ /**
208
+ * Return supported model definitions, optionally filtered.
209
+ *
210
+ * @param filter - Optional predicate to filter models.
211
+ * @returns A readonly array of matching model definitions.
212
+ *
213
+ * @example
214
+ * \`\`\`typescript
215
+ * const all = models()
216
+ * const reasoning = models((m) => m.category === 'reasoning')
217
+ * \`\`\`
218
+ */
219
+ export function models(filter?: (m: ModelDefinition) => boolean): readonly ModelDefinition[] {
220
+ return filter ? MODELS.filter(filter) : MODELS
221
+ }
222
+ `;
223
+ }
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Main
227
+ // ---------------------------------------------------------------------------
228
+
229
+ async function main(): Promise<void> {
230
+ if (isFresh()) {
231
+ console.log("generate-models: skipping — last fetch was less than 24h ago");
232
+ return;
233
+ }
234
+
235
+ const config: Record<string, ConfigEntry[]> = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
236
+
237
+ const providers = Object.keys(config);
238
+ if (providers.length === 0) {
239
+ throw new Error("models.config.json has no providers");
240
+ }
241
+
242
+ console.log("generate-models: fetching models from OpenRouter SDK");
243
+ const client = new OpenRouter();
244
+ const response = await client.models.list();
245
+ const apiModels = response.data ?? [];
246
+ const modelMap = new Map(apiModels.map((m) => [m.id, m]));
247
+
248
+ console.log(`generate-models: ${apiModels.length} models from API`);
249
+
250
+ mkdirSync(GENERATED_DIR, { recursive: true });
251
+
252
+ // Clean and recreate entire models dir (everything is generated)
253
+ rmSync(MODELS_DIR, { recursive: true, force: true });
254
+ mkdirSync(PROVIDERS_DIR, { recursive: true });
255
+
256
+ const providerFiles: { provider: string; constName: string }[] = [];
257
+
258
+ for (const provider of providers) {
259
+ // eslint-disable-next-line security/detect-object-injection -- Provider key from controlled iteration, not user input
260
+ const entries = config[provider];
261
+ const constName = toConstName(provider);
262
+ const lines: string[] = [];
263
+
264
+ for (const entry of entries) {
265
+ const apiModel = modelMap.get(entry.id);
266
+ if (!apiModel) {
267
+ console.warn(` ⚠ ${entry.id} not found in OpenRouter API — skipping`);
268
+ continue;
269
+ }
270
+
271
+ const pricing = buildPricing(
272
+ apiModel.pricing as unknown as Record<string, string | undefined>,
273
+ );
274
+
275
+ lines.push(` { id: '${entry.id}', category: '${entry.category}', pricing: ${pricing} },`);
276
+ }
277
+
278
+ const content = `${BANNER}
279
+
280
+ export const ${constName} = [
281
+ ${lines.join("\n")}
282
+ ] as const
283
+ `;
284
+
285
+ const filePath = join(PROVIDERS_DIR, `${provider}.ts`);
286
+ writeFileSync(filePath, content, "utf-8");
287
+ console.log(` ✓ providers/${provider}.ts (${lines.length} models)`);
288
+
289
+ providerFiles.push({ provider, constName });
290
+ }
291
+
292
+ // Providers barrel
293
+ const imports = providerFiles
294
+ .map((p) => `import { ${p.constName} } from './${p.provider}.js'`)
295
+ .join("\n");
296
+
297
+ const spreads = providerFiles.map((p) => ` ...${p.constName},`).join("\n");
298
+
299
+ const providersBarrel = `${BANNER}
300
+
301
+ ${imports}
302
+
303
+ export const MODELS = [
304
+ ${spreads}
305
+ ] as const
306
+ `;
307
+
308
+ writeFileSync(join(PROVIDERS_DIR, "index.ts"), providersBarrel, "utf-8");
309
+ console.log(" ✓ providers/index.ts (barrel)");
310
+
311
+ // Root index
312
+ writeFileSync(join(MODELS_DIR, "index.ts"), rootIndex(), "utf-8");
313
+ console.log(" ✓ index.ts (types + helpers)");
314
+
315
+ // Staleness timestamp
316
+ writeFileSync(REQ_PATH, new Date().toISOString(), "utf-8");
317
+
318
+ console.log("generate-models: done");
319
+ }
320
+
321
+ main().catch((err) => {
322
+ console.error("generate-models: fatal error", err);
323
+ process.exit(1);
324
+ });