llmist 0.1.1
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 +394 -0
- package/dist/chunk-DCW33WV7.js +901 -0
- package/dist/chunk-DCW33WV7.js.map +1 -0
- package/dist/chunk-JEBGLCDW.js +22 -0
- package/dist/chunk-JEBGLCDW.js.map +1 -0
- package/dist/chunk-TP7HE3MN.js +4450 -0
- package/dist/chunk-TP7HE3MN.js.map +1 -0
- package/dist/cli.cjs +5333 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +987 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +5511 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1101 -0
- package/dist/index.d.ts +1101 -0
- package/dist/index.js +421 -0
- package/dist/index.js.map +1 -0
- package/dist/mock-stream-D4erlo7B.d.cts +2602 -0
- package/dist/mock-stream-D4erlo7B.d.ts +2602 -0
- package/dist/testing/index.cjs +5260 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +274 -0
- package/dist/testing/index.d.ts +274 -0
- package/dist/testing/index.js +34 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +102 -0
|
@@ -0,0 +1,4450 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __esm = (fn, res) => function __init() {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
};
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/core/model-shortcuts.ts
|
|
23
|
+
function isKnownModelPattern(model) {
|
|
24
|
+
const normalized = model.toLowerCase();
|
|
25
|
+
if (MODEL_ALIASES[normalized]) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return KNOWN_MODEL_PATTERNS.some((pattern) => pattern.test(model));
|
|
29
|
+
}
|
|
30
|
+
function resolveModel(model, options = {}) {
|
|
31
|
+
if (model.includes(":")) {
|
|
32
|
+
return model;
|
|
33
|
+
}
|
|
34
|
+
const normalized = model.toLowerCase();
|
|
35
|
+
if (MODEL_ALIASES[normalized]) {
|
|
36
|
+
return MODEL_ALIASES[normalized];
|
|
37
|
+
}
|
|
38
|
+
const modelLower = model.toLowerCase();
|
|
39
|
+
if (modelLower.startsWith("gpt")) {
|
|
40
|
+
return `openai:${model}`;
|
|
41
|
+
}
|
|
42
|
+
if (modelLower.startsWith("claude")) {
|
|
43
|
+
return `anthropic:${model}`;
|
|
44
|
+
}
|
|
45
|
+
if (modelLower.startsWith("gemini")) {
|
|
46
|
+
return `gemini:${model}`;
|
|
47
|
+
}
|
|
48
|
+
if (modelLower.match(/^o\d/)) {
|
|
49
|
+
return `openai:${model}`;
|
|
50
|
+
}
|
|
51
|
+
if (!isKnownModelPattern(model)) {
|
|
52
|
+
if (options.strict) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Unknown model '${model}'. Did you mean one of: gpt4, sonnet, haiku, flash? Use explicit provider prefix like 'openai:${model}' to bypass this check.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (!options.silent) {
|
|
58
|
+
console.warn(
|
|
59
|
+
`\u26A0\uFE0F Unknown model '${model}', falling back to 'openai:${model}'. This might be a typo. Did you mean: gpt4, gpt5, gpt5-nano, sonnet, haiku, flash? Use { strict: true } to error on unknown models, or { silent: true } to suppress this warning.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return `openai:${model}`;
|
|
64
|
+
}
|
|
65
|
+
function hasProviderPrefix(model) {
|
|
66
|
+
return model.includes(":");
|
|
67
|
+
}
|
|
68
|
+
function getProvider(model) {
|
|
69
|
+
const separatorIndex = model.indexOf(":");
|
|
70
|
+
if (separatorIndex === -1) {
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
return model.slice(0, separatorIndex);
|
|
74
|
+
}
|
|
75
|
+
function getModelId(model) {
|
|
76
|
+
const separatorIndex = model.indexOf(":");
|
|
77
|
+
if (separatorIndex === -1) {
|
|
78
|
+
return model;
|
|
79
|
+
}
|
|
80
|
+
return model.slice(separatorIndex + 1);
|
|
81
|
+
}
|
|
82
|
+
var MODEL_ALIASES, KNOWN_MODEL_PATTERNS;
|
|
83
|
+
var init_model_shortcuts = __esm({
|
|
84
|
+
"src/core/model-shortcuts.ts"() {
|
|
85
|
+
"use strict";
|
|
86
|
+
MODEL_ALIASES = {
|
|
87
|
+
// OpenAI aliases
|
|
88
|
+
gpt4: "openai:gpt-4o",
|
|
89
|
+
gpt4o: "openai:gpt-4o",
|
|
90
|
+
gpt5: "openai:gpt-5",
|
|
91
|
+
"gpt5-mini": "openai:gpt-5-mini",
|
|
92
|
+
"gpt5-nano": "openai:gpt-5-nano",
|
|
93
|
+
// Anthropic aliases
|
|
94
|
+
sonnet: "anthropic:claude-3-5-sonnet-latest",
|
|
95
|
+
"claude-sonnet": "anthropic:claude-3-5-sonnet-latest",
|
|
96
|
+
haiku: "anthropic:claude-3-5-haiku-latest",
|
|
97
|
+
"claude-haiku": "anthropic:claude-3-5-haiku-latest",
|
|
98
|
+
opus: "anthropic:claude-3-opus-latest",
|
|
99
|
+
"claude-opus": "anthropic:claude-3-opus-latest",
|
|
100
|
+
// Gemini aliases
|
|
101
|
+
flash: "gemini:gemini-2.0-flash",
|
|
102
|
+
"gemini-flash": "gemini:gemini-2.0-flash",
|
|
103
|
+
"gemini-pro": "gemini:gemini-2.0-pro",
|
|
104
|
+
pro: "gemini:gemini-2.0-pro"
|
|
105
|
+
};
|
|
106
|
+
KNOWN_MODEL_PATTERNS = [
|
|
107
|
+
/^gpt-?\d/i,
|
|
108
|
+
// gpt-4, gpt-3.5, gpt4, etc.
|
|
109
|
+
/^claude-?\d/i,
|
|
110
|
+
// claude-3, claude-2, etc.
|
|
111
|
+
/^gemini-?(\d|pro|flash)/i,
|
|
112
|
+
// gemini-2.0, gemini-pro, gemini-flash, etc.
|
|
113
|
+
/^o\d/i
|
|
114
|
+
// OpenAI o1, o3, etc.
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// src/gadgets/schema-validator.ts
|
|
120
|
+
import * as z from "zod";
|
|
121
|
+
function validateGadgetSchema(schema, gadgetName) {
|
|
122
|
+
let jsonSchema;
|
|
123
|
+
try {
|
|
124
|
+
jsonSchema = z.toJSONSchema(schema, { target: "draft-7" });
|
|
125
|
+
} catch (error) {
|
|
126
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Gadget "${gadgetName}" has a schema that cannot be serialized to JSON Schema.
|
|
129
|
+
This usually happens with unsupported patterns like:
|
|
130
|
+
- z.record() - use z.object({}).passthrough() instead
|
|
131
|
+
- Complex transforms or custom refinements
|
|
132
|
+
- Circular references
|
|
133
|
+
|
|
134
|
+
Original error: ${errorMessage}
|
|
135
|
+
|
|
136
|
+
Only use schema patterns that Zod v4's native toJSONSchema() supports.`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const issues = findUnknownTypes(jsonSchema);
|
|
140
|
+
if (issues.length > 0) {
|
|
141
|
+
const fieldList = issues.join(", ");
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Gadget "${gadgetName}" uses z.unknown() which produces incomplete schemas.
|
|
144
|
+
Problematic fields: ${fieldList}
|
|
145
|
+
|
|
146
|
+
z.unknown() doesn't generate type information in JSON Schema, making it unclear
|
|
147
|
+
to the LLM what data structure to provide.
|
|
148
|
+
|
|
149
|
+
Suggestions:
|
|
150
|
+
- Use z.object({}).passthrough() for flexible objects
|
|
151
|
+
- Use z.record(z.string()) for key-value objects with string values
|
|
152
|
+
- Define specific structure if possible
|
|
153
|
+
|
|
154
|
+
Example fixes:
|
|
155
|
+
// \u274C Bad
|
|
156
|
+
content: z.unknown()
|
|
157
|
+
|
|
158
|
+
// \u2705 Good
|
|
159
|
+
content: z.object({}).passthrough() // for flexible objects
|
|
160
|
+
content: z.record(z.string()) // for key-value objects
|
|
161
|
+
content: z.array(z.string()) // for arrays of strings
|
|
162
|
+
`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function findUnknownTypes(schema, path = []) {
|
|
167
|
+
const issues = [];
|
|
168
|
+
if (!schema || typeof schema !== "object") {
|
|
169
|
+
return issues;
|
|
170
|
+
}
|
|
171
|
+
if (schema.definitions) {
|
|
172
|
+
for (const defSchema of Object.values(schema.definitions)) {
|
|
173
|
+
issues.push(...findUnknownTypes(defSchema, []));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (schema.properties) {
|
|
177
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
178
|
+
const propPath = [...path, propName];
|
|
179
|
+
if (hasNoType(propSchema)) {
|
|
180
|
+
issues.push(propPath.join(".") || propName);
|
|
181
|
+
}
|
|
182
|
+
issues.push(...findUnknownTypes(propSchema, propPath));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (schema.items) {
|
|
186
|
+
const itemPath = [...path, "[]"];
|
|
187
|
+
if (hasNoType(schema.items)) {
|
|
188
|
+
issues.push(itemPath.join("."));
|
|
189
|
+
}
|
|
190
|
+
issues.push(...findUnknownTypes(schema.items, itemPath));
|
|
191
|
+
}
|
|
192
|
+
if (schema.anyOf) {
|
|
193
|
+
schema.anyOf.forEach((subSchema, index) => {
|
|
194
|
+
issues.push(...findUnknownTypes(subSchema, [...path, `anyOf[${index}]`]));
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (schema.oneOf) {
|
|
198
|
+
schema.oneOf.forEach((subSchema, index) => {
|
|
199
|
+
issues.push(...findUnknownTypes(subSchema, [...path, `oneOf[${index}]`]));
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (schema.allOf) {
|
|
203
|
+
schema.allOf.forEach((subSchema, index) => {
|
|
204
|
+
issues.push(...findUnknownTypes(subSchema, [...path, `allOf[${index}]`]));
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return issues;
|
|
208
|
+
}
|
|
209
|
+
function hasNoType(prop) {
|
|
210
|
+
if (!prop || typeof prop !== "object") {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
const hasType = prop.type !== void 0;
|
|
214
|
+
const hasRef = prop.$ref !== void 0;
|
|
215
|
+
const hasUnion = prop.anyOf !== void 0 || prop.oneOf !== void 0 || prop.allOf !== void 0;
|
|
216
|
+
if (hasType || hasRef || hasUnion) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
const keys = Object.keys(prop);
|
|
220
|
+
const metadataKeys = ["description", "title", "default", "examples"];
|
|
221
|
+
const hasOnlyMetadata = keys.every((key) => metadataKeys.includes(key));
|
|
222
|
+
return hasOnlyMetadata || keys.length === 0;
|
|
223
|
+
}
|
|
224
|
+
var init_schema_validator = __esm({
|
|
225
|
+
"src/gadgets/schema-validator.ts"() {
|
|
226
|
+
"use strict";
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// src/gadgets/registry.ts
|
|
231
|
+
var GadgetRegistry;
|
|
232
|
+
var init_registry = __esm({
|
|
233
|
+
"src/gadgets/registry.ts"() {
|
|
234
|
+
"use strict";
|
|
235
|
+
init_schema_validator();
|
|
236
|
+
GadgetRegistry = class _GadgetRegistry {
|
|
237
|
+
gadgets = /* @__PURE__ */ new Map();
|
|
238
|
+
/**
|
|
239
|
+
* Creates a registry from an array of gadget classes or instances,
|
|
240
|
+
* or an object mapping names to gadgets.
|
|
241
|
+
*
|
|
242
|
+
* @param gadgets - Array of gadgets/classes or object with custom names
|
|
243
|
+
* @returns New GadgetRegistry with all gadgets registered
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* // From array of classes
|
|
248
|
+
* const registry = GadgetRegistry.from([Calculator, Weather]);
|
|
249
|
+
*
|
|
250
|
+
* // From array of instances
|
|
251
|
+
* const registry = GadgetRegistry.from([new Calculator(), new Weather()]);
|
|
252
|
+
*
|
|
253
|
+
* // From object with custom names
|
|
254
|
+
* const registry = GadgetRegistry.from({
|
|
255
|
+
* calc: Calculator,
|
|
256
|
+
* weather: new Weather({ apiKey: "..." })
|
|
257
|
+
* });
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
static from(gadgets) {
|
|
261
|
+
const registry = new _GadgetRegistry();
|
|
262
|
+
if (Array.isArray(gadgets)) {
|
|
263
|
+
registry.registerMany(gadgets);
|
|
264
|
+
} else {
|
|
265
|
+
for (const [name, gadget] of Object.entries(gadgets)) {
|
|
266
|
+
const instance = typeof gadget === "function" ? new gadget() : gadget;
|
|
267
|
+
registry.register(name, instance);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return registry;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Registers multiple gadgets at once from an array.
|
|
274
|
+
*
|
|
275
|
+
* @param gadgets - Array of gadget instances or classes
|
|
276
|
+
* @returns This registry for chaining
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```typescript
|
|
280
|
+
* registry.registerMany([Calculator, Weather, Email]);
|
|
281
|
+
* registry.registerMany([new Calculator(), new Weather()]);
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
registerMany(gadgets) {
|
|
285
|
+
for (const gadget of gadgets) {
|
|
286
|
+
const instance = typeof gadget === "function" ? new gadget() : gadget;
|
|
287
|
+
this.registerByClass(instance);
|
|
288
|
+
}
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
// Register a gadget by name
|
|
292
|
+
register(name, gadget) {
|
|
293
|
+
const normalizedName = name.toLowerCase();
|
|
294
|
+
if (this.gadgets.has(normalizedName)) {
|
|
295
|
+
throw new Error(`Gadget '${name}' is already registered`);
|
|
296
|
+
}
|
|
297
|
+
if (gadget.parameterSchema) {
|
|
298
|
+
validateGadgetSchema(gadget.parameterSchema, name);
|
|
299
|
+
}
|
|
300
|
+
this.gadgets.set(normalizedName, gadget);
|
|
301
|
+
}
|
|
302
|
+
// Register a gadget using its name property or class name
|
|
303
|
+
registerByClass(gadget) {
|
|
304
|
+
const name = gadget.name ?? gadget.constructor.name;
|
|
305
|
+
this.register(name, gadget);
|
|
306
|
+
}
|
|
307
|
+
// Get gadget by name (case-insensitive)
|
|
308
|
+
get(name) {
|
|
309
|
+
return this.gadgets.get(name.toLowerCase());
|
|
310
|
+
}
|
|
311
|
+
// Check if gadget exists (case-insensitive)
|
|
312
|
+
has(name) {
|
|
313
|
+
return this.gadgets.has(name.toLowerCase());
|
|
314
|
+
}
|
|
315
|
+
// Get all registered gadget names
|
|
316
|
+
getNames() {
|
|
317
|
+
return Array.from(this.gadgets.keys());
|
|
318
|
+
}
|
|
319
|
+
// Get all gadgets for instruction generation
|
|
320
|
+
getAll() {
|
|
321
|
+
return Array.from(this.gadgets.values());
|
|
322
|
+
}
|
|
323
|
+
// Unregister gadget (useful for testing, case-insensitive)
|
|
324
|
+
unregister(name) {
|
|
325
|
+
return this.gadgets.delete(name.toLowerCase());
|
|
326
|
+
}
|
|
327
|
+
// Clear all gadgets (useful for testing)
|
|
328
|
+
clear() {
|
|
329
|
+
this.gadgets.clear();
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// src/core/prompt-config.ts
|
|
336
|
+
function resolvePromptTemplate(template, defaultValue, context) {
|
|
337
|
+
const resolved = template ?? defaultValue;
|
|
338
|
+
return typeof resolved === "function" ? resolved(context) : resolved;
|
|
339
|
+
}
|
|
340
|
+
function resolveRulesTemplate(rules, context) {
|
|
341
|
+
const resolved = rules ?? DEFAULT_PROMPTS.rules;
|
|
342
|
+
if (Array.isArray(resolved)) {
|
|
343
|
+
return resolved;
|
|
344
|
+
}
|
|
345
|
+
if (typeof resolved === "function") {
|
|
346
|
+
const result = resolved(context);
|
|
347
|
+
return Array.isArray(result) ? result : [result];
|
|
348
|
+
}
|
|
349
|
+
return [resolved];
|
|
350
|
+
}
|
|
351
|
+
var DEFAULT_PROMPTS;
|
|
352
|
+
var init_prompt_config = __esm({
|
|
353
|
+
"src/core/prompt-config.ts"() {
|
|
354
|
+
"use strict";
|
|
355
|
+
DEFAULT_PROMPTS = {
|
|
356
|
+
mainInstruction: [
|
|
357
|
+
"\u26A0\uFE0F CRITICAL: RESPOND ONLY WITH GADGET INVOCATIONS",
|
|
358
|
+
"DO NOT use function calling or tool calling",
|
|
359
|
+
"You must output the exact text markers shown below in plain text.",
|
|
360
|
+
"EACH MARKER MUST START WITH A NEWLINE."
|
|
361
|
+
].join("\n"),
|
|
362
|
+
criticalUsage: "INVOKE gadgets using the markers - do not describe what you want to do.",
|
|
363
|
+
formatDescriptionYaml: "Parameters in YAML format (one per line)",
|
|
364
|
+
formatDescriptionJson: "Parameters in JSON format (valid JSON object)",
|
|
365
|
+
rules: () => [
|
|
366
|
+
"Output ONLY plain text with the exact markers - never use function/tool calling",
|
|
367
|
+
"You can invoke multiple gadgets in a single response",
|
|
368
|
+
"For dependent gadgets, invoke the first one and wait for the result"
|
|
369
|
+
],
|
|
370
|
+
schemaLabelJson: "\n\nInput Schema (JSON):",
|
|
371
|
+
schemaLabelYaml: "\n\nInput Schema (YAML):",
|
|
372
|
+
customExamples: null
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// src/core/constants.ts
|
|
378
|
+
var GADGET_START_PREFIX, GADGET_END_PREFIX;
|
|
379
|
+
var init_constants = __esm({
|
|
380
|
+
"src/core/constants.ts"() {
|
|
381
|
+
"use strict";
|
|
382
|
+
GADGET_START_PREFIX = "!!!GADGET_START:";
|
|
383
|
+
GADGET_END_PREFIX = "!!!GADGET_END";
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// src/core/messages.ts
|
|
388
|
+
var LLMMessageBuilder;
|
|
389
|
+
var init_messages = __esm({
|
|
390
|
+
"src/core/messages.ts"() {
|
|
391
|
+
"use strict";
|
|
392
|
+
init_constants();
|
|
393
|
+
init_prompt_config();
|
|
394
|
+
LLMMessageBuilder = class {
|
|
395
|
+
messages = [];
|
|
396
|
+
startPrefix = GADGET_START_PREFIX;
|
|
397
|
+
endPrefix = GADGET_END_PREFIX;
|
|
398
|
+
promptConfig;
|
|
399
|
+
constructor(promptConfig) {
|
|
400
|
+
this.promptConfig = promptConfig ?? {};
|
|
401
|
+
}
|
|
402
|
+
addSystem(content, metadata) {
|
|
403
|
+
this.messages.push({ role: "system", content, metadata });
|
|
404
|
+
return this;
|
|
405
|
+
}
|
|
406
|
+
addGadgets(gadgets, parameterFormat = "json", options) {
|
|
407
|
+
if (options?.startPrefix) {
|
|
408
|
+
this.startPrefix = options.startPrefix;
|
|
409
|
+
}
|
|
410
|
+
if (options?.endPrefix) {
|
|
411
|
+
this.endPrefix = options.endPrefix;
|
|
412
|
+
}
|
|
413
|
+
const context = {
|
|
414
|
+
parameterFormat,
|
|
415
|
+
startPrefix: this.startPrefix,
|
|
416
|
+
endPrefix: this.endPrefix,
|
|
417
|
+
gadgetCount: gadgets.length,
|
|
418
|
+
gadgetNames: gadgets.map((g) => g.name ?? g.constructor.name)
|
|
419
|
+
};
|
|
420
|
+
const parts = [];
|
|
421
|
+
const mainInstruction = resolvePromptTemplate(
|
|
422
|
+
this.promptConfig.mainInstruction,
|
|
423
|
+
DEFAULT_PROMPTS.mainInstruction,
|
|
424
|
+
context
|
|
425
|
+
);
|
|
426
|
+
parts.push(mainInstruction);
|
|
427
|
+
parts.push(this.buildGadgetsXmlSection(gadgets, parameterFormat));
|
|
428
|
+
parts.push(this.buildUsageSection(parameterFormat, context));
|
|
429
|
+
this.messages.push({ role: "system", content: parts.join("") });
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
buildGadgetsXmlSection(gadgets, parameterFormat) {
|
|
433
|
+
const parts = [];
|
|
434
|
+
parts.push("<GADGETS>");
|
|
435
|
+
for (const gadget of gadgets) {
|
|
436
|
+
const gadgetName = gadget.name ?? gadget.constructor.name;
|
|
437
|
+
const instruction = gadget.getInstruction(parameterFormat);
|
|
438
|
+
const schemaMarker = parameterFormat === "yaml" ? "\n\nInput Schema (YAML):" : "\n\nInput Schema (JSON):";
|
|
439
|
+
const schemaIndex = instruction.indexOf(schemaMarker);
|
|
440
|
+
const description = (schemaIndex !== -1 ? instruction.substring(0, schemaIndex) : instruction).trim();
|
|
441
|
+
const schema = schemaIndex !== -1 ? instruction.substring(schemaIndex + schemaMarker.length).trim() : "";
|
|
442
|
+
parts.push("\n <gadget>");
|
|
443
|
+
parts.push(`
|
|
444
|
+
<name>${gadgetName}</name>`);
|
|
445
|
+
parts.push(`
|
|
446
|
+
<description>${description}</description>`);
|
|
447
|
+
if (schema) {
|
|
448
|
+
parts.push(`
|
|
449
|
+
<schema format="${parameterFormat}">
|
|
450
|
+
${schema}
|
|
451
|
+
</schema>`);
|
|
452
|
+
}
|
|
453
|
+
parts.push("\n </gadget>");
|
|
454
|
+
}
|
|
455
|
+
return parts.join("");
|
|
456
|
+
}
|
|
457
|
+
buildUsageSection(parameterFormat, context) {
|
|
458
|
+
const parts = [];
|
|
459
|
+
const formatDescription = parameterFormat === "yaml" ? resolvePromptTemplate(
|
|
460
|
+
this.promptConfig.formatDescriptionYaml,
|
|
461
|
+
DEFAULT_PROMPTS.formatDescriptionYaml,
|
|
462
|
+
context
|
|
463
|
+
) : resolvePromptTemplate(
|
|
464
|
+
this.promptConfig.formatDescriptionJson,
|
|
465
|
+
DEFAULT_PROMPTS.formatDescriptionJson,
|
|
466
|
+
context
|
|
467
|
+
);
|
|
468
|
+
parts.push("<usage>");
|
|
469
|
+
const criticalUsage = resolvePromptTemplate(
|
|
470
|
+
this.promptConfig.criticalUsage,
|
|
471
|
+
DEFAULT_PROMPTS.criticalUsage,
|
|
472
|
+
context
|
|
473
|
+
);
|
|
474
|
+
parts.push(`
|
|
475
|
+
<critical>${criticalUsage}</critical>`);
|
|
476
|
+
parts.push("\n <format>");
|
|
477
|
+
parts.push(`
|
|
478
|
+
<step>Start marker: ${this.startPrefix}gadget_name</step>`);
|
|
479
|
+
parts.push(`
|
|
480
|
+
<step>${formatDescription}</step>`);
|
|
481
|
+
parts.push(`
|
|
482
|
+
<step>End marker: ${this.endPrefix}</step>`);
|
|
483
|
+
parts.push("\n </format>");
|
|
484
|
+
parts.push(this.buildExamplesSection(parameterFormat, context));
|
|
485
|
+
parts.push(this.buildRulesSection(context));
|
|
486
|
+
parts.push("\n</usage>");
|
|
487
|
+
parts.push("\n</GADGETS>\n\n");
|
|
488
|
+
return parts.join("");
|
|
489
|
+
}
|
|
490
|
+
buildExamplesSection(parameterFormat, context) {
|
|
491
|
+
if (this.promptConfig.customExamples) {
|
|
492
|
+
return this.promptConfig.customExamples(context);
|
|
493
|
+
}
|
|
494
|
+
const parts = [];
|
|
495
|
+
parts.push("\n <examples>");
|
|
496
|
+
const singleExample = parameterFormat === "yaml" ? `${this.startPrefix}translate
|
|
497
|
+
from: English
|
|
498
|
+
to: Polish
|
|
499
|
+
content: Paris is the capital of France.
|
|
500
|
+
${this.endPrefix}` : `${this.startPrefix}translate
|
|
501
|
+
{"from": "English", "to": "Polish", "content": "Paris is the capital of France."}
|
|
502
|
+
${this.endPrefix}`;
|
|
503
|
+
parts.push(`
|
|
504
|
+
<example title="Single Gadget">
|
|
505
|
+
${singleExample}
|
|
506
|
+
</example>`);
|
|
507
|
+
const multipleExample = parameterFormat === "yaml" ? `${this.startPrefix}translate
|
|
508
|
+
from: English
|
|
509
|
+
to: Polish
|
|
510
|
+
content: Paris is the capital of France.
|
|
511
|
+
${this.endPrefix}
|
|
512
|
+
${this.startPrefix}analyze
|
|
513
|
+
type: economic_analysis
|
|
514
|
+
matter: "Polish Economy"
|
|
515
|
+
question: Polish arms exports 2025.
|
|
516
|
+
${this.endPrefix}` : `${this.startPrefix}translate
|
|
517
|
+
{"from": "English", "to": "Polish", "content": "Paris is the capital of France."}
|
|
518
|
+
${this.endPrefix}
|
|
519
|
+
${this.startPrefix}analyze
|
|
520
|
+
{"type": "economic_analysis", "matter": "Polish Economy", "question": "Polish arms exports 2025."}
|
|
521
|
+
${this.endPrefix}`;
|
|
522
|
+
parts.push(`
|
|
523
|
+
<example title="Multiple Gadgets">
|
|
524
|
+
${multipleExample}
|
|
525
|
+
</example>`);
|
|
526
|
+
parts.push("\n </examples>");
|
|
527
|
+
return parts.join("");
|
|
528
|
+
}
|
|
529
|
+
buildRulesSection(context) {
|
|
530
|
+
const parts = [];
|
|
531
|
+
parts.push("\n <rules>");
|
|
532
|
+
const rules = resolveRulesTemplate(this.promptConfig.rules, context);
|
|
533
|
+
for (const rule of rules) {
|
|
534
|
+
parts.push(`
|
|
535
|
+
<rule>${rule}</rule>`);
|
|
536
|
+
}
|
|
537
|
+
parts.push("\n </rules>");
|
|
538
|
+
return parts.join("");
|
|
539
|
+
}
|
|
540
|
+
addUser(content, metadata) {
|
|
541
|
+
this.messages.push({ role: "user", content, metadata });
|
|
542
|
+
return this;
|
|
543
|
+
}
|
|
544
|
+
addAssistant(content, metadata) {
|
|
545
|
+
this.messages.push({ role: "assistant", content, metadata });
|
|
546
|
+
return this;
|
|
547
|
+
}
|
|
548
|
+
addGadgetCall(gadget, parameters, result, parameterFormat = "json") {
|
|
549
|
+
const paramStr = this.formatParameters(parameters, parameterFormat);
|
|
550
|
+
this.messages.push({
|
|
551
|
+
role: "assistant",
|
|
552
|
+
content: `${this.startPrefix}${gadget}
|
|
553
|
+
${paramStr}
|
|
554
|
+
${this.endPrefix}`
|
|
555
|
+
});
|
|
556
|
+
this.messages.push({
|
|
557
|
+
role: "user",
|
|
558
|
+
content: `Result: ${result}`
|
|
559
|
+
});
|
|
560
|
+
return this;
|
|
561
|
+
}
|
|
562
|
+
formatParameters(parameters, format) {
|
|
563
|
+
if (format === "yaml") {
|
|
564
|
+
return Object.entries(parameters).map(([key, value]) => {
|
|
565
|
+
if (typeof value === "string") {
|
|
566
|
+
return `${key}: ${value}`;
|
|
567
|
+
}
|
|
568
|
+
return `${key}: ${JSON.stringify(value)}`;
|
|
569
|
+
}).join("\n");
|
|
570
|
+
}
|
|
571
|
+
return JSON.stringify(parameters);
|
|
572
|
+
}
|
|
573
|
+
build() {
|
|
574
|
+
return [...this.messages];
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
// src/logging/logger.ts
|
|
581
|
+
import { createWriteStream, mkdirSync } from "node:fs";
|
|
582
|
+
import { dirname } from "node:path";
|
|
583
|
+
import { Logger } from "tslog";
|
|
584
|
+
function parseLogLevel(value) {
|
|
585
|
+
if (!value) {
|
|
586
|
+
return void 0;
|
|
587
|
+
}
|
|
588
|
+
const normalized = value.trim().toLowerCase();
|
|
589
|
+
if (normalized === "") {
|
|
590
|
+
return void 0;
|
|
591
|
+
}
|
|
592
|
+
const numericLevel = Number(normalized);
|
|
593
|
+
if (Number.isFinite(numericLevel)) {
|
|
594
|
+
return Math.max(0, Math.min(6, Math.floor(numericLevel)));
|
|
595
|
+
}
|
|
596
|
+
return LEVEL_NAME_TO_ID[normalized];
|
|
597
|
+
}
|
|
598
|
+
function createLogger(options = {}) {
|
|
599
|
+
const envMinLevel = parseLogLevel(process.env.LLMIST_LOG_LEVEL);
|
|
600
|
+
const envLogFile = process.env.LLMIST_LOG_FILE?.trim() ?? "";
|
|
601
|
+
const minLevel = options.minLevel ?? envMinLevel ?? 4;
|
|
602
|
+
const defaultType = options.type ?? "pretty";
|
|
603
|
+
const name = options.name ?? "llmist";
|
|
604
|
+
let logFileStream;
|
|
605
|
+
let finalType = defaultType;
|
|
606
|
+
if (envLogFile) {
|
|
607
|
+
try {
|
|
608
|
+
mkdirSync(dirname(envLogFile), { recursive: true });
|
|
609
|
+
logFileStream = createWriteStream(envLogFile, { flags: "a" });
|
|
610
|
+
finalType = "hidden";
|
|
611
|
+
} catch (error) {
|
|
612
|
+
console.error("Failed to initialize LLMIST_LOG_FILE output:", error);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const logger = new Logger({
|
|
616
|
+
name,
|
|
617
|
+
minLevel,
|
|
618
|
+
type: finalType,
|
|
619
|
+
// Optimize for production
|
|
620
|
+
hideLogPositionForProduction: finalType !== "pretty",
|
|
621
|
+
// Pretty output settings
|
|
622
|
+
prettyLogTemplate: finalType === "pretty" ? "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}} {{logLevelName}} [{{name}}] " : void 0
|
|
623
|
+
});
|
|
624
|
+
if (logFileStream) {
|
|
625
|
+
logger.attachTransport((logObj) => {
|
|
626
|
+
logFileStream?.write(`${JSON.stringify(logObj)}
|
|
627
|
+
`);
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
return logger;
|
|
631
|
+
}
|
|
632
|
+
var LEVEL_NAME_TO_ID, defaultLogger;
|
|
633
|
+
var init_logger = __esm({
|
|
634
|
+
"src/logging/logger.ts"() {
|
|
635
|
+
"use strict";
|
|
636
|
+
LEVEL_NAME_TO_ID = {
|
|
637
|
+
silly: 0,
|
|
638
|
+
trace: 1,
|
|
639
|
+
debug: 2,
|
|
640
|
+
info: 3,
|
|
641
|
+
warn: 4,
|
|
642
|
+
error: 5,
|
|
643
|
+
fatal: 6
|
|
644
|
+
};
|
|
645
|
+
defaultLogger = createLogger();
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// src/agent/conversation-manager.ts
|
|
650
|
+
var ConversationManager;
|
|
651
|
+
var init_conversation_manager = __esm({
|
|
652
|
+
"src/agent/conversation-manager.ts"() {
|
|
653
|
+
"use strict";
|
|
654
|
+
init_messages();
|
|
655
|
+
ConversationManager = class {
|
|
656
|
+
baseMessages;
|
|
657
|
+
initialMessages;
|
|
658
|
+
historyBuilder;
|
|
659
|
+
parameterFormat;
|
|
660
|
+
constructor(baseMessages, initialMessages, parameterFormat = "json") {
|
|
661
|
+
this.baseMessages = baseMessages;
|
|
662
|
+
this.initialMessages = initialMessages;
|
|
663
|
+
this.parameterFormat = parameterFormat;
|
|
664
|
+
this.historyBuilder = new LLMMessageBuilder();
|
|
665
|
+
}
|
|
666
|
+
addUserMessage(content) {
|
|
667
|
+
this.historyBuilder.addUser(content);
|
|
668
|
+
}
|
|
669
|
+
addAssistantMessage(content) {
|
|
670
|
+
this.historyBuilder.addAssistant(content);
|
|
671
|
+
}
|
|
672
|
+
addGadgetCall(gadgetName, parameters, result) {
|
|
673
|
+
this.historyBuilder.addGadgetCall(gadgetName, parameters, result, this.parameterFormat);
|
|
674
|
+
}
|
|
675
|
+
getMessages() {
|
|
676
|
+
return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// src/agent/event-handlers.ts
|
|
683
|
+
async function runWithHandlers(agentGenerator, handlers) {
|
|
684
|
+
for await (const event of agentGenerator) {
|
|
685
|
+
switch (event.type) {
|
|
686
|
+
case "text":
|
|
687
|
+
if (handlers.onText) {
|
|
688
|
+
await handlers.onText(event.content);
|
|
689
|
+
}
|
|
690
|
+
break;
|
|
691
|
+
case "gadget_call":
|
|
692
|
+
if (handlers.onGadgetCall) {
|
|
693
|
+
await handlers.onGadgetCall({
|
|
694
|
+
gadgetName: event.call.gadgetName,
|
|
695
|
+
parameters: event.call.parameters,
|
|
696
|
+
parametersYaml: event.call.parametersYaml
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
break;
|
|
700
|
+
case "gadget_result":
|
|
701
|
+
if (handlers.onGadgetResult) {
|
|
702
|
+
await handlers.onGadgetResult(event.result);
|
|
703
|
+
}
|
|
704
|
+
break;
|
|
705
|
+
case "human_input_required":
|
|
706
|
+
if (handlers.onHumanInputRequired) {
|
|
707
|
+
await handlers.onHumanInputRequired({
|
|
708
|
+
question: event.question,
|
|
709
|
+
gadgetName: event.gadgetName
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
break;
|
|
713
|
+
default:
|
|
714
|
+
if (handlers.onOther) {
|
|
715
|
+
await handlers.onOther(event);
|
|
716
|
+
}
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
async function collectEvents(agentGenerator, collect) {
|
|
722
|
+
const result = {
|
|
723
|
+
text: [],
|
|
724
|
+
gadgetCalls: [],
|
|
725
|
+
gadgetResults: []
|
|
726
|
+
};
|
|
727
|
+
for await (const event of agentGenerator) {
|
|
728
|
+
switch (event.type) {
|
|
729
|
+
case "text":
|
|
730
|
+
if (collect.text) {
|
|
731
|
+
result.text.push(event.content);
|
|
732
|
+
}
|
|
733
|
+
break;
|
|
734
|
+
case "gadget_call":
|
|
735
|
+
if (collect.gadgetCalls && event.call.parameters) {
|
|
736
|
+
result.gadgetCalls.push({
|
|
737
|
+
gadgetName: event.call.gadgetName,
|
|
738
|
+
parameters: event.call.parameters
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
break;
|
|
742
|
+
case "gadget_result":
|
|
743
|
+
if (collect.gadgetResults) {
|
|
744
|
+
result.gadgetResults.push(event.result);
|
|
745
|
+
}
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return result;
|
|
750
|
+
}
|
|
751
|
+
async function collectText(agentGenerator) {
|
|
752
|
+
const chunks = [];
|
|
753
|
+
for await (const event of agentGenerator) {
|
|
754
|
+
if (event.type === "text") {
|
|
755
|
+
chunks.push(event.content);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return chunks.join("");
|
|
759
|
+
}
|
|
760
|
+
var init_event_handlers = __esm({
|
|
761
|
+
"src/agent/event-handlers.ts"() {
|
|
762
|
+
"use strict";
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
// src/gadgets/exceptions.ts
|
|
767
|
+
var BreakLoopException, HumanInputException, TimeoutException;
|
|
768
|
+
var init_exceptions = __esm({
|
|
769
|
+
"src/gadgets/exceptions.ts"() {
|
|
770
|
+
"use strict";
|
|
771
|
+
BreakLoopException = class extends Error {
|
|
772
|
+
constructor(message) {
|
|
773
|
+
super(message ?? "Agent loop terminated by gadget");
|
|
774
|
+
this.name = "BreakLoopException";
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
HumanInputException = class extends Error {
|
|
778
|
+
question;
|
|
779
|
+
constructor(question) {
|
|
780
|
+
super(`Human input required: ${question}`);
|
|
781
|
+
this.name = "HumanInputException";
|
|
782
|
+
this.question = question;
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
TimeoutException = class extends Error {
|
|
786
|
+
timeoutMs;
|
|
787
|
+
gadgetName;
|
|
788
|
+
constructor(gadgetName, timeoutMs) {
|
|
789
|
+
super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
|
|
790
|
+
this.name = "TimeoutException";
|
|
791
|
+
this.gadgetName = gadgetName;
|
|
792
|
+
this.timeoutMs = timeoutMs;
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
// src/gadgets/executor.ts
|
|
799
|
+
var GadgetExecutor;
|
|
800
|
+
var init_executor = __esm({
|
|
801
|
+
"src/gadgets/executor.ts"() {
|
|
802
|
+
"use strict";
|
|
803
|
+
init_logger();
|
|
804
|
+
init_exceptions();
|
|
805
|
+
GadgetExecutor = class {
|
|
806
|
+
constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs) {
|
|
807
|
+
this.registry = registry;
|
|
808
|
+
this.onHumanInputRequired = onHumanInputRequired;
|
|
809
|
+
this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
|
|
810
|
+
this.logger = logger ?? createLogger({ name: "llmist:executor" });
|
|
811
|
+
}
|
|
812
|
+
logger;
|
|
813
|
+
/**
|
|
814
|
+
* Creates a promise that rejects with a TimeoutException after the specified timeout.
|
|
815
|
+
*/
|
|
816
|
+
createTimeoutPromise(gadgetName, timeoutMs) {
|
|
817
|
+
return new Promise((_, reject) => {
|
|
818
|
+
setTimeout(() => {
|
|
819
|
+
reject(new TimeoutException(gadgetName, timeoutMs));
|
|
820
|
+
}, timeoutMs);
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
// Execute a gadget call asynchronously
|
|
824
|
+
async execute(call) {
|
|
825
|
+
const startTime = Date.now();
|
|
826
|
+
this.logger.debug("Executing gadget", {
|
|
827
|
+
gadgetName: call.gadgetName,
|
|
828
|
+
invocationId: call.invocationId,
|
|
829
|
+
parameters: call.parameters
|
|
830
|
+
});
|
|
831
|
+
const rawParameters = call.parameters ?? {};
|
|
832
|
+
let validatedParameters = rawParameters;
|
|
833
|
+
try {
|
|
834
|
+
const gadget = this.registry.get(call.gadgetName);
|
|
835
|
+
if (!gadget) {
|
|
836
|
+
this.logger.error("Gadget not found", { gadgetName: call.gadgetName });
|
|
837
|
+
return {
|
|
838
|
+
gadgetName: call.gadgetName,
|
|
839
|
+
invocationId: call.invocationId,
|
|
840
|
+
parameters: call.parameters ?? {},
|
|
841
|
+
error: `Gadget '${call.gadgetName}' not found in registry`,
|
|
842
|
+
executionTimeMs: Date.now() - startTime
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
if (call.parseError || !call.parameters) {
|
|
846
|
+
this.logger.error("Gadget parameter parse error", {
|
|
847
|
+
gadgetName: call.gadgetName,
|
|
848
|
+
parseError: call.parseError
|
|
849
|
+
});
|
|
850
|
+
return {
|
|
851
|
+
gadgetName: call.gadgetName,
|
|
852
|
+
invocationId: call.invocationId,
|
|
853
|
+
parameters: {},
|
|
854
|
+
error: call.parseError ?? "Failed to parse parameters",
|
|
855
|
+
executionTimeMs: Date.now() - startTime
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
if (gadget.parameterSchema) {
|
|
859
|
+
const validationResult = gadget.parameterSchema.safeParse(rawParameters);
|
|
860
|
+
if (!validationResult.success) {
|
|
861
|
+
const formattedIssues = validationResult.error.issues.map((issue) => {
|
|
862
|
+
const path = issue.path.join(".") || "root";
|
|
863
|
+
return `${path}: ${issue.message}`;
|
|
864
|
+
}).join("; ");
|
|
865
|
+
const validationError = `Invalid parameters: ${formattedIssues}`;
|
|
866
|
+
this.logger.error("Gadget parameter validation failed", {
|
|
867
|
+
gadgetName: call.gadgetName,
|
|
868
|
+
error: validationError
|
|
869
|
+
});
|
|
870
|
+
return {
|
|
871
|
+
gadgetName: call.gadgetName,
|
|
872
|
+
invocationId: call.invocationId,
|
|
873
|
+
parameters: rawParameters,
|
|
874
|
+
error: validationError,
|
|
875
|
+
executionTimeMs: Date.now() - startTime
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
validatedParameters = validationResult.data;
|
|
879
|
+
}
|
|
880
|
+
const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
|
|
881
|
+
let result;
|
|
882
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
883
|
+
this.logger.debug("Executing gadget with timeout", {
|
|
884
|
+
gadgetName: call.gadgetName,
|
|
885
|
+
timeoutMs
|
|
886
|
+
});
|
|
887
|
+
result = await Promise.race([
|
|
888
|
+
Promise.resolve(gadget.execute(validatedParameters)),
|
|
889
|
+
this.createTimeoutPromise(call.gadgetName, timeoutMs)
|
|
890
|
+
]);
|
|
891
|
+
} else {
|
|
892
|
+
result = await Promise.resolve(gadget.execute(validatedParameters));
|
|
893
|
+
}
|
|
894
|
+
const executionTimeMs = Date.now() - startTime;
|
|
895
|
+
this.logger.info("Gadget executed successfully", {
|
|
896
|
+
gadgetName: call.gadgetName,
|
|
897
|
+
invocationId: call.invocationId,
|
|
898
|
+
executionTimeMs
|
|
899
|
+
});
|
|
900
|
+
this.logger.debug("Gadget result", {
|
|
901
|
+
gadgetName: call.gadgetName,
|
|
902
|
+
invocationId: call.invocationId,
|
|
903
|
+
parameters: validatedParameters,
|
|
904
|
+
result,
|
|
905
|
+
executionTimeMs
|
|
906
|
+
});
|
|
907
|
+
return {
|
|
908
|
+
gadgetName: call.gadgetName,
|
|
909
|
+
invocationId: call.invocationId,
|
|
910
|
+
parameters: validatedParameters,
|
|
911
|
+
result,
|
|
912
|
+
executionTimeMs
|
|
913
|
+
};
|
|
914
|
+
} catch (error) {
|
|
915
|
+
if (error instanceof BreakLoopException) {
|
|
916
|
+
this.logger.info("Gadget requested loop termination", {
|
|
917
|
+
gadgetName: call.gadgetName,
|
|
918
|
+
message: error.message
|
|
919
|
+
});
|
|
920
|
+
return {
|
|
921
|
+
gadgetName: call.gadgetName,
|
|
922
|
+
invocationId: call.invocationId,
|
|
923
|
+
parameters: validatedParameters,
|
|
924
|
+
result: error.message,
|
|
925
|
+
breaksLoop: true,
|
|
926
|
+
executionTimeMs: Date.now() - startTime
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (error instanceof TimeoutException) {
|
|
930
|
+
this.logger.error("Gadget execution timed out", {
|
|
931
|
+
gadgetName: call.gadgetName,
|
|
932
|
+
timeoutMs: error.timeoutMs,
|
|
933
|
+
executionTimeMs: Date.now() - startTime
|
|
934
|
+
});
|
|
935
|
+
return {
|
|
936
|
+
gadgetName: call.gadgetName,
|
|
937
|
+
invocationId: call.invocationId,
|
|
938
|
+
parameters: validatedParameters,
|
|
939
|
+
error: error.message,
|
|
940
|
+
executionTimeMs: Date.now() - startTime
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
if (error instanceof HumanInputException) {
|
|
944
|
+
this.logger.info("Gadget requested human input", {
|
|
945
|
+
gadgetName: call.gadgetName,
|
|
946
|
+
question: error.question
|
|
947
|
+
});
|
|
948
|
+
if (this.onHumanInputRequired) {
|
|
949
|
+
try {
|
|
950
|
+
const answer = await this.onHumanInputRequired(error.question);
|
|
951
|
+
this.logger.debug("Human input received", {
|
|
952
|
+
gadgetName: call.gadgetName,
|
|
953
|
+
answerLength: answer.length
|
|
954
|
+
});
|
|
955
|
+
return {
|
|
956
|
+
gadgetName: call.gadgetName,
|
|
957
|
+
invocationId: call.invocationId,
|
|
958
|
+
parameters: validatedParameters,
|
|
959
|
+
result: answer,
|
|
960
|
+
executionTimeMs: Date.now() - startTime
|
|
961
|
+
};
|
|
962
|
+
} catch (inputError) {
|
|
963
|
+
this.logger.error("Human input callback error", {
|
|
964
|
+
gadgetName: call.gadgetName,
|
|
965
|
+
error: inputError instanceof Error ? inputError.message : String(inputError)
|
|
966
|
+
});
|
|
967
|
+
return {
|
|
968
|
+
gadgetName: call.gadgetName,
|
|
969
|
+
invocationId: call.invocationId,
|
|
970
|
+
parameters: validatedParameters,
|
|
971
|
+
error: inputError instanceof Error ? inputError.message : String(inputError),
|
|
972
|
+
executionTimeMs: Date.now() - startTime
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
this.logger.warn("Human input required but no callback provided", {
|
|
977
|
+
gadgetName: call.gadgetName
|
|
978
|
+
});
|
|
979
|
+
return {
|
|
980
|
+
gadgetName: call.gadgetName,
|
|
981
|
+
invocationId: call.invocationId,
|
|
982
|
+
parameters: validatedParameters,
|
|
983
|
+
error: "Human input required but not available (stdin is not interactive)",
|
|
984
|
+
executionTimeMs: Date.now() - startTime
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
const executionTimeMs = Date.now() - startTime;
|
|
988
|
+
this.logger.error("Gadget execution failed", {
|
|
989
|
+
gadgetName: call.gadgetName,
|
|
990
|
+
error: error instanceof Error ? error.message : String(error),
|
|
991
|
+
executionTimeMs
|
|
992
|
+
});
|
|
993
|
+
return {
|
|
994
|
+
gadgetName: call.gadgetName,
|
|
995
|
+
invocationId: call.invocationId,
|
|
996
|
+
parameters: validatedParameters,
|
|
997
|
+
error: error instanceof Error ? error.message : String(error),
|
|
998
|
+
executionTimeMs
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
// Execute multiple gadget calls in parallel
|
|
1003
|
+
async executeAll(calls) {
|
|
1004
|
+
return Promise.all(calls.map((call) => this.execute(call)));
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
// src/gadgets/parser.ts
|
|
1011
|
+
import * as yaml from "js-yaml";
|
|
1012
|
+
var StreamParser;
|
|
1013
|
+
var init_parser = __esm({
|
|
1014
|
+
"src/gadgets/parser.ts"() {
|
|
1015
|
+
"use strict";
|
|
1016
|
+
init_constants();
|
|
1017
|
+
StreamParser = class {
|
|
1018
|
+
buffer = "";
|
|
1019
|
+
lastReportedTextLength = 0;
|
|
1020
|
+
startPrefix;
|
|
1021
|
+
endPrefix;
|
|
1022
|
+
parameterFormat;
|
|
1023
|
+
invocationCounter = 0;
|
|
1024
|
+
constructor(options = {}) {
|
|
1025
|
+
this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
|
|
1026
|
+
this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
|
|
1027
|
+
this.parameterFormat = options.parameterFormat ?? "json";
|
|
1028
|
+
}
|
|
1029
|
+
takeTextUntil(index) {
|
|
1030
|
+
if (index <= this.lastReportedTextLength) {
|
|
1031
|
+
return void 0;
|
|
1032
|
+
}
|
|
1033
|
+
const segment = this.buffer.slice(this.lastReportedTextLength, index);
|
|
1034
|
+
this.lastReportedTextLength = index;
|
|
1035
|
+
return segment.trim().length > 0 ? segment : void 0;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Parse parameter string according to configured format
|
|
1039
|
+
*/
|
|
1040
|
+
parseParameters(raw) {
|
|
1041
|
+
if (this.parameterFormat === "json") {
|
|
1042
|
+
try {
|
|
1043
|
+
return { parameters: JSON.parse(raw) };
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
return { parseError: error instanceof Error ? error.message : "Failed to parse JSON" };
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (this.parameterFormat === "yaml") {
|
|
1049
|
+
try {
|
|
1050
|
+
return { parameters: yaml.load(raw) };
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
return { parseError: error instanceof Error ? error.message : "Failed to parse YAML" };
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
try {
|
|
1056
|
+
return { parameters: JSON.parse(raw) };
|
|
1057
|
+
} catch {
|
|
1058
|
+
try {
|
|
1059
|
+
return { parameters: yaml.load(raw) };
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
return {
|
|
1062
|
+
parseError: error instanceof Error ? error.message : "Failed to parse as JSON or YAML"
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
// Feed a chunk of text and get parsed events
|
|
1068
|
+
*feed(chunk) {
|
|
1069
|
+
this.buffer += chunk;
|
|
1070
|
+
let startIndex = 0;
|
|
1071
|
+
while (true) {
|
|
1072
|
+
const partStartIndex = this.buffer.indexOf(this.startPrefix, startIndex);
|
|
1073
|
+
if (partStartIndex === -1) break;
|
|
1074
|
+
const textBefore = this.takeTextUntil(partStartIndex);
|
|
1075
|
+
if (textBefore !== void 0) {
|
|
1076
|
+
yield { type: "text", content: textBefore };
|
|
1077
|
+
}
|
|
1078
|
+
const metadataStartIndex = partStartIndex + this.startPrefix.length;
|
|
1079
|
+
const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
|
|
1080
|
+
if (metadataEndIndex === -1) break;
|
|
1081
|
+
const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
|
|
1082
|
+
let invocationId;
|
|
1083
|
+
let actualGadgetName;
|
|
1084
|
+
if (gadgetName.includes(":")) {
|
|
1085
|
+
const parts = gadgetName.split(":");
|
|
1086
|
+
actualGadgetName = parts[0];
|
|
1087
|
+
invocationId = parts[1];
|
|
1088
|
+
} else {
|
|
1089
|
+
actualGadgetName = gadgetName;
|
|
1090
|
+
invocationId = `auto_${++this.invocationCounter}`;
|
|
1091
|
+
}
|
|
1092
|
+
const contentStartIndex = metadataEndIndex + 1;
|
|
1093
|
+
let partEndIndex;
|
|
1094
|
+
let endMarkerLength = 0;
|
|
1095
|
+
if (gadgetName.includes(":")) {
|
|
1096
|
+
const oldEndMarker = `${this.endPrefix + actualGadgetName}:${invocationId}`;
|
|
1097
|
+
partEndIndex = this.buffer.indexOf(oldEndMarker, contentStartIndex);
|
|
1098
|
+
if (partEndIndex === -1) break;
|
|
1099
|
+
endMarkerLength = oldEndMarker.length;
|
|
1100
|
+
} else {
|
|
1101
|
+
partEndIndex = contentStartIndex;
|
|
1102
|
+
while (true) {
|
|
1103
|
+
const endPos = this.buffer.indexOf(this.endPrefix, partEndIndex);
|
|
1104
|
+
if (endPos === -1) {
|
|
1105
|
+
partEndIndex = -1;
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
const afterEnd = this.buffer.substring(endPos + this.endPrefix.length);
|
|
1109
|
+
if (afterEnd.startsWith("\n") || afterEnd.startsWith("\r") || afterEnd.startsWith(this.startPrefix) || afterEnd.length === 0) {
|
|
1110
|
+
partEndIndex = endPos;
|
|
1111
|
+
endMarkerLength = this.endPrefix.length;
|
|
1112
|
+
break;
|
|
1113
|
+
} else {
|
|
1114
|
+
partEndIndex = endPos + this.endPrefix.length;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (partEndIndex === -1) break;
|
|
1118
|
+
}
|
|
1119
|
+
const parametersRaw = this.buffer.substring(contentStartIndex, partEndIndex).trim();
|
|
1120
|
+
const { parameters, parseError } = this.parseParameters(parametersRaw);
|
|
1121
|
+
yield {
|
|
1122
|
+
type: "gadget_call",
|
|
1123
|
+
call: {
|
|
1124
|
+
gadgetName: actualGadgetName,
|
|
1125
|
+
invocationId,
|
|
1126
|
+
parametersYaml: parametersRaw,
|
|
1127
|
+
// Keep property name for backward compatibility
|
|
1128
|
+
parameters,
|
|
1129
|
+
parseError
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
startIndex = partEndIndex + endMarkerLength;
|
|
1133
|
+
this.lastReportedTextLength = startIndex;
|
|
1134
|
+
}
|
|
1135
|
+
if (startIndex > 0) {
|
|
1136
|
+
this.buffer = this.buffer.substring(startIndex);
|
|
1137
|
+
this.lastReportedTextLength = 0;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
// Finalize parsing and return remaining text
|
|
1141
|
+
*finalize() {
|
|
1142
|
+
const remainingText = this.takeTextUntil(this.buffer.length);
|
|
1143
|
+
if (remainingText !== void 0) {
|
|
1144
|
+
yield { type: "text", content: remainingText };
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
// Reset parser state
|
|
1148
|
+
reset() {
|
|
1149
|
+
this.buffer = "";
|
|
1150
|
+
this.lastReportedTextLength = 0;
|
|
1151
|
+
this.invocationCounter = 0;
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
// src/agent/hook-validators.ts
|
|
1158
|
+
function validateBeforeLLMCallAction(action) {
|
|
1159
|
+
if (!action || typeof action !== "object" || !("action" in action)) {
|
|
1160
|
+
throw new HookValidationError(
|
|
1161
|
+
"beforeLLMCall",
|
|
1162
|
+
"Must return an action object with an 'action' field"
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
const actionType = action.action;
|
|
1166
|
+
if (actionType !== "proceed" && actionType !== "skip") {
|
|
1167
|
+
throw new HookValidationError(
|
|
1168
|
+
"beforeLLMCall",
|
|
1169
|
+
`Invalid action type: ${actionType}. Must be 'proceed' or 'skip'`
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
if (actionType === "skip" && !action.syntheticResponse) {
|
|
1173
|
+
throw new HookValidationError(
|
|
1174
|
+
"beforeLLMCall",
|
|
1175
|
+
"When action is 'skip', syntheticResponse is required"
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
function validateAfterLLMCallAction(action) {
|
|
1180
|
+
if (!action || typeof action !== "object" || !("action" in action)) {
|
|
1181
|
+
throw new HookValidationError(
|
|
1182
|
+
"afterLLMCall",
|
|
1183
|
+
"Must return an action object with an 'action' field"
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
const actionType = action.action;
|
|
1187
|
+
const validActions = ["continue", "append_messages", "modify_and_continue", "append_and_modify"];
|
|
1188
|
+
if (!validActions.includes(actionType)) {
|
|
1189
|
+
throw new HookValidationError(
|
|
1190
|
+
"afterLLMCall",
|
|
1191
|
+
`Invalid action type: ${actionType}. Must be one of: ${validActions.join(", ")}`
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
if (actionType === "append_messages" || actionType === "append_and_modify") {
|
|
1195
|
+
if (!("messages" in action) || !action.messages || !Array.isArray(action.messages)) {
|
|
1196
|
+
throw new HookValidationError(
|
|
1197
|
+
"afterLLMCall",
|
|
1198
|
+
`When action is '${actionType}', messages array is required`
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
if (action.messages.length === 0) {
|
|
1202
|
+
throw new HookValidationError(
|
|
1203
|
+
"afterLLMCall",
|
|
1204
|
+
`When action is '${actionType}', messages array must not be empty`
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
for (let i = 0; i < action.messages.length; i++) {
|
|
1208
|
+
const msg = action.messages[i];
|
|
1209
|
+
if (!msg || typeof msg !== "object") {
|
|
1210
|
+
throw new HookValidationError("afterLLMCall", `Message at index ${i} must be an object`);
|
|
1211
|
+
}
|
|
1212
|
+
if (!msg.role || !msg.content) {
|
|
1213
|
+
throw new HookValidationError(
|
|
1214
|
+
"afterLLMCall",
|
|
1215
|
+
`Message at index ${i} must have 'role' and 'content' fields`
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
if (!["system", "user", "assistant"].includes(msg.role)) {
|
|
1219
|
+
throw new HookValidationError(
|
|
1220
|
+
"afterLLMCall",
|
|
1221
|
+
`Message at index ${i} has invalid role: ${msg.role}`
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (actionType === "modify_and_continue" || actionType === "append_and_modify") {
|
|
1227
|
+
if (!("modifiedMessage" in action) || !action.modifiedMessage) {
|
|
1228
|
+
throw new HookValidationError(
|
|
1229
|
+
"afterLLMCall",
|
|
1230
|
+
`When action is '${actionType}', modifiedMessage is required`
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function validateAfterLLMErrorAction(action) {
|
|
1236
|
+
if (!action || typeof action !== "object" || !("action" in action)) {
|
|
1237
|
+
throw new HookValidationError(
|
|
1238
|
+
"afterLLMError",
|
|
1239
|
+
"Must return an action object with an 'action' field"
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
const actionType = action.action;
|
|
1243
|
+
if (actionType !== "rethrow" && actionType !== "recover") {
|
|
1244
|
+
throw new HookValidationError(
|
|
1245
|
+
"afterLLMError",
|
|
1246
|
+
`Invalid action type: ${actionType}. Must be 'rethrow' or 'recover'`
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
if (actionType === "recover" && !action.fallbackResponse) {
|
|
1250
|
+
throw new HookValidationError(
|
|
1251
|
+
"afterLLMError",
|
|
1252
|
+
"When action is 'recover', fallbackResponse is required"
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
function validateBeforeGadgetExecutionAction(action) {
|
|
1257
|
+
if (!action || typeof action !== "object" || !("action" in action)) {
|
|
1258
|
+
throw new HookValidationError(
|
|
1259
|
+
"beforeGadgetExecution",
|
|
1260
|
+
"Must return an action object with an 'action' field"
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
const actionType = action.action;
|
|
1264
|
+
if (actionType !== "proceed" && actionType !== "skip") {
|
|
1265
|
+
throw new HookValidationError(
|
|
1266
|
+
"beforeGadgetExecution",
|
|
1267
|
+
`Invalid action type: ${actionType}. Must be 'proceed' or 'skip'`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
if (actionType === "skip" && !action.syntheticResult) {
|
|
1271
|
+
throw new HookValidationError(
|
|
1272
|
+
"beforeGadgetExecution",
|
|
1273
|
+
"When action is 'skip', syntheticResult is required"
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function validateAfterGadgetExecutionAction(action) {
|
|
1278
|
+
if (!action || typeof action !== "object" || !("action" in action)) {
|
|
1279
|
+
throw new HookValidationError(
|
|
1280
|
+
"afterGadgetExecution",
|
|
1281
|
+
"Must return an action object with an 'action' field"
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
const actionType = action.action;
|
|
1285
|
+
if (actionType !== "continue" && actionType !== "recover") {
|
|
1286
|
+
throw new HookValidationError(
|
|
1287
|
+
"afterGadgetExecution",
|
|
1288
|
+
`Invalid action type: ${actionType}. Must be 'continue' or 'recover'`
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
if (actionType === "recover" && !action.fallbackResult) {
|
|
1292
|
+
throw new HookValidationError(
|
|
1293
|
+
"afterGadgetExecution",
|
|
1294
|
+
"When action is 'recover', fallbackResult is required"
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
var HookValidationError;
|
|
1299
|
+
var init_hook_validators = __esm({
|
|
1300
|
+
"src/agent/hook-validators.ts"() {
|
|
1301
|
+
"use strict";
|
|
1302
|
+
HookValidationError = class extends Error {
|
|
1303
|
+
constructor(hookName, message) {
|
|
1304
|
+
super(`Invalid action from ${hookName}: ${message}`);
|
|
1305
|
+
this.name = "HookValidationError";
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
// src/agent/stream-processor.ts
|
|
1312
|
+
var StreamProcessor;
|
|
1313
|
+
var init_stream_processor = __esm({
|
|
1314
|
+
"src/agent/stream-processor.ts"() {
|
|
1315
|
+
"use strict";
|
|
1316
|
+
init_executor();
|
|
1317
|
+
init_parser();
|
|
1318
|
+
init_logger();
|
|
1319
|
+
init_hook_validators();
|
|
1320
|
+
StreamProcessor = class {
|
|
1321
|
+
iteration;
|
|
1322
|
+
registry;
|
|
1323
|
+
hooks;
|
|
1324
|
+
logger;
|
|
1325
|
+
parser;
|
|
1326
|
+
executor;
|
|
1327
|
+
stopOnGadgetError;
|
|
1328
|
+
shouldContinueAfterError;
|
|
1329
|
+
accumulatedText = "";
|
|
1330
|
+
shouldStopExecution = false;
|
|
1331
|
+
observerFailureCount = 0;
|
|
1332
|
+
constructor(options) {
|
|
1333
|
+
this.iteration = options.iteration;
|
|
1334
|
+
this.registry = options.registry;
|
|
1335
|
+
this.hooks = options.hooks ?? {};
|
|
1336
|
+
this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
|
|
1337
|
+
this.stopOnGadgetError = options.stopOnGadgetError ?? true;
|
|
1338
|
+
this.shouldContinueAfterError = options.shouldContinueAfterError;
|
|
1339
|
+
this.parser = new StreamParser({
|
|
1340
|
+
parameterFormat: options.parameterFormat,
|
|
1341
|
+
startPrefix: options.gadgetStartPrefix,
|
|
1342
|
+
endPrefix: options.gadgetEndPrefix
|
|
1343
|
+
});
|
|
1344
|
+
this.executor = new GadgetExecutor(
|
|
1345
|
+
options.registry,
|
|
1346
|
+
options.onHumanInputRequired,
|
|
1347
|
+
this.logger.getSubLogger({ name: "executor" }),
|
|
1348
|
+
options.defaultGadgetTimeoutMs
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Process an LLM stream and return structured results.
|
|
1353
|
+
*/
|
|
1354
|
+
async process(stream2) {
|
|
1355
|
+
const outputs = [];
|
|
1356
|
+
let finishReason = null;
|
|
1357
|
+
let usage;
|
|
1358
|
+
let didExecuteGadgets = false;
|
|
1359
|
+
let shouldBreakLoop = false;
|
|
1360
|
+
for await (const chunk of stream2) {
|
|
1361
|
+
if (chunk.finishReason) finishReason = chunk.finishReason;
|
|
1362
|
+
if (chunk.usage) usage = chunk.usage;
|
|
1363
|
+
let processedChunk = "";
|
|
1364
|
+
if (chunk.text) {
|
|
1365
|
+
processedChunk = chunk.text;
|
|
1366
|
+
if (this.hooks.interceptors?.interceptRawChunk) {
|
|
1367
|
+
const context = {
|
|
1368
|
+
iteration: this.iteration,
|
|
1369
|
+
accumulatedText: this.accumulatedText,
|
|
1370
|
+
logger: this.logger
|
|
1371
|
+
};
|
|
1372
|
+
const intercepted = this.hooks.interceptors.interceptRawChunk(processedChunk, context);
|
|
1373
|
+
if (intercepted === null) {
|
|
1374
|
+
processedChunk = "";
|
|
1375
|
+
} else {
|
|
1376
|
+
processedChunk = intercepted;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (processedChunk) {
|
|
1380
|
+
this.accumulatedText += processedChunk;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
if (this.hooks.observers?.onStreamChunk && (processedChunk || chunk.usage)) {
|
|
1384
|
+
const chunkObservers = [];
|
|
1385
|
+
chunkObservers.push(async () => {
|
|
1386
|
+
const context = {
|
|
1387
|
+
iteration: this.iteration,
|
|
1388
|
+
rawChunk: processedChunk,
|
|
1389
|
+
accumulatedText: this.accumulatedText,
|
|
1390
|
+
usage,
|
|
1391
|
+
logger: this.logger
|
|
1392
|
+
};
|
|
1393
|
+
await this.hooks.observers.onStreamChunk(context);
|
|
1394
|
+
});
|
|
1395
|
+
await this.runObserversInParallel(chunkObservers);
|
|
1396
|
+
}
|
|
1397
|
+
if (!processedChunk) {
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
for (const event of this.parser.feed(processedChunk)) {
|
|
1401
|
+
const processedEvents = await this.processEvent(event);
|
|
1402
|
+
outputs.push(...processedEvents);
|
|
1403
|
+
if (processedEvents.some((e) => e.type === "gadget_result")) {
|
|
1404
|
+
didExecuteGadgets = true;
|
|
1405
|
+
}
|
|
1406
|
+
for (const evt of processedEvents) {
|
|
1407
|
+
if (evt.type === "gadget_result" && evt.result.breaksLoop) {
|
|
1408
|
+
shouldBreakLoop = true;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (this.shouldStopExecution) {
|
|
1413
|
+
this.logger.info("Breaking from LLM stream due to gadget error");
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (!this.shouldStopExecution) {
|
|
1418
|
+
for (const event of this.parser.finalize()) {
|
|
1419
|
+
const processedEvents = await this.processEvent(event);
|
|
1420
|
+
outputs.push(...processedEvents);
|
|
1421
|
+
if (processedEvents.some((e) => e.type === "gadget_result")) {
|
|
1422
|
+
didExecuteGadgets = true;
|
|
1423
|
+
}
|
|
1424
|
+
for (const evt of processedEvents) {
|
|
1425
|
+
if (evt.type === "gadget_result" && evt.result.breaksLoop) {
|
|
1426
|
+
shouldBreakLoop = true;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
let finalMessage = this.accumulatedText;
|
|
1432
|
+
if (this.hooks.interceptors?.interceptAssistantMessage) {
|
|
1433
|
+
const context = {
|
|
1434
|
+
iteration: this.iteration,
|
|
1435
|
+
rawResponse: this.accumulatedText,
|
|
1436
|
+
logger: this.logger
|
|
1437
|
+
};
|
|
1438
|
+
finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
|
|
1439
|
+
}
|
|
1440
|
+
return {
|
|
1441
|
+
outputs,
|
|
1442
|
+
shouldBreakLoop,
|
|
1443
|
+
didExecuteGadgets,
|
|
1444
|
+
finishReason,
|
|
1445
|
+
usage,
|
|
1446
|
+
rawResponse: this.accumulatedText,
|
|
1447
|
+
finalMessage
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Process a single parsed event (text or gadget call).
|
|
1452
|
+
*/
|
|
1453
|
+
async processEvent(event) {
|
|
1454
|
+
if (event.type === "text") {
|
|
1455
|
+
return this.processTextEvent(event);
|
|
1456
|
+
} else if (event.type === "gadget_call") {
|
|
1457
|
+
return this.processGadgetCall(event.call);
|
|
1458
|
+
}
|
|
1459
|
+
return [event];
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Process a text event through interceptors.
|
|
1463
|
+
*/
|
|
1464
|
+
async processTextEvent(event) {
|
|
1465
|
+
let content = event.content;
|
|
1466
|
+
if (this.hooks.interceptors?.interceptTextChunk) {
|
|
1467
|
+
const context = {
|
|
1468
|
+
iteration: this.iteration,
|
|
1469
|
+
accumulatedText: this.accumulatedText,
|
|
1470
|
+
logger: this.logger
|
|
1471
|
+
};
|
|
1472
|
+
const intercepted = this.hooks.interceptors.interceptTextChunk(content, context);
|
|
1473
|
+
if (intercepted === null) {
|
|
1474
|
+
return [];
|
|
1475
|
+
}
|
|
1476
|
+
content = intercepted;
|
|
1477
|
+
}
|
|
1478
|
+
return [{ type: "text", content }];
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Process a gadget call through the full lifecycle.
|
|
1482
|
+
*/
|
|
1483
|
+
async processGadgetCall(call) {
|
|
1484
|
+
if (this.shouldStopExecution) {
|
|
1485
|
+
this.logger.debug("Skipping gadget execution due to previous error", {
|
|
1486
|
+
gadgetName: call.gadgetName
|
|
1487
|
+
});
|
|
1488
|
+
return [];
|
|
1489
|
+
}
|
|
1490
|
+
const events = [];
|
|
1491
|
+
events.push({ type: "gadget_call", call });
|
|
1492
|
+
if (call.parseError) {
|
|
1493
|
+
this.logger.warn("Gadget has parse error", {
|
|
1494
|
+
gadgetName: call.gadgetName,
|
|
1495
|
+
error: call.parseError
|
|
1496
|
+
});
|
|
1497
|
+
const shouldContinue = await this.checkContinueAfterError(
|
|
1498
|
+
call.parseError,
|
|
1499
|
+
call.gadgetName,
|
|
1500
|
+
"parse",
|
|
1501
|
+
call.parameters
|
|
1502
|
+
);
|
|
1503
|
+
if (!shouldContinue) {
|
|
1504
|
+
this.shouldStopExecution = true;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
let parameters = call.parameters ?? {};
|
|
1508
|
+
if (this.hooks.interceptors?.interceptGadgetParameters) {
|
|
1509
|
+
const context = {
|
|
1510
|
+
iteration: this.iteration,
|
|
1511
|
+
gadgetName: call.gadgetName,
|
|
1512
|
+
invocationId: call.invocationId,
|
|
1513
|
+
logger: this.logger
|
|
1514
|
+
};
|
|
1515
|
+
parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
|
|
1516
|
+
}
|
|
1517
|
+
call.parameters = parameters;
|
|
1518
|
+
let shouldSkip = false;
|
|
1519
|
+
let syntheticResult;
|
|
1520
|
+
if (this.hooks.controllers?.beforeGadgetExecution) {
|
|
1521
|
+
const context = {
|
|
1522
|
+
iteration: this.iteration,
|
|
1523
|
+
gadgetName: call.gadgetName,
|
|
1524
|
+
invocationId: call.invocationId,
|
|
1525
|
+
parameters,
|
|
1526
|
+
logger: this.logger
|
|
1527
|
+
};
|
|
1528
|
+
const action = await this.hooks.controllers.beforeGadgetExecution(context);
|
|
1529
|
+
validateBeforeGadgetExecutionAction(action);
|
|
1530
|
+
if (action.action === "skip") {
|
|
1531
|
+
shouldSkip = true;
|
|
1532
|
+
syntheticResult = action.syntheticResult;
|
|
1533
|
+
this.logger.info("Controller skipped gadget execution", {
|
|
1534
|
+
gadgetName: call.gadgetName
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
const startObservers = [];
|
|
1539
|
+
if (this.hooks.observers?.onGadgetExecutionStart) {
|
|
1540
|
+
startObservers.push(async () => {
|
|
1541
|
+
const context = {
|
|
1542
|
+
iteration: this.iteration,
|
|
1543
|
+
gadgetName: call.gadgetName,
|
|
1544
|
+
invocationId: call.invocationId,
|
|
1545
|
+
parameters,
|
|
1546
|
+
logger: this.logger
|
|
1547
|
+
};
|
|
1548
|
+
await this.hooks.observers.onGadgetExecutionStart(context);
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
await this.runObserversInParallel(startObservers);
|
|
1552
|
+
let result;
|
|
1553
|
+
if (shouldSkip) {
|
|
1554
|
+
result = {
|
|
1555
|
+
gadgetName: call.gadgetName,
|
|
1556
|
+
invocationId: call.invocationId,
|
|
1557
|
+
parameters,
|
|
1558
|
+
result: syntheticResult ?? "Execution skipped",
|
|
1559
|
+
executionTimeMs: 0
|
|
1560
|
+
};
|
|
1561
|
+
} else {
|
|
1562
|
+
result = await this.executor.execute(call);
|
|
1563
|
+
}
|
|
1564
|
+
const originalResult = result.result;
|
|
1565
|
+
if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
|
|
1566
|
+
const context = {
|
|
1567
|
+
iteration: this.iteration,
|
|
1568
|
+
gadgetName: result.gadgetName,
|
|
1569
|
+
invocationId: result.invocationId,
|
|
1570
|
+
parameters,
|
|
1571
|
+
executionTimeMs: result.executionTimeMs,
|
|
1572
|
+
logger: this.logger
|
|
1573
|
+
};
|
|
1574
|
+
result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
|
|
1575
|
+
}
|
|
1576
|
+
if (this.hooks.controllers?.afterGadgetExecution) {
|
|
1577
|
+
const context = {
|
|
1578
|
+
iteration: this.iteration,
|
|
1579
|
+
gadgetName: result.gadgetName,
|
|
1580
|
+
invocationId: result.invocationId,
|
|
1581
|
+
parameters,
|
|
1582
|
+
result: result.result,
|
|
1583
|
+
error: result.error,
|
|
1584
|
+
executionTimeMs: result.executionTimeMs,
|
|
1585
|
+
logger: this.logger
|
|
1586
|
+
};
|
|
1587
|
+
const action = await this.hooks.controllers.afterGadgetExecution(context);
|
|
1588
|
+
validateAfterGadgetExecutionAction(action);
|
|
1589
|
+
if (action.action === "recover" && result.error) {
|
|
1590
|
+
this.logger.info("Controller recovered from gadget error", {
|
|
1591
|
+
gadgetName: result.gadgetName,
|
|
1592
|
+
originalError: result.error
|
|
1593
|
+
});
|
|
1594
|
+
result = {
|
|
1595
|
+
...result,
|
|
1596
|
+
error: void 0,
|
|
1597
|
+
result: action.fallbackResult
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
const completeObservers = [];
|
|
1602
|
+
if (this.hooks.observers?.onGadgetExecutionComplete) {
|
|
1603
|
+
completeObservers.push(async () => {
|
|
1604
|
+
const context = {
|
|
1605
|
+
iteration: this.iteration,
|
|
1606
|
+
gadgetName: result.gadgetName,
|
|
1607
|
+
invocationId: result.invocationId,
|
|
1608
|
+
parameters,
|
|
1609
|
+
originalResult,
|
|
1610
|
+
finalResult: result.result,
|
|
1611
|
+
error: result.error,
|
|
1612
|
+
executionTimeMs: result.executionTimeMs,
|
|
1613
|
+
breaksLoop: result.breaksLoop,
|
|
1614
|
+
logger: this.logger
|
|
1615
|
+
};
|
|
1616
|
+
await this.hooks.observers.onGadgetExecutionComplete(context);
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
await this.runObserversInParallel(completeObservers);
|
|
1620
|
+
events.push({ type: "gadget_result", result });
|
|
1621
|
+
if (result.error) {
|
|
1622
|
+
const errorType = this.determineErrorType(call, result);
|
|
1623
|
+
const shouldContinue = await this.checkContinueAfterError(
|
|
1624
|
+
result.error,
|
|
1625
|
+
result.gadgetName,
|
|
1626
|
+
errorType,
|
|
1627
|
+
result.parameters
|
|
1628
|
+
);
|
|
1629
|
+
if (!shouldContinue) {
|
|
1630
|
+
this.shouldStopExecution = true;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
return events;
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Safely execute an observer, catching and logging any errors.
|
|
1637
|
+
* Observers are non-critical, so errors are logged but don't crash the system.
|
|
1638
|
+
*/
|
|
1639
|
+
async safeObserve(fn) {
|
|
1640
|
+
try {
|
|
1641
|
+
await fn();
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
this.observerFailureCount++;
|
|
1644
|
+
this.logger.error("Observer threw error (ignoring)", {
|
|
1645
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1646
|
+
failureCount: this.observerFailureCount
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Execute multiple observers in parallel.
|
|
1652
|
+
* All observers run concurrently and failures are tracked but don't crash.
|
|
1653
|
+
*/
|
|
1654
|
+
async runObserversInParallel(observers) {
|
|
1655
|
+
if (observers.length === 0) return;
|
|
1656
|
+
const results = await Promise.allSettled(
|
|
1657
|
+
observers.map((observer) => this.safeObserve(observer))
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Check if execution should continue after an error.
|
|
1662
|
+
*
|
|
1663
|
+
* Returns true if we should continue processing subsequent gadgets, false if we should stop.
|
|
1664
|
+
*
|
|
1665
|
+
* Logic:
|
|
1666
|
+
* - If custom shouldContinueAfterError is provided, use it
|
|
1667
|
+
* - Otherwise, use stopOnGadgetError config:
|
|
1668
|
+
* - stopOnGadgetError=true → return false (stop execution)
|
|
1669
|
+
* - stopOnGadgetError=false → return true (continue execution)
|
|
1670
|
+
*/
|
|
1671
|
+
async checkContinueAfterError(error, gadgetName, errorType, parameters) {
|
|
1672
|
+
if (this.shouldContinueAfterError) {
|
|
1673
|
+
return await this.shouldContinueAfterError({
|
|
1674
|
+
error,
|
|
1675
|
+
gadgetName,
|
|
1676
|
+
errorType,
|
|
1677
|
+
parameters
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
const shouldContinue = !this.stopOnGadgetError;
|
|
1681
|
+
this.logger.debug("Checking if should continue after error", {
|
|
1682
|
+
error,
|
|
1683
|
+
gadgetName,
|
|
1684
|
+
errorType,
|
|
1685
|
+
stopOnGadgetError: this.stopOnGadgetError,
|
|
1686
|
+
shouldContinue
|
|
1687
|
+
});
|
|
1688
|
+
return shouldContinue;
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Determine the type of error from a gadget execution.
|
|
1692
|
+
*/
|
|
1693
|
+
determineErrorType(call, result) {
|
|
1694
|
+
if (call.parseError) {
|
|
1695
|
+
return "parse";
|
|
1696
|
+
}
|
|
1697
|
+
if (result.error?.includes("Invalid parameters:")) {
|
|
1698
|
+
return "validation";
|
|
1699
|
+
}
|
|
1700
|
+
return "execution";
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
// src/providers/anthropic-models.ts
|
|
1707
|
+
var ANTHROPIC_MODELS;
|
|
1708
|
+
var init_anthropic_models = __esm({
|
|
1709
|
+
"src/providers/anthropic-models.ts"() {
|
|
1710
|
+
"use strict";
|
|
1711
|
+
ANTHROPIC_MODELS = [
|
|
1712
|
+
{
|
|
1713
|
+
provider: "anthropic",
|
|
1714
|
+
modelId: "claude-opus-4-5-20251124",
|
|
1715
|
+
displayName: "Claude Opus 4.5",
|
|
1716
|
+
contextWindow: 2e5,
|
|
1717
|
+
maxOutputTokens: 64e3,
|
|
1718
|
+
pricing: {
|
|
1719
|
+
input: 5,
|
|
1720
|
+
output: 25,
|
|
1721
|
+
cachedInput: 0.5
|
|
1722
|
+
},
|
|
1723
|
+
knowledgeCutoff: "2025-03",
|
|
1724
|
+
features: {
|
|
1725
|
+
streaming: true,
|
|
1726
|
+
functionCalling: true,
|
|
1727
|
+
vision: true,
|
|
1728
|
+
reasoning: true
|
|
1729
|
+
},
|
|
1730
|
+
metadata: {
|
|
1731
|
+
family: "Claude 4",
|
|
1732
|
+
releaseDate: "2025-11-24",
|
|
1733
|
+
notes: "Most powerful model. 80.9% SWE-bench Verified, 66.3% OSWorld. Best for coding and computer use."
|
|
1734
|
+
}
|
|
1735
|
+
},
|
|
1736
|
+
{
|
|
1737
|
+
provider: "anthropic",
|
|
1738
|
+
modelId: "claude-sonnet-4-5-20250929",
|
|
1739
|
+
displayName: "Claude Sonnet 4.5",
|
|
1740
|
+
contextWindow: 2e5,
|
|
1741
|
+
maxOutputTokens: 64e3,
|
|
1742
|
+
pricing: {
|
|
1743
|
+
input: 3,
|
|
1744
|
+
output: 15,
|
|
1745
|
+
cachedInput: 0.3
|
|
1746
|
+
},
|
|
1747
|
+
knowledgeCutoff: "2025-01",
|
|
1748
|
+
features: {
|
|
1749
|
+
streaming: true,
|
|
1750
|
+
functionCalling: true,
|
|
1751
|
+
vision: true,
|
|
1752
|
+
reasoning: true
|
|
1753
|
+
},
|
|
1754
|
+
metadata: {
|
|
1755
|
+
family: "Claude 4",
|
|
1756
|
+
releaseDate: "2025-09-29",
|
|
1757
|
+
notes: "Smartest model for complex agents and coding. Extended thinking. 1M context in beta."
|
|
1758
|
+
}
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
provider: "anthropic",
|
|
1762
|
+
modelId: "claude-haiku-4-5-20251001",
|
|
1763
|
+
displayName: "Claude Haiku 4.5",
|
|
1764
|
+
contextWindow: 2e5,
|
|
1765
|
+
maxOutputTokens: 64e3,
|
|
1766
|
+
pricing: {
|
|
1767
|
+
input: 1,
|
|
1768
|
+
output: 5,
|
|
1769
|
+
cachedInput: 0.1
|
|
1770
|
+
},
|
|
1771
|
+
knowledgeCutoff: "2025-02",
|
|
1772
|
+
features: {
|
|
1773
|
+
streaming: true,
|
|
1774
|
+
functionCalling: true,
|
|
1775
|
+
vision: true,
|
|
1776
|
+
reasoning: true
|
|
1777
|
+
},
|
|
1778
|
+
metadata: {
|
|
1779
|
+
family: "Claude 4",
|
|
1780
|
+
releaseDate: "2025-10-01",
|
|
1781
|
+
notes: "Fastest model with near-frontier intelligence. Excellent for coding (73.3% SWE-bench)."
|
|
1782
|
+
}
|
|
1783
|
+
},
|
|
1784
|
+
{
|
|
1785
|
+
provider: "anthropic",
|
|
1786
|
+
modelId: "claude-sonnet-4-20250514",
|
|
1787
|
+
displayName: "Claude Sonnet 4",
|
|
1788
|
+
contextWindow: 2e5,
|
|
1789
|
+
maxOutputTokens: 64e3,
|
|
1790
|
+
pricing: {
|
|
1791
|
+
input: 3,
|
|
1792
|
+
output: 15,
|
|
1793
|
+
cachedInput: 0.3
|
|
1794
|
+
},
|
|
1795
|
+
knowledgeCutoff: "2025-03",
|
|
1796
|
+
features: {
|
|
1797
|
+
streaming: true,
|
|
1798
|
+
functionCalling: true,
|
|
1799
|
+
vision: true,
|
|
1800
|
+
reasoning: true
|
|
1801
|
+
},
|
|
1802
|
+
metadata: {
|
|
1803
|
+
family: "Claude 4",
|
|
1804
|
+
releaseDate: "2025-05-14",
|
|
1805
|
+
notes: "High performance with vision and extended thinking"
|
|
1806
|
+
}
|
|
1807
|
+
},
|
|
1808
|
+
{
|
|
1809
|
+
provider: "anthropic",
|
|
1810
|
+
modelId: "claude-3-7-sonnet-20250219",
|
|
1811
|
+
displayName: "Claude Sonnet 3.7",
|
|
1812
|
+
contextWindow: 2e5,
|
|
1813
|
+
maxOutputTokens: 64e3,
|
|
1814
|
+
pricing: {
|
|
1815
|
+
input: 3,
|
|
1816
|
+
output: 15,
|
|
1817
|
+
cachedInput: 0.3
|
|
1818
|
+
},
|
|
1819
|
+
knowledgeCutoff: "2024-11",
|
|
1820
|
+
features: {
|
|
1821
|
+
streaming: true,
|
|
1822
|
+
functionCalling: true,
|
|
1823
|
+
vision: true,
|
|
1824
|
+
reasoning: true
|
|
1825
|
+
},
|
|
1826
|
+
metadata: {
|
|
1827
|
+
family: "Claude 3",
|
|
1828
|
+
releaseDate: "2025-02-19",
|
|
1829
|
+
notes: "Legacy model - consider upgrading to Claude 4 family"
|
|
1830
|
+
}
|
|
1831
|
+
},
|
|
1832
|
+
{
|
|
1833
|
+
provider: "anthropic",
|
|
1834
|
+
modelId: "claude-opus-4-1-20250805",
|
|
1835
|
+
displayName: "Claude Opus 4.1",
|
|
1836
|
+
contextWindow: 2e5,
|
|
1837
|
+
maxOutputTokens: 32e3,
|
|
1838
|
+
pricing: {
|
|
1839
|
+
input: 15,
|
|
1840
|
+
output: 75,
|
|
1841
|
+
cachedInput: 1.5
|
|
1842
|
+
},
|
|
1843
|
+
knowledgeCutoff: "2025-01",
|
|
1844
|
+
features: {
|
|
1845
|
+
streaming: true,
|
|
1846
|
+
functionCalling: true,
|
|
1847
|
+
vision: true,
|
|
1848
|
+
reasoning: true
|
|
1849
|
+
},
|
|
1850
|
+
metadata: {
|
|
1851
|
+
family: "Claude 4",
|
|
1852
|
+
releaseDate: "2025-08-05",
|
|
1853
|
+
notes: "Exceptional for specialized reasoning tasks. Extended thinking support."
|
|
1854
|
+
}
|
|
1855
|
+
},
|
|
1856
|
+
{
|
|
1857
|
+
provider: "anthropic",
|
|
1858
|
+
modelId: "claude-opus-4-20250514",
|
|
1859
|
+
displayName: "Claude Opus 4",
|
|
1860
|
+
contextWindow: 2e5,
|
|
1861
|
+
maxOutputTokens: 32e3,
|
|
1862
|
+
pricing: {
|
|
1863
|
+
input: 15,
|
|
1864
|
+
output: 75,
|
|
1865
|
+
cachedInput: 1.5
|
|
1866
|
+
},
|
|
1867
|
+
knowledgeCutoff: "2025-03",
|
|
1868
|
+
features: {
|
|
1869
|
+
streaming: true,
|
|
1870
|
+
functionCalling: true,
|
|
1871
|
+
vision: true
|
|
1872
|
+
},
|
|
1873
|
+
metadata: {
|
|
1874
|
+
family: "Claude 4",
|
|
1875
|
+
releaseDate: "2025-05-14",
|
|
1876
|
+
notes: "Legacy Opus model - consider Opus 4.1 for improved reasoning"
|
|
1877
|
+
}
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
provider: "anthropic",
|
|
1881
|
+
modelId: "claude-3-5-haiku-20241022",
|
|
1882
|
+
displayName: "Claude Haiku 3.5",
|
|
1883
|
+
contextWindow: 2e5,
|
|
1884
|
+
maxOutputTokens: 8192,
|
|
1885
|
+
pricing: {
|
|
1886
|
+
input: 0.8,
|
|
1887
|
+
output: 4,
|
|
1888
|
+
cachedInput: 0.08
|
|
1889
|
+
},
|
|
1890
|
+
knowledgeCutoff: "2024-07",
|
|
1891
|
+
features: {
|
|
1892
|
+
streaming: true,
|
|
1893
|
+
functionCalling: true,
|
|
1894
|
+
vision: true
|
|
1895
|
+
},
|
|
1896
|
+
metadata: {
|
|
1897
|
+
family: "Claude 3",
|
|
1898
|
+
releaseDate: "2024-10-22",
|
|
1899
|
+
notes: "Legacy model - upgrade to Haiku 4.5 for better performance"
|
|
1900
|
+
}
|
|
1901
|
+
},
|
|
1902
|
+
{
|
|
1903
|
+
provider: "anthropic",
|
|
1904
|
+
modelId: "claude-3-haiku-20240307",
|
|
1905
|
+
displayName: "Claude Haiku 3",
|
|
1906
|
+
contextWindow: 2e5,
|
|
1907
|
+
maxOutputTokens: 4096,
|
|
1908
|
+
pricing: {
|
|
1909
|
+
input: 0.25,
|
|
1910
|
+
output: 1.25,
|
|
1911
|
+
cachedInput: 0.025
|
|
1912
|
+
},
|
|
1913
|
+
knowledgeCutoff: "2023-08",
|
|
1914
|
+
features: {
|
|
1915
|
+
streaming: true,
|
|
1916
|
+
functionCalling: true,
|
|
1917
|
+
vision: true
|
|
1918
|
+
},
|
|
1919
|
+
metadata: {
|
|
1920
|
+
family: "Claude 3",
|
|
1921
|
+
releaseDate: "2024-03-07",
|
|
1922
|
+
notes: "Legacy model - upgrade to Haiku 4.5 for better performance"
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
];
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
|
|
1929
|
+
// src/providers/base-provider.ts
|
|
1930
|
+
var BaseProviderAdapter;
|
|
1931
|
+
var init_base_provider = __esm({
|
|
1932
|
+
"src/providers/base-provider.ts"() {
|
|
1933
|
+
"use strict";
|
|
1934
|
+
BaseProviderAdapter = class {
|
|
1935
|
+
constructor(client) {
|
|
1936
|
+
this.client = client;
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Template method that defines the skeleton of the streaming algorithm.
|
|
1940
|
+
* This orchestrates the four-step process without dictating provider-specific details.
|
|
1941
|
+
*/
|
|
1942
|
+
async *stream(options, descriptor, spec) {
|
|
1943
|
+
const preparedMessages = this.prepareMessages(options.messages);
|
|
1944
|
+
const payload = this.buildRequestPayload(options, descriptor, spec, preparedMessages);
|
|
1945
|
+
const rawStream = await this.executeStreamRequest(payload);
|
|
1946
|
+
yield* this.wrapStream(rawStream);
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Prepare messages for the request.
|
|
1950
|
+
* Default implementation returns messages unchanged.
|
|
1951
|
+
* Override this to implement provider-specific message transformations
|
|
1952
|
+
* (e.g., Gemini's consecutive message merging, Anthropic's system message extraction).
|
|
1953
|
+
*
|
|
1954
|
+
* @param messages - The input messages
|
|
1955
|
+
* @returns Prepared messages
|
|
1956
|
+
*/
|
|
1957
|
+
prepareMessages(messages) {
|
|
1958
|
+
return messages;
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
// src/providers/constants.ts
|
|
1965
|
+
var ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS, FALLBACK_CHARS_PER_TOKEN, OPENAI_MESSAGE_OVERHEAD_TOKENS, OPENAI_REPLY_PRIMING_TOKENS, OPENAI_NAME_FIELD_OVERHEAD_TOKENS;
|
|
1966
|
+
var init_constants2 = __esm({
|
|
1967
|
+
"src/providers/constants.ts"() {
|
|
1968
|
+
"use strict";
|
|
1969
|
+
ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 4096;
|
|
1970
|
+
FALLBACK_CHARS_PER_TOKEN = 4;
|
|
1971
|
+
OPENAI_MESSAGE_OVERHEAD_TOKENS = 4;
|
|
1972
|
+
OPENAI_REPLY_PRIMING_TOKENS = 2;
|
|
1973
|
+
OPENAI_NAME_FIELD_OVERHEAD_TOKENS = 1;
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
// src/providers/utils.ts
|
|
1978
|
+
function readEnvVar(key) {
|
|
1979
|
+
if (typeof process === "undefined" || typeof process.env === "undefined") {
|
|
1980
|
+
return void 0;
|
|
1981
|
+
}
|
|
1982
|
+
const value = process.env[key];
|
|
1983
|
+
return typeof value === "string" ? value : void 0;
|
|
1984
|
+
}
|
|
1985
|
+
function isNonEmpty(value) {
|
|
1986
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1987
|
+
}
|
|
1988
|
+
function createProviderFromEnv(envVarName, ClientClass, ProviderClass, clientOptions) {
|
|
1989
|
+
const apiKey = readEnvVar(envVarName);
|
|
1990
|
+
if (!isNonEmpty(apiKey)) {
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
const client = new ClientClass({ apiKey: apiKey.trim(), ...clientOptions });
|
|
1994
|
+
return new ProviderClass(client);
|
|
1995
|
+
}
|
|
1996
|
+
var init_utils = __esm({
|
|
1997
|
+
"src/providers/utils.ts"() {
|
|
1998
|
+
"use strict";
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
// src/providers/anthropic.ts
|
|
2003
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2004
|
+
function createAnthropicProviderFromEnv() {
|
|
2005
|
+
return createProviderFromEnv("ANTHROPIC_API_KEY", Anthropic, AnthropicMessagesProvider);
|
|
2006
|
+
}
|
|
2007
|
+
var AnthropicMessagesProvider;
|
|
2008
|
+
var init_anthropic = __esm({
|
|
2009
|
+
"src/providers/anthropic.ts"() {
|
|
2010
|
+
"use strict";
|
|
2011
|
+
init_anthropic_models();
|
|
2012
|
+
init_base_provider();
|
|
2013
|
+
init_constants2();
|
|
2014
|
+
init_utils();
|
|
2015
|
+
AnthropicMessagesProvider = class extends BaseProviderAdapter {
|
|
2016
|
+
providerId = "anthropic";
|
|
2017
|
+
supports(descriptor) {
|
|
2018
|
+
return descriptor.provider === this.providerId;
|
|
2019
|
+
}
|
|
2020
|
+
getModelSpecs() {
|
|
2021
|
+
return ANTHROPIC_MODELS;
|
|
2022
|
+
}
|
|
2023
|
+
buildRequestPayload(options, descriptor, spec, messages) {
|
|
2024
|
+
const systemMessages = messages.filter((message) => message.role === "system");
|
|
2025
|
+
const system = systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n\n") : void 0;
|
|
2026
|
+
const conversation = messages.filter(
|
|
2027
|
+
(message) => message.role !== "system"
|
|
2028
|
+
).map((message) => ({
|
|
2029
|
+
role: message.role,
|
|
2030
|
+
content: [
|
|
2031
|
+
{
|
|
2032
|
+
type: "text",
|
|
2033
|
+
text: message.content
|
|
2034
|
+
}
|
|
2035
|
+
]
|
|
2036
|
+
}));
|
|
2037
|
+
const defaultMaxTokens = spec?.maxOutputTokens ?? ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS;
|
|
2038
|
+
const payload = {
|
|
2039
|
+
model: descriptor.name,
|
|
2040
|
+
system,
|
|
2041
|
+
messages: conversation,
|
|
2042
|
+
max_tokens: options.maxTokens ?? defaultMaxTokens,
|
|
2043
|
+
temperature: options.temperature,
|
|
2044
|
+
top_p: options.topP,
|
|
2045
|
+
stop_sequences: options.stopSequences,
|
|
2046
|
+
stream: true,
|
|
2047
|
+
...options.extra
|
|
2048
|
+
};
|
|
2049
|
+
return payload;
|
|
2050
|
+
}
|
|
2051
|
+
async executeStreamRequest(payload) {
|
|
2052
|
+
const client = this.client;
|
|
2053
|
+
const stream2 = await client.messages.create(payload);
|
|
2054
|
+
return stream2;
|
|
2055
|
+
}
|
|
2056
|
+
async *wrapStream(iterable) {
|
|
2057
|
+
const stream2 = iterable;
|
|
2058
|
+
let inputTokens = 0;
|
|
2059
|
+
for await (const event of stream2) {
|
|
2060
|
+
if (event.type === "message_start") {
|
|
2061
|
+
inputTokens = event.message.usage.input_tokens;
|
|
2062
|
+
yield {
|
|
2063
|
+
text: "",
|
|
2064
|
+
usage: {
|
|
2065
|
+
inputTokens,
|
|
2066
|
+
outputTokens: 0,
|
|
2067
|
+
totalTokens: inputTokens
|
|
2068
|
+
},
|
|
2069
|
+
rawEvent: event
|
|
2070
|
+
};
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
2074
|
+
yield { text: event.delta.text ?? "", rawEvent: event };
|
|
2075
|
+
continue;
|
|
2076
|
+
}
|
|
2077
|
+
if (event.type === "message_delta") {
|
|
2078
|
+
const usage = event.usage ? {
|
|
2079
|
+
inputTokens,
|
|
2080
|
+
outputTokens: event.usage.output_tokens,
|
|
2081
|
+
totalTokens: inputTokens + event.usage.output_tokens
|
|
2082
|
+
} : void 0;
|
|
2083
|
+
if (event.delta.stop_reason || usage) {
|
|
2084
|
+
yield {
|
|
2085
|
+
text: "",
|
|
2086
|
+
finishReason: event.delta.stop_reason ?? void 0,
|
|
2087
|
+
usage,
|
|
2088
|
+
rawEvent: event
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
if (event.type === "message_stop") {
|
|
2094
|
+
yield { text: "", finishReason: "stop", rawEvent: event };
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Count tokens in messages using Anthropic's native token counting API.
|
|
2100
|
+
*
|
|
2101
|
+
* This method provides accurate token estimation for Anthropic models by:
|
|
2102
|
+
* - Using the native messages.countTokens() API
|
|
2103
|
+
* - Properly handling system messages and conversation structure
|
|
2104
|
+
* - Transforming messages to Anthropic's expected format
|
|
2105
|
+
*
|
|
2106
|
+
* @param messages - The messages to count tokens for
|
|
2107
|
+
* @param descriptor - Model descriptor containing the model name
|
|
2108
|
+
* @param _spec - Optional model specification (currently unused)
|
|
2109
|
+
* @returns Promise resolving to the estimated input token count
|
|
2110
|
+
*
|
|
2111
|
+
* @throws Never throws - falls back to character-based estimation (4 chars/token) on error
|
|
2112
|
+
*
|
|
2113
|
+
* @example
|
|
2114
|
+
* ```typescript
|
|
2115
|
+
* const count = await provider.countTokens(
|
|
2116
|
+
* [{ role: "user", content: "Hello!" }],
|
|
2117
|
+
* { provider: "anthropic", name: "claude-3-5-sonnet-20241022" }
|
|
2118
|
+
* );
|
|
2119
|
+
* ```
|
|
2120
|
+
*/
|
|
2121
|
+
async countTokens(messages, descriptor, _spec) {
|
|
2122
|
+
const client = this.client;
|
|
2123
|
+
const systemMessages = messages.filter((message) => message.role === "system");
|
|
2124
|
+
const system = systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n\n") : void 0;
|
|
2125
|
+
const conversation = messages.filter(
|
|
2126
|
+
(message) => message.role !== "system"
|
|
2127
|
+
).map((message) => ({
|
|
2128
|
+
role: message.role,
|
|
2129
|
+
content: [
|
|
2130
|
+
{
|
|
2131
|
+
type: "text",
|
|
2132
|
+
text: message.content
|
|
2133
|
+
}
|
|
2134
|
+
]
|
|
2135
|
+
}));
|
|
2136
|
+
try {
|
|
2137
|
+
const response = await client.messages.countTokens({
|
|
2138
|
+
model: descriptor.name,
|
|
2139
|
+
messages: conversation,
|
|
2140
|
+
...system ? { system } : {}
|
|
2141
|
+
});
|
|
2142
|
+
return response.input_tokens;
|
|
2143
|
+
} catch (error) {
|
|
2144
|
+
console.warn(
|
|
2145
|
+
`Token counting failed for ${descriptor.name}, using fallback estimation:`,
|
|
2146
|
+
error
|
|
2147
|
+
);
|
|
2148
|
+
const totalChars = messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0);
|
|
2149
|
+
return Math.ceil(totalChars / FALLBACK_CHARS_PER_TOKEN);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
});
|
|
2155
|
+
|
|
2156
|
+
// src/providers/gemini-models.ts
|
|
2157
|
+
var GEMINI_MODELS;
|
|
2158
|
+
var init_gemini_models = __esm({
|
|
2159
|
+
"src/providers/gemini-models.ts"() {
|
|
2160
|
+
"use strict";
|
|
2161
|
+
GEMINI_MODELS = [
|
|
2162
|
+
{
|
|
2163
|
+
provider: "gemini",
|
|
2164
|
+
modelId: "gemini-3-pro-preview",
|
|
2165
|
+
displayName: "Gemini 3 Pro (Preview)",
|
|
2166
|
+
contextWindow: 1048576,
|
|
2167
|
+
maxOutputTokens: 65536,
|
|
2168
|
+
pricing: {
|
|
2169
|
+
input: 2,
|
|
2170
|
+
output: 12,
|
|
2171
|
+
cachedInput: 0.2
|
|
2172
|
+
},
|
|
2173
|
+
knowledgeCutoff: "2025-01",
|
|
2174
|
+
features: {
|
|
2175
|
+
streaming: true,
|
|
2176
|
+
functionCalling: true,
|
|
2177
|
+
vision: true,
|
|
2178
|
+
reasoning: true,
|
|
2179
|
+
structuredOutputs: true
|
|
2180
|
+
},
|
|
2181
|
+
metadata: {
|
|
2182
|
+
family: "Gemini 3",
|
|
2183
|
+
releaseDate: "2025-11-18",
|
|
2184
|
+
notes: "Most advanced model. 1501 Elo LMArena, 91.9% GPQA Diamond, 76.2% SWE-bench. Deep Think mode available."
|
|
2185
|
+
}
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
provider: "gemini",
|
|
2189
|
+
modelId: "gemini-2.5-pro",
|
|
2190
|
+
displayName: "Gemini 2.5 Pro",
|
|
2191
|
+
contextWindow: 1048576,
|
|
2192
|
+
maxOutputTokens: 65536,
|
|
2193
|
+
pricing: {
|
|
2194
|
+
input: 1.25,
|
|
2195
|
+
output: 10,
|
|
2196
|
+
cachedInput: 0.125
|
|
2197
|
+
},
|
|
2198
|
+
knowledgeCutoff: "2025-01",
|
|
2199
|
+
features: {
|
|
2200
|
+
streaming: true,
|
|
2201
|
+
functionCalling: true,
|
|
2202
|
+
vision: true,
|
|
2203
|
+
reasoning: true,
|
|
2204
|
+
structuredOutputs: true
|
|
2205
|
+
},
|
|
2206
|
+
metadata: {
|
|
2207
|
+
family: "Gemini 2.5",
|
|
2208
|
+
releaseDate: "2025-06",
|
|
2209
|
+
notes: "Balanced multimodal model with 1M context. Best for complex agents and reasoning."
|
|
2210
|
+
}
|
|
2211
|
+
},
|
|
2212
|
+
{
|
|
2213
|
+
provider: "gemini",
|
|
2214
|
+
modelId: "gemini-2.5-flash",
|
|
2215
|
+
displayName: "Gemini 2.5 Flash",
|
|
2216
|
+
contextWindow: 1048576,
|
|
2217
|
+
maxOutputTokens: 65536,
|
|
2218
|
+
pricing: {
|
|
2219
|
+
input: 0.3,
|
|
2220
|
+
output: 2.5,
|
|
2221
|
+
cachedInput: 0.03
|
|
2222
|
+
},
|
|
2223
|
+
knowledgeCutoff: "2025-01",
|
|
2224
|
+
features: {
|
|
2225
|
+
streaming: true,
|
|
2226
|
+
functionCalling: true,
|
|
2227
|
+
vision: true,
|
|
2228
|
+
reasoning: true,
|
|
2229
|
+
structuredOutputs: true
|
|
2230
|
+
},
|
|
2231
|
+
metadata: {
|
|
2232
|
+
family: "Gemini 2.5",
|
|
2233
|
+
releaseDate: "2025-06",
|
|
2234
|
+
notes: "Best price-performance ratio with thinking enabled by default"
|
|
2235
|
+
}
|
|
2236
|
+
},
|
|
2237
|
+
{
|
|
2238
|
+
provider: "gemini",
|
|
2239
|
+
modelId: "gemini-2.5-flash-lite",
|
|
2240
|
+
displayName: "Gemini 2.5 Flash-Lite",
|
|
2241
|
+
contextWindow: 1048576,
|
|
2242
|
+
maxOutputTokens: 65536,
|
|
2243
|
+
pricing: {
|
|
2244
|
+
input: 0.1,
|
|
2245
|
+
output: 0.4,
|
|
2246
|
+
cachedInput: 0.01
|
|
2247
|
+
},
|
|
2248
|
+
knowledgeCutoff: "2025-01",
|
|
2249
|
+
features: {
|
|
2250
|
+
streaming: true,
|
|
2251
|
+
functionCalling: true,
|
|
2252
|
+
vision: true,
|
|
2253
|
+
structuredOutputs: true
|
|
2254
|
+
},
|
|
2255
|
+
metadata: {
|
|
2256
|
+
family: "Gemini 2.5",
|
|
2257
|
+
releaseDate: "2025-06",
|
|
2258
|
+
notes: "Fastest and most cost-efficient model for high-volume, low-latency tasks"
|
|
2259
|
+
}
|
|
2260
|
+
},
|
|
2261
|
+
{
|
|
2262
|
+
provider: "gemini",
|
|
2263
|
+
modelId: "gemini-2.0-flash",
|
|
2264
|
+
displayName: "Gemini 2.0 Flash",
|
|
2265
|
+
contextWindow: 1048576,
|
|
2266
|
+
maxOutputTokens: 8192,
|
|
2267
|
+
pricing: {
|
|
2268
|
+
input: 0.1,
|
|
2269
|
+
output: 0.4,
|
|
2270
|
+
cachedInput: 0.01
|
|
2271
|
+
},
|
|
2272
|
+
knowledgeCutoff: "2024-08",
|
|
2273
|
+
features: {
|
|
2274
|
+
streaming: true,
|
|
2275
|
+
functionCalling: true,
|
|
2276
|
+
vision: true,
|
|
2277
|
+
structuredOutputs: true
|
|
2278
|
+
},
|
|
2279
|
+
metadata: {
|
|
2280
|
+
family: "Gemini 2.0",
|
|
2281
|
+
notes: "Previous generation with 1M context and multimodal capabilities"
|
|
2282
|
+
}
|
|
2283
|
+
},
|
|
2284
|
+
{
|
|
2285
|
+
provider: "gemini",
|
|
2286
|
+
modelId: "gemini-2.0-flash-lite",
|
|
2287
|
+
displayName: "Gemini 2.0 Flash-Lite",
|
|
2288
|
+
contextWindow: 1048576,
|
|
2289
|
+
maxOutputTokens: 8192,
|
|
2290
|
+
pricing: {
|
|
2291
|
+
input: 0.075,
|
|
2292
|
+
output: 0.3,
|
|
2293
|
+
cachedInput: 75e-4
|
|
2294
|
+
},
|
|
2295
|
+
knowledgeCutoff: "2024-08",
|
|
2296
|
+
features: {
|
|
2297
|
+
streaming: true,
|
|
2298
|
+
functionCalling: true,
|
|
2299
|
+
vision: true,
|
|
2300
|
+
structuredOutputs: true
|
|
2301
|
+
},
|
|
2302
|
+
metadata: {
|
|
2303
|
+
family: "Gemini 2.0",
|
|
2304
|
+
notes: "Lightweight previous generation model for cost-sensitive applications"
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
];
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
|
|
2311
|
+
// src/providers/gemini.ts
|
|
2312
|
+
import { FunctionCallingConfigMode, GoogleGenAI } from "@google/genai";
|
|
2313
|
+
function createGeminiProviderFromEnv() {
|
|
2314
|
+
return createProviderFromEnv("GEMINI_API_KEY", GoogleGenAI, GeminiGenerativeProvider);
|
|
2315
|
+
}
|
|
2316
|
+
var GEMINI_ROLE_MAP, GeminiGenerativeProvider;
|
|
2317
|
+
var init_gemini = __esm({
|
|
2318
|
+
"src/providers/gemini.ts"() {
|
|
2319
|
+
"use strict";
|
|
2320
|
+
init_base_provider();
|
|
2321
|
+
init_constants2();
|
|
2322
|
+
init_gemini_models();
|
|
2323
|
+
init_utils();
|
|
2324
|
+
GEMINI_ROLE_MAP = {
|
|
2325
|
+
system: "user",
|
|
2326
|
+
user: "user",
|
|
2327
|
+
assistant: "model"
|
|
2328
|
+
};
|
|
2329
|
+
GeminiGenerativeProvider = class extends BaseProviderAdapter {
|
|
2330
|
+
providerId = "gemini";
|
|
2331
|
+
supports(descriptor) {
|
|
2332
|
+
return descriptor.provider === this.providerId;
|
|
2333
|
+
}
|
|
2334
|
+
getModelSpecs() {
|
|
2335
|
+
return GEMINI_MODELS;
|
|
2336
|
+
}
|
|
2337
|
+
buildRequestPayload(options, descriptor, _spec, messages) {
|
|
2338
|
+
const { systemInstruction, contents } = this.extractSystemAndContents(messages);
|
|
2339
|
+
const generationConfig = this.buildGenerationConfig(options);
|
|
2340
|
+
const config = {
|
|
2341
|
+
...systemInstruction ? { systemInstruction: systemInstruction.parts.map((p) => p.text).join("\n") } : {},
|
|
2342
|
+
...generationConfig ? { ...generationConfig } : {},
|
|
2343
|
+
// Explicitly disable function calling to prevent UNEXPECTED_TOOL_CALL errors
|
|
2344
|
+
toolConfig: {
|
|
2345
|
+
functionCallingConfig: {
|
|
2346
|
+
mode: FunctionCallingConfigMode.NONE
|
|
2347
|
+
}
|
|
2348
|
+
},
|
|
2349
|
+
...options.extra
|
|
2350
|
+
};
|
|
2351
|
+
return {
|
|
2352
|
+
model: descriptor.name,
|
|
2353
|
+
contents: this.convertContentsForNewSDK(contents),
|
|
2354
|
+
config
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
async executeStreamRequest(payload) {
|
|
2358
|
+
const client = this.client;
|
|
2359
|
+
const streamResponse = await client.models.generateContentStream(payload);
|
|
2360
|
+
return streamResponse;
|
|
2361
|
+
}
|
|
2362
|
+
extractSystemAndContents(messages) {
|
|
2363
|
+
const firstSystemIndex = messages.findIndex((message) => message.role === "system");
|
|
2364
|
+
if (firstSystemIndex === -1) {
|
|
2365
|
+
return {
|
|
2366
|
+
systemInstruction: null,
|
|
2367
|
+
contents: this.mergeConsecutiveMessages(messages)
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
let systemBlockEnd = firstSystemIndex;
|
|
2371
|
+
while (systemBlockEnd < messages.length && messages[systemBlockEnd].role === "system") {
|
|
2372
|
+
systemBlockEnd++;
|
|
2373
|
+
}
|
|
2374
|
+
const systemMessages = messages.slice(firstSystemIndex, systemBlockEnd);
|
|
2375
|
+
const nonSystemMessages = [
|
|
2376
|
+
...messages.slice(0, firstSystemIndex),
|
|
2377
|
+
...messages.slice(systemBlockEnd)
|
|
2378
|
+
];
|
|
2379
|
+
const systemInstruction = {
|
|
2380
|
+
role: "system",
|
|
2381
|
+
parts: systemMessages.map((message) => ({ text: message.content }))
|
|
2382
|
+
};
|
|
2383
|
+
return {
|
|
2384
|
+
systemInstruction,
|
|
2385
|
+
contents: this.mergeConsecutiveMessages(nonSystemMessages)
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
mergeConsecutiveMessages(messages) {
|
|
2389
|
+
if (messages.length === 0) {
|
|
2390
|
+
return [];
|
|
2391
|
+
}
|
|
2392
|
+
const result = [];
|
|
2393
|
+
let currentGroup = null;
|
|
2394
|
+
for (const message of messages) {
|
|
2395
|
+
const geminiRole = GEMINI_ROLE_MAP[message.role];
|
|
2396
|
+
if (currentGroup && currentGroup.role === geminiRole) {
|
|
2397
|
+
currentGroup.parts.push({ text: message.content });
|
|
2398
|
+
} else {
|
|
2399
|
+
if (currentGroup) {
|
|
2400
|
+
result.push(currentGroup);
|
|
2401
|
+
}
|
|
2402
|
+
currentGroup = {
|
|
2403
|
+
role: geminiRole,
|
|
2404
|
+
parts: [{ text: message.content }]
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
if (currentGroup) {
|
|
2409
|
+
result.push(currentGroup);
|
|
2410
|
+
}
|
|
2411
|
+
return result;
|
|
2412
|
+
}
|
|
2413
|
+
convertContentsForNewSDK(contents) {
|
|
2414
|
+
return contents.map((content) => ({
|
|
2415
|
+
role: content.role,
|
|
2416
|
+
parts: content.parts.map((part) => ({ text: part.text }))
|
|
2417
|
+
}));
|
|
2418
|
+
}
|
|
2419
|
+
buildGenerationConfig(options) {
|
|
2420
|
+
const config = {};
|
|
2421
|
+
if (typeof options.maxTokens === "number") {
|
|
2422
|
+
config.maxOutputTokens = options.maxTokens;
|
|
2423
|
+
}
|
|
2424
|
+
if (typeof options.temperature === "number") {
|
|
2425
|
+
config.temperature = options.temperature;
|
|
2426
|
+
}
|
|
2427
|
+
if (typeof options.topP === "number") {
|
|
2428
|
+
config.topP = options.topP;
|
|
2429
|
+
}
|
|
2430
|
+
if (options.stopSequences?.length) {
|
|
2431
|
+
config.stopSequences = options.stopSequences;
|
|
2432
|
+
}
|
|
2433
|
+
return Object.keys(config).length > 0 ? config : null;
|
|
2434
|
+
}
|
|
2435
|
+
async *wrapStream(iterable) {
|
|
2436
|
+
const stream2 = iterable;
|
|
2437
|
+
for await (const chunk of stream2) {
|
|
2438
|
+
const text = this.extractText(chunk);
|
|
2439
|
+
if (text) {
|
|
2440
|
+
yield { text, rawEvent: chunk };
|
|
2441
|
+
}
|
|
2442
|
+
const finishReason = this.extractFinishReason(chunk);
|
|
2443
|
+
const usage = this.extractUsage(chunk);
|
|
2444
|
+
if (finishReason || usage) {
|
|
2445
|
+
yield { text: "", finishReason, usage, rawEvent: chunk };
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
extractText(chunk) {
|
|
2450
|
+
if (!chunk?.candidates) {
|
|
2451
|
+
return "";
|
|
2452
|
+
}
|
|
2453
|
+
return chunk.candidates.flatMap((candidate) => candidate.content?.parts ?? []).map((part) => part.text ?? "").join("");
|
|
2454
|
+
}
|
|
2455
|
+
extractFinishReason(chunk) {
|
|
2456
|
+
const candidate = chunk?.candidates?.find((item) => item.finishReason);
|
|
2457
|
+
return candidate?.finishReason ?? null;
|
|
2458
|
+
}
|
|
2459
|
+
extractUsage(chunk) {
|
|
2460
|
+
const usageMetadata = chunk?.usageMetadata;
|
|
2461
|
+
if (!usageMetadata) {
|
|
2462
|
+
return void 0;
|
|
2463
|
+
}
|
|
2464
|
+
return {
|
|
2465
|
+
inputTokens: usageMetadata.promptTokenCount ?? 0,
|
|
2466
|
+
outputTokens: usageMetadata.candidatesTokenCount ?? 0,
|
|
2467
|
+
totalTokens: usageMetadata.totalTokenCount ?? 0
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Count tokens in messages using Gemini's native token counting API.
|
|
2472
|
+
*
|
|
2473
|
+
* This method provides accurate token estimation for Gemini models by:
|
|
2474
|
+
* - Using the SDK's countTokens() method
|
|
2475
|
+
* - Properly extracting and handling system instructions
|
|
2476
|
+
* - Transforming messages to Gemini's expected format
|
|
2477
|
+
*
|
|
2478
|
+
* @param messages - The messages to count tokens for
|
|
2479
|
+
* @param descriptor - Model descriptor containing the model name
|
|
2480
|
+
* @param _spec - Optional model specification (currently unused)
|
|
2481
|
+
* @returns Promise resolving to the estimated input token count
|
|
2482
|
+
*
|
|
2483
|
+
* @throws Never throws - falls back to character-based estimation (4 chars/token) on error
|
|
2484
|
+
*
|
|
2485
|
+
* @example
|
|
2486
|
+
* ```typescript
|
|
2487
|
+
* const count = await provider.countTokens(
|
|
2488
|
+
* [{ role: "user", content: "Hello!" }],
|
|
2489
|
+
* { provider: "gemini", name: "gemini-1.5-pro" }
|
|
2490
|
+
* );
|
|
2491
|
+
* ```
|
|
2492
|
+
*/
|
|
2493
|
+
async countTokens(messages, descriptor, _spec) {
|
|
2494
|
+
const client = this.client;
|
|
2495
|
+
const { systemInstruction, contents } = this.extractSystemAndContents(messages);
|
|
2496
|
+
const request = {
|
|
2497
|
+
model: descriptor.name,
|
|
2498
|
+
contents: this.convertContentsForNewSDK(contents)
|
|
2499
|
+
};
|
|
2500
|
+
if (systemInstruction) {
|
|
2501
|
+
request.systemInstruction = systemInstruction.parts.map((p) => p.text).join("\n");
|
|
2502
|
+
}
|
|
2503
|
+
try {
|
|
2504
|
+
const response = await client.models.countTokens(request);
|
|
2505
|
+
return response.totalTokens ?? 0;
|
|
2506
|
+
} catch (error) {
|
|
2507
|
+
console.warn(
|
|
2508
|
+
`Token counting failed for ${descriptor.name}, using fallback estimation:`,
|
|
2509
|
+
error
|
|
2510
|
+
);
|
|
2511
|
+
const totalChars = messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0);
|
|
2512
|
+
return Math.ceil(totalChars / FALLBACK_CHARS_PER_TOKEN);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
});
|
|
2518
|
+
|
|
2519
|
+
// src/providers/openai-models.ts
|
|
2520
|
+
var OPENAI_MODELS;
|
|
2521
|
+
var init_openai_models = __esm({
|
|
2522
|
+
"src/providers/openai-models.ts"() {
|
|
2523
|
+
"use strict";
|
|
2524
|
+
OPENAI_MODELS = [
|
|
2525
|
+
{
|
|
2526
|
+
provider: "openai",
|
|
2527
|
+
modelId: "gpt-5.1",
|
|
2528
|
+
displayName: "GPT-5.1 Instant",
|
|
2529
|
+
contextWindow: 128e3,
|
|
2530
|
+
maxOutputTokens: 32768,
|
|
2531
|
+
pricing: {
|
|
2532
|
+
input: 1.25,
|
|
2533
|
+
output: 10,
|
|
2534
|
+
cachedInput: 0.125
|
|
2535
|
+
},
|
|
2536
|
+
knowledgeCutoff: "2024-09-30",
|
|
2537
|
+
features: {
|
|
2538
|
+
streaming: true,
|
|
2539
|
+
functionCalling: true,
|
|
2540
|
+
vision: true,
|
|
2541
|
+
reasoning: true,
|
|
2542
|
+
structuredOutputs: true,
|
|
2543
|
+
fineTuning: true
|
|
2544
|
+
},
|
|
2545
|
+
metadata: {
|
|
2546
|
+
family: "GPT-5",
|
|
2547
|
+
releaseDate: "2025-11-12",
|
|
2548
|
+
notes: "Warmer, more intelligent, better instruction following. 2-3x faster than GPT-5.",
|
|
2549
|
+
supportsTemperature: false
|
|
2550
|
+
}
|
|
2551
|
+
},
|
|
2552
|
+
{
|
|
2553
|
+
provider: "openai",
|
|
2554
|
+
modelId: "gpt-5.1-thinking",
|
|
2555
|
+
displayName: "GPT-5.1 Thinking",
|
|
2556
|
+
contextWindow: 196e3,
|
|
2557
|
+
maxOutputTokens: 32768,
|
|
2558
|
+
pricing: {
|
|
2559
|
+
input: 1.25,
|
|
2560
|
+
output: 10,
|
|
2561
|
+
cachedInput: 0.125
|
|
2562
|
+
},
|
|
2563
|
+
knowledgeCutoff: "2024-09-30",
|
|
2564
|
+
features: {
|
|
2565
|
+
streaming: true,
|
|
2566
|
+
functionCalling: true,
|
|
2567
|
+
vision: true,
|
|
2568
|
+
reasoning: true,
|
|
2569
|
+
structuredOutputs: true,
|
|
2570
|
+
fineTuning: true
|
|
2571
|
+
},
|
|
2572
|
+
metadata: {
|
|
2573
|
+
family: "GPT-5",
|
|
2574
|
+
releaseDate: "2025-11-12",
|
|
2575
|
+
notes: "Advanced reasoning with thinking levels: Light, Standard, Extended, Heavy. Best for complex tasks.",
|
|
2576
|
+
supportsTemperature: false
|
|
2577
|
+
}
|
|
2578
|
+
},
|
|
2579
|
+
{
|
|
2580
|
+
provider: "openai",
|
|
2581
|
+
modelId: "gpt-5",
|
|
2582
|
+
displayName: "GPT-5",
|
|
2583
|
+
contextWindow: 272e3,
|
|
2584
|
+
maxOutputTokens: 128e3,
|
|
2585
|
+
pricing: {
|
|
2586
|
+
input: 1.25,
|
|
2587
|
+
output: 10,
|
|
2588
|
+
cachedInput: 0.125
|
|
2589
|
+
},
|
|
2590
|
+
knowledgeCutoff: "2024-09-30",
|
|
2591
|
+
features: {
|
|
2592
|
+
streaming: true,
|
|
2593
|
+
functionCalling: true,
|
|
2594
|
+
vision: true,
|
|
2595
|
+
reasoning: true,
|
|
2596
|
+
structuredOutputs: true,
|
|
2597
|
+
fineTuning: true
|
|
2598
|
+
},
|
|
2599
|
+
metadata: {
|
|
2600
|
+
family: "GPT-5",
|
|
2601
|
+
releaseDate: "2025-08-07",
|
|
2602
|
+
notes: "Best model for coding and agentic tasks. Adaptive reasoning with 90% caching discount.",
|
|
2603
|
+
supportsTemperature: false
|
|
2604
|
+
}
|
|
2605
|
+
},
|
|
2606
|
+
{
|
|
2607
|
+
provider: "openai",
|
|
2608
|
+
modelId: "gpt-5-mini",
|
|
2609
|
+
displayName: "GPT-5 Mini",
|
|
2610
|
+
contextWindow: 272e3,
|
|
2611
|
+
maxOutputTokens: 32768,
|
|
2612
|
+
pricing: {
|
|
2613
|
+
input: 0.25,
|
|
2614
|
+
output: 2,
|
|
2615
|
+
cachedInput: 0.025
|
|
2616
|
+
},
|
|
2617
|
+
knowledgeCutoff: "2024-06-01",
|
|
2618
|
+
features: {
|
|
2619
|
+
streaming: true,
|
|
2620
|
+
functionCalling: true,
|
|
2621
|
+
vision: true,
|
|
2622
|
+
structuredOutputs: true,
|
|
2623
|
+
fineTuning: true
|
|
2624
|
+
},
|
|
2625
|
+
metadata: {
|
|
2626
|
+
family: "GPT-5",
|
|
2627
|
+
notes: "Fast and cost-efficient with adaptive reasoning",
|
|
2628
|
+
supportsTemperature: false
|
|
2629
|
+
}
|
|
2630
|
+
},
|
|
2631
|
+
{
|
|
2632
|
+
provider: "openai",
|
|
2633
|
+
modelId: "gpt-5-nano",
|
|
2634
|
+
displayName: "GPT-5 Nano",
|
|
2635
|
+
contextWindow: 272e3,
|
|
2636
|
+
maxOutputTokens: 32768,
|
|
2637
|
+
pricing: {
|
|
2638
|
+
input: 0.05,
|
|
2639
|
+
output: 0.4,
|
|
2640
|
+
cachedInput: 5e-3
|
|
2641
|
+
},
|
|
2642
|
+
knowledgeCutoff: "2024-05-31",
|
|
2643
|
+
features: {
|
|
2644
|
+
streaming: true,
|
|
2645
|
+
functionCalling: true,
|
|
2646
|
+
vision: true,
|
|
2647
|
+
structuredOutputs: true,
|
|
2648
|
+
fineTuning: true
|
|
2649
|
+
},
|
|
2650
|
+
metadata: {
|
|
2651
|
+
family: "GPT-5",
|
|
2652
|
+
notes: "Fastest, most cost-efficient version for well-defined tasks",
|
|
2653
|
+
supportsTemperature: false
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
];
|
|
2657
|
+
}
|
|
2658
|
+
});
|
|
2659
|
+
|
|
2660
|
+
// src/providers/openai.ts
|
|
2661
|
+
import OpenAI from "openai";
|
|
2662
|
+
import { encoding_for_model } from "tiktoken";
|
|
2663
|
+
function sanitizeExtra(extra, allowTemperature) {
|
|
2664
|
+
if (!extra) {
|
|
2665
|
+
return void 0;
|
|
2666
|
+
}
|
|
2667
|
+
if (allowTemperature || !Object.hasOwn(extra, "temperature")) {
|
|
2668
|
+
return extra;
|
|
2669
|
+
}
|
|
2670
|
+
return Object.fromEntries(Object.entries(extra).filter(([key]) => key !== "temperature"));
|
|
2671
|
+
}
|
|
2672
|
+
function createOpenAIProviderFromEnv() {
|
|
2673
|
+
return createProviderFromEnv("OPENAI_API_KEY", OpenAI, OpenAIChatProvider);
|
|
2674
|
+
}
|
|
2675
|
+
var ROLE_MAP, OpenAIChatProvider;
|
|
2676
|
+
var init_openai = __esm({
|
|
2677
|
+
"src/providers/openai.ts"() {
|
|
2678
|
+
"use strict";
|
|
2679
|
+
init_base_provider();
|
|
2680
|
+
init_constants2();
|
|
2681
|
+
init_openai_models();
|
|
2682
|
+
init_utils();
|
|
2683
|
+
ROLE_MAP = {
|
|
2684
|
+
system: "system",
|
|
2685
|
+
user: "user",
|
|
2686
|
+
assistant: "assistant"
|
|
2687
|
+
};
|
|
2688
|
+
OpenAIChatProvider = class extends BaseProviderAdapter {
|
|
2689
|
+
providerId = "openai";
|
|
2690
|
+
supports(descriptor) {
|
|
2691
|
+
return descriptor.provider === this.providerId;
|
|
2692
|
+
}
|
|
2693
|
+
getModelSpecs() {
|
|
2694
|
+
return OPENAI_MODELS;
|
|
2695
|
+
}
|
|
2696
|
+
buildRequestPayload(options, descriptor, spec, messages) {
|
|
2697
|
+
const { maxTokens, temperature, topP, stopSequences, extra } = options;
|
|
2698
|
+
const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
|
|
2699
|
+
const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
|
|
2700
|
+
const sanitizedExtra = sanitizeExtra(extra, shouldIncludeTemperature);
|
|
2701
|
+
return {
|
|
2702
|
+
model: descriptor.name,
|
|
2703
|
+
messages: messages.map((message) => ({
|
|
2704
|
+
role: ROLE_MAP[message.role],
|
|
2705
|
+
content: message.content,
|
|
2706
|
+
name: message.name
|
|
2707
|
+
})),
|
|
2708
|
+
// Only set max_completion_tokens if explicitly provided
|
|
2709
|
+
// Otherwise let the API use "as much as fits" in the context window
|
|
2710
|
+
...maxTokens !== void 0 ? { max_completion_tokens: maxTokens } : {},
|
|
2711
|
+
top_p: topP,
|
|
2712
|
+
stop: stopSequences,
|
|
2713
|
+
stream: true,
|
|
2714
|
+
stream_options: { include_usage: true },
|
|
2715
|
+
...sanitizedExtra ?? {},
|
|
2716
|
+
...shouldIncludeTemperature ? { temperature } : {}
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
async executeStreamRequest(payload) {
|
|
2720
|
+
const client = this.client;
|
|
2721
|
+
const stream2 = await client.chat.completions.create(payload);
|
|
2722
|
+
return stream2;
|
|
2723
|
+
}
|
|
2724
|
+
async *wrapStream(iterable) {
|
|
2725
|
+
const stream2 = iterable;
|
|
2726
|
+
for await (const chunk of stream2) {
|
|
2727
|
+
const text = chunk.choices.map((choice) => choice.delta?.content ?? "").join("");
|
|
2728
|
+
if (text) {
|
|
2729
|
+
yield { text, rawEvent: chunk };
|
|
2730
|
+
}
|
|
2731
|
+
const finishReason = chunk.choices.find((choice) => choice.finish_reason)?.finish_reason;
|
|
2732
|
+
const usage = chunk.usage ? {
|
|
2733
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
2734
|
+
outputTokens: chunk.usage.completion_tokens,
|
|
2735
|
+
totalTokens: chunk.usage.total_tokens
|
|
2736
|
+
} : void 0;
|
|
2737
|
+
if (finishReason || usage) {
|
|
2738
|
+
yield { text: "", finishReason, usage, rawEvent: chunk };
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Count tokens in messages using OpenAI's tiktoken library.
|
|
2744
|
+
*
|
|
2745
|
+
* This method provides accurate token estimation for OpenAI models by:
|
|
2746
|
+
* - Using the model-specific tokenizer encoding
|
|
2747
|
+
* - Accounting for message formatting overhead
|
|
2748
|
+
* - Falling back to gpt-4o encoding for unknown models
|
|
2749
|
+
*
|
|
2750
|
+
* @param messages - The messages to count tokens for
|
|
2751
|
+
* @param descriptor - Model descriptor containing the model name
|
|
2752
|
+
* @param _spec - Optional model specification (currently unused)
|
|
2753
|
+
* @returns Promise resolving to the estimated input token count
|
|
2754
|
+
*
|
|
2755
|
+
* @throws Never throws - falls back to character-based estimation (4 chars/token) on error
|
|
2756
|
+
*
|
|
2757
|
+
* @example
|
|
2758
|
+
* ```typescript
|
|
2759
|
+
* const count = await provider.countTokens(
|
|
2760
|
+
* [{ role: "user", content: "Hello!" }],
|
|
2761
|
+
* { provider: "openai", name: "gpt-4" }
|
|
2762
|
+
* );
|
|
2763
|
+
* ```
|
|
2764
|
+
*/
|
|
2765
|
+
async countTokens(messages, descriptor, _spec) {
|
|
2766
|
+
try {
|
|
2767
|
+
const modelName = descriptor.name;
|
|
2768
|
+
let encoding;
|
|
2769
|
+
try {
|
|
2770
|
+
encoding = encoding_for_model(modelName);
|
|
2771
|
+
} catch {
|
|
2772
|
+
encoding = encoding_for_model("gpt-4o");
|
|
2773
|
+
}
|
|
2774
|
+
try {
|
|
2775
|
+
let tokenCount = 0;
|
|
2776
|
+
for (const message of messages) {
|
|
2777
|
+
tokenCount += OPENAI_MESSAGE_OVERHEAD_TOKENS;
|
|
2778
|
+
const roleText = ROLE_MAP[message.role];
|
|
2779
|
+
tokenCount += encoding.encode(roleText).length;
|
|
2780
|
+
tokenCount += encoding.encode(message.content ?? "").length;
|
|
2781
|
+
if (message.name) {
|
|
2782
|
+
tokenCount += encoding.encode(message.name).length;
|
|
2783
|
+
tokenCount += OPENAI_NAME_FIELD_OVERHEAD_TOKENS;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
tokenCount += OPENAI_REPLY_PRIMING_TOKENS;
|
|
2787
|
+
return tokenCount;
|
|
2788
|
+
} finally {
|
|
2789
|
+
encoding.free();
|
|
2790
|
+
}
|
|
2791
|
+
} catch (error) {
|
|
2792
|
+
console.warn(
|
|
2793
|
+
`Token counting failed for ${descriptor.name}, using fallback estimation:`,
|
|
2794
|
+
error
|
|
2795
|
+
);
|
|
2796
|
+
const totalChars = messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0);
|
|
2797
|
+
return Math.ceil(totalChars / FALLBACK_CHARS_PER_TOKEN);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
// src/providers/discovery.ts
|
|
2805
|
+
function discoverProviderAdapters() {
|
|
2806
|
+
const adapters = [];
|
|
2807
|
+
for (const discover of DISCOVERERS) {
|
|
2808
|
+
const adapter = discover();
|
|
2809
|
+
if (adapter) {
|
|
2810
|
+
adapters.push(adapter);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
return adapters;
|
|
2814
|
+
}
|
|
2815
|
+
var DISCOVERERS;
|
|
2816
|
+
var init_discovery = __esm({
|
|
2817
|
+
"src/providers/discovery.ts"() {
|
|
2818
|
+
"use strict";
|
|
2819
|
+
init_anthropic();
|
|
2820
|
+
init_gemini();
|
|
2821
|
+
init_openai();
|
|
2822
|
+
DISCOVERERS = [
|
|
2823
|
+
createOpenAIProviderFromEnv,
|
|
2824
|
+
createAnthropicProviderFromEnv,
|
|
2825
|
+
createGeminiProviderFromEnv
|
|
2826
|
+
];
|
|
2827
|
+
}
|
|
2828
|
+
});
|
|
2829
|
+
|
|
2830
|
+
// src/core/model-registry.ts
|
|
2831
|
+
var ModelRegistry;
|
|
2832
|
+
var init_model_registry = __esm({
|
|
2833
|
+
"src/core/model-registry.ts"() {
|
|
2834
|
+
"use strict";
|
|
2835
|
+
ModelRegistry = class {
|
|
2836
|
+
modelSpecs = [];
|
|
2837
|
+
providerMap = /* @__PURE__ */ new Map();
|
|
2838
|
+
/**
|
|
2839
|
+
* Register a provider and collect its model specifications
|
|
2840
|
+
*/
|
|
2841
|
+
registerProvider(provider) {
|
|
2842
|
+
const specs = provider.getModelSpecs?.() ?? [];
|
|
2843
|
+
if (specs.length > 0) {
|
|
2844
|
+
this.modelSpecs.push(...specs);
|
|
2845
|
+
this.providerMap.set(provider.providerId, specs);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Register a custom model specification at runtime
|
|
2850
|
+
*
|
|
2851
|
+
* Use this to add models that aren't in the built-in catalog, such as:
|
|
2852
|
+
* - Fine-tuned models with custom pricing
|
|
2853
|
+
* - New models not yet supported by llmist
|
|
2854
|
+
* - Custom deployments with different configurations
|
|
2855
|
+
*
|
|
2856
|
+
* @param spec - Complete model specification
|
|
2857
|
+
* @throws {Error} If spec is missing required fields
|
|
2858
|
+
*
|
|
2859
|
+
* @example
|
|
2860
|
+
* ```ts
|
|
2861
|
+
* client.modelRegistry.registerModel({
|
|
2862
|
+
* provider: "openai",
|
|
2863
|
+
* modelId: "ft:gpt-4o-2024-08-06:my-org:custom:abc123",
|
|
2864
|
+
* displayName: "My Fine-tuned GPT-4o",
|
|
2865
|
+
* contextWindow: 128_000,
|
|
2866
|
+
* maxOutputTokens: 16_384,
|
|
2867
|
+
* pricing: { input: 7.5, output: 30.0 },
|
|
2868
|
+
* knowledgeCutoff: "2024-08",
|
|
2869
|
+
* features: { streaming: true, functionCalling: true, vision: true }
|
|
2870
|
+
* });
|
|
2871
|
+
* ```
|
|
2872
|
+
*/
|
|
2873
|
+
registerModel(spec) {
|
|
2874
|
+
if (!spec.modelId || !spec.provider) {
|
|
2875
|
+
throw new Error("ModelSpec must have modelId and provider");
|
|
2876
|
+
}
|
|
2877
|
+
const existing = this.getModelSpec(spec.modelId);
|
|
2878
|
+
if (existing) {
|
|
2879
|
+
console.warn(
|
|
2880
|
+
`[llmist] Overwriting existing model spec for "${spec.modelId}". Previous: ${existing.displayName}, New: ${spec.displayName}`
|
|
2881
|
+
);
|
|
2882
|
+
const index = this.modelSpecs.findIndex((m) => m.modelId === spec.modelId);
|
|
2883
|
+
if (index !== -1) {
|
|
2884
|
+
this.modelSpecs.splice(index, 1);
|
|
2885
|
+
}
|
|
2886
|
+
const providerSpecs2 = this.providerMap.get(spec.provider);
|
|
2887
|
+
if (providerSpecs2) {
|
|
2888
|
+
const providerIndex = providerSpecs2.findIndex((m) => m.modelId === spec.modelId);
|
|
2889
|
+
if (providerIndex !== -1) {
|
|
2890
|
+
providerSpecs2.splice(providerIndex, 1);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
this.modelSpecs.push(spec);
|
|
2895
|
+
const providerSpecs = this.providerMap.get(spec.provider) ?? [];
|
|
2896
|
+
providerSpecs.push(spec);
|
|
2897
|
+
this.providerMap.set(spec.provider, providerSpecs);
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Register multiple custom model specifications at once
|
|
2901
|
+
*
|
|
2902
|
+
* @param specs - Array of complete model specifications
|
|
2903
|
+
*
|
|
2904
|
+
* @example
|
|
2905
|
+
* ```ts
|
|
2906
|
+
* client.modelRegistry.registerModels([
|
|
2907
|
+
* { provider: "openai", modelId: "gpt-5", ... },
|
|
2908
|
+
* { provider: "openai", modelId: "gpt-5-mini", ... }
|
|
2909
|
+
* ]);
|
|
2910
|
+
* ```
|
|
2911
|
+
*/
|
|
2912
|
+
registerModels(specs) {
|
|
2913
|
+
for (const spec of specs) {
|
|
2914
|
+
this.registerModel(spec);
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
/**
|
|
2918
|
+
* Get model specification by model ID
|
|
2919
|
+
* @param modelId - Full model identifier (e.g., 'gpt-5', 'claude-sonnet-4-5-20250929')
|
|
2920
|
+
* @returns ModelSpec if found, undefined otherwise
|
|
2921
|
+
*/
|
|
2922
|
+
getModelSpec(modelId) {
|
|
2923
|
+
return this.modelSpecs.find((model) => model.modelId === modelId);
|
|
2924
|
+
}
|
|
2925
|
+
/**
|
|
2926
|
+
* List all models, optionally filtered by provider
|
|
2927
|
+
* @param providerId - Optional provider ID to filter by (e.g., 'openai', 'anthropic')
|
|
2928
|
+
* @returns Array of ModelSpec objects
|
|
2929
|
+
*/
|
|
2930
|
+
listModels(providerId) {
|
|
2931
|
+
if (!providerId) {
|
|
2932
|
+
return [...this.modelSpecs];
|
|
2933
|
+
}
|
|
2934
|
+
return this.providerMap.get(providerId) ?? [];
|
|
2935
|
+
}
|
|
2936
|
+
/**
|
|
2937
|
+
* Get context window and output limits for a model
|
|
2938
|
+
* @param modelId - Full model identifier
|
|
2939
|
+
* @returns ModelLimits if model found, undefined otherwise
|
|
2940
|
+
*/
|
|
2941
|
+
getModelLimits(modelId) {
|
|
2942
|
+
const spec = this.getModelSpec(modelId);
|
|
2943
|
+
if (!spec) return void 0;
|
|
2944
|
+
return {
|
|
2945
|
+
contextWindow: spec.contextWindow,
|
|
2946
|
+
maxOutputTokens: spec.maxOutputTokens
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
/**
|
|
2950
|
+
* Estimate API cost for a given model and token usage
|
|
2951
|
+
* @param modelId - Full model identifier
|
|
2952
|
+
* @param inputTokens - Number of input tokens
|
|
2953
|
+
* @param outputTokens - Number of output tokens
|
|
2954
|
+
* @param useCachedInput - Whether to use cached input pricing (if supported by provider)
|
|
2955
|
+
* @returns CostEstimate if model found, undefined otherwise
|
|
2956
|
+
*/
|
|
2957
|
+
estimateCost(modelId, inputTokens, outputTokens, useCachedInput = false) {
|
|
2958
|
+
const spec = this.getModelSpec(modelId);
|
|
2959
|
+
if (!spec) return void 0;
|
|
2960
|
+
const inputRate = useCachedInput && spec.pricing.cachedInput !== void 0 ? spec.pricing.cachedInput : spec.pricing.input;
|
|
2961
|
+
const inputCost = inputTokens / 1e6 * inputRate;
|
|
2962
|
+
const outputCost = outputTokens / 1e6 * spec.pricing.output;
|
|
2963
|
+
const totalCost = inputCost + outputCost;
|
|
2964
|
+
return {
|
|
2965
|
+
inputCost,
|
|
2966
|
+
outputCost,
|
|
2967
|
+
totalCost,
|
|
2968
|
+
currency: "USD"
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Validate that requested token count fits within model limits
|
|
2973
|
+
* @param modelId - Full model identifier
|
|
2974
|
+
* @param requestedTokens - Total tokens requested (input + output)
|
|
2975
|
+
* @returns true if valid, false if model not found or exceeds limits
|
|
2976
|
+
*/
|
|
2977
|
+
validateModelConfig(modelId, requestedTokens) {
|
|
2978
|
+
const limits = this.getModelLimits(modelId);
|
|
2979
|
+
if (!limits) return false;
|
|
2980
|
+
return requestedTokens <= limits.contextWindow;
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Check if a model supports a specific feature
|
|
2984
|
+
* @param modelId - Full model identifier
|
|
2985
|
+
* @param feature - Feature to check ('streaming', 'functionCalling', 'vision', etc.)
|
|
2986
|
+
* @returns true if model supports feature, false otherwise
|
|
2987
|
+
*/
|
|
2988
|
+
supportsFeature(modelId, feature) {
|
|
2989
|
+
const spec = this.getModelSpec(modelId);
|
|
2990
|
+
if (!spec) return false;
|
|
2991
|
+
return spec.features[feature] === true;
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Get all models that support a specific feature
|
|
2995
|
+
* @param feature - Feature to filter by
|
|
2996
|
+
* @param providerId - Optional provider ID to filter by
|
|
2997
|
+
* @returns Array of ModelSpec objects that support the feature
|
|
2998
|
+
*/
|
|
2999
|
+
getModelsByFeature(feature, providerId) {
|
|
3000
|
+
const models = this.listModels(providerId);
|
|
3001
|
+
return models.filter((model) => model.features[feature] === true);
|
|
3002
|
+
}
|
|
3003
|
+
/**
|
|
3004
|
+
* Get the most cost-effective model for a given provider and token budget
|
|
3005
|
+
* @param inputTokens - Expected input tokens
|
|
3006
|
+
* @param outputTokens - Expected output tokens
|
|
3007
|
+
* @param providerId - Optional provider ID to filter by
|
|
3008
|
+
* @returns ModelSpec with lowest total cost, or undefined if no models found
|
|
3009
|
+
*/
|
|
3010
|
+
getCheapestModel(inputTokens, outputTokens, providerId) {
|
|
3011
|
+
const models = this.listModels(providerId);
|
|
3012
|
+
if (models.length === 0) return void 0;
|
|
3013
|
+
let cheapest;
|
|
3014
|
+
for (const model of models) {
|
|
3015
|
+
const estimate = this.estimateCost(model.modelId, inputTokens, outputTokens);
|
|
3016
|
+
if (!estimate) continue;
|
|
3017
|
+
if (!cheapest || estimate.totalCost < cheapest.cost) {
|
|
3018
|
+
cheapest = { model, cost: estimate.totalCost };
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
return cheapest?.model;
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
});
|
|
3026
|
+
|
|
3027
|
+
// src/core/options.ts
|
|
3028
|
+
var ModelIdentifierParser;
|
|
3029
|
+
var init_options = __esm({
|
|
3030
|
+
"src/core/options.ts"() {
|
|
3031
|
+
"use strict";
|
|
3032
|
+
ModelIdentifierParser = class {
|
|
3033
|
+
constructor(defaultProvider = "openai") {
|
|
3034
|
+
this.defaultProvider = defaultProvider;
|
|
3035
|
+
}
|
|
3036
|
+
parse(identifier) {
|
|
3037
|
+
const trimmed = identifier.trim();
|
|
3038
|
+
if (!trimmed) {
|
|
3039
|
+
throw new Error("Model identifier cannot be empty");
|
|
3040
|
+
}
|
|
3041
|
+
const [maybeProvider, ...rest] = trimmed.split(":");
|
|
3042
|
+
if (rest.length === 0) {
|
|
3043
|
+
return { provider: this.defaultProvider, name: maybeProvider };
|
|
3044
|
+
}
|
|
3045
|
+
const provider = maybeProvider;
|
|
3046
|
+
const name = rest.join(":");
|
|
3047
|
+
if (!name) {
|
|
3048
|
+
throw new Error("Model name cannot be empty");
|
|
3049
|
+
}
|
|
3050
|
+
return { provider, name };
|
|
3051
|
+
}
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
});
|
|
3055
|
+
|
|
3056
|
+
// src/core/quick-methods.ts
|
|
3057
|
+
async function complete(client, prompt, options = {}) {
|
|
3058
|
+
const model = resolveModel(options.model ?? "gpt-5-nano");
|
|
3059
|
+
const builder = new LLMMessageBuilder();
|
|
3060
|
+
if (options.systemPrompt) {
|
|
3061
|
+
builder.addSystem(options.systemPrompt);
|
|
3062
|
+
}
|
|
3063
|
+
builder.addUser(prompt);
|
|
3064
|
+
let fullResponse = "";
|
|
3065
|
+
for await (const chunk of client.stream({
|
|
3066
|
+
model,
|
|
3067
|
+
messages: builder.build(),
|
|
3068
|
+
temperature: options.temperature,
|
|
3069
|
+
maxTokens: options.maxTokens
|
|
3070
|
+
})) {
|
|
3071
|
+
fullResponse += chunk.text;
|
|
3072
|
+
}
|
|
3073
|
+
return fullResponse.trim();
|
|
3074
|
+
}
|
|
3075
|
+
async function* stream(client, prompt, options = {}) {
|
|
3076
|
+
const model = resolveModel(options.model ?? "gpt-5-nano");
|
|
3077
|
+
const builder = new LLMMessageBuilder();
|
|
3078
|
+
if (options.systemPrompt) {
|
|
3079
|
+
builder.addSystem(options.systemPrompt);
|
|
3080
|
+
}
|
|
3081
|
+
builder.addUser(prompt);
|
|
3082
|
+
for await (const chunk of client.stream({
|
|
3083
|
+
model,
|
|
3084
|
+
messages: builder.build(),
|
|
3085
|
+
temperature: options.temperature,
|
|
3086
|
+
maxTokens: options.maxTokens
|
|
3087
|
+
})) {
|
|
3088
|
+
yield chunk.text;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
var init_quick_methods = __esm({
|
|
3092
|
+
"src/core/quick-methods.ts"() {
|
|
3093
|
+
"use strict";
|
|
3094
|
+
init_messages();
|
|
3095
|
+
init_model_shortcuts();
|
|
3096
|
+
}
|
|
3097
|
+
});
|
|
3098
|
+
|
|
3099
|
+
// src/agent/agent-internal-key.ts
|
|
3100
|
+
function isValidAgentKey(key) {
|
|
3101
|
+
return key === AGENT_INTERNAL_KEY;
|
|
3102
|
+
}
|
|
3103
|
+
var AGENT_INTERNAL_KEY;
|
|
3104
|
+
var init_agent_internal_key = __esm({
|
|
3105
|
+
"src/agent/agent-internal-key.ts"() {
|
|
3106
|
+
"use strict";
|
|
3107
|
+
AGENT_INTERNAL_KEY = Symbol("AGENT_INTERNAL_KEY");
|
|
3108
|
+
}
|
|
3109
|
+
});
|
|
3110
|
+
|
|
3111
|
+
// src/agent/agent.ts
|
|
3112
|
+
var Agent;
|
|
3113
|
+
var init_agent = __esm({
|
|
3114
|
+
"src/agent/agent.ts"() {
|
|
3115
|
+
"use strict";
|
|
3116
|
+
init_messages();
|
|
3117
|
+
init_model_shortcuts();
|
|
3118
|
+
init_logger();
|
|
3119
|
+
init_agent_internal_key();
|
|
3120
|
+
init_conversation_manager();
|
|
3121
|
+
init_event_handlers();
|
|
3122
|
+
init_hook_validators();
|
|
3123
|
+
init_stream_processor();
|
|
3124
|
+
Agent = class {
|
|
3125
|
+
client;
|
|
3126
|
+
model;
|
|
3127
|
+
maxIterations;
|
|
3128
|
+
temperature;
|
|
3129
|
+
logger;
|
|
3130
|
+
hooks;
|
|
3131
|
+
conversation;
|
|
3132
|
+
registry;
|
|
3133
|
+
parameterFormat;
|
|
3134
|
+
gadgetStartPrefix;
|
|
3135
|
+
gadgetEndPrefix;
|
|
3136
|
+
onHumanInputRequired;
|
|
3137
|
+
textOnlyHandler;
|
|
3138
|
+
stopOnGadgetError;
|
|
3139
|
+
shouldContinueAfterError;
|
|
3140
|
+
defaultGadgetTimeoutMs;
|
|
3141
|
+
defaultMaxTokens;
|
|
3142
|
+
userPromptProvided;
|
|
3143
|
+
/**
|
|
3144
|
+
* Creates a new Agent instance.
|
|
3145
|
+
* @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
|
|
3146
|
+
*/
|
|
3147
|
+
constructor(key, options) {
|
|
3148
|
+
if (!isValidAgentKey(key)) {
|
|
3149
|
+
throw new Error(
|
|
3150
|
+
"Agent cannot be instantiated directly. Use LLMist.createAgent() or new AgentBuilder() instead."
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
this.client = options.client;
|
|
3154
|
+
this.model = resolveModel(options.model);
|
|
3155
|
+
this.maxIterations = options.maxIterations ?? 10;
|
|
3156
|
+
this.temperature = options.temperature;
|
|
3157
|
+
this.logger = options.logger ?? createLogger({ name: "llmist:agent" });
|
|
3158
|
+
this.hooks = options.hooks ?? {};
|
|
3159
|
+
this.registry = options.registry;
|
|
3160
|
+
this.parameterFormat = options.parameterFormat ?? "json";
|
|
3161
|
+
this.gadgetStartPrefix = options.gadgetStartPrefix;
|
|
3162
|
+
this.gadgetEndPrefix = options.gadgetEndPrefix;
|
|
3163
|
+
this.onHumanInputRequired = options.onHumanInputRequired;
|
|
3164
|
+
this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
|
|
3165
|
+
this.stopOnGadgetError = options.stopOnGadgetError ?? true;
|
|
3166
|
+
this.shouldContinueAfterError = options.shouldContinueAfterError;
|
|
3167
|
+
this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
|
|
3168
|
+
this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
|
|
3169
|
+
const baseBuilder = new LLMMessageBuilder(options.promptConfig);
|
|
3170
|
+
if (options.systemPrompt) {
|
|
3171
|
+
baseBuilder.addSystem(options.systemPrompt);
|
|
3172
|
+
}
|
|
3173
|
+
baseBuilder.addGadgets(this.registry.getAll(), this.parameterFormat, {
|
|
3174
|
+
startPrefix: options.gadgetStartPrefix,
|
|
3175
|
+
endPrefix: options.gadgetEndPrefix
|
|
3176
|
+
});
|
|
3177
|
+
const baseMessages = baseBuilder.build();
|
|
3178
|
+
const initialMessages = (options.initialMessages ?? []).map((message) => ({
|
|
3179
|
+
role: message.role,
|
|
3180
|
+
content: message.content
|
|
3181
|
+
}));
|
|
3182
|
+
this.conversation = new ConversationManager(
|
|
3183
|
+
baseMessages,
|
|
3184
|
+
initialMessages,
|
|
3185
|
+
this.parameterFormat
|
|
3186
|
+
);
|
|
3187
|
+
this.userPromptProvided = !!options.userPrompt;
|
|
3188
|
+
if (options.userPrompt) {
|
|
3189
|
+
this.conversation.addUserMessage(options.userPrompt);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
/**
|
|
3193
|
+
* Get the gadget registry for this agent.
|
|
3194
|
+
*
|
|
3195
|
+
* Useful for inspecting registered gadgets in tests or advanced use cases.
|
|
3196
|
+
*
|
|
3197
|
+
* @returns The GadgetRegistry instance
|
|
3198
|
+
*
|
|
3199
|
+
* @example
|
|
3200
|
+
* ```typescript
|
|
3201
|
+
* const agent = new AgentBuilder()
|
|
3202
|
+
* .withModel("sonnet")
|
|
3203
|
+
* .withGadgets(Calculator, Weather)
|
|
3204
|
+
* .build();
|
|
3205
|
+
*
|
|
3206
|
+
* // Inspect registered gadgets
|
|
3207
|
+
* console.log(agent.getRegistry().getNames()); // ['Calculator', 'Weather']
|
|
3208
|
+
* ```
|
|
3209
|
+
*/
|
|
3210
|
+
getRegistry() {
|
|
3211
|
+
return this.registry;
|
|
3212
|
+
}
|
|
3213
|
+
/**
|
|
3214
|
+
* Run the agent loop.
|
|
3215
|
+
* Clean, simple orchestration - all complexity is in StreamProcessor.
|
|
3216
|
+
*
|
|
3217
|
+
* @throws {Error} If no user prompt was provided (when using build() without ask())
|
|
3218
|
+
*/
|
|
3219
|
+
async *run() {
|
|
3220
|
+
if (!this.userPromptProvided) {
|
|
3221
|
+
throw new Error(
|
|
3222
|
+
"No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
|
|
3223
|
+
);
|
|
3224
|
+
}
|
|
3225
|
+
let currentIteration = 0;
|
|
3226
|
+
this.logger.info("Starting agent loop", {
|
|
3227
|
+
model: this.model,
|
|
3228
|
+
maxIterations: this.maxIterations
|
|
3229
|
+
});
|
|
3230
|
+
while (currentIteration < this.maxIterations) {
|
|
3231
|
+
this.logger.debug("Starting iteration", { iteration: currentIteration });
|
|
3232
|
+
try {
|
|
3233
|
+
let llmOptions = {
|
|
3234
|
+
model: this.model,
|
|
3235
|
+
messages: this.conversation.getMessages(),
|
|
3236
|
+
temperature: this.temperature,
|
|
3237
|
+
maxTokens: this.defaultMaxTokens
|
|
3238
|
+
};
|
|
3239
|
+
await this.safeObserve(async () => {
|
|
3240
|
+
if (this.hooks.observers?.onLLMCallStart) {
|
|
3241
|
+
const context = {
|
|
3242
|
+
iteration: currentIteration,
|
|
3243
|
+
options: llmOptions,
|
|
3244
|
+
logger: this.logger
|
|
3245
|
+
};
|
|
3246
|
+
await this.hooks.observers.onLLMCallStart(context);
|
|
3247
|
+
}
|
|
3248
|
+
});
|
|
3249
|
+
if (this.hooks.controllers?.beforeLLMCall) {
|
|
3250
|
+
const context = {
|
|
3251
|
+
iteration: currentIteration,
|
|
3252
|
+
options: llmOptions,
|
|
3253
|
+
logger: this.logger
|
|
3254
|
+
};
|
|
3255
|
+
const action = await this.hooks.controllers.beforeLLMCall(context);
|
|
3256
|
+
validateBeforeLLMCallAction(action);
|
|
3257
|
+
if (action.action === "skip") {
|
|
3258
|
+
this.logger.info("Controller skipped LLM call, using synthetic response");
|
|
3259
|
+
this.conversation.addAssistantMessage(action.syntheticResponse);
|
|
3260
|
+
yield { type: "text", content: action.syntheticResponse };
|
|
3261
|
+
break;
|
|
3262
|
+
} else if (action.action === "proceed" && action.modifiedOptions) {
|
|
3263
|
+
llmOptions = { ...llmOptions, ...action.modifiedOptions };
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
this.logger.info("Calling LLM", { model: this.model });
|
|
3267
|
+
this.logger.silly("LLM request details", {
|
|
3268
|
+
model: llmOptions.model,
|
|
3269
|
+
temperature: llmOptions.temperature,
|
|
3270
|
+
maxTokens: llmOptions.maxTokens,
|
|
3271
|
+
messageCount: llmOptions.messages.length,
|
|
3272
|
+
messages: llmOptions.messages
|
|
3273
|
+
});
|
|
3274
|
+
const stream2 = this.client.stream(llmOptions);
|
|
3275
|
+
const processor = new StreamProcessor({
|
|
3276
|
+
iteration: currentIteration,
|
|
3277
|
+
registry: this.registry,
|
|
3278
|
+
parameterFormat: this.parameterFormat,
|
|
3279
|
+
gadgetStartPrefix: this.gadgetStartPrefix,
|
|
3280
|
+
gadgetEndPrefix: this.gadgetEndPrefix,
|
|
3281
|
+
hooks: this.hooks,
|
|
3282
|
+
logger: this.logger.getSubLogger({ name: "stream-processor" }),
|
|
3283
|
+
onHumanInputRequired: this.onHumanInputRequired,
|
|
3284
|
+
stopOnGadgetError: this.stopOnGadgetError,
|
|
3285
|
+
shouldContinueAfterError: this.shouldContinueAfterError,
|
|
3286
|
+
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
|
|
3287
|
+
});
|
|
3288
|
+
const result = await processor.process(stream2);
|
|
3289
|
+
for (const output of result.outputs) {
|
|
3290
|
+
yield output;
|
|
3291
|
+
}
|
|
3292
|
+
this.logger.info("LLM response completed", {
|
|
3293
|
+
finishReason: result.finishReason,
|
|
3294
|
+
usage: result.usage,
|
|
3295
|
+
didExecuteGadgets: result.didExecuteGadgets
|
|
3296
|
+
});
|
|
3297
|
+
this.logger.silly("LLM response details", {
|
|
3298
|
+
rawResponse: result.rawResponse
|
|
3299
|
+
});
|
|
3300
|
+
await this.safeObserve(async () => {
|
|
3301
|
+
if (this.hooks.observers?.onLLMCallComplete) {
|
|
3302
|
+
const context = {
|
|
3303
|
+
iteration: currentIteration,
|
|
3304
|
+
options: llmOptions,
|
|
3305
|
+
finishReason: result.finishReason,
|
|
3306
|
+
usage: result.usage,
|
|
3307
|
+
rawResponse: result.rawResponse,
|
|
3308
|
+
finalMessage: result.finalMessage,
|
|
3309
|
+
logger: this.logger
|
|
3310
|
+
};
|
|
3311
|
+
await this.hooks.observers.onLLMCallComplete(context);
|
|
3312
|
+
}
|
|
3313
|
+
});
|
|
3314
|
+
let finalMessage = result.finalMessage;
|
|
3315
|
+
if (this.hooks.controllers?.afterLLMCall) {
|
|
3316
|
+
const context = {
|
|
3317
|
+
iteration: currentIteration,
|
|
3318
|
+
options: llmOptions,
|
|
3319
|
+
finishReason: result.finishReason,
|
|
3320
|
+
usage: result.usage,
|
|
3321
|
+
finalMessage: result.finalMessage,
|
|
3322
|
+
logger: this.logger
|
|
3323
|
+
};
|
|
3324
|
+
const action = await this.hooks.controllers.afterLLMCall(context);
|
|
3325
|
+
validateAfterLLMCallAction(action);
|
|
3326
|
+
if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
|
|
3327
|
+
finalMessage = action.modifiedMessage;
|
|
3328
|
+
}
|
|
3329
|
+
if (action.action === "append_messages" || action.action === "append_and_modify") {
|
|
3330
|
+
for (const msg of action.messages) {
|
|
3331
|
+
if (msg.role === "user") {
|
|
3332
|
+
this.conversation.addUserMessage(msg.content);
|
|
3333
|
+
} else if (msg.role === "assistant") {
|
|
3334
|
+
this.conversation.addAssistantMessage(msg.content);
|
|
3335
|
+
} else if (msg.role === "system") {
|
|
3336
|
+
this.conversation.addUserMessage(`[System] ${msg.content}`);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
if (result.didExecuteGadgets) {
|
|
3342
|
+
for (const output of result.outputs) {
|
|
3343
|
+
if (output.type === "gadget_result") {
|
|
3344
|
+
const gadgetResult = output.result;
|
|
3345
|
+
this.conversation.addGadgetCall(
|
|
3346
|
+
gadgetResult.gadgetName,
|
|
3347
|
+
gadgetResult.parameters,
|
|
3348
|
+
gadgetResult.error ?? gadgetResult.result ?? ""
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
} else {
|
|
3353
|
+
this.conversation.addAssistantMessage(finalMessage);
|
|
3354
|
+
const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
|
|
3355
|
+
if (shouldBreak) {
|
|
3356
|
+
break;
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
if (result.shouldBreakLoop) {
|
|
3360
|
+
this.logger.info("Loop terminated by gadget or processor");
|
|
3361
|
+
break;
|
|
3362
|
+
}
|
|
3363
|
+
} catch (error) {
|
|
3364
|
+
const errorHandled = await this.handleLLMError(error, currentIteration);
|
|
3365
|
+
await this.safeObserve(async () => {
|
|
3366
|
+
if (this.hooks.observers?.onLLMCallError) {
|
|
3367
|
+
const context = {
|
|
3368
|
+
iteration: currentIteration,
|
|
3369
|
+
options: {
|
|
3370
|
+
model: this.model,
|
|
3371
|
+
messages: this.conversation.getMessages(),
|
|
3372
|
+
temperature: this.temperature,
|
|
3373
|
+
maxTokens: this.defaultMaxTokens
|
|
3374
|
+
},
|
|
3375
|
+
error,
|
|
3376
|
+
recovered: errorHandled,
|
|
3377
|
+
logger: this.logger
|
|
3378
|
+
};
|
|
3379
|
+
await this.hooks.observers.onLLMCallError(context);
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3382
|
+
if (!errorHandled) {
|
|
3383
|
+
throw error;
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
currentIteration++;
|
|
3387
|
+
}
|
|
3388
|
+
this.logger.info("Agent loop completed", {
|
|
3389
|
+
totalIterations: currentIteration,
|
|
3390
|
+
reason: currentIteration >= this.maxIterations ? "max_iterations" : "natural_completion"
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
/**
|
|
3394
|
+
* Handle LLM error through controller.
|
|
3395
|
+
*/
|
|
3396
|
+
async handleLLMError(error, iteration) {
|
|
3397
|
+
this.logger.error("LLM call failed", { error: error.message });
|
|
3398
|
+
if (this.hooks.controllers?.afterLLMError) {
|
|
3399
|
+
const context = {
|
|
3400
|
+
iteration,
|
|
3401
|
+
options: {
|
|
3402
|
+
model: this.model,
|
|
3403
|
+
messages: this.conversation.getMessages(),
|
|
3404
|
+
temperature: this.temperature,
|
|
3405
|
+
maxTokens: this.defaultMaxTokens
|
|
3406
|
+
},
|
|
3407
|
+
error,
|
|
3408
|
+
logger: this.logger
|
|
3409
|
+
};
|
|
3410
|
+
const action = await this.hooks.controllers.afterLLMError(context);
|
|
3411
|
+
validateAfterLLMErrorAction(action);
|
|
3412
|
+
if (action.action === "recover") {
|
|
3413
|
+
this.logger.info("Controller recovered from LLM error");
|
|
3414
|
+
this.conversation.addAssistantMessage(action.fallbackResponse);
|
|
3415
|
+
return true;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
return false;
|
|
3419
|
+
}
|
|
3420
|
+
/**
|
|
3421
|
+
* Handle text-only response (no gadgets called).
|
|
3422
|
+
*/
|
|
3423
|
+
async handleTextOnlyResponse(_text) {
|
|
3424
|
+
const handler = this.textOnlyHandler;
|
|
3425
|
+
if (typeof handler === "string") {
|
|
3426
|
+
switch (handler) {
|
|
3427
|
+
case "terminate":
|
|
3428
|
+
this.logger.info("No gadgets called, ending loop");
|
|
3429
|
+
return true;
|
|
3430
|
+
case "acknowledge":
|
|
3431
|
+
this.logger.info("No gadgets called, continuing loop");
|
|
3432
|
+
return false;
|
|
3433
|
+
case "wait_for_input":
|
|
3434
|
+
this.logger.info("No gadgets called, waiting for input");
|
|
3435
|
+
return true;
|
|
3436
|
+
default:
|
|
3437
|
+
this.logger.warn(`Unknown text-only strategy: ${handler}, defaulting to terminate`);
|
|
3438
|
+
return true;
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
return true;
|
|
3442
|
+
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Safely execute an observer, catching and logging any errors.
|
|
3445
|
+
*/
|
|
3446
|
+
async safeObserve(fn) {
|
|
3447
|
+
try {
|
|
3448
|
+
await fn();
|
|
3449
|
+
} catch (error) {
|
|
3450
|
+
this.logger.error("Observer threw error (ignoring)", {
|
|
3451
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
/**
|
|
3456
|
+
* Resolve max tokens from model catalog.
|
|
3457
|
+
*/
|
|
3458
|
+
resolveMaxTokensFromCatalog(modelId) {
|
|
3459
|
+
const limits = this.client.modelRegistry.getModelLimits(modelId);
|
|
3460
|
+
if (limits?.maxOutputTokens !== void 0) {
|
|
3461
|
+
return limits.maxOutputTokens;
|
|
3462
|
+
}
|
|
3463
|
+
const separatorIndex = modelId.indexOf(":");
|
|
3464
|
+
if (separatorIndex === -1) {
|
|
3465
|
+
return void 0;
|
|
3466
|
+
}
|
|
3467
|
+
const unprefixedModelId = modelId.slice(separatorIndex + 1).trim();
|
|
3468
|
+
if (!unprefixedModelId) {
|
|
3469
|
+
return void 0;
|
|
3470
|
+
}
|
|
3471
|
+
return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
|
|
3472
|
+
}
|
|
3473
|
+
/**
|
|
3474
|
+
* Run agent with named event handlers (syntactic sugar).
|
|
3475
|
+
*
|
|
3476
|
+
* Instead of verbose if/else chains, use named handlers for cleaner code.
|
|
3477
|
+
*
|
|
3478
|
+
* @param handlers - Named event handlers
|
|
3479
|
+
*
|
|
3480
|
+
* @example
|
|
3481
|
+
* ```typescript
|
|
3482
|
+
* await agent.runWith({
|
|
3483
|
+
* onText: (text) => console.log("LLM:", text),
|
|
3484
|
+
* onGadgetResult: (result) => console.log("Result:", result.result),
|
|
3485
|
+
* onGadgetCall: (call) => console.log("Calling:", call.gadgetName),
|
|
3486
|
+
* });
|
|
3487
|
+
* ```
|
|
3488
|
+
*/
|
|
3489
|
+
async runWith(handlers) {
|
|
3490
|
+
return runWithHandlers(this.run(), handlers);
|
|
3491
|
+
}
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3494
|
+
});
|
|
3495
|
+
|
|
3496
|
+
// src/agent/builder.ts
|
|
3497
|
+
var AgentBuilder;
|
|
3498
|
+
var init_builder = __esm({
|
|
3499
|
+
"src/agent/builder.ts"() {
|
|
3500
|
+
"use strict";
|
|
3501
|
+
init_model_shortcuts();
|
|
3502
|
+
init_registry();
|
|
3503
|
+
init_agent();
|
|
3504
|
+
init_agent_internal_key();
|
|
3505
|
+
init_event_handlers();
|
|
3506
|
+
AgentBuilder = class {
|
|
3507
|
+
client;
|
|
3508
|
+
model;
|
|
3509
|
+
systemPrompt;
|
|
3510
|
+
temperature;
|
|
3511
|
+
maxIterations;
|
|
3512
|
+
logger;
|
|
3513
|
+
hooks;
|
|
3514
|
+
promptConfig;
|
|
3515
|
+
gadgets = [];
|
|
3516
|
+
initialMessages = [];
|
|
3517
|
+
onHumanInputRequired;
|
|
3518
|
+
parameterFormat;
|
|
3519
|
+
gadgetStartPrefix;
|
|
3520
|
+
gadgetEndPrefix;
|
|
3521
|
+
textOnlyHandler;
|
|
3522
|
+
stopOnGadgetError;
|
|
3523
|
+
shouldContinueAfterError;
|
|
3524
|
+
defaultGadgetTimeoutMs;
|
|
3525
|
+
constructor(client) {
|
|
3526
|
+
this.client = client;
|
|
3527
|
+
}
|
|
3528
|
+
/**
|
|
3529
|
+
* Set the model to use.
|
|
3530
|
+
* Supports aliases like "gpt4", "sonnet", "flash".
|
|
3531
|
+
*
|
|
3532
|
+
* @param model - Model name or alias
|
|
3533
|
+
* @returns This builder for chaining
|
|
3534
|
+
*
|
|
3535
|
+
* @example
|
|
3536
|
+
* ```typescript
|
|
3537
|
+
* .withModel("sonnet") // Alias
|
|
3538
|
+
* .withModel("gpt-5-nano") // Auto-detects provider
|
|
3539
|
+
* .withModel("openai:gpt-5") // Explicit provider
|
|
3540
|
+
* ```
|
|
3541
|
+
*/
|
|
3542
|
+
withModel(model) {
|
|
3543
|
+
this.model = resolveModel(model);
|
|
3544
|
+
return this;
|
|
3545
|
+
}
|
|
3546
|
+
/**
|
|
3547
|
+
* Set the system prompt.
|
|
3548
|
+
*
|
|
3549
|
+
* @param prompt - System prompt
|
|
3550
|
+
* @returns This builder for chaining
|
|
3551
|
+
*/
|
|
3552
|
+
withSystem(prompt) {
|
|
3553
|
+
this.systemPrompt = prompt;
|
|
3554
|
+
return this;
|
|
3555
|
+
}
|
|
3556
|
+
/**
|
|
3557
|
+
* Set the temperature (0-1).
|
|
3558
|
+
*
|
|
3559
|
+
* @param temperature - Temperature value
|
|
3560
|
+
* @returns This builder for chaining
|
|
3561
|
+
*/
|
|
3562
|
+
withTemperature(temperature) {
|
|
3563
|
+
this.temperature = temperature;
|
|
3564
|
+
return this;
|
|
3565
|
+
}
|
|
3566
|
+
/**
|
|
3567
|
+
* Set maximum iterations.
|
|
3568
|
+
*
|
|
3569
|
+
* @param max - Maximum number of iterations
|
|
3570
|
+
* @returns This builder for chaining
|
|
3571
|
+
*/
|
|
3572
|
+
withMaxIterations(max) {
|
|
3573
|
+
this.maxIterations = max;
|
|
3574
|
+
return this;
|
|
3575
|
+
}
|
|
3576
|
+
/**
|
|
3577
|
+
* Set logger instance.
|
|
3578
|
+
*
|
|
3579
|
+
* @param logger - Logger instance
|
|
3580
|
+
* @returns This builder for chaining
|
|
3581
|
+
*/
|
|
3582
|
+
withLogger(logger) {
|
|
3583
|
+
this.logger = logger;
|
|
3584
|
+
return this;
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
* Add hooks for agent lifecycle events.
|
|
3588
|
+
*
|
|
3589
|
+
* @param hooks - Agent hooks configuration
|
|
3590
|
+
* @returns This builder for chaining
|
|
3591
|
+
*
|
|
3592
|
+
* @example
|
|
3593
|
+
* ```typescript
|
|
3594
|
+
* import { HookPresets } from 'llmist/hooks';
|
|
3595
|
+
*
|
|
3596
|
+
* .withHooks(HookPresets.logging())
|
|
3597
|
+
* .withHooks(HookPresets.merge(
|
|
3598
|
+
* HookPresets.logging(),
|
|
3599
|
+
* HookPresets.timing()
|
|
3600
|
+
* ))
|
|
3601
|
+
* ```
|
|
3602
|
+
*/
|
|
3603
|
+
withHooks(hooks) {
|
|
3604
|
+
this.hooks = hooks;
|
|
3605
|
+
return this;
|
|
3606
|
+
}
|
|
3607
|
+
/**
|
|
3608
|
+
* Configure custom prompts for gadget system messages.
|
|
3609
|
+
*
|
|
3610
|
+
* @param config - Prompt configuration object
|
|
3611
|
+
* @returns This builder for chaining
|
|
3612
|
+
*
|
|
3613
|
+
* @example
|
|
3614
|
+
* ```typescript
|
|
3615
|
+
* .withPromptConfig({
|
|
3616
|
+
* mainInstruction: "Use the gadget markers below:",
|
|
3617
|
+
* rules: ["Always use markers", "Never use function calling"]
|
|
3618
|
+
* })
|
|
3619
|
+
* ```
|
|
3620
|
+
*/
|
|
3621
|
+
withPromptConfig(config) {
|
|
3622
|
+
this.promptConfig = config;
|
|
3623
|
+
return this;
|
|
3624
|
+
}
|
|
3625
|
+
/**
|
|
3626
|
+
* Add gadgets (classes or instances).
|
|
3627
|
+
* Can be called multiple times to add more gadgets.
|
|
3628
|
+
*
|
|
3629
|
+
* @param gadgets - Gadget classes or instances
|
|
3630
|
+
* @returns This builder for chaining
|
|
3631
|
+
*
|
|
3632
|
+
* @example
|
|
3633
|
+
* ```typescript
|
|
3634
|
+
* .withGadgets(Calculator, Weather, Email)
|
|
3635
|
+
* .withGadgets(new Calculator(), new Weather())
|
|
3636
|
+
* .withGadgets(createGadget({ ... }))
|
|
3637
|
+
* ```
|
|
3638
|
+
*/
|
|
3639
|
+
withGadgets(...gadgets) {
|
|
3640
|
+
this.gadgets.push(...gadgets);
|
|
3641
|
+
return this;
|
|
3642
|
+
}
|
|
3643
|
+
/**
|
|
3644
|
+
* Add conversation history messages.
|
|
3645
|
+
* Useful for continuing previous conversations.
|
|
3646
|
+
*
|
|
3647
|
+
* @param messages - Array of history messages
|
|
3648
|
+
* @returns This builder for chaining
|
|
3649
|
+
*
|
|
3650
|
+
* @example
|
|
3651
|
+
* ```typescript
|
|
3652
|
+
* .withHistory([
|
|
3653
|
+
* { user: "Hello" },
|
|
3654
|
+
* { assistant: "Hi there!" },
|
|
3655
|
+
* { user: "How are you?" },
|
|
3656
|
+
* { assistant: "I'm doing well, thanks!" }
|
|
3657
|
+
* ])
|
|
3658
|
+
* ```
|
|
3659
|
+
*/
|
|
3660
|
+
withHistory(messages) {
|
|
3661
|
+
for (const msg of messages) {
|
|
3662
|
+
if ("user" in msg) {
|
|
3663
|
+
this.initialMessages.push({ role: "user", content: msg.user });
|
|
3664
|
+
} else if ("assistant" in msg) {
|
|
3665
|
+
this.initialMessages.push({ role: "assistant", content: msg.assistant });
|
|
3666
|
+
} else if ("system" in msg) {
|
|
3667
|
+
this.initialMessages.push({ role: "system", content: msg.system });
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
return this;
|
|
3671
|
+
}
|
|
3672
|
+
/**
|
|
3673
|
+
* Add a single message to the conversation history.
|
|
3674
|
+
*
|
|
3675
|
+
* @param message - Single history message
|
|
3676
|
+
* @returns This builder for chaining
|
|
3677
|
+
*
|
|
3678
|
+
* @example
|
|
3679
|
+
* ```typescript
|
|
3680
|
+
* .addMessage({ user: "Hello" })
|
|
3681
|
+
* .addMessage({ assistant: "Hi there!" })
|
|
3682
|
+
* ```
|
|
3683
|
+
*/
|
|
3684
|
+
addMessage(message) {
|
|
3685
|
+
return this.withHistory([message]);
|
|
3686
|
+
}
|
|
3687
|
+
/**
|
|
3688
|
+
* Set the human input handler for interactive conversations.
|
|
3689
|
+
*
|
|
3690
|
+
* @param handler - Function to handle human input requests
|
|
3691
|
+
* @returns This builder for chaining
|
|
3692
|
+
*
|
|
3693
|
+
* @example
|
|
3694
|
+
* ```typescript
|
|
3695
|
+
* .onHumanInput(async (question) => {
|
|
3696
|
+
* return await promptUser(question);
|
|
3697
|
+
* })
|
|
3698
|
+
* ```
|
|
3699
|
+
*/
|
|
3700
|
+
onHumanInput(handler) {
|
|
3701
|
+
this.onHumanInputRequired = handler;
|
|
3702
|
+
return this;
|
|
3703
|
+
}
|
|
3704
|
+
/**
|
|
3705
|
+
* Set the parameter format for gadget calls.
|
|
3706
|
+
*
|
|
3707
|
+
* @param format - Parameter format ("json" or "xml")
|
|
3708
|
+
* @returns This builder for chaining
|
|
3709
|
+
*
|
|
3710
|
+
* @example
|
|
3711
|
+
* ```typescript
|
|
3712
|
+
* .withParameterFormat("xml")
|
|
3713
|
+
* ```
|
|
3714
|
+
*/
|
|
3715
|
+
withParameterFormat(format) {
|
|
3716
|
+
this.parameterFormat = format;
|
|
3717
|
+
return this;
|
|
3718
|
+
}
|
|
3719
|
+
/**
|
|
3720
|
+
* Set custom gadget marker prefix.
|
|
3721
|
+
*
|
|
3722
|
+
* @param prefix - Custom start prefix for gadget markers
|
|
3723
|
+
* @returns This builder for chaining
|
|
3724
|
+
*
|
|
3725
|
+
* @example
|
|
3726
|
+
* ```typescript
|
|
3727
|
+
* .withGadgetStartPrefix("<<GADGET_START>>")
|
|
3728
|
+
* ```
|
|
3729
|
+
*/
|
|
3730
|
+
withGadgetStartPrefix(prefix) {
|
|
3731
|
+
this.gadgetStartPrefix = prefix;
|
|
3732
|
+
return this;
|
|
3733
|
+
}
|
|
3734
|
+
/**
|
|
3735
|
+
* Set custom gadget marker suffix.
|
|
3736
|
+
*
|
|
3737
|
+
* @param suffix - Custom end suffix for gadget markers
|
|
3738
|
+
* @returns This builder for chaining
|
|
3739
|
+
*
|
|
3740
|
+
* @example
|
|
3741
|
+
* ```typescript
|
|
3742
|
+
* .withGadgetEndPrefix("<<GADGET_END>>")
|
|
3743
|
+
* ```
|
|
3744
|
+
*/
|
|
3745
|
+
withGadgetEndPrefix(suffix) {
|
|
3746
|
+
this.gadgetEndPrefix = suffix;
|
|
3747
|
+
return this;
|
|
3748
|
+
}
|
|
3749
|
+
/**
|
|
3750
|
+
* Set the text-only handler strategy.
|
|
3751
|
+
*
|
|
3752
|
+
* Controls what happens when the LLM returns text without calling any gadgets:
|
|
3753
|
+
* - "terminate": End the agent loop (default)
|
|
3754
|
+
* - "acknowledge": Continue the loop for another iteration
|
|
3755
|
+
* - "wait_for_input": Wait for human input
|
|
3756
|
+
* - Custom handler: Provide a function for dynamic behavior
|
|
3757
|
+
*
|
|
3758
|
+
* @param handler - Text-only handler strategy or custom handler
|
|
3759
|
+
* @returns This builder for chaining
|
|
3760
|
+
*
|
|
3761
|
+
* @example
|
|
3762
|
+
* ```typescript
|
|
3763
|
+
* // Simple strategy
|
|
3764
|
+
* .withTextOnlyHandler("acknowledge")
|
|
3765
|
+
*
|
|
3766
|
+
* // Custom handler
|
|
3767
|
+
* .withTextOnlyHandler({
|
|
3768
|
+
* type: "custom",
|
|
3769
|
+
* handler: async (context) => {
|
|
3770
|
+
* if (context.text.includes("?")) {
|
|
3771
|
+
* return { action: "wait_for_input", question: context.text };
|
|
3772
|
+
* }
|
|
3773
|
+
* return { action: "continue" };
|
|
3774
|
+
* }
|
|
3775
|
+
* })
|
|
3776
|
+
* ```
|
|
3777
|
+
*/
|
|
3778
|
+
withTextOnlyHandler(handler) {
|
|
3779
|
+
this.textOnlyHandler = handler;
|
|
3780
|
+
return this;
|
|
3781
|
+
}
|
|
3782
|
+
/**
|
|
3783
|
+
* Set whether to stop gadget execution on first error.
|
|
3784
|
+
*
|
|
3785
|
+
* When true (default), if a gadget fails:
|
|
3786
|
+
* - Subsequent gadgets in the same response are skipped
|
|
3787
|
+
* - LLM stream is cancelled to save costs
|
|
3788
|
+
* - Agent loop continues with error in context
|
|
3789
|
+
*
|
|
3790
|
+
* When false:
|
|
3791
|
+
* - All gadgets in the response still execute
|
|
3792
|
+
* - LLM stream continues to completion
|
|
3793
|
+
*
|
|
3794
|
+
* @param stop - Whether to stop on gadget error
|
|
3795
|
+
* @returns This builder for chaining
|
|
3796
|
+
*
|
|
3797
|
+
* @example
|
|
3798
|
+
* ```typescript
|
|
3799
|
+
* .withStopOnGadgetError(false)
|
|
3800
|
+
* ```
|
|
3801
|
+
*/
|
|
3802
|
+
withStopOnGadgetError(stop) {
|
|
3803
|
+
this.stopOnGadgetError = stop;
|
|
3804
|
+
return this;
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Set custom error handling logic.
|
|
3808
|
+
*
|
|
3809
|
+
* Provides fine-grained control over whether to continue after different types of errors.
|
|
3810
|
+
* Overrides `stopOnGadgetError` when provided.
|
|
3811
|
+
*
|
|
3812
|
+
* **Note:** This builder method configures the underlying `shouldContinueAfterError` option
|
|
3813
|
+
* in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
|
|
3814
|
+
* but maps to the `shouldContinueAfterError` property internally.
|
|
3815
|
+
*
|
|
3816
|
+
* @param handler - Function that decides whether to continue after an error.
|
|
3817
|
+
* Return `true` to continue execution, `false` to stop.
|
|
3818
|
+
* @returns This builder for chaining
|
|
3819
|
+
*
|
|
3820
|
+
* @example
|
|
3821
|
+
* ```typescript
|
|
3822
|
+
* .withErrorHandler((context) => {
|
|
3823
|
+
* // Stop on parse errors, continue on validation/execution errors
|
|
3824
|
+
* if (context.errorType === "parse") {
|
|
3825
|
+
* return false;
|
|
3826
|
+
* }
|
|
3827
|
+
* if (context.error.includes("CRITICAL")) {
|
|
3828
|
+
* return false;
|
|
3829
|
+
* }
|
|
3830
|
+
* return true;
|
|
3831
|
+
* })
|
|
3832
|
+
* ```
|
|
3833
|
+
*/
|
|
3834
|
+
withErrorHandler(handler) {
|
|
3835
|
+
this.shouldContinueAfterError = handler;
|
|
3836
|
+
return this;
|
|
3837
|
+
}
|
|
3838
|
+
/**
|
|
3839
|
+
* Set default timeout for gadget execution.
|
|
3840
|
+
*
|
|
3841
|
+
* @param timeoutMs - Timeout in milliseconds (must be non-negative)
|
|
3842
|
+
* @returns This builder for chaining
|
|
3843
|
+
* @throws {Error} If timeout is negative
|
|
3844
|
+
*
|
|
3845
|
+
* @example
|
|
3846
|
+
* ```typescript
|
|
3847
|
+
* .withDefaultGadgetTimeout(5000) // 5 second timeout
|
|
3848
|
+
* ```
|
|
3849
|
+
*/
|
|
3850
|
+
withDefaultGadgetTimeout(timeoutMs) {
|
|
3851
|
+
if (timeoutMs < 0) {
|
|
3852
|
+
throw new Error("Timeout must be a non-negative number");
|
|
3853
|
+
}
|
|
3854
|
+
this.defaultGadgetTimeoutMs = timeoutMs;
|
|
3855
|
+
return this;
|
|
3856
|
+
}
|
|
3857
|
+
/**
|
|
3858
|
+
* Build and create the agent with the given user prompt.
|
|
3859
|
+
* Returns the Agent instance ready to run.
|
|
3860
|
+
*
|
|
3861
|
+
* @param userPrompt - User's question or request
|
|
3862
|
+
* @returns Configured Agent instance
|
|
3863
|
+
*
|
|
3864
|
+
* @example
|
|
3865
|
+
* ```typescript
|
|
3866
|
+
* const agent = await LLMist.createAgent()
|
|
3867
|
+
* .withModel("sonnet")
|
|
3868
|
+
* .withGadgets(Calculator)
|
|
3869
|
+
* .ask("What is 2+2?");
|
|
3870
|
+
*
|
|
3871
|
+
* for await (const event of agent.run()) {
|
|
3872
|
+
* // handle events
|
|
3873
|
+
* }
|
|
3874
|
+
* ```
|
|
3875
|
+
*/
|
|
3876
|
+
ask(userPrompt) {
|
|
3877
|
+
if (!this.client) {
|
|
3878
|
+
const { LLMist: LLMistClass } = (init_client(), __toCommonJS(client_exports));
|
|
3879
|
+
this.client = new LLMistClass();
|
|
3880
|
+
}
|
|
3881
|
+
const registry = GadgetRegistry.from(this.gadgets);
|
|
3882
|
+
const options = {
|
|
3883
|
+
client: this.client,
|
|
3884
|
+
model: this.model ?? "openai:gpt-5-nano",
|
|
3885
|
+
systemPrompt: this.systemPrompt,
|
|
3886
|
+
userPrompt,
|
|
3887
|
+
registry,
|
|
3888
|
+
maxIterations: this.maxIterations,
|
|
3889
|
+
temperature: this.temperature,
|
|
3890
|
+
logger: this.logger,
|
|
3891
|
+
hooks: this.hooks,
|
|
3892
|
+
promptConfig: this.promptConfig,
|
|
3893
|
+
initialMessages: this.initialMessages,
|
|
3894
|
+
onHumanInputRequired: this.onHumanInputRequired,
|
|
3895
|
+
parameterFormat: this.parameterFormat,
|
|
3896
|
+
gadgetStartPrefix: this.gadgetStartPrefix,
|
|
3897
|
+
gadgetEndPrefix: this.gadgetEndPrefix,
|
|
3898
|
+
textOnlyHandler: this.textOnlyHandler,
|
|
3899
|
+
stopOnGadgetError: this.stopOnGadgetError,
|
|
3900
|
+
shouldContinueAfterError: this.shouldContinueAfterError,
|
|
3901
|
+
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
|
|
3902
|
+
};
|
|
3903
|
+
return new Agent(AGENT_INTERNAL_KEY, options);
|
|
3904
|
+
}
|
|
3905
|
+
/**
|
|
3906
|
+
* Build, run, and collect only the text response.
|
|
3907
|
+
* Convenient for simple queries where you just want the final answer.
|
|
3908
|
+
*
|
|
3909
|
+
* @param userPrompt - User's question or request
|
|
3910
|
+
* @returns Promise resolving to the complete text response
|
|
3911
|
+
*
|
|
3912
|
+
* @example
|
|
3913
|
+
* ```typescript
|
|
3914
|
+
* const answer = await LLMist.createAgent()
|
|
3915
|
+
* .withModel("gpt4-mini")
|
|
3916
|
+
* .withGadgets(Calculator)
|
|
3917
|
+
* .askAndCollect("What is 42 * 7?");
|
|
3918
|
+
*
|
|
3919
|
+
* console.log(answer); // "294"
|
|
3920
|
+
* ```
|
|
3921
|
+
*/
|
|
3922
|
+
async askAndCollect(userPrompt) {
|
|
3923
|
+
const agent = this.ask(userPrompt);
|
|
3924
|
+
return collectText(agent.run());
|
|
3925
|
+
}
|
|
3926
|
+
/**
|
|
3927
|
+
* Build and run with event handlers.
|
|
3928
|
+
* Combines agent creation and event handling in one call.
|
|
3929
|
+
*
|
|
3930
|
+
* @param userPrompt - User's question or request
|
|
3931
|
+
* @param handlers - Event handlers
|
|
3932
|
+
*
|
|
3933
|
+
* @example
|
|
3934
|
+
* ```typescript
|
|
3935
|
+
* await LLMist.createAgent()
|
|
3936
|
+
* .withModel("sonnet")
|
|
3937
|
+
* .withGadgets(Calculator)
|
|
3938
|
+
* .askWith("What is 2+2?", {
|
|
3939
|
+
* onText: (text) => console.log("LLM:", text),
|
|
3940
|
+
* onGadgetResult: (result) => console.log("Result:", result.result),
|
|
3941
|
+
* });
|
|
3942
|
+
* ```
|
|
3943
|
+
*/
|
|
3944
|
+
async askWith(userPrompt, handlers) {
|
|
3945
|
+
const agent = this.ask(userPrompt);
|
|
3946
|
+
await agent.runWith(handlers);
|
|
3947
|
+
}
|
|
3948
|
+
/**
|
|
3949
|
+
* Build the agent without a user prompt.
|
|
3950
|
+
*
|
|
3951
|
+
* Returns an Agent instance that can be inspected (e.g., check registered gadgets)
|
|
3952
|
+
* but cannot be run without first calling .ask(prompt).
|
|
3953
|
+
*
|
|
3954
|
+
* This is useful for:
|
|
3955
|
+
* - Testing: Inspect the registry, configuration, etc.
|
|
3956
|
+
* - Advanced use cases: Build agent configuration separately from execution
|
|
3957
|
+
*
|
|
3958
|
+
* @returns Configured Agent instance (without user prompt)
|
|
3959
|
+
*
|
|
3960
|
+
* @example
|
|
3961
|
+
* ```typescript
|
|
3962
|
+
* // Build agent for inspection
|
|
3963
|
+
* const agent = new AgentBuilder()
|
|
3964
|
+
* .withModel("sonnet")
|
|
3965
|
+
* .withGadgets(Calculator, Weather)
|
|
3966
|
+
* .build();
|
|
3967
|
+
*
|
|
3968
|
+
* // Inspect registered gadgets
|
|
3969
|
+
* console.log(agent.getRegistry().getNames()); // ['Calculator', 'Weather']
|
|
3970
|
+
*
|
|
3971
|
+
* // Note: Calling agent.run() will throw an error
|
|
3972
|
+
* // Use .ask(prompt) instead if you want to run the agent
|
|
3973
|
+
* ```
|
|
3974
|
+
*/
|
|
3975
|
+
build() {
|
|
3976
|
+
if (!this.client) {
|
|
3977
|
+
const { LLMist: LLMistClass } = (init_client(), __toCommonJS(client_exports));
|
|
3978
|
+
this.client = new LLMistClass();
|
|
3979
|
+
}
|
|
3980
|
+
const registry = GadgetRegistry.from(this.gadgets);
|
|
3981
|
+
const options = {
|
|
3982
|
+
client: this.client,
|
|
3983
|
+
model: this.model ?? "openai:gpt-5-nano",
|
|
3984
|
+
systemPrompt: this.systemPrompt,
|
|
3985
|
+
// No userPrompt - agent.run() will throw if called directly
|
|
3986
|
+
registry,
|
|
3987
|
+
maxIterations: this.maxIterations,
|
|
3988
|
+
temperature: this.temperature,
|
|
3989
|
+
logger: this.logger,
|
|
3990
|
+
hooks: this.hooks,
|
|
3991
|
+
promptConfig: this.promptConfig,
|
|
3992
|
+
initialMessages: this.initialMessages,
|
|
3993
|
+
onHumanInputRequired: this.onHumanInputRequired,
|
|
3994
|
+
parameterFormat: this.parameterFormat,
|
|
3995
|
+
gadgetStartPrefix: this.gadgetStartPrefix,
|
|
3996
|
+
gadgetEndPrefix: this.gadgetEndPrefix,
|
|
3997
|
+
textOnlyHandler: this.textOnlyHandler,
|
|
3998
|
+
stopOnGadgetError: this.stopOnGadgetError,
|
|
3999
|
+
shouldContinueAfterError: this.shouldContinueAfterError,
|
|
4000
|
+
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
|
|
4001
|
+
};
|
|
4002
|
+
return new Agent(AGENT_INTERNAL_KEY, options);
|
|
4003
|
+
}
|
|
4004
|
+
};
|
|
4005
|
+
}
|
|
4006
|
+
});
|
|
4007
|
+
|
|
4008
|
+
// src/core/client.ts
|
|
4009
|
+
var client_exports = {};
|
|
4010
|
+
__export(client_exports, {
|
|
4011
|
+
LLMist: () => LLMist
|
|
4012
|
+
});
|
|
4013
|
+
var LLMist;
|
|
4014
|
+
var init_client = __esm({
|
|
4015
|
+
"src/core/client.ts"() {
|
|
4016
|
+
"use strict";
|
|
4017
|
+
init_builder();
|
|
4018
|
+
init_discovery();
|
|
4019
|
+
init_model_registry();
|
|
4020
|
+
init_options();
|
|
4021
|
+
init_quick_methods();
|
|
4022
|
+
LLMist = class _LLMist {
|
|
4023
|
+
parser;
|
|
4024
|
+
modelRegistry;
|
|
4025
|
+
adapters;
|
|
4026
|
+
constructor(...args) {
|
|
4027
|
+
let adapters = [];
|
|
4028
|
+
let defaultProvider;
|
|
4029
|
+
let autoDiscoverProviders = true;
|
|
4030
|
+
let customModels = [];
|
|
4031
|
+
if (args.length === 0) {
|
|
4032
|
+
} else if (Array.isArray(args[0])) {
|
|
4033
|
+
adapters = args[0];
|
|
4034
|
+
if (args.length > 1) {
|
|
4035
|
+
defaultProvider = args[1];
|
|
4036
|
+
}
|
|
4037
|
+
} else if (typeof args[0] === "object" && args[0] !== null) {
|
|
4038
|
+
const options = args[0];
|
|
4039
|
+
adapters = options.adapters ?? [];
|
|
4040
|
+
defaultProvider = options.defaultProvider;
|
|
4041
|
+
customModels = options.customModels ?? [];
|
|
4042
|
+
if (typeof options.autoDiscoverProviders === "boolean") {
|
|
4043
|
+
autoDiscoverProviders = options.autoDiscoverProviders;
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
const discoveredAdapters = autoDiscoverProviders ? discoverProviderAdapters() : [];
|
|
4047
|
+
const combinedAdapters = [...adapters];
|
|
4048
|
+
for (const adapter of discoveredAdapters) {
|
|
4049
|
+
if (!combinedAdapters.some((existing) => existing.providerId === adapter.providerId)) {
|
|
4050
|
+
combinedAdapters.push(adapter);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
if (combinedAdapters.length === 0) {
|
|
4054
|
+
throw new Error(
|
|
4055
|
+
"No LLM providers available. Provide adapters explicitly or set provider API keys in the environment."
|
|
4056
|
+
);
|
|
4057
|
+
}
|
|
4058
|
+
const resolvedDefaultProvider = defaultProvider ?? combinedAdapters[0]?.providerId ?? "openai";
|
|
4059
|
+
this.adapters = [...combinedAdapters].sort((a, b) => {
|
|
4060
|
+
const priorityA = a.priority ?? 0;
|
|
4061
|
+
const priorityB = b.priority ?? 0;
|
|
4062
|
+
return priorityB - priorityA;
|
|
4063
|
+
});
|
|
4064
|
+
this.parser = new ModelIdentifierParser(resolvedDefaultProvider);
|
|
4065
|
+
this.modelRegistry = new ModelRegistry();
|
|
4066
|
+
for (const adapter of this.adapters) {
|
|
4067
|
+
this.modelRegistry.registerProvider(adapter);
|
|
4068
|
+
}
|
|
4069
|
+
if (customModels.length > 0) {
|
|
4070
|
+
this.modelRegistry.registerModels(customModels);
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
stream(options) {
|
|
4074
|
+
const descriptor = this.parser.parse(options.model);
|
|
4075
|
+
const spec = this.modelRegistry.getModelSpec(descriptor.name);
|
|
4076
|
+
const adapter = this.resolveAdapter(descriptor);
|
|
4077
|
+
return adapter.stream(options, descriptor, spec);
|
|
4078
|
+
}
|
|
4079
|
+
/**
|
|
4080
|
+
* Count tokens in messages for a given model.
|
|
4081
|
+
*
|
|
4082
|
+
* Uses provider-specific token counting methods for accurate estimation:
|
|
4083
|
+
* - OpenAI: tiktoken library with model-specific encodings
|
|
4084
|
+
* - Anthropic: Native messages.countTokens() API
|
|
4085
|
+
* - Gemini: SDK's countTokens() method
|
|
4086
|
+
*
|
|
4087
|
+
* Falls back to character-based estimation (4 chars/token) if the provider
|
|
4088
|
+
* doesn't support native token counting or if counting fails.
|
|
4089
|
+
*
|
|
4090
|
+
* This is useful for:
|
|
4091
|
+
* - Pre-request cost estimation
|
|
4092
|
+
* - Context window management
|
|
4093
|
+
* - Request batching optimization
|
|
4094
|
+
*
|
|
4095
|
+
* @param model - Model identifier (e.g., "openai:gpt-4", "anthropic:claude-3-5-sonnet-20241022")
|
|
4096
|
+
* @param messages - Array of messages to count tokens for
|
|
4097
|
+
* @returns Promise resolving to the estimated input token count
|
|
4098
|
+
*
|
|
4099
|
+
* @example
|
|
4100
|
+
* ```typescript
|
|
4101
|
+
* const client = new LLMist();
|
|
4102
|
+
* const messages = [
|
|
4103
|
+
* { role: 'system', content: 'You are a helpful assistant.' },
|
|
4104
|
+
* { role: 'user', content: 'Hello!' }
|
|
4105
|
+
* ];
|
|
4106
|
+
*
|
|
4107
|
+
* const tokenCount = await client.countTokens('openai:gpt-4', messages);
|
|
4108
|
+
* console.log(`Estimated tokens: ${tokenCount}`);
|
|
4109
|
+
* ```
|
|
4110
|
+
*/
|
|
4111
|
+
async countTokens(model, messages) {
|
|
4112
|
+
const descriptor = this.parser.parse(model);
|
|
4113
|
+
const spec = this.modelRegistry.getModelSpec(descriptor.name);
|
|
4114
|
+
const adapter = this.resolveAdapter(descriptor);
|
|
4115
|
+
if (adapter.countTokens) {
|
|
4116
|
+
return adapter.countTokens(messages, descriptor, spec);
|
|
4117
|
+
}
|
|
4118
|
+
const totalChars = messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0);
|
|
4119
|
+
return Math.ceil(totalChars / 4);
|
|
4120
|
+
}
|
|
4121
|
+
resolveAdapter(descriptor) {
|
|
4122
|
+
const adapter = this.adapters.find((item) => item.supports(descriptor));
|
|
4123
|
+
if (!adapter) {
|
|
4124
|
+
throw new Error(`No adapter registered for provider ${descriptor.provider}`);
|
|
4125
|
+
}
|
|
4126
|
+
return adapter;
|
|
4127
|
+
}
|
|
4128
|
+
/**
|
|
4129
|
+
* Quick completion - returns final text response.
|
|
4130
|
+
* Convenient for simple queries without needing agent setup.
|
|
4131
|
+
*
|
|
4132
|
+
* @param prompt - User prompt
|
|
4133
|
+
* @param options - Optional configuration
|
|
4134
|
+
* @returns Complete text response
|
|
4135
|
+
*
|
|
4136
|
+
* @example
|
|
4137
|
+
* ```typescript
|
|
4138
|
+
* const answer = await LLMist.complete("What is 2+2?");
|
|
4139
|
+
* console.log(answer); // "4" or "2+2 equals 4"
|
|
4140
|
+
*
|
|
4141
|
+
* const answer = await LLMist.complete("Tell me a joke", {
|
|
4142
|
+
* model: "sonnet",
|
|
4143
|
+
* temperature: 0.9
|
|
4144
|
+
* });
|
|
4145
|
+
* ```
|
|
4146
|
+
*/
|
|
4147
|
+
static async complete(prompt, options) {
|
|
4148
|
+
const client = new _LLMist();
|
|
4149
|
+
return complete(client, prompt, options);
|
|
4150
|
+
}
|
|
4151
|
+
/**
|
|
4152
|
+
* Quick streaming - returns async generator of text chunks.
|
|
4153
|
+
* Convenient for streaming responses without needing agent setup.
|
|
4154
|
+
*
|
|
4155
|
+
* @param prompt - User prompt
|
|
4156
|
+
* @param options - Optional configuration
|
|
4157
|
+
* @returns Async generator yielding text chunks
|
|
4158
|
+
*
|
|
4159
|
+
* @example
|
|
4160
|
+
* ```typescript
|
|
4161
|
+
* for await (const chunk of LLMist.stream("Tell me a story")) {
|
|
4162
|
+
* process.stdout.write(chunk);
|
|
4163
|
+
* }
|
|
4164
|
+
*
|
|
4165
|
+
* // With options
|
|
4166
|
+
* for await (const chunk of LLMist.stream("Generate code", {
|
|
4167
|
+
* model: "gpt4",
|
|
4168
|
+
* systemPrompt: "You are a coding assistant"
|
|
4169
|
+
* })) {
|
|
4170
|
+
* process.stdout.write(chunk);
|
|
4171
|
+
* }
|
|
4172
|
+
* ```
|
|
4173
|
+
*/
|
|
4174
|
+
static stream(prompt, options) {
|
|
4175
|
+
const client = new _LLMist();
|
|
4176
|
+
return stream(client, prompt, options);
|
|
4177
|
+
}
|
|
4178
|
+
/**
|
|
4179
|
+
* Instance method: Quick completion using this client instance.
|
|
4180
|
+
*
|
|
4181
|
+
* @param prompt - User prompt
|
|
4182
|
+
* @param options - Optional configuration
|
|
4183
|
+
* @returns Complete text response
|
|
4184
|
+
*/
|
|
4185
|
+
async complete(prompt, options) {
|
|
4186
|
+
return complete(this, prompt, options);
|
|
4187
|
+
}
|
|
4188
|
+
/**
|
|
4189
|
+
* Instance method: Quick streaming using this client instance.
|
|
4190
|
+
*
|
|
4191
|
+
* @param prompt - User prompt
|
|
4192
|
+
* @param options - Optional configuration
|
|
4193
|
+
* @returns Async generator yielding text chunks
|
|
4194
|
+
*/
|
|
4195
|
+
streamText(prompt, options) {
|
|
4196
|
+
return stream(this, prompt, options);
|
|
4197
|
+
}
|
|
4198
|
+
/**
|
|
4199
|
+
* Create a fluent agent builder.
|
|
4200
|
+
* Provides a chainable API for configuring and creating agents.
|
|
4201
|
+
*
|
|
4202
|
+
* @returns AgentBuilder instance for chaining
|
|
4203
|
+
*
|
|
4204
|
+
* @example
|
|
4205
|
+
* ```typescript
|
|
4206
|
+
* const agent = LLMist.createAgent()
|
|
4207
|
+
* .withModel("sonnet")
|
|
4208
|
+
* .withSystem("You are a helpful assistant")
|
|
4209
|
+
* .withGadgets(Calculator, Weather)
|
|
4210
|
+
* .ask("What's the weather in Paris?");
|
|
4211
|
+
*
|
|
4212
|
+
* for await (const event of agent.run()) {
|
|
4213
|
+
* // handle events
|
|
4214
|
+
* }
|
|
4215
|
+
* ```
|
|
4216
|
+
*
|
|
4217
|
+
* @example
|
|
4218
|
+
* ```typescript
|
|
4219
|
+
* // Quick one-liner for simple queries
|
|
4220
|
+
* const answer = await LLMist.createAgent()
|
|
4221
|
+
* .withModel("gpt4-mini")
|
|
4222
|
+
* .askAndCollect("What is 2+2?");
|
|
4223
|
+
* ```
|
|
4224
|
+
*/
|
|
4225
|
+
static createAgent() {
|
|
4226
|
+
return new AgentBuilder();
|
|
4227
|
+
}
|
|
4228
|
+
/**
|
|
4229
|
+
* Create agent builder with this client instance.
|
|
4230
|
+
* Useful when you want to reuse a configured client.
|
|
4231
|
+
*
|
|
4232
|
+
* @returns AgentBuilder instance using this client
|
|
4233
|
+
*
|
|
4234
|
+
* @example
|
|
4235
|
+
* ```typescript
|
|
4236
|
+
* const client = new LLMist({ ... });
|
|
4237
|
+
*
|
|
4238
|
+
* const agent = client.createAgent()
|
|
4239
|
+
* .withModel("sonnet")
|
|
4240
|
+
* .ask("Hello");
|
|
4241
|
+
* ```
|
|
4242
|
+
*/
|
|
4243
|
+
createAgent() {
|
|
4244
|
+
return new AgentBuilder(this);
|
|
4245
|
+
}
|
|
4246
|
+
};
|
|
4247
|
+
}
|
|
4248
|
+
});
|
|
4249
|
+
|
|
4250
|
+
// src/gadgets/gadget.ts
|
|
4251
|
+
import * as yaml2 from "js-yaml";
|
|
4252
|
+
|
|
4253
|
+
// src/gadgets/schema-to-json.ts
|
|
4254
|
+
init_logger();
|
|
4255
|
+
import * as z2 from "zod";
|
|
4256
|
+
function schemaToJSONSchema(schema, options) {
|
|
4257
|
+
const jsonSchema = z2.toJSONSchema(schema, options ?? { target: "draft-7" });
|
|
4258
|
+
const mismatches = detectDescriptionMismatch(schema, jsonSchema);
|
|
4259
|
+
if (mismatches.length > 0) {
|
|
4260
|
+
defaultLogger.warn(
|
|
4261
|
+
`Zod instance mismatch detected: ${mismatches.length} description(s) lost. For best results, use: import { z } from "llmist"`
|
|
4262
|
+
);
|
|
4263
|
+
return mergeDescriptions(schema, jsonSchema);
|
|
4264
|
+
}
|
|
4265
|
+
return jsonSchema;
|
|
4266
|
+
}
|
|
4267
|
+
function detectDescriptionMismatch(schema, jsonSchema) {
|
|
4268
|
+
const mismatches = [];
|
|
4269
|
+
function checkSchema(zodSchema, json, path) {
|
|
4270
|
+
if (!zodSchema || typeof zodSchema !== "object") return;
|
|
4271
|
+
const def = zodSchema._def;
|
|
4272
|
+
const jsonObj = json;
|
|
4273
|
+
if (def?.description && !jsonObj?.description) {
|
|
4274
|
+
mismatches.push(path || "root");
|
|
4275
|
+
}
|
|
4276
|
+
if (def?.typeName === "ZodObject" && def?.shape) {
|
|
4277
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
4278
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
4279
|
+
const properties = jsonObj?.properties;
|
|
4280
|
+
const jsonProp = properties?.[key];
|
|
4281
|
+
checkSchema(fieldSchema, jsonProp, path ? `${path}.${key}` : key);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
if (def?.typeName === "ZodArray" && def?.type) {
|
|
4285
|
+
checkSchema(def.type, jsonObj?.items, path ? `${path}[]` : "[]");
|
|
4286
|
+
}
|
|
4287
|
+
if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
|
|
4288
|
+
checkSchema(def.innerType, json, path);
|
|
4289
|
+
}
|
|
4290
|
+
if (def?.typeName === "ZodDefault" && def?.innerType) {
|
|
4291
|
+
checkSchema(def.innerType, json, path);
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
checkSchema(schema, jsonSchema, "");
|
|
4295
|
+
return mismatches;
|
|
4296
|
+
}
|
|
4297
|
+
function mergeDescriptions(schema, jsonSchema) {
|
|
4298
|
+
function merge(zodSchema, json) {
|
|
4299
|
+
if (!json || typeof json !== "object") return json;
|
|
4300
|
+
const def = zodSchema._def;
|
|
4301
|
+
const jsonObj = json;
|
|
4302
|
+
const merged = { ...jsonObj };
|
|
4303
|
+
if (def?.description && !jsonObj.description) {
|
|
4304
|
+
merged.description = def.description;
|
|
4305
|
+
}
|
|
4306
|
+
if (def?.typeName === "ZodObject" && def?.shape && jsonObj.properties) {
|
|
4307
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
4308
|
+
const properties = jsonObj.properties;
|
|
4309
|
+
merged.properties = { ...properties };
|
|
4310
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
4311
|
+
if (properties[key]) {
|
|
4312
|
+
merged.properties[key] = merge(fieldSchema, properties[key]);
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
if (def?.typeName === "ZodArray" && def?.type && jsonObj.items) {
|
|
4317
|
+
merged.items = merge(def.type, jsonObj.items);
|
|
4318
|
+
}
|
|
4319
|
+
if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
|
|
4320
|
+
return merge(def.innerType, json);
|
|
4321
|
+
}
|
|
4322
|
+
if (def?.typeName === "ZodDefault" && def?.innerType) {
|
|
4323
|
+
return merge(def.innerType, json);
|
|
4324
|
+
}
|
|
4325
|
+
return merged;
|
|
4326
|
+
}
|
|
4327
|
+
return merge(schema, jsonSchema);
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
// src/gadgets/gadget.ts
|
|
4331
|
+
init_schema_validator();
|
|
4332
|
+
var BaseGadget = class {
|
|
4333
|
+
/**
|
|
4334
|
+
* The name of the gadget. Used for identification when LLM calls it.
|
|
4335
|
+
* If not provided, defaults to the class name.
|
|
4336
|
+
*/
|
|
4337
|
+
name;
|
|
4338
|
+
/**
|
|
4339
|
+
* Optional Zod schema describing the expected input payload. When provided,
|
|
4340
|
+
* it will be validated before execution and transformed into a JSON Schema
|
|
4341
|
+
* representation that is surfaced to the LLM as part of the instructions.
|
|
4342
|
+
*/
|
|
4343
|
+
parameterSchema;
|
|
4344
|
+
/**
|
|
4345
|
+
* Optional timeout in milliseconds for gadget execution.
|
|
4346
|
+
* If execution exceeds this timeout, a TimeoutException will be thrown.
|
|
4347
|
+
* If not set, the global defaultGadgetTimeoutMs from runtime options will be used.
|
|
4348
|
+
* Set to 0 or undefined to disable timeout for this gadget.
|
|
4349
|
+
*/
|
|
4350
|
+
timeoutMs;
|
|
4351
|
+
/**
|
|
4352
|
+
* Auto-generated instruction text for the LLM.
|
|
4353
|
+
* Combines name, description, and parameter schema into a formatted instruction.
|
|
4354
|
+
* @deprecated Use getInstruction(format) instead for format-specific schemas
|
|
4355
|
+
*/
|
|
4356
|
+
get instruction() {
|
|
4357
|
+
return this.getInstruction("yaml");
|
|
4358
|
+
}
|
|
4359
|
+
/**
|
|
4360
|
+
* Generate instruction text for the LLM with format-specific schema.
|
|
4361
|
+
* Combines name, description, and parameter schema into a formatted instruction.
|
|
4362
|
+
*
|
|
4363
|
+
* @param format - Format for the schema representation ('json' | 'yaml' | 'auto')
|
|
4364
|
+
* @returns Formatted instruction string
|
|
4365
|
+
*/
|
|
4366
|
+
getInstruction(format = "json") {
|
|
4367
|
+
const parts = [];
|
|
4368
|
+
parts.push(this.description);
|
|
4369
|
+
if (this.parameterSchema) {
|
|
4370
|
+
const gadgetName = this.name ?? this.constructor.name;
|
|
4371
|
+
validateGadgetSchema(this.parameterSchema, gadgetName);
|
|
4372
|
+
const jsonSchema = schemaToJSONSchema(this.parameterSchema, {
|
|
4373
|
+
target: "draft-7"
|
|
4374
|
+
});
|
|
4375
|
+
if (format === "json" || format === "auto") {
|
|
4376
|
+
parts.push("\n\nInput Schema (JSON):");
|
|
4377
|
+
parts.push(JSON.stringify(jsonSchema, null, 2));
|
|
4378
|
+
} else {
|
|
4379
|
+
const yamlSchema = yaml2.dump(jsonSchema).trimEnd();
|
|
4380
|
+
parts.push("\n\nInput Schema (YAML):");
|
|
4381
|
+
parts.push(yamlSchema);
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
return parts.join("\n");
|
|
4385
|
+
}
|
|
4386
|
+
};
|
|
4387
|
+
|
|
4388
|
+
export {
|
|
4389
|
+
MODEL_ALIASES,
|
|
4390
|
+
resolveModel,
|
|
4391
|
+
hasProviderPrefix,
|
|
4392
|
+
getProvider,
|
|
4393
|
+
getModelId,
|
|
4394
|
+
init_model_shortcuts,
|
|
4395
|
+
GadgetRegistry,
|
|
4396
|
+
init_registry,
|
|
4397
|
+
GADGET_START_PREFIX,
|
|
4398
|
+
GADGET_END_PREFIX,
|
|
4399
|
+
init_constants,
|
|
4400
|
+
DEFAULT_PROMPTS,
|
|
4401
|
+
resolvePromptTemplate,
|
|
4402
|
+
resolveRulesTemplate,
|
|
4403
|
+
init_prompt_config,
|
|
4404
|
+
LLMMessageBuilder,
|
|
4405
|
+
init_messages,
|
|
4406
|
+
createLogger,
|
|
4407
|
+
defaultLogger,
|
|
4408
|
+
init_logger,
|
|
4409
|
+
ConversationManager,
|
|
4410
|
+
init_conversation_manager,
|
|
4411
|
+
runWithHandlers,
|
|
4412
|
+
collectEvents,
|
|
4413
|
+
collectText,
|
|
4414
|
+
init_event_handlers,
|
|
4415
|
+
BreakLoopException,
|
|
4416
|
+
HumanInputException,
|
|
4417
|
+
init_exceptions,
|
|
4418
|
+
GadgetExecutor,
|
|
4419
|
+
init_executor,
|
|
4420
|
+
StreamParser,
|
|
4421
|
+
init_parser,
|
|
4422
|
+
StreamProcessor,
|
|
4423
|
+
init_stream_processor,
|
|
4424
|
+
FALLBACK_CHARS_PER_TOKEN,
|
|
4425
|
+
init_constants2,
|
|
4426
|
+
AnthropicMessagesProvider,
|
|
4427
|
+
createAnthropicProviderFromEnv,
|
|
4428
|
+
init_anthropic,
|
|
4429
|
+
GeminiGenerativeProvider,
|
|
4430
|
+
createGeminiProviderFromEnv,
|
|
4431
|
+
init_gemini,
|
|
4432
|
+
OpenAIChatProvider,
|
|
4433
|
+
createOpenAIProviderFromEnv,
|
|
4434
|
+
init_openai,
|
|
4435
|
+
discoverProviderAdapters,
|
|
4436
|
+
init_discovery,
|
|
4437
|
+
ModelRegistry,
|
|
4438
|
+
init_model_registry,
|
|
4439
|
+
ModelIdentifierParser,
|
|
4440
|
+
init_options,
|
|
4441
|
+
complete,
|
|
4442
|
+
stream,
|
|
4443
|
+
init_quick_methods,
|
|
4444
|
+
LLMist,
|
|
4445
|
+
init_client,
|
|
4446
|
+
AgentBuilder,
|
|
4447
|
+
init_builder,
|
|
4448
|
+
BaseGadget
|
|
4449
|
+
};
|
|
4450
|
+
//# sourceMappingURL=chunk-TP7HE3MN.js.map
|