llmist 1.7.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-JGORHSHC.js → chunk-LSCCBXS7.js} +10 -3
- package/dist/chunk-LSCCBXS7.js.map +1 -0
- package/dist/{chunk-E52IO2NO.js → chunk-PDYVT3FI.js} +421 -53
- package/dist/chunk-PDYVT3FI.js.map +1 -0
- package/dist/cli.cjs +591 -154
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +188 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +415 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -13
- package/dist/index.d.ts +72 -13
- package/dist/index.js +4 -2
- package/dist/{mock-stream-BMuFlQI1.d.cts → mock-stream-HF7MBNhi.d.cts} +650 -319
- package/dist/{mock-stream-BMuFlQI1.d.ts → mock-stream-HF7MBNhi.d.ts} +650 -319
- package/dist/testing/index.cjs +416 -49
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +4 -2
- package/dist/testing/index.d.ts +4 -2
- package/dist/testing/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-E52IO2NO.js.map +0 -1
- package/dist/chunk-JGORHSHC.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -644,6 +644,44 @@ ${this.endPrefix}`
|
|
|
644
644
|
}
|
|
645
645
|
});
|
|
646
646
|
|
|
647
|
+
// src/gadgets/exceptions.ts
|
|
648
|
+
var BreakLoopException, HumanInputException, TimeoutException, AbortError;
|
|
649
|
+
var init_exceptions = __esm({
|
|
650
|
+
"src/gadgets/exceptions.ts"() {
|
|
651
|
+
"use strict";
|
|
652
|
+
BreakLoopException = class extends Error {
|
|
653
|
+
constructor(message) {
|
|
654
|
+
super(message ?? "Agent loop terminated by gadget");
|
|
655
|
+
this.name = "BreakLoopException";
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
HumanInputException = class extends Error {
|
|
659
|
+
question;
|
|
660
|
+
constructor(question) {
|
|
661
|
+
super(`Human input required: ${question}`);
|
|
662
|
+
this.name = "HumanInputException";
|
|
663
|
+
this.question = question;
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
TimeoutException = class extends Error {
|
|
667
|
+
timeoutMs;
|
|
668
|
+
gadgetName;
|
|
669
|
+
constructor(gadgetName, timeoutMs) {
|
|
670
|
+
super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
|
|
671
|
+
this.name = "TimeoutException";
|
|
672
|
+
this.gadgetName = gadgetName;
|
|
673
|
+
this.timeoutMs = timeoutMs;
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
AbortError = class extends Error {
|
|
677
|
+
constructor(message) {
|
|
678
|
+
super(message || "Gadget execution was aborted");
|
|
679
|
+
this.name = "AbortError";
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
|
|
647
685
|
// src/logging/logger.ts
|
|
648
686
|
function parseLogLevel(value) {
|
|
649
687
|
if (!value) {
|
|
@@ -916,6 +954,7 @@ var init_gadget = __esm({
|
|
|
916
954
|
"src/gadgets/gadget.ts"() {
|
|
917
955
|
"use strict";
|
|
918
956
|
init_constants();
|
|
957
|
+
init_exceptions();
|
|
919
958
|
init_schema_to_json();
|
|
920
959
|
init_schema_validator();
|
|
921
960
|
BaseGadget = class {
|
|
@@ -945,6 +984,136 @@ var init_gadget = __esm({
|
|
|
945
984
|
* while maintaining runtime compatibility.
|
|
946
985
|
*/
|
|
947
986
|
examples;
|
|
987
|
+
/**
|
|
988
|
+
* Throws an AbortError if the execution has been aborted.
|
|
989
|
+
*
|
|
990
|
+
* Call this at key checkpoints in long-running gadgets to allow early exit
|
|
991
|
+
* when the gadget has been cancelled (e.g., due to timeout). This enables
|
|
992
|
+
* resource cleanup and prevents unnecessary work after cancellation.
|
|
993
|
+
*
|
|
994
|
+
* @param ctx - The execution context containing the abort signal
|
|
995
|
+
* @throws AbortError if ctx.signal.aborted is true
|
|
996
|
+
*
|
|
997
|
+
* @example
|
|
998
|
+
* ```typescript
|
|
999
|
+
* class DataProcessor extends Gadget({
|
|
1000
|
+
* description: 'Processes data in multiple steps',
|
|
1001
|
+
* schema: z.object({ items: z.array(z.string()) }),
|
|
1002
|
+
* }) {
|
|
1003
|
+
* async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
|
|
1004
|
+
* const results: string[] = [];
|
|
1005
|
+
*
|
|
1006
|
+
* for (const item of params.items) {
|
|
1007
|
+
* // Check before each expensive operation
|
|
1008
|
+
* this.throwIfAborted(ctx);
|
|
1009
|
+
*
|
|
1010
|
+
* results.push(await this.processItem(item));
|
|
1011
|
+
* }
|
|
1012
|
+
*
|
|
1013
|
+
* return results.join(', ');
|
|
1014
|
+
* }
|
|
1015
|
+
* }
|
|
1016
|
+
* ```
|
|
1017
|
+
*/
|
|
1018
|
+
throwIfAborted(ctx) {
|
|
1019
|
+
if (ctx?.signal?.aborted) {
|
|
1020
|
+
throw new AbortError();
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Register a cleanup function to run when execution is aborted (timeout or cancellation).
|
|
1025
|
+
* The cleanup function is called immediately if the signal is already aborted.
|
|
1026
|
+
* Errors thrown by the cleanup function are silently ignored.
|
|
1027
|
+
*
|
|
1028
|
+
* Use this to clean up resources like browser instances, database connections,
|
|
1029
|
+
* or child processes when the gadget is cancelled due to timeout.
|
|
1030
|
+
*
|
|
1031
|
+
* @param ctx - The execution context containing the abort signal
|
|
1032
|
+
* @param cleanup - Function to run on abort (can be sync or async)
|
|
1033
|
+
*
|
|
1034
|
+
* @example
|
|
1035
|
+
* ```typescript
|
|
1036
|
+
* class BrowserGadget extends Gadget({
|
|
1037
|
+
* description: 'Fetches web page content',
|
|
1038
|
+
* schema: z.object({ url: z.string() }),
|
|
1039
|
+
* }) {
|
|
1040
|
+
* async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
|
|
1041
|
+
* const browser = await chromium.launch();
|
|
1042
|
+
* this.onAbort(ctx, () => browser.close());
|
|
1043
|
+
*
|
|
1044
|
+
* const page = await browser.newPage();
|
|
1045
|
+
* this.onAbort(ctx, () => page.close());
|
|
1046
|
+
*
|
|
1047
|
+
* await page.goto(params.url);
|
|
1048
|
+
* const content = await page.content();
|
|
1049
|
+
*
|
|
1050
|
+
* await browser.close();
|
|
1051
|
+
* return content;
|
|
1052
|
+
* }
|
|
1053
|
+
* }
|
|
1054
|
+
* ```
|
|
1055
|
+
*/
|
|
1056
|
+
onAbort(ctx, cleanup) {
|
|
1057
|
+
if (!ctx?.signal) return;
|
|
1058
|
+
const safeCleanup = () => {
|
|
1059
|
+
try {
|
|
1060
|
+
const result = cleanup();
|
|
1061
|
+
if (result && typeof result === "object" && "catch" in result) {
|
|
1062
|
+
result.catch(() => {
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
} catch {
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
if (ctx.signal.aborted) {
|
|
1069
|
+
safeCleanup();
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
ctx.signal.addEventListener("abort", safeCleanup, { once: true });
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Create an AbortController linked to the execution context's signal.
|
|
1076
|
+
* When the parent signal aborts, the returned controller also aborts with the same reason.
|
|
1077
|
+
*
|
|
1078
|
+
* Useful for passing abort signals to child operations like fetch() while still
|
|
1079
|
+
* being able to abort them independently if needed.
|
|
1080
|
+
*
|
|
1081
|
+
* @param ctx - The execution context containing the parent abort signal
|
|
1082
|
+
* @returns A new AbortController linked to the parent signal
|
|
1083
|
+
*
|
|
1084
|
+
* @example
|
|
1085
|
+
* ```typescript
|
|
1086
|
+
* class FetchGadget extends Gadget({
|
|
1087
|
+
* description: 'Fetches data from URL',
|
|
1088
|
+
* schema: z.object({ url: z.string() }),
|
|
1089
|
+
* }) {
|
|
1090
|
+
* async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
|
|
1091
|
+
* const controller = this.createLinkedAbortController(ctx);
|
|
1092
|
+
*
|
|
1093
|
+
* // fetch() will automatically abort when parent times out
|
|
1094
|
+
* const response = await fetch(params.url, { signal: controller.signal });
|
|
1095
|
+
* return response.text();
|
|
1096
|
+
* }
|
|
1097
|
+
* }
|
|
1098
|
+
* ```
|
|
1099
|
+
*/
|
|
1100
|
+
createLinkedAbortController(ctx) {
|
|
1101
|
+
const controller = new AbortController();
|
|
1102
|
+
if (ctx?.signal) {
|
|
1103
|
+
if (ctx.signal.aborted) {
|
|
1104
|
+
controller.abort(ctx.signal.reason);
|
|
1105
|
+
} else {
|
|
1106
|
+
ctx.signal.addEventListener(
|
|
1107
|
+
"abort",
|
|
1108
|
+
() => {
|
|
1109
|
+
controller.abort(ctx.signal.reason);
|
|
1110
|
+
},
|
|
1111
|
+
{ once: true }
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return controller;
|
|
1116
|
+
}
|
|
948
1117
|
/**
|
|
949
1118
|
* Auto-generated instruction text for the LLM.
|
|
950
1119
|
* Combines name, description, and parameter schema into a formatted instruction.
|
|
@@ -1012,8 +1181,8 @@ function createGadget(config) {
|
|
|
1012
1181
|
parameterSchema = config.schema;
|
|
1013
1182
|
timeoutMs = config.timeoutMs;
|
|
1014
1183
|
examples = config.examples;
|
|
1015
|
-
execute(params) {
|
|
1016
|
-
return config.execute(params);
|
|
1184
|
+
execute(params, ctx) {
|
|
1185
|
+
return config.execute(params, ctx);
|
|
1017
1186
|
}
|
|
1018
1187
|
}
|
|
1019
1188
|
return new DynamicGadget();
|
|
@@ -2319,6 +2488,162 @@ var init_block_params = __esm({
|
|
|
2319
2488
|
}
|
|
2320
2489
|
});
|
|
2321
2490
|
|
|
2491
|
+
// src/gadgets/cost-reporting-client.ts
|
|
2492
|
+
var CostReportingLLMistWrapper;
|
|
2493
|
+
var init_cost_reporting_client = __esm({
|
|
2494
|
+
"src/gadgets/cost-reporting-client.ts"() {
|
|
2495
|
+
"use strict";
|
|
2496
|
+
init_model_shortcuts();
|
|
2497
|
+
CostReportingLLMistWrapper = class {
|
|
2498
|
+
constructor(client, reportCost) {
|
|
2499
|
+
this.client = client;
|
|
2500
|
+
this.reportCost = reportCost;
|
|
2501
|
+
}
|
|
2502
|
+
/**
|
|
2503
|
+
* Access to model registry for cost estimation.
|
|
2504
|
+
*/
|
|
2505
|
+
get modelRegistry() {
|
|
2506
|
+
return this.client.modelRegistry;
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* Quick completion with automatic cost reporting.
|
|
2510
|
+
*
|
|
2511
|
+
* Streams internally to track token usage, then reports the calculated cost.
|
|
2512
|
+
*
|
|
2513
|
+
* @param prompt - User prompt
|
|
2514
|
+
* @param options - Optional configuration (model, temperature, etc.)
|
|
2515
|
+
* @returns Complete text response
|
|
2516
|
+
*/
|
|
2517
|
+
async complete(prompt, options) {
|
|
2518
|
+
const model = resolveModel(options?.model ?? "haiku");
|
|
2519
|
+
let result = "";
|
|
2520
|
+
let inputTokens = 0;
|
|
2521
|
+
let outputTokens = 0;
|
|
2522
|
+
let cachedInputTokens = 0;
|
|
2523
|
+
let cacheCreationInputTokens = 0;
|
|
2524
|
+
const messages = [
|
|
2525
|
+
...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
|
|
2526
|
+
{ role: "user", content: prompt }
|
|
2527
|
+
];
|
|
2528
|
+
for await (const chunk of this.client.stream({
|
|
2529
|
+
model,
|
|
2530
|
+
messages,
|
|
2531
|
+
temperature: options?.temperature,
|
|
2532
|
+
maxTokens: options?.maxTokens
|
|
2533
|
+
})) {
|
|
2534
|
+
result += chunk.text ?? "";
|
|
2535
|
+
if (chunk.usage) {
|
|
2536
|
+
inputTokens = chunk.usage.inputTokens;
|
|
2537
|
+
outputTokens = chunk.usage.outputTokens;
|
|
2538
|
+
cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
|
|
2539
|
+
cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
|
|
2543
|
+
return result;
|
|
2544
|
+
}
|
|
2545
|
+
/**
|
|
2546
|
+
* Quick streaming with automatic cost reporting when stream completes.
|
|
2547
|
+
*
|
|
2548
|
+
* Yields text chunks as they arrive, then reports cost in finally block.
|
|
2549
|
+
*
|
|
2550
|
+
* @param prompt - User prompt
|
|
2551
|
+
* @param options - Optional configuration (model, temperature, etc.)
|
|
2552
|
+
* @returns Async generator yielding text chunks
|
|
2553
|
+
*/
|
|
2554
|
+
async *streamText(prompt, options) {
|
|
2555
|
+
const model = resolveModel(options?.model ?? "haiku");
|
|
2556
|
+
let inputTokens = 0;
|
|
2557
|
+
let outputTokens = 0;
|
|
2558
|
+
let cachedInputTokens = 0;
|
|
2559
|
+
let cacheCreationInputTokens = 0;
|
|
2560
|
+
const messages = [
|
|
2561
|
+
...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
|
|
2562
|
+
{ role: "user", content: prompt }
|
|
2563
|
+
];
|
|
2564
|
+
try {
|
|
2565
|
+
for await (const chunk of this.client.stream({
|
|
2566
|
+
model,
|
|
2567
|
+
messages,
|
|
2568
|
+
temperature: options?.temperature,
|
|
2569
|
+
maxTokens: options?.maxTokens
|
|
2570
|
+
})) {
|
|
2571
|
+
if (chunk.text) {
|
|
2572
|
+
yield chunk.text;
|
|
2573
|
+
}
|
|
2574
|
+
if (chunk.usage) {
|
|
2575
|
+
inputTokens = chunk.usage.inputTokens;
|
|
2576
|
+
outputTokens = chunk.usage.outputTokens;
|
|
2577
|
+
cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
|
|
2578
|
+
cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
} finally {
|
|
2582
|
+
this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
/**
|
|
2586
|
+
* Low-level stream access with automatic cost reporting.
|
|
2587
|
+
*
|
|
2588
|
+
* Returns a wrapped stream that reports costs when iteration completes.
|
|
2589
|
+
*
|
|
2590
|
+
* @param options - Full LLM generation options
|
|
2591
|
+
* @returns Wrapped LLM stream that auto-reports costs
|
|
2592
|
+
*/
|
|
2593
|
+
stream(options) {
|
|
2594
|
+
return this.createCostReportingStream(options);
|
|
2595
|
+
}
|
|
2596
|
+
/**
|
|
2597
|
+
* Creates a wrapped stream that tracks usage and reports costs on completion.
|
|
2598
|
+
*/
|
|
2599
|
+
createCostReportingStream(options) {
|
|
2600
|
+
const innerStream = this.client.stream(options);
|
|
2601
|
+
const reportCostFromUsage = this.reportCostFromUsage.bind(this);
|
|
2602
|
+
const model = options.model;
|
|
2603
|
+
async function* costReportingWrapper() {
|
|
2604
|
+
let inputTokens = 0;
|
|
2605
|
+
let outputTokens = 0;
|
|
2606
|
+
let cachedInputTokens = 0;
|
|
2607
|
+
let cacheCreationInputTokens = 0;
|
|
2608
|
+
try {
|
|
2609
|
+
for await (const chunk of innerStream) {
|
|
2610
|
+
if (chunk.usage) {
|
|
2611
|
+
inputTokens = chunk.usage.inputTokens;
|
|
2612
|
+
outputTokens = chunk.usage.outputTokens;
|
|
2613
|
+
cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
|
|
2614
|
+
cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
|
|
2615
|
+
}
|
|
2616
|
+
yield chunk;
|
|
2617
|
+
}
|
|
2618
|
+
} finally {
|
|
2619
|
+
if (inputTokens > 0 || outputTokens > 0) {
|
|
2620
|
+
reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
return costReportingWrapper();
|
|
2625
|
+
}
|
|
2626
|
+
/**
|
|
2627
|
+
* Calculates and reports cost from token usage.
|
|
2628
|
+
*/
|
|
2629
|
+
reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
|
|
2630
|
+
if (inputTokens === 0 && outputTokens === 0) return;
|
|
2631
|
+
const modelName = model.includes(":") ? model.split(":")[1] : model;
|
|
2632
|
+
const estimate = this.client.modelRegistry.estimateCost(
|
|
2633
|
+
modelName,
|
|
2634
|
+
inputTokens,
|
|
2635
|
+
outputTokens,
|
|
2636
|
+
cachedInputTokens,
|
|
2637
|
+
cacheCreationInputTokens
|
|
2638
|
+
);
|
|
2639
|
+
if (estimate && estimate.totalCost > 0) {
|
|
2640
|
+
this.reportCost(estimate.totalCost);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
});
|
|
2646
|
+
|
|
2322
2647
|
// src/gadgets/error-formatter.ts
|
|
2323
2648
|
var GadgetErrorFormatter;
|
|
2324
2649
|
var init_error_formatter = __esm({
|
|
@@ -2402,38 +2727,6 @@ var init_error_formatter = __esm({
|
|
|
2402
2727
|
}
|
|
2403
2728
|
});
|
|
2404
2729
|
|
|
2405
|
-
// src/gadgets/exceptions.ts
|
|
2406
|
-
var BreakLoopException, HumanInputException, TimeoutException;
|
|
2407
|
-
var init_exceptions = __esm({
|
|
2408
|
-
"src/gadgets/exceptions.ts"() {
|
|
2409
|
-
"use strict";
|
|
2410
|
-
BreakLoopException = class extends Error {
|
|
2411
|
-
constructor(message) {
|
|
2412
|
-
super(message ?? "Agent loop terminated by gadget");
|
|
2413
|
-
this.name = "BreakLoopException";
|
|
2414
|
-
}
|
|
2415
|
-
};
|
|
2416
|
-
HumanInputException = class extends Error {
|
|
2417
|
-
question;
|
|
2418
|
-
constructor(question) {
|
|
2419
|
-
super(`Human input required: ${question}`);
|
|
2420
|
-
this.name = "HumanInputException";
|
|
2421
|
-
this.question = question;
|
|
2422
|
-
}
|
|
2423
|
-
};
|
|
2424
|
-
TimeoutException = class extends Error {
|
|
2425
|
-
timeoutMs;
|
|
2426
|
-
gadgetName;
|
|
2427
|
-
constructor(gadgetName, timeoutMs) {
|
|
2428
|
-
super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
|
|
2429
|
-
this.name = "TimeoutException";
|
|
2430
|
-
this.gadgetName = gadgetName;
|
|
2431
|
-
this.timeoutMs = timeoutMs;
|
|
2432
|
-
}
|
|
2433
|
-
};
|
|
2434
|
-
}
|
|
2435
|
-
});
|
|
2436
|
-
|
|
2437
2730
|
// src/gadgets/parser.ts
|
|
2438
2731
|
function stripMarkdownFences(content) {
|
|
2439
2732
|
let cleaned = content.trim();
|
|
@@ -2619,14 +2912,16 @@ var init_executor = __esm({
|
|
|
2619
2912
|
init_constants();
|
|
2620
2913
|
init_logger();
|
|
2621
2914
|
init_block_params();
|
|
2915
|
+
init_cost_reporting_client();
|
|
2622
2916
|
init_error_formatter();
|
|
2623
2917
|
init_exceptions();
|
|
2624
2918
|
init_parser();
|
|
2625
2919
|
GadgetExecutor = class {
|
|
2626
|
-
constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
|
|
2920
|
+
constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client) {
|
|
2627
2921
|
this.registry = registry;
|
|
2628
2922
|
this.onHumanInputRequired = onHumanInputRequired;
|
|
2629
2923
|
this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
|
|
2924
|
+
this.client = client;
|
|
2630
2925
|
this.logger = logger ?? createLogger({ name: "llmist:executor" });
|
|
2631
2926
|
this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
|
|
2632
2927
|
this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
|
|
@@ -2636,14 +2931,27 @@ var init_executor = __esm({
|
|
|
2636
2931
|
argPrefix;
|
|
2637
2932
|
/**
|
|
2638
2933
|
* Creates a promise that rejects with a TimeoutException after the specified timeout.
|
|
2934
|
+
* Aborts the provided AbortController before rejecting, allowing gadgets to clean up.
|
|
2639
2935
|
*/
|
|
2640
|
-
createTimeoutPromise(gadgetName, timeoutMs) {
|
|
2936
|
+
createTimeoutPromise(gadgetName, timeoutMs, abortController) {
|
|
2641
2937
|
return new Promise((_, reject) => {
|
|
2642
2938
|
setTimeout(() => {
|
|
2643
|
-
|
|
2939
|
+
const timeoutError = new TimeoutException(gadgetName, timeoutMs);
|
|
2940
|
+
abortController.abort(timeoutError.message);
|
|
2941
|
+
reject(timeoutError);
|
|
2644
2942
|
}, timeoutMs);
|
|
2645
2943
|
});
|
|
2646
2944
|
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Normalizes gadget execute result to consistent format.
|
|
2947
|
+
* Handles both string returns (backwards compat) and object returns with cost.
|
|
2948
|
+
*/
|
|
2949
|
+
normalizeExecuteResult(raw) {
|
|
2950
|
+
if (typeof raw === "string") {
|
|
2951
|
+
return { result: raw, cost: 0 };
|
|
2952
|
+
}
|
|
2953
|
+
return { result: raw.result, cost: raw.cost ?? 0 };
|
|
2954
|
+
}
|
|
2647
2955
|
// Execute a gadget call asynchronously
|
|
2648
2956
|
async execute(call) {
|
|
2649
2957
|
const startTime = Date.now();
|
|
@@ -2738,30 +3046,53 @@ var init_executor = __esm({
|
|
|
2738
3046
|
validatedParameters = schemaAwareParameters;
|
|
2739
3047
|
}
|
|
2740
3048
|
const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
|
|
2741
|
-
|
|
3049
|
+
const abortController = new AbortController();
|
|
3050
|
+
let callbackCost = 0;
|
|
3051
|
+
const reportCost = (amount) => {
|
|
3052
|
+
if (amount > 0) {
|
|
3053
|
+
callbackCost += amount;
|
|
3054
|
+
this.logger.debug("Gadget reported cost via callback", {
|
|
3055
|
+
gadgetName: call.gadgetName,
|
|
3056
|
+
amount,
|
|
3057
|
+
totalCallbackCost: callbackCost
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
};
|
|
3061
|
+
const ctx = {
|
|
3062
|
+
reportCost,
|
|
3063
|
+
llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
|
|
3064
|
+
signal: abortController.signal
|
|
3065
|
+
};
|
|
3066
|
+
let rawResult;
|
|
2742
3067
|
if (timeoutMs && timeoutMs > 0) {
|
|
2743
3068
|
this.logger.debug("Executing gadget with timeout", {
|
|
2744
3069
|
gadgetName: call.gadgetName,
|
|
2745
3070
|
timeoutMs
|
|
2746
3071
|
});
|
|
2747
|
-
|
|
2748
|
-
Promise.resolve(gadget.execute(validatedParameters)),
|
|
2749
|
-
this.createTimeoutPromise(call.gadgetName, timeoutMs)
|
|
3072
|
+
rawResult = await Promise.race([
|
|
3073
|
+
Promise.resolve(gadget.execute(validatedParameters, ctx)),
|
|
3074
|
+
this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController)
|
|
2750
3075
|
]);
|
|
2751
3076
|
} else {
|
|
2752
|
-
|
|
3077
|
+
rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
|
|
2753
3078
|
}
|
|
3079
|
+
const { result, cost: returnCost } = this.normalizeExecuteResult(rawResult);
|
|
3080
|
+
const totalCost = callbackCost + returnCost;
|
|
2754
3081
|
const executionTimeMs = Date.now() - startTime;
|
|
2755
3082
|
this.logger.info("Gadget executed successfully", {
|
|
2756
3083
|
gadgetName: call.gadgetName,
|
|
2757
3084
|
invocationId: call.invocationId,
|
|
2758
|
-
executionTimeMs
|
|
3085
|
+
executionTimeMs,
|
|
3086
|
+
cost: totalCost > 0 ? totalCost : void 0,
|
|
3087
|
+
callbackCost: callbackCost > 0 ? callbackCost : void 0,
|
|
3088
|
+
returnCost: returnCost > 0 ? returnCost : void 0
|
|
2759
3089
|
});
|
|
2760
3090
|
this.logger.debug("Gadget result", {
|
|
2761
3091
|
gadgetName: call.gadgetName,
|
|
2762
3092
|
invocationId: call.invocationId,
|
|
2763
3093
|
parameters: validatedParameters,
|
|
2764
3094
|
result,
|
|
3095
|
+
cost: totalCost,
|
|
2765
3096
|
executionTimeMs
|
|
2766
3097
|
});
|
|
2767
3098
|
return {
|
|
@@ -2769,7 +3100,8 @@ var init_executor = __esm({
|
|
|
2769
3100
|
invocationId: call.invocationId,
|
|
2770
3101
|
parameters: validatedParameters,
|
|
2771
3102
|
result,
|
|
2772
|
-
executionTimeMs
|
|
3103
|
+
executionTimeMs,
|
|
3104
|
+
cost: totalCost
|
|
2773
3105
|
};
|
|
2774
3106
|
} catch (error) {
|
|
2775
3107
|
if (error instanceof BreakLoopException) {
|
|
@@ -2800,6 +3132,19 @@ var init_executor = __esm({
|
|
|
2800
3132
|
executionTimeMs: Date.now() - startTime
|
|
2801
3133
|
};
|
|
2802
3134
|
}
|
|
3135
|
+
if (error instanceof AbortError) {
|
|
3136
|
+
this.logger.info("Gadget execution was aborted", {
|
|
3137
|
+
gadgetName: call.gadgetName,
|
|
3138
|
+
executionTimeMs: Date.now() - startTime
|
|
3139
|
+
});
|
|
3140
|
+
return {
|
|
3141
|
+
gadgetName: call.gadgetName,
|
|
3142
|
+
invocationId: call.invocationId,
|
|
3143
|
+
parameters: validatedParameters,
|
|
3144
|
+
error: error.message,
|
|
3145
|
+
executionTimeMs: Date.now() - startTime
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
2803
3148
|
if (error instanceof HumanInputException) {
|
|
2804
3149
|
this.logger.info("Gadget requested human input", {
|
|
2805
3150
|
gadgetName: call.gadgetName,
|
|
@@ -2926,7 +3271,8 @@ var init_stream_processor = __esm({
|
|
|
2926
3271
|
options.onHumanInputRequired,
|
|
2927
3272
|
this.logger.getSubLogger({ name: "executor" }),
|
|
2928
3273
|
options.defaultGadgetTimeoutMs,
|
|
2929
|
-
{ argPrefix: options.gadgetArgPrefix }
|
|
3274
|
+
{ argPrefix: options.gadgetArgPrefix },
|
|
3275
|
+
options.client
|
|
2930
3276
|
);
|
|
2931
3277
|
}
|
|
2932
3278
|
/**
|
|
@@ -3193,6 +3539,7 @@ var init_stream_processor = __esm({
|
|
|
3193
3539
|
error: result.error,
|
|
3194
3540
|
executionTimeMs: result.executionTimeMs,
|
|
3195
3541
|
breaksLoop: result.breaksLoop,
|
|
3542
|
+
cost: result.cost,
|
|
3196
3543
|
logger: this.logger
|
|
3197
3544
|
};
|
|
3198
3545
|
await this.hooks.observers.onGadgetExecutionComplete(context);
|
|
@@ -3549,6 +3896,17 @@ var init_agent = __esm({
|
|
|
3549
3896
|
llmOptions = { ...llmOptions, ...action.modifiedOptions };
|
|
3550
3897
|
}
|
|
3551
3898
|
}
|
|
3899
|
+
await this.safeObserve(async () => {
|
|
3900
|
+
if (this.hooks.observers?.onLLMCallReady) {
|
|
3901
|
+
const context = {
|
|
3902
|
+
iteration: currentIteration,
|
|
3903
|
+
maxIterations: this.maxIterations,
|
|
3904
|
+
options: llmOptions,
|
|
3905
|
+
logger: this.logger
|
|
3906
|
+
};
|
|
3907
|
+
await this.hooks.observers.onLLMCallReady(context);
|
|
3908
|
+
}
|
|
3909
|
+
});
|
|
3552
3910
|
this.logger.info("Calling LLM", { model: this.model });
|
|
3553
3911
|
this.logger.silly("LLM request details", {
|
|
3554
3912
|
model: llmOptions.model,
|
|
@@ -3569,7 +3927,8 @@ var init_agent = __esm({
|
|
|
3569
3927
|
onHumanInputRequired: this.onHumanInputRequired,
|
|
3570
3928
|
stopOnGadgetError: this.stopOnGadgetError,
|
|
3571
3929
|
shouldContinueAfterError: this.shouldContinueAfterError,
|
|
3572
|
-
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
|
|
3930
|
+
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
|
|
3931
|
+
client: this.client
|
|
3573
3932
|
});
|
|
3574
3933
|
const result = await processor.process(stream2);
|
|
3575
3934
|
for (const output of result.outputs) {
|
|
@@ -6634,7 +6993,6 @@ var OPTION_FLAGS = {
|
|
|
6634
6993
|
logFile: "--log-file <path>",
|
|
6635
6994
|
logReset: "--log-reset",
|
|
6636
6995
|
logLlmRequests: "--log-llm-requests [dir]",
|
|
6637
|
-
logLlmResponses: "--log-llm-responses [dir]",
|
|
6638
6996
|
noBuiltins: "--no-builtins",
|
|
6639
6997
|
noBuiltinInteraction: "--no-builtin-interaction",
|
|
6640
6998
|
quiet: "-q, --quiet",
|
|
@@ -6653,8 +7011,7 @@ var OPTION_DESCRIPTIONS = {
|
|
|
6653
7011
|
logLevel: "Log level: silly, trace, debug, info, warn, error, fatal.",
|
|
6654
7012
|
logFile: "Path to log file. When set, logs are written to file instead of stderr.",
|
|
6655
7013
|
logReset: "Reset (truncate) the log file at session start instead of appending.",
|
|
6656
|
-
logLlmRequests: "Save
|
|
6657
|
-
logLlmResponses: "Save raw LLM responses as plain text. Optional dir, defaults to ~/.llmist/logs/responses/",
|
|
7014
|
+
logLlmRequests: "Save LLM requests/responses to session directories. Optional dir, defaults to ~/.llmist/logs/requests/",
|
|
6658
7015
|
noBuiltins: "Disable built-in gadgets (AskUser, TellUser).",
|
|
6659
7016
|
noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser.",
|
|
6660
7017
|
quiet: "Suppress all output except content (text and TellUser messages).",
|
|
@@ -6671,7 +7028,7 @@ var import_commander2 = require("commander");
|
|
|
6671
7028
|
// package.json
|
|
6672
7029
|
var package_default = {
|
|
6673
7030
|
name: "llmist",
|
|
6674
|
-
version: "
|
|
7031
|
+
version: "2.0.0",
|
|
6675
7032
|
description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
|
|
6676
7033
|
type: "module",
|
|
6677
7034
|
main: "dist/index.cjs",
|
|
@@ -6844,6 +7201,14 @@ ${addedLines}`;
|
|
|
6844
7201
|
}
|
|
6845
7202
|
|
|
6846
7203
|
// src/cli/approval/context-providers.ts
|
|
7204
|
+
function formatGadgetSummary(gadgetName, params) {
|
|
7205
|
+
const paramEntries = Object.entries(params);
|
|
7206
|
+
if (paramEntries.length === 0) {
|
|
7207
|
+
return `${gadgetName}()`;
|
|
7208
|
+
}
|
|
7209
|
+
const paramStr = paramEntries.map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
|
|
7210
|
+
return `${gadgetName}(${paramStr})`;
|
|
7211
|
+
}
|
|
6847
7212
|
var WriteFileContextProvider = class {
|
|
6848
7213
|
gadgetName = "WriteFile";
|
|
6849
7214
|
async getContext(params) {
|
|
@@ -6852,14 +7217,14 @@ var WriteFileContextProvider = class {
|
|
|
6852
7217
|
const resolvedPath = (0, import_node_path2.resolve)(process.cwd(), filePath);
|
|
6853
7218
|
if (!(0, import_node_fs2.existsSync)(resolvedPath)) {
|
|
6854
7219
|
return {
|
|
6855
|
-
summary:
|
|
7220
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
6856
7221
|
details: formatNewFileDiff(filePath, newContent)
|
|
6857
7222
|
};
|
|
6858
7223
|
}
|
|
6859
7224
|
const oldContent = (0, import_node_fs2.readFileSync)(resolvedPath, "utf-8");
|
|
6860
7225
|
const diff = (0, import_diff.createPatch)(filePath, oldContent, newContent, "original", "modified");
|
|
6861
7226
|
return {
|
|
6862
|
-
summary:
|
|
7227
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
6863
7228
|
details: diff
|
|
6864
7229
|
};
|
|
6865
7230
|
}
|
|
@@ -6873,37 +7238,27 @@ var EditFileContextProvider = class {
|
|
|
6873
7238
|
const newContent = String(params.content);
|
|
6874
7239
|
if (!(0, import_node_fs2.existsSync)(resolvedPath)) {
|
|
6875
7240
|
return {
|
|
6876
|
-
summary:
|
|
7241
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
6877
7242
|
details: formatNewFileDiff(filePath, newContent)
|
|
6878
7243
|
};
|
|
6879
7244
|
}
|
|
6880
7245
|
const oldContent = (0, import_node_fs2.readFileSync)(resolvedPath, "utf-8");
|
|
6881
7246
|
const diff = (0, import_diff.createPatch)(filePath, oldContent, newContent, "original", "modified");
|
|
6882
7247
|
return {
|
|
6883
|
-
summary:
|
|
7248
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
6884
7249
|
details: diff
|
|
6885
7250
|
};
|
|
6886
7251
|
}
|
|
6887
7252
|
if ("commands" in params) {
|
|
6888
7253
|
const commands = String(params.commands);
|
|
6889
7254
|
return {
|
|
6890
|
-
summary:
|
|
7255
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
6891
7256
|
details: `Commands:
|
|
6892
7257
|
${commands}`
|
|
6893
7258
|
};
|
|
6894
7259
|
}
|
|
6895
7260
|
return {
|
|
6896
|
-
summary:
|
|
6897
|
-
};
|
|
6898
|
-
}
|
|
6899
|
-
};
|
|
6900
|
-
var RunCommandContextProvider = class {
|
|
6901
|
-
gadgetName = "RunCommand";
|
|
6902
|
-
async getContext(params) {
|
|
6903
|
-
const command = String(params.command ?? "");
|
|
6904
|
-
const cwd = params.cwd ? ` (in ${params.cwd})` : "";
|
|
6905
|
-
return {
|
|
6906
|
-
summary: `Execute: ${command}${cwd}`
|
|
7261
|
+
summary: formatGadgetSummary(this.gadgetName, params)
|
|
6907
7262
|
};
|
|
6908
7263
|
}
|
|
6909
7264
|
};
|
|
@@ -6912,27 +7267,15 @@ var DefaultContextProvider = class {
|
|
|
6912
7267
|
this.gadgetName = gadgetName;
|
|
6913
7268
|
}
|
|
6914
7269
|
async getContext(params) {
|
|
6915
|
-
const paramEntries = Object.entries(params);
|
|
6916
|
-
if (paramEntries.length === 0) {
|
|
6917
|
-
return {
|
|
6918
|
-
summary: `${this.gadgetName}()`
|
|
6919
|
-
};
|
|
6920
|
-
}
|
|
6921
|
-
const formatValue = (value) => {
|
|
6922
|
-
const MAX_LEN = 50;
|
|
6923
|
-
const str = JSON.stringify(value);
|
|
6924
|
-
return str.length > MAX_LEN ? `${str.slice(0, MAX_LEN - 3)}...` : str;
|
|
6925
|
-
};
|
|
6926
|
-
const paramStr = paramEntries.map(([k, v]) => `${k}=${formatValue(v)}`).join(", ");
|
|
6927
7270
|
return {
|
|
6928
|
-
summary:
|
|
7271
|
+
summary: formatGadgetSummary(this.gadgetName, params)
|
|
6929
7272
|
};
|
|
6930
7273
|
}
|
|
6931
7274
|
};
|
|
6932
7275
|
var builtinContextProviders = [
|
|
6933
7276
|
new WriteFileContextProvider(),
|
|
6934
|
-
new EditFileContextProvider()
|
|
6935
|
-
|
|
7277
|
+
new EditFileContextProvider()
|
|
7278
|
+
// Note: RunCommand uses DefaultContextProvider - no custom details needed
|
|
6936
7279
|
];
|
|
6937
7280
|
|
|
6938
7281
|
// src/cli/approval/manager.ts
|
|
@@ -6943,11 +7286,13 @@ var ApprovalManager = class {
|
|
|
6943
7286
|
* @param config - Approval configuration with per-gadget modes
|
|
6944
7287
|
* @param env - CLI environment for I/O operations
|
|
6945
7288
|
* @param progress - Optional progress indicator to pause during prompts
|
|
7289
|
+
* @param keyboard - Optional keyboard coordinator to disable ESC listener during prompts
|
|
6946
7290
|
*/
|
|
6947
|
-
constructor(config, env, progress) {
|
|
7291
|
+
constructor(config, env, progress, keyboard) {
|
|
6948
7292
|
this.config = config;
|
|
6949
7293
|
this.env = env;
|
|
6950
7294
|
this.progress = progress;
|
|
7295
|
+
this.keyboard = keyboard;
|
|
6951
7296
|
for (const provider of builtinContextProviders) {
|
|
6952
7297
|
this.registerProvider(provider);
|
|
6953
7298
|
}
|
|
@@ -7016,26 +7361,34 @@ var ApprovalManager = class {
|
|
|
7016
7361
|
const provider = this.providers.get(gadgetName.toLowerCase()) ?? new DefaultContextProvider(gadgetName);
|
|
7017
7362
|
const context = await provider.getContext(params);
|
|
7018
7363
|
this.progress?.pause();
|
|
7019
|
-
this.
|
|
7364
|
+
if (this.keyboard?.cleanupEsc) {
|
|
7365
|
+
this.keyboard.cleanupEsc();
|
|
7366
|
+
this.keyboard.cleanupEsc = null;
|
|
7367
|
+
}
|
|
7368
|
+
try {
|
|
7369
|
+
this.env.stderr.write(`
|
|
7020
7370
|
${import_chalk2.default.yellow("\u{1F512} Approval required:")} ${context.summary}
|
|
7021
7371
|
`);
|
|
7022
|
-
|
|
7023
|
-
|
|
7372
|
+
if (context.details) {
|
|
7373
|
+
this.env.stderr.write(`
|
|
7024
7374
|
${renderColoredDiff(context.details)}
|
|
7025
7375
|
`);
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7376
|
+
}
|
|
7377
|
+
const response = await this.prompt(" \u23CE approve, or type to reject: ");
|
|
7378
|
+
const isApproved = response === "" || response.toLowerCase() === "y";
|
|
7379
|
+
if (isApproved) {
|
|
7380
|
+
this.env.stderr.write(` ${import_chalk2.default.green("\u2713 Approved")}
|
|
7031
7381
|
|
|
7032
7382
|
`);
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7383
|
+
return { approved: true };
|
|
7384
|
+
}
|
|
7385
|
+
this.env.stderr.write(` ${import_chalk2.default.red("\u2717 Denied")}
|
|
7036
7386
|
|
|
7037
7387
|
`);
|
|
7038
|
-
|
|
7388
|
+
return { approved: false, reason: response || "Rejected by user" };
|
|
7389
|
+
} finally {
|
|
7390
|
+
this.keyboard?.restore();
|
|
7391
|
+
}
|
|
7039
7392
|
}
|
|
7040
7393
|
/**
|
|
7041
7394
|
* Prompts for user input.
|
|
@@ -7534,49 +7887,77 @@ error: ${message}`;
|
|
|
7534
7887
|
var import_zod8 = require("zod");
|
|
7535
7888
|
var runCommand = createGadget({
|
|
7536
7889
|
name: "RunCommand",
|
|
7537
|
-
description: "Execute a
|
|
7890
|
+
description: "Execute a command with arguments and return its output. Uses argv array to bypass shell - arguments are passed directly without interpretation. Returns stdout/stderr combined with exit status.",
|
|
7538
7891
|
schema: import_zod8.z.object({
|
|
7539
|
-
|
|
7892
|
+
argv: import_zod8.z.array(import_zod8.z.string()).describe("Command and arguments as array (e.g., ['git', 'commit', '-m', 'message'])"),
|
|
7540
7893
|
cwd: import_zod8.z.string().optional().describe("Working directory for the command (default: current directory)"),
|
|
7541
7894
|
timeout: import_zod8.z.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
|
|
7542
7895
|
}),
|
|
7543
7896
|
examples: [
|
|
7544
7897
|
{
|
|
7545
|
-
params: {
|
|
7898
|
+
params: { argv: ["ls", "-la"], timeout: 3e4 },
|
|
7546
7899
|
output: "status=0\n\ntotal 24\ndrwxr-xr-x 5 user staff 160 Nov 27 10:00 .\ndrwxr-xr-x 3 user staff 96 Nov 27 09:00 ..\n-rw-r--r-- 1 user staff 1024 Nov 27 10:00 package.json",
|
|
7547
7900
|
comment: "List directory contents with details"
|
|
7548
7901
|
},
|
|
7549
7902
|
{
|
|
7550
|
-
params: {
|
|
7903
|
+
params: { argv: ["echo", "Hello World"], timeout: 3e4 },
|
|
7551
7904
|
output: "status=0\n\nHello World",
|
|
7552
|
-
comment: "
|
|
7905
|
+
comment: "Echo without shell - argument passed directly"
|
|
7553
7906
|
},
|
|
7554
7907
|
{
|
|
7555
|
-
params: {
|
|
7908
|
+
params: { argv: ["cat", "nonexistent.txt"], timeout: 3e4 },
|
|
7556
7909
|
output: "status=1\n\ncat: nonexistent.txt: No such file or directory",
|
|
7557
7910
|
comment: "Command that fails returns non-zero status"
|
|
7558
7911
|
},
|
|
7559
7912
|
{
|
|
7560
|
-
params: {
|
|
7913
|
+
params: { argv: ["pwd"], cwd: "/tmp", timeout: 3e4 },
|
|
7561
7914
|
output: "status=0\n\n/tmp",
|
|
7562
7915
|
comment: "Execute command in a specific directory"
|
|
7916
|
+
},
|
|
7917
|
+
{
|
|
7918
|
+
params: { argv: ["gh", "pr", "review", "123", "--comment", "--body", "Review with `backticks` and 'quotes'"], timeout: 3e4 },
|
|
7919
|
+
output: "status=0\n\n(no output)",
|
|
7920
|
+
comment: "Complex arguments with special characters - no escaping needed"
|
|
7921
|
+
},
|
|
7922
|
+
{
|
|
7923
|
+
params: {
|
|
7924
|
+
argv: [
|
|
7925
|
+
"gh",
|
|
7926
|
+
"pr",
|
|
7927
|
+
"review",
|
|
7928
|
+
"123",
|
|
7929
|
+
"--approve",
|
|
7930
|
+
"--body",
|
|
7931
|
+
"## Review Summary\n\n**Looks good!**\n\n- Clean code\n- Tests pass"
|
|
7932
|
+
],
|
|
7933
|
+
timeout: 3e4
|
|
7934
|
+
},
|
|
7935
|
+
output: "status=0\n\nApproving pull request #123",
|
|
7936
|
+
comment: "Multiline body: --body flag and content must be SEPARATE array elements"
|
|
7563
7937
|
}
|
|
7564
7938
|
],
|
|
7565
|
-
execute: async ({
|
|
7939
|
+
execute: async ({ argv, cwd, timeout }) => {
|
|
7566
7940
|
const workingDir = cwd ?? process.cwd();
|
|
7941
|
+
if (argv.length === 0) {
|
|
7942
|
+
return "status=1\n\nerror: argv array cannot be empty";
|
|
7943
|
+
}
|
|
7944
|
+
let timeoutId;
|
|
7567
7945
|
try {
|
|
7568
|
-
const proc = Bun.spawn(
|
|
7946
|
+
const proc = Bun.spawn(argv, {
|
|
7569
7947
|
cwd: workingDir,
|
|
7570
7948
|
stdout: "pipe",
|
|
7571
7949
|
stderr: "pipe"
|
|
7572
7950
|
});
|
|
7573
7951
|
const timeoutPromise = new Promise((_, reject) => {
|
|
7574
|
-
setTimeout(() => {
|
|
7952
|
+
timeoutId = setTimeout(() => {
|
|
7575
7953
|
proc.kill();
|
|
7576
7954
|
reject(new Error(`Command timed out after ${timeout}ms`));
|
|
7577
7955
|
}, timeout);
|
|
7578
7956
|
});
|
|
7579
7957
|
const exitCode = await Promise.race([proc.exited, timeoutPromise]);
|
|
7958
|
+
if (timeoutId) {
|
|
7959
|
+
clearTimeout(timeoutId);
|
|
7960
|
+
}
|
|
7580
7961
|
const stdout = await new Response(proc.stdout).text();
|
|
7581
7962
|
const stderr = await new Response(proc.stderr).text();
|
|
7582
7963
|
const output = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
@@ -7584,6 +7965,9 @@ var runCommand = createGadget({
|
|
|
7584
7965
|
|
|
7585
7966
|
${output || "(no output)"}`;
|
|
7586
7967
|
} catch (error) {
|
|
7968
|
+
if (timeoutId) {
|
|
7969
|
+
clearTimeout(timeoutId);
|
|
7970
|
+
}
|
|
7587
7971
|
const message = error instanceof Error ? error.message : String(error);
|
|
7588
7972
|
return `status=1
|
|
7589
7973
|
|
|
@@ -7756,6 +8140,30 @@ async function writeLogFile(dir, filename, content) {
|
|
|
7756
8140
|
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
7757
8141
|
await (0, import_promises2.writeFile)((0, import_node_path7.join)(dir, filename), content, "utf-8");
|
|
7758
8142
|
}
|
|
8143
|
+
function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
8144
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
8145
|
+
const year = date.getFullYear();
|
|
8146
|
+
const month = pad(date.getMonth() + 1);
|
|
8147
|
+
const day = pad(date.getDate());
|
|
8148
|
+
const hours = pad(date.getHours());
|
|
8149
|
+
const minutes = pad(date.getMinutes());
|
|
8150
|
+
const seconds = pad(date.getSeconds());
|
|
8151
|
+
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
|
|
8152
|
+
}
|
|
8153
|
+
async function createSessionDir(baseDir) {
|
|
8154
|
+
const timestamp = formatSessionTimestamp();
|
|
8155
|
+
const sessionDir = (0, import_node_path7.join)(baseDir, timestamp);
|
|
8156
|
+
try {
|
|
8157
|
+
await (0, import_promises2.mkdir)(sessionDir, { recursive: true });
|
|
8158
|
+
return sessionDir;
|
|
8159
|
+
} catch (error) {
|
|
8160
|
+
console.warn(`[llmist] Failed to create log session directory: ${sessionDir}`, error);
|
|
8161
|
+
return void 0;
|
|
8162
|
+
}
|
|
8163
|
+
}
|
|
8164
|
+
function formatCallNumber(n) {
|
|
8165
|
+
return n.toString().padStart(4, "0");
|
|
8166
|
+
}
|
|
7759
8167
|
|
|
7760
8168
|
// src/cli/utils.ts
|
|
7761
8169
|
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
@@ -7919,7 +8327,7 @@ function formatBytes(bytes) {
|
|
|
7919
8327
|
}
|
|
7920
8328
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
7921
8329
|
}
|
|
7922
|
-
function
|
|
8330
|
+
function formatGadgetSummary2(result) {
|
|
7923
8331
|
const gadgetLabel = import_chalk3.default.magenta.bold(result.gadgetName);
|
|
7924
8332
|
const timeLabel = import_chalk3.default.dim(`${Math.round(result.executionTimeMs)}ms`);
|
|
7925
8333
|
const paramsStr = formatParametersInline(result.parameters);
|
|
@@ -8004,12 +8412,21 @@ function isInteractive(stream2) {
|
|
|
8004
8412
|
}
|
|
8005
8413
|
var ESC_KEY = 27;
|
|
8006
8414
|
var ESC_TIMEOUT_MS = 50;
|
|
8007
|
-
|
|
8415
|
+
var CTRL_C = 3;
|
|
8416
|
+
function createEscKeyListener(stdin, onEsc, onCtrlC) {
|
|
8008
8417
|
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
8009
8418
|
return null;
|
|
8010
8419
|
}
|
|
8011
8420
|
let escTimeout = null;
|
|
8012
8421
|
const handleData = (data) => {
|
|
8422
|
+
if (data[0] === CTRL_C && onCtrlC) {
|
|
8423
|
+
if (escTimeout) {
|
|
8424
|
+
clearTimeout(escTimeout);
|
|
8425
|
+
escTimeout = null;
|
|
8426
|
+
}
|
|
8427
|
+
onCtrlC();
|
|
8428
|
+
return;
|
|
8429
|
+
}
|
|
8013
8430
|
if (data[0] === ESC_KEY) {
|
|
8014
8431
|
if (data.length === 1) {
|
|
8015
8432
|
escTimeout = setTimeout(() => {
|
|
@@ -8457,7 +8874,7 @@ function addCompleteOptions(cmd, defaults) {
|
|
|
8457
8874
|
OPTION_DESCRIPTIONS.maxTokens,
|
|
8458
8875
|
createNumericParser({ label: "Max tokens", integer: true, min: 1 }),
|
|
8459
8876
|
defaults?.["max-tokens"]
|
|
8460
|
-
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"])
|
|
8877
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]);
|
|
8461
8878
|
}
|
|
8462
8879
|
function addAgentOptions(cmd, defaults) {
|
|
8463
8880
|
const gadgetAccumulator = (value, previous = []) => [
|
|
@@ -8481,7 +8898,7 @@ function addAgentOptions(cmd, defaults) {
|
|
|
8481
8898
|
OPTION_FLAGS.noBuiltinInteraction,
|
|
8482
8899
|
OPTION_DESCRIPTIONS.noBuiltinInteraction,
|
|
8483
8900
|
defaults?.["builtin-interaction"] !== false
|
|
8484
|
-
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]).option(OPTION_FLAGS.
|
|
8901
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]).option(OPTION_FLAGS.docker, OPTION_DESCRIPTIONS.docker).option(OPTION_FLAGS.dockerRo, OPTION_DESCRIPTIONS.dockerRo).option(OPTION_FLAGS.noDocker, OPTION_DESCRIPTIONS.noDocker).option(OPTION_FLAGS.dockerDev, OPTION_DESCRIPTIONS.dockerDev);
|
|
8485
8902
|
}
|
|
8486
8903
|
function configToCompleteOptions(config) {
|
|
8487
8904
|
const result = {};
|
|
@@ -8491,7 +8908,6 @@ function configToCompleteOptions(config) {
|
|
|
8491
8908
|
if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
|
|
8492
8909
|
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
8493
8910
|
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
8494
|
-
if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
|
|
8495
8911
|
return result;
|
|
8496
8912
|
}
|
|
8497
8913
|
function configToAgentOptions(config) {
|
|
@@ -8515,7 +8931,6 @@ function configToAgentOptions(config) {
|
|
|
8515
8931
|
result.gadgetApproval = config["gadget-approval"];
|
|
8516
8932
|
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
8517
8933
|
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
8518
|
-
if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
|
|
8519
8934
|
if (config.docker !== void 0) result.docker = config.docker;
|
|
8520
8935
|
if (config["docker-cwd-permission"] !== void 0)
|
|
8521
8936
|
result.dockerCwdPermission = config["docker-cwd-permission"];
|
|
@@ -8533,7 +8948,8 @@ var DOCKER_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
8533
8948
|
"env-vars",
|
|
8534
8949
|
"image-name",
|
|
8535
8950
|
"dev-mode",
|
|
8536
|
-
"dev-source"
|
|
8951
|
+
"dev-source",
|
|
8952
|
+
"docker-args"
|
|
8537
8953
|
]);
|
|
8538
8954
|
var DEFAULT_IMAGE_NAME = "llmist-sandbox";
|
|
8539
8955
|
var DEFAULT_CWD_PERMISSION = "rw";
|
|
@@ -8648,7 +9064,6 @@ var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
8648
9064
|
"log-file",
|
|
8649
9065
|
"log-reset",
|
|
8650
9066
|
"log-llm-requests",
|
|
8651
|
-
"log-llm-responses",
|
|
8652
9067
|
"type",
|
|
8653
9068
|
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
8654
9069
|
"docker",
|
|
@@ -8681,7 +9096,6 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
8681
9096
|
"log-file",
|
|
8682
9097
|
"log-reset",
|
|
8683
9098
|
"log-llm-requests",
|
|
8684
|
-
"log-llm-responses",
|
|
8685
9099
|
"type",
|
|
8686
9100
|
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
8687
9101
|
"docker",
|
|
@@ -8869,13 +9283,6 @@ function validateCompleteConfig(raw, section) {
|
|
|
8869
9283
|
section
|
|
8870
9284
|
);
|
|
8871
9285
|
}
|
|
8872
|
-
if ("log-llm-responses" in rawObj) {
|
|
8873
|
-
result["log-llm-responses"] = validateStringOrBoolean(
|
|
8874
|
-
rawObj["log-llm-responses"],
|
|
8875
|
-
"log-llm-responses",
|
|
8876
|
-
section
|
|
8877
|
-
);
|
|
8878
|
-
}
|
|
8879
9286
|
return result;
|
|
8880
9287
|
}
|
|
8881
9288
|
function validateAgentConfig(raw, section) {
|
|
@@ -8954,13 +9361,6 @@ function validateAgentConfig(raw, section) {
|
|
|
8954
9361
|
section
|
|
8955
9362
|
);
|
|
8956
9363
|
}
|
|
8957
|
-
if ("log-llm-responses" in rawObj) {
|
|
8958
|
-
result["log-llm-responses"] = validateStringOrBoolean(
|
|
8959
|
-
rawObj["log-llm-responses"],
|
|
8960
|
-
"log-llm-responses",
|
|
8961
|
-
section
|
|
8962
|
-
);
|
|
8963
|
-
}
|
|
8964
9364
|
return result;
|
|
8965
9365
|
}
|
|
8966
9366
|
function validateStringOrBoolean(value, field, section) {
|
|
@@ -9402,6 +9802,9 @@ function validateDockerConfig(raw, section) {
|
|
|
9402
9802
|
if ("dev-source" in rawObj) {
|
|
9403
9803
|
result["dev-source"] = validateString2(rawObj["dev-source"], "dev-source", section);
|
|
9404
9804
|
}
|
|
9805
|
+
if ("docker-args" in rawObj) {
|
|
9806
|
+
result["docker-args"] = validateStringArray2(rawObj["docker-args"], "docker-args", section);
|
|
9807
|
+
}
|
|
9405
9808
|
return result;
|
|
9406
9809
|
}
|
|
9407
9810
|
|
|
@@ -9413,6 +9816,8 @@ FROM oven/bun:1-debian
|
|
|
9413
9816
|
|
|
9414
9817
|
# Install essential tools
|
|
9415
9818
|
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
9819
|
+
# ed for EditFile gadget (line-oriented editor)
|
|
9820
|
+
ed \\
|
|
9416
9821
|
# ripgrep for fast file searching
|
|
9417
9822
|
ripgrep \\
|
|
9418
9823
|
# git for version control operations
|
|
@@ -9445,6 +9850,7 @@ FROM oven/bun:1-debian
|
|
|
9445
9850
|
|
|
9446
9851
|
# Install essential tools (same as production)
|
|
9447
9852
|
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
9853
|
+
ed \\
|
|
9448
9854
|
ripgrep \\
|
|
9449
9855
|
git \\
|
|
9450
9856
|
curl \\
|
|
@@ -9679,6 +10085,9 @@ function buildDockerRunArgs(ctx, imageName, devMode) {
|
|
|
9679
10085
|
}
|
|
9680
10086
|
}
|
|
9681
10087
|
}
|
|
10088
|
+
if (ctx.config["docker-args"]) {
|
|
10089
|
+
args.push(...ctx.config["docker-args"]);
|
|
10090
|
+
}
|
|
9682
10091
|
args.push(imageName);
|
|
9683
10092
|
args.push(...ctx.forwardArgs);
|
|
9684
10093
|
return args;
|
|
@@ -9845,6 +10254,8 @@ async function executeAgent(promptArg, options, env) {
|
|
|
9845
10254
|
env.stderr.write(import_chalk5.default.yellow(`
|
|
9846
10255
|
[Cancelled] ${progress.formatStats()}
|
|
9847
10256
|
`));
|
|
10257
|
+
} else {
|
|
10258
|
+
handleQuit();
|
|
9848
10259
|
}
|
|
9849
10260
|
};
|
|
9850
10261
|
const keyboard = {
|
|
@@ -9852,7 +10263,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
9852
10263
|
cleanupSigint: null,
|
|
9853
10264
|
restore: () => {
|
|
9854
10265
|
if (stdinIsInteractive && stdinStream.isTTY && !wasCancelled) {
|
|
9855
|
-
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
|
|
10266
|
+
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel, handleCancel);
|
|
9856
10267
|
}
|
|
9857
10268
|
}
|
|
9858
10269
|
};
|
|
@@ -9877,7 +10288,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
9877
10288
|
process.exit(130);
|
|
9878
10289
|
};
|
|
9879
10290
|
if (stdinIsInteractive && stdinStream.isTTY) {
|
|
9880
|
-
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
|
|
10291
|
+
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel, handleCancel);
|
|
9881
10292
|
}
|
|
9882
10293
|
keyboard.cleanupSigint = createSigintListener(
|
|
9883
10294
|
handleCancel,
|
|
@@ -9903,11 +10314,11 @@ async function executeAgent(promptArg, options, env) {
|
|
|
9903
10314
|
gadgetApprovals,
|
|
9904
10315
|
defaultMode: "allowed"
|
|
9905
10316
|
};
|
|
9906
|
-
const approvalManager = new ApprovalManager(approvalConfig, env, progress);
|
|
10317
|
+
const approvalManager = new ApprovalManager(approvalConfig, env, progress, keyboard);
|
|
9907
10318
|
let usage;
|
|
9908
10319
|
let iterations = 0;
|
|
9909
|
-
const
|
|
9910
|
-
|
|
10320
|
+
const llmLogsBaseDir = resolveLogDir(options.logLlmRequests, "requests");
|
|
10321
|
+
let llmSessionDir;
|
|
9911
10322
|
let llmCallCounter = 0;
|
|
9912
10323
|
const countMessagesTokens = async (model, messages) => {
|
|
9913
10324
|
try {
|
|
@@ -9939,10 +10350,19 @@ async function executeAgent(promptArg, options, env) {
|
|
|
9939
10350
|
);
|
|
9940
10351
|
progress.startCall(context.options.model, inputTokens);
|
|
9941
10352
|
progress.setInputTokens(inputTokens, false);
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
10353
|
+
},
|
|
10354
|
+
// onLLMCallReady: Log the exact request being sent to the LLM
|
|
10355
|
+
// This fires AFTER controller modifications (e.g., trailing messages)
|
|
10356
|
+
onLLMCallReady: async (context) => {
|
|
10357
|
+
if (llmLogsBaseDir) {
|
|
10358
|
+
if (!llmSessionDir) {
|
|
10359
|
+
llmSessionDir = await createSessionDir(llmLogsBaseDir);
|
|
10360
|
+
}
|
|
10361
|
+
if (llmSessionDir) {
|
|
10362
|
+
const filename = `${formatCallNumber(llmCallCounter)}.request`;
|
|
10363
|
+
const content = formatLlmRequest(context.options.messages);
|
|
10364
|
+
await writeLogFile(llmSessionDir, filename, content);
|
|
10365
|
+
}
|
|
9946
10366
|
}
|
|
9947
10367
|
},
|
|
9948
10368
|
// onStreamChunk: Real-time updates as LLM generates tokens
|
|
@@ -10007,9 +10427,9 @@ async function executeAgent(promptArg, options, env) {
|
|
|
10007
10427
|
`);
|
|
10008
10428
|
}
|
|
10009
10429
|
}
|
|
10010
|
-
if (
|
|
10011
|
-
const filename = `${
|
|
10012
|
-
await writeLogFile(
|
|
10430
|
+
if (llmSessionDir) {
|
|
10431
|
+
const filename = `${formatCallNumber(llmCallCounter)}.response`;
|
|
10432
|
+
await writeLogFile(llmSessionDir, filename, context.rawResponse);
|
|
10013
10433
|
}
|
|
10014
10434
|
}
|
|
10015
10435
|
},
|
|
@@ -10106,6 +10526,13 @@ Denied: ${result.reason ?? "by user"}`
|
|
|
10106
10526
|
parameterMapping: (text) => ({ message: text, done: false, type: "info" }),
|
|
10107
10527
|
resultMapping: (text) => `\u2139\uFE0F ${text}`
|
|
10108
10528
|
});
|
|
10529
|
+
builder.withTrailingMessage(
|
|
10530
|
+
(ctx) => [
|
|
10531
|
+
`[Iteration ${ctx.iteration + 1}/${ctx.maxIterations}]`,
|
|
10532
|
+
"Think carefully: what gadget invocations can you make in parallel right now?",
|
|
10533
|
+
"Maximize efficiency by batching independent operations in a single response."
|
|
10534
|
+
].join(" ")
|
|
10535
|
+
);
|
|
10109
10536
|
const agent = builder.ask(prompt);
|
|
10110
10537
|
let textBuffer = "";
|
|
10111
10538
|
const flushTextBuffer = () => {
|
|
@@ -10131,7 +10558,7 @@ Denied: ${result.reason ?? "by user"}`
|
|
|
10131
10558
|
}
|
|
10132
10559
|
} else {
|
|
10133
10560
|
const tokenCount = await countGadgetOutputTokens(event.result.result);
|
|
10134
|
-
env.stderr.write(`${
|
|
10561
|
+
env.stderr.write(`${formatGadgetSummary2({ ...event.result, tokenCount })}
|
|
10135
10562
|
`);
|
|
10136
10563
|
}
|
|
10137
10564
|
}
|
|
@@ -10143,7 +10570,10 @@ Denied: ${result.reason ?? "by user"}`
|
|
|
10143
10570
|
} finally {
|
|
10144
10571
|
isStreaming = false;
|
|
10145
10572
|
keyboard.cleanupEsc?.();
|
|
10146
|
-
keyboard.cleanupSigint
|
|
10573
|
+
if (keyboard.cleanupSigint) {
|
|
10574
|
+
keyboard.cleanupSigint();
|
|
10575
|
+
process.once("SIGINT", () => process.exit(130));
|
|
10576
|
+
}
|
|
10147
10577
|
}
|
|
10148
10578
|
flushTextBuffer();
|
|
10149
10579
|
progress.complete();
|
|
@@ -10191,13 +10621,15 @@ async function executeComplete(promptArg, options, env) {
|
|
|
10191
10621
|
}
|
|
10192
10622
|
builder.addUser(prompt);
|
|
10193
10623
|
const messages = builder.build();
|
|
10194
|
-
const
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10624
|
+
const llmLogsBaseDir = resolveLogDir(options.logLlmRequests, "requests");
|
|
10625
|
+
let llmSessionDir;
|
|
10626
|
+
if (llmLogsBaseDir) {
|
|
10627
|
+
llmSessionDir = await createSessionDir(llmLogsBaseDir);
|
|
10628
|
+
if (llmSessionDir) {
|
|
10629
|
+
const filename = "0001.request";
|
|
10630
|
+
const content = formatLlmRequest(messages);
|
|
10631
|
+
await writeLogFile(llmSessionDir, filename, content);
|
|
10632
|
+
}
|
|
10201
10633
|
}
|
|
10202
10634
|
const stream2 = client.stream({
|
|
10203
10635
|
model,
|
|
@@ -10236,9 +10668,9 @@ async function executeComplete(promptArg, options, env) {
|
|
|
10236
10668
|
progress.endCall(usage);
|
|
10237
10669
|
progress.complete();
|
|
10238
10670
|
printer.ensureNewline();
|
|
10239
|
-
if (
|
|
10240
|
-
const filename =
|
|
10241
|
-
await writeLogFile(
|
|
10671
|
+
if (llmSessionDir) {
|
|
10672
|
+
const filename = "0001.response";
|
|
10673
|
+
await writeLogFile(llmSessionDir, filename, accumulatedResponse);
|
|
10242
10674
|
}
|
|
10243
10675
|
if (stderrTTY && !options.quiet) {
|
|
10244
10676
|
const summary = renderSummary({ finishReason, usage, cost: progress.getTotalCost() });
|
|
@@ -10473,9 +10905,11 @@ ${issues}`);
|
|
|
10473
10905
|
env.stderr.write(import_chalk7.default.dim("\nExecuting...\n"));
|
|
10474
10906
|
const startTime = Date.now();
|
|
10475
10907
|
let result;
|
|
10908
|
+
let cost;
|
|
10476
10909
|
try {
|
|
10910
|
+
let rawResult;
|
|
10477
10911
|
if (gadget.timeoutMs && gadget.timeoutMs > 0) {
|
|
10478
|
-
|
|
10912
|
+
rawResult = await Promise.race([
|
|
10479
10913
|
Promise.resolve(gadget.execute(params)),
|
|
10480
10914
|
new Promise(
|
|
10481
10915
|
(_, reject) => setTimeout(
|
|
@@ -10485,15 +10919,18 @@ ${issues}`);
|
|
|
10485
10919
|
)
|
|
10486
10920
|
]);
|
|
10487
10921
|
} else {
|
|
10488
|
-
|
|
10922
|
+
rawResult = await Promise.resolve(gadget.execute(params));
|
|
10489
10923
|
}
|
|
10924
|
+
result = typeof rawResult === "string" ? rawResult : rawResult.result;
|
|
10925
|
+
cost = typeof rawResult === "object" ? rawResult.cost : void 0;
|
|
10490
10926
|
} catch (error) {
|
|
10491
10927
|
const message = error instanceof Error ? error.message : String(error);
|
|
10492
10928
|
throw new Error(`Execution failed: ${message}`);
|
|
10493
10929
|
}
|
|
10494
10930
|
const elapsed = Date.now() - startTime;
|
|
10931
|
+
const costInfo = cost !== void 0 && cost > 0 ? ` (Cost: $${cost.toFixed(6)})` : "";
|
|
10495
10932
|
env.stderr.write(import_chalk7.default.green(`
|
|
10496
|
-
\u2713 Completed in ${elapsed}ms
|
|
10933
|
+
\u2713 Completed in ${elapsed}ms${costInfo}
|
|
10497
10934
|
|
|
10498
10935
|
`));
|
|
10499
10936
|
formatOutput(result, options, env.stdout);
|