@samrahimi/smol-js 0.6.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +407 -154
- package/dist/cli.js +826 -134
- package/dist/cli.js.map +1 -1
- package/dist/index.d.mts +320 -9
- package/dist/index.d.ts +320 -9
- package/dist/index.js +727 -123
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +722 -122
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -710,9 +710,21 @@ Total time: ${(duration / 1e3).toFixed(2)}s`);
|
|
|
710
710
|
getName() {
|
|
711
711
|
return this.config.name;
|
|
712
712
|
}
|
|
713
|
+
/** Get agent type identifier */
|
|
714
|
+
getType() {
|
|
715
|
+
return this.constructor.name;
|
|
716
|
+
}
|
|
717
|
+
/** Get max steps configuration */
|
|
718
|
+
getMaxSteps() {
|
|
719
|
+
return this.config.maxSteps;
|
|
720
|
+
}
|
|
721
|
+
/** Set the event callback (useful for orchestration) */
|
|
722
|
+
setOnEvent(callback) {
|
|
723
|
+
this.config.onEvent = callback;
|
|
724
|
+
}
|
|
713
725
|
/** Sleep for a specified duration */
|
|
714
726
|
sleep(ms) {
|
|
715
|
-
return new Promise((
|
|
727
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
716
728
|
}
|
|
717
729
|
};
|
|
718
730
|
|
|
@@ -1381,12 +1393,12 @@ var UserInputTool = class extends Tool {
|
|
|
1381
1393
|
input: process.stdin,
|
|
1382
1394
|
output: process.stdout
|
|
1383
1395
|
});
|
|
1384
|
-
return new Promise((
|
|
1396
|
+
return new Promise((resolve6) => {
|
|
1385
1397
|
rl.question(`
|
|
1386
1398
|
[Agent asks]: ${question}
|
|
1387
1399
|
Your response: `, (answer) => {
|
|
1388
1400
|
rl.close();
|
|
1389
|
-
|
|
1401
|
+
resolve6(answer);
|
|
1390
1402
|
});
|
|
1391
1403
|
});
|
|
1392
1404
|
}
|
|
@@ -1865,6 +1877,10 @@ Please try a different approach.`
|
|
|
1865
1877
|
memoryStep.tokenUsage = response.tokenUsage;
|
|
1866
1878
|
if (response.content && response.content.trim()) {
|
|
1867
1879
|
this.logger.reasoning(response.content.trim());
|
|
1880
|
+
this.emitEvent("agent_thinking", {
|
|
1881
|
+
step: this.currentStep,
|
|
1882
|
+
content: response.content.trim()
|
|
1883
|
+
});
|
|
1868
1884
|
}
|
|
1869
1885
|
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
1870
1886
|
this.logger.warn("No tool calls in response. Prompting model to use tools.");
|
|
@@ -1897,42 +1913,84 @@ Please try a different approach.`
|
|
|
1897
1913
|
async processToolCalls(toolCalls) {
|
|
1898
1914
|
const results = [];
|
|
1899
1915
|
const executeTool = async (tc) => {
|
|
1916
|
+
const startTime = Date.now();
|
|
1900
1917
|
const toolName = tc.function.name;
|
|
1901
1918
|
const tool = this.tools.get(toolName);
|
|
1902
1919
|
if (!tool) {
|
|
1920
|
+
const error = `Unknown tool: ${toolName}. Available tools: ${Array.from(this.tools.keys()).join(", ")}`;
|
|
1921
|
+
this.emitEvent("agent_tool_result", {
|
|
1922
|
+
step: this.currentStep,
|
|
1923
|
+
toolCallId: tc.id,
|
|
1924
|
+
toolName,
|
|
1925
|
+
result: null,
|
|
1926
|
+
error,
|
|
1927
|
+
duration: Date.now() - startTime
|
|
1928
|
+
});
|
|
1903
1929
|
return {
|
|
1904
1930
|
toolCallId: tc.id,
|
|
1905
1931
|
toolName,
|
|
1906
1932
|
result: null,
|
|
1907
|
-
error
|
|
1933
|
+
error
|
|
1908
1934
|
};
|
|
1909
1935
|
}
|
|
1910
1936
|
let args;
|
|
1911
1937
|
try {
|
|
1912
1938
|
args = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
|
|
1913
1939
|
} catch {
|
|
1940
|
+
const error = `Failed to parse tool arguments: ${tc.function.arguments}`;
|
|
1941
|
+
this.emitEvent("agent_tool_result", {
|
|
1942
|
+
step: this.currentStep,
|
|
1943
|
+
toolCallId: tc.id,
|
|
1944
|
+
toolName,
|
|
1945
|
+
result: null,
|
|
1946
|
+
error,
|
|
1947
|
+
duration: Date.now() - startTime
|
|
1948
|
+
});
|
|
1914
1949
|
return {
|
|
1915
1950
|
toolCallId: tc.id,
|
|
1916
1951
|
toolName,
|
|
1917
1952
|
result: null,
|
|
1918
|
-
error
|
|
1953
|
+
error
|
|
1919
1954
|
};
|
|
1920
1955
|
}
|
|
1921
1956
|
this.logger.info(` Calling tool: ${toolName}(${JSON.stringify(args).slice(0, 100)}...)`);
|
|
1922
|
-
this.emitEvent("agent_tool_call", {
|
|
1957
|
+
this.emitEvent("agent_tool_call", {
|
|
1958
|
+
step: this.currentStep,
|
|
1959
|
+
toolCallId: tc.id,
|
|
1960
|
+
toolName,
|
|
1961
|
+
arguments: args
|
|
1962
|
+
});
|
|
1923
1963
|
try {
|
|
1924
1964
|
const result = await tool.call(args);
|
|
1965
|
+
const duration = Date.now() - startTime;
|
|
1966
|
+
this.emitEvent("agent_tool_result", {
|
|
1967
|
+
step: this.currentStep,
|
|
1968
|
+
toolCallId: tc.id,
|
|
1969
|
+
toolName,
|
|
1970
|
+
result,
|
|
1971
|
+
duration
|
|
1972
|
+
});
|
|
1925
1973
|
return {
|
|
1926
1974
|
toolCallId: tc.id,
|
|
1927
1975
|
toolName,
|
|
1928
1976
|
result
|
|
1929
1977
|
};
|
|
1930
1978
|
} catch (error) {
|
|
1979
|
+
const errorMsg = `Tool execution error: ${error.message}`;
|
|
1980
|
+
const duration = Date.now() - startTime;
|
|
1981
|
+
this.emitEvent("agent_tool_result", {
|
|
1982
|
+
step: this.currentStep,
|
|
1983
|
+
toolCallId: tc.id,
|
|
1984
|
+
toolName,
|
|
1985
|
+
result: null,
|
|
1986
|
+
error: errorMsg,
|
|
1987
|
+
duration
|
|
1988
|
+
});
|
|
1931
1989
|
return {
|
|
1932
1990
|
toolCallId: tc.id,
|
|
1933
1991
|
toolName,
|
|
1934
1992
|
result: null,
|
|
1935
|
-
error:
|
|
1993
|
+
error: errorMsg
|
|
1936
1994
|
};
|
|
1937
1995
|
}
|
|
1938
1996
|
};
|
|
@@ -2010,7 +2068,7 @@ var Model = class {
|
|
|
2010
2068
|
};
|
|
2011
2069
|
|
|
2012
2070
|
// src/models/OpenAIModel.ts
|
|
2013
|
-
import OpenAI from "openai";
|
|
2071
|
+
import OpenAI from "openai/index.mjs";
|
|
2014
2072
|
var DEFAULT_MODEL_ID = "anthropic/claude-sonnet-4.5";
|
|
2015
2073
|
var DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
|
|
2016
2074
|
var DEFAULT_TIMEOUT = 12e4;
|
|
@@ -2599,40 +2657,35 @@ var ExaGetContentsTool = class extends Tool {
|
|
|
2599
2657
|
// src/tools/ExaResearchTool.ts
|
|
2600
2658
|
var ExaResearchTool = class extends Tool {
|
|
2601
2659
|
name = "exa_research";
|
|
2602
|
-
description = "Perform
|
|
2660
|
+
description = "Perform comprehensive web research using Exa.ai's agentic research system. Provide natural-language instructions about what to research, and the AI research agent will plan searches, gather sources, extract facts, and synthesize findings into a detailed markdown report with citations. Ideal for deep research on any topic. Results typically complete in 20-90 seconds.";
|
|
2603
2661
|
inputs = {
|
|
2604
|
-
|
|
2662
|
+
instructions: {
|
|
2605
2663
|
type: "string",
|
|
2606
|
-
description: "
|
|
2664
|
+
description: "Natural-language task description of what to research (max 4096 characters). Be clear about what information you want, how research should be conducted, and what the output should contain.",
|
|
2607
2665
|
required: true
|
|
2608
2666
|
},
|
|
2609
|
-
|
|
2610
|
-
type: "number",
|
|
2611
|
-
description: "Number of primary sources to retrieve (default: 5, max: 10)",
|
|
2612
|
-
required: false,
|
|
2613
|
-
default: 5
|
|
2614
|
-
},
|
|
2615
|
-
category: {
|
|
2667
|
+
model: {
|
|
2616
2668
|
type: "string",
|
|
2617
|
-
description: '
|
|
2618
|
-
required: false
|
|
2619
|
-
},
|
|
2620
|
-
includeDomains: {
|
|
2621
|
-
type: "array",
|
|
2622
|
-
description: "Only include results from these domains",
|
|
2669
|
+
description: 'Research model: "exa-research-fast" (faster, cheaper), "exa-research" (balanced, default), or "exa-research-pro" (most thorough)',
|
|
2623
2670
|
required: false
|
|
2624
2671
|
},
|
|
2625
|
-
|
|
2626
|
-
type: "
|
|
2627
|
-
description: "
|
|
2672
|
+
outputSchema: {
|
|
2673
|
+
type: "object",
|
|
2674
|
+
description: "Optional JSON Schema to enforce structured output format (max 8 root fields, 5 levels deep). If omitted, returns markdown report.",
|
|
2628
2675
|
required: false
|
|
2629
2676
|
}
|
|
2630
2677
|
};
|
|
2631
2678
|
outputType = "string";
|
|
2632
2679
|
apiKey;
|
|
2680
|
+
defaultModel;
|
|
2681
|
+
pollInterval;
|
|
2682
|
+
maxPollTime;
|
|
2633
2683
|
constructor(config) {
|
|
2634
2684
|
super();
|
|
2635
2685
|
this.apiKey = config?.apiKey ?? process.env.EXA_API_KEY ?? "";
|
|
2686
|
+
this.defaultModel = config?.model ?? "exa-research";
|
|
2687
|
+
this.pollInterval = config?.pollInterval ?? 2e3;
|
|
2688
|
+
this.maxPollTime = config?.maxPollTime ?? 3e5;
|
|
2636
2689
|
}
|
|
2637
2690
|
async setup() {
|
|
2638
2691
|
if (!this.apiKey) {
|
|
@@ -2641,97 +2694,386 @@ var ExaResearchTool = class extends Tool {
|
|
|
2641
2694
|
this.isSetup = true;
|
|
2642
2695
|
}
|
|
2643
2696
|
async execute(args) {
|
|
2644
|
-
const
|
|
2645
|
-
const
|
|
2646
|
-
const
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2697
|
+
const instructions = args.instructions;
|
|
2698
|
+
const model = args.model ?? this.defaultModel;
|
|
2699
|
+
const outputSchema = args.outputSchema;
|
|
2700
|
+
if (!instructions || instructions.length > 4096) {
|
|
2701
|
+
throw new Error("Instructions are required and must be <= 4096 characters");
|
|
2702
|
+
}
|
|
2703
|
+
const createBody = {
|
|
2704
|
+
instructions,
|
|
2705
|
+
model
|
|
2651
2706
|
};
|
|
2652
|
-
if (
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
const
|
|
2707
|
+
if (outputSchema) {
|
|
2708
|
+
createBody.outputSchema = outputSchema;
|
|
2709
|
+
}
|
|
2710
|
+
const createResponse = await fetch("https://api.exa.ai/research/v1", {
|
|
2656
2711
|
method: "POST",
|
|
2657
2712
|
headers: {
|
|
2658
2713
|
"x-api-key": this.apiKey,
|
|
2659
2714
|
"Content-Type": "application/json"
|
|
2660
2715
|
},
|
|
2661
|
-
body: JSON.stringify(
|
|
2716
|
+
body: JSON.stringify(createBody)
|
|
2662
2717
|
});
|
|
2663
|
-
if (!
|
|
2664
|
-
const errorText = await
|
|
2665
|
-
throw new Error(`
|
|
2718
|
+
if (!createResponse.ok) {
|
|
2719
|
+
const errorText = await createResponse.text();
|
|
2720
|
+
throw new Error(`Failed to create research task (${createResponse.status}): ${errorText}`);
|
|
2666
2721
|
}
|
|
2667
|
-
const
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
let
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
headers: {
|
|
2682
|
-
"x-api-key": this.apiKey,
|
|
2683
|
-
"Content-Type": "application/json"
|
|
2684
|
-
},
|
|
2685
|
-
body: JSON.stringify(similarBody)
|
|
2686
|
-
});
|
|
2687
|
-
if (similarResponse.ok) {
|
|
2688
|
-
const similarData = await similarResponse.json();
|
|
2689
|
-
similarResults = similarData.results ?? [];
|
|
2722
|
+
const createData = await createResponse.json();
|
|
2723
|
+
const researchId = createData.researchId;
|
|
2724
|
+
console.log(`Research task created: ${researchId}. Polling for results...`);
|
|
2725
|
+
const startTime = Date.now();
|
|
2726
|
+
let attempts = 0;
|
|
2727
|
+
while (Date.now() - startTime < this.maxPollTime) {
|
|
2728
|
+
attempts++;
|
|
2729
|
+
if (attempts > 1) {
|
|
2730
|
+
await new Promise((resolve6) => setTimeout(resolve6, this.pollInterval));
|
|
2731
|
+
}
|
|
2732
|
+
const statusResponse = await fetch(`https://api.exa.ai/research/v1/${researchId}`, {
|
|
2733
|
+
method: "GET",
|
|
2734
|
+
headers: {
|
|
2735
|
+
"x-api-key": this.apiKey
|
|
2690
2736
|
}
|
|
2691
|
-
}
|
|
2737
|
+
});
|
|
2738
|
+
if (!statusResponse.ok) {
|
|
2739
|
+
const errorText = await statusResponse.text();
|
|
2740
|
+
throw new Error(`Failed to check research status (${statusResponse.status}): ${errorText}`);
|
|
2692
2741
|
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2742
|
+
const statusData = await statusResponse.json();
|
|
2743
|
+
if (statusData.status === "completed") {
|
|
2744
|
+
const output = statusData.output;
|
|
2745
|
+
if (!output) {
|
|
2746
|
+
throw new Error("Research completed but no output was returned");
|
|
2747
|
+
}
|
|
2748
|
+
const sections = [];
|
|
2749
|
+
if (outputSchema && output.parsed) {
|
|
2750
|
+
sections.push("# Research Results (Structured)\n");
|
|
2751
|
+
sections.push(JSON.stringify(output.parsed, null, 2));
|
|
2752
|
+
} else {
|
|
2753
|
+
sections.push(output.content);
|
|
2754
|
+
}
|
|
2755
|
+
if (statusData.costDollars) {
|
|
2756
|
+
const cost = statusData.costDollars;
|
|
2757
|
+
sections.push("\n---\n");
|
|
2758
|
+
sections.push("## Research Metrics\n");
|
|
2759
|
+
sections.push(`- **Cost**: $${cost.total.toFixed(4)}`);
|
|
2760
|
+
sections.push(`- **Searches**: ${cost.numSearches}`);
|
|
2761
|
+
sections.push(`- **Pages analyzed**: ${cost.numPages}`);
|
|
2762
|
+
sections.push(`- **Reasoning tokens**: ${cost.reasoningTokens.toLocaleString()}`);
|
|
2763
|
+
}
|
|
2764
|
+
console.log(`Research completed in ${attempts} polls (${((Date.now() - startTime) / 1e3).toFixed(1)}s)`);
|
|
2765
|
+
return sections.join("\n");
|
|
2766
|
+
}
|
|
2767
|
+
if (statusData.status === "failed") {
|
|
2768
|
+
throw new Error(`Research failed: ${statusData.error ?? "Unknown error"}`);
|
|
2713
2769
|
}
|
|
2714
|
-
if ("
|
|
2715
|
-
|
|
2770
|
+
if (statusData.status === "canceled") {
|
|
2771
|
+
throw new Error("Research was canceled");
|
|
2716
2772
|
}
|
|
2717
|
-
if (
|
|
2718
|
-
|
|
2719
|
-
Content:
|
|
2720
|
-
${source.text.slice(0, 2e3)}`);
|
|
2773
|
+
if (attempts % 10 === 0) {
|
|
2774
|
+
console.log(`Still researching... (${attempts} polls, ${((Date.now() - startTime) / 1e3).toFixed(0)}s elapsed)`);
|
|
2721
2775
|
}
|
|
2722
|
-
sections.push("");
|
|
2723
2776
|
}
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2777
|
+
throw new Error(`Research timed out after ${this.maxPollTime / 1e3}s. Task ID: ${researchId}`);
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
|
|
2781
|
+
// src/tools/ProxyTool.ts
|
|
2782
|
+
import { spawn } from "child_process";
|
|
2783
|
+
|
|
2784
|
+
// src/utils/bunInstaller.ts
|
|
2785
|
+
import { execSync } from "child_process";
|
|
2786
|
+
import * as path5 from "path";
|
|
2787
|
+
import * as fs5 from "fs";
|
|
2788
|
+
import * as os3 from "os";
|
|
2789
|
+
var cachedBunPath = null;
|
|
2790
|
+
async function ensureBunAvailable() {
|
|
2791
|
+
if (cachedBunPath) return cachedBunPath;
|
|
2792
|
+
const fromPath = whichBun();
|
|
2793
|
+
if (fromPath) {
|
|
2794
|
+
cachedBunPath = fromPath;
|
|
2795
|
+
return fromPath;
|
|
2796
|
+
}
|
|
2797
|
+
const localPath = path5.join(os3.homedir(), ".bun", "bin", "bun");
|
|
2798
|
+
if (fs5.existsSync(localPath)) {
|
|
2799
|
+
cachedBunPath = localPath;
|
|
2800
|
+
return localPath;
|
|
2801
|
+
}
|
|
2802
|
+
console.log(
|
|
2803
|
+
"\n[smol-js] Bun is required to run custom tools but was not found. Installing Bun automatically...\n"
|
|
2804
|
+
);
|
|
2805
|
+
try {
|
|
2806
|
+
execSync("curl --proto =https --tlsv1.2 -sSf https://bun.sh | bash", {
|
|
2807
|
+
stdio: "inherit",
|
|
2808
|
+
shell: "/bin/bash",
|
|
2809
|
+
env: { ...process.env, HOME: os3.homedir() }
|
|
2727
2810
|
});
|
|
2728
|
-
|
|
2811
|
+
} catch (err) {
|
|
2812
|
+
throw new Error(
|
|
2813
|
+
`[smol-js] Failed to auto-install Bun. Please install it manually: https://bun.sh
|
|
2814
|
+
Details: ${err.message}`
|
|
2815
|
+
);
|
|
2816
|
+
}
|
|
2817
|
+
const afterInstall = whichBun() || (fs5.existsSync(localPath) ? localPath : null);
|
|
2818
|
+
if (!afterInstall) {
|
|
2819
|
+
throw new Error(
|
|
2820
|
+
"[smol-js] Bun installation appeared to succeed but the binary was not found. Please install manually: https://bun.sh"
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
console.log(`[smol-js] Bun installed successfully at: ${afterInstall}
|
|
2824
|
+
`);
|
|
2825
|
+
cachedBunPath = afterInstall;
|
|
2826
|
+
return afterInstall;
|
|
2827
|
+
}
|
|
2828
|
+
function whichBun() {
|
|
2829
|
+
try {
|
|
2830
|
+
const cmd = process.platform === "win32" ? "where bun" : "which bun";
|
|
2831
|
+
const result = execSync(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
|
|
2832
|
+
const first = result.split("\n")[0]?.trim();
|
|
2833
|
+
if (first && fs5.existsSync(first)) return first;
|
|
2834
|
+
return null;
|
|
2835
|
+
} catch {
|
|
2836
|
+
return null;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
// src/tools/ProxyTool.ts
|
|
2841
|
+
var TOOL_OUTPUT_PREFIX = "[TOOL_OUTPUT]";
|
|
2842
|
+
var TOOL_RESULT_PREFIX = "[TOOL_RESULT]";
|
|
2843
|
+
var TOOL_ERROR_PREFIX = "[TOOL_ERROR]";
|
|
2844
|
+
var DEFAULT_TOOL_TIMEOUT_MS = 6e4;
|
|
2845
|
+
var ProxyTool = class extends Tool {
|
|
2846
|
+
name;
|
|
2847
|
+
description;
|
|
2848
|
+
inputs;
|
|
2849
|
+
outputType;
|
|
2850
|
+
toolPath;
|
|
2851
|
+
timeout;
|
|
2852
|
+
bunPath = null;
|
|
2853
|
+
constructor(config) {
|
|
2854
|
+
super();
|
|
2855
|
+
this.name = config.name;
|
|
2856
|
+
this.description = config.description;
|
|
2857
|
+
this.inputs = config.inputs;
|
|
2858
|
+
this.outputType = config.outputType;
|
|
2859
|
+
this.toolPath = config.toolPath;
|
|
2860
|
+
this.timeout = config.timeout ?? DEFAULT_TOOL_TIMEOUT_MS;
|
|
2861
|
+
}
|
|
2862
|
+
/**
|
|
2863
|
+
* Ensure Bun is available before first invocation.
|
|
2864
|
+
*/
|
|
2865
|
+
async setup() {
|
|
2866
|
+
this.bunPath = await ensureBunAvailable();
|
|
2867
|
+
this.isSetup = true;
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* Spawn the tool in a Bun child process, pass serialized args via CLI,
|
|
2871
|
+
* stream stdout back as log lines, and parse the final result.
|
|
2872
|
+
*/
|
|
2873
|
+
async execute(args) {
|
|
2874
|
+
if (!this.bunPath) {
|
|
2875
|
+
await this.setup();
|
|
2876
|
+
}
|
|
2877
|
+
const serializedArgs = JSON.stringify(args);
|
|
2878
|
+
return new Promise((resolve6, reject) => {
|
|
2879
|
+
const child = spawn(this.bunPath, ["run", this.toolPath, serializedArgs], {
|
|
2880
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2881
|
+
env: { ...process.env }
|
|
2882
|
+
});
|
|
2883
|
+
let result = void 0;
|
|
2884
|
+
let resultReceived = false;
|
|
2885
|
+
let errorMessage = null;
|
|
2886
|
+
const logBuffer = [];
|
|
2887
|
+
let stderr = "";
|
|
2888
|
+
let partialLine = "";
|
|
2889
|
+
child.stdout.on("data", (chunk) => {
|
|
2890
|
+
partialLine += chunk.toString("utf8");
|
|
2891
|
+
const lines = partialLine.split("\n");
|
|
2892
|
+
partialLine = lines.pop();
|
|
2893
|
+
for (const line of lines) {
|
|
2894
|
+
this.processLine(line, {
|
|
2895
|
+
onOutput: (msg) => logBuffer.push(msg),
|
|
2896
|
+
onResult: (value) => {
|
|
2897
|
+
result = value;
|
|
2898
|
+
resultReceived = true;
|
|
2899
|
+
},
|
|
2900
|
+
onError: (msg) => {
|
|
2901
|
+
errorMessage = msg;
|
|
2902
|
+
}
|
|
2903
|
+
});
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
child.stderr.on("data", (chunk) => {
|
|
2907
|
+
stderr += chunk.toString("utf8");
|
|
2908
|
+
});
|
|
2909
|
+
const timer = setTimeout(() => {
|
|
2910
|
+
child.kill("SIGTERM");
|
|
2911
|
+
reject(new Error(
|
|
2912
|
+
`Custom tool "${this.name}" timed out after ${this.timeout}ms. The process was terminated. Check the tool for infinite loops or slow operations.`
|
|
2913
|
+
));
|
|
2914
|
+
}, this.timeout);
|
|
2915
|
+
timer.unref();
|
|
2916
|
+
child.on("close", (code) => {
|
|
2917
|
+
clearTimeout(timer);
|
|
2918
|
+
if (partialLine.trim()) {
|
|
2919
|
+
this.processLine(partialLine, {
|
|
2920
|
+
onOutput: (msg) => logBuffer.push(msg),
|
|
2921
|
+
onResult: (value) => {
|
|
2922
|
+
result = value;
|
|
2923
|
+
resultReceived = true;
|
|
2924
|
+
},
|
|
2925
|
+
onError: (msg) => {
|
|
2926
|
+
errorMessage = msg;
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
if (errorMessage) {
|
|
2931
|
+
reject(new Error(
|
|
2932
|
+
`Custom tool "${this.name}" reported an error: ${errorMessage}`
|
|
2933
|
+
));
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
if (resultReceived) {
|
|
2937
|
+
if (logBuffer.length > 0) {
|
|
2938
|
+
const logPrefix = `[Tool output logs]
|
|
2939
|
+
${logBuffer.join("\n")}
|
|
2940
|
+
|
|
2941
|
+
[Tool result]
|
|
2942
|
+
`;
|
|
2943
|
+
if (typeof result === "string") {
|
|
2944
|
+
resolve6(logPrefix + result);
|
|
2945
|
+
} else {
|
|
2946
|
+
resolve6({ logs: logBuffer.join("\n"), result });
|
|
2947
|
+
}
|
|
2948
|
+
} else {
|
|
2949
|
+
resolve6(result);
|
|
2950
|
+
}
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
const combined = (logBuffer.join("\n") + "\n" + stderr).trim();
|
|
2954
|
+
if (code !== 0) {
|
|
2955
|
+
reject(new Error(
|
|
2956
|
+
`Custom tool "${this.name}" exited with code ${code}. Output: ${combined || "(none)"}`
|
|
2957
|
+
));
|
|
2958
|
+
} else {
|
|
2959
|
+
resolve6(combined || `Tool "${this.name}" produced no output.`);
|
|
2960
|
+
}
|
|
2961
|
+
});
|
|
2962
|
+
child.on("error", (err) => {
|
|
2963
|
+
clearTimeout(timer);
|
|
2964
|
+
reject(new Error(
|
|
2965
|
+
`Failed to spawn custom tool "${this.name}": ${err.message}`
|
|
2966
|
+
));
|
|
2967
|
+
});
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
// --- internal line parser ---
|
|
2971
|
+
processLine(line, handlers) {
|
|
2972
|
+
const trimmed = line.trimEnd();
|
|
2973
|
+
if (!trimmed) return;
|
|
2974
|
+
if (trimmed.startsWith(TOOL_RESULT_PREFIX)) {
|
|
2975
|
+
const json = trimmed.slice(TOOL_RESULT_PREFIX.length).trim();
|
|
2976
|
+
try {
|
|
2977
|
+
handlers.onResult(JSON.parse(json));
|
|
2978
|
+
} catch {
|
|
2979
|
+
handlers.onResult(json);
|
|
2980
|
+
}
|
|
2981
|
+
} else if (trimmed.startsWith(TOOL_ERROR_PREFIX)) {
|
|
2982
|
+
handlers.onError(trimmed.slice(TOOL_ERROR_PREFIX.length).trim());
|
|
2983
|
+
} else if (trimmed.startsWith(TOOL_OUTPUT_PREFIX)) {
|
|
2984
|
+
handlers.onOutput(trimmed.slice(TOOL_OUTPUT_PREFIX.length).trim());
|
|
2985
|
+
} else {
|
|
2986
|
+
handlers.onOutput(trimmed);
|
|
2987
|
+
}
|
|
2729
2988
|
}
|
|
2730
2989
|
};
|
|
2731
2990
|
|
|
2991
|
+
// src/tools/CustomToolScanner.ts
|
|
2992
|
+
import * as fs6 from "fs";
|
|
2993
|
+
import * as path6 from "path";
|
|
2994
|
+
var METADATA_REGEX = /export\s+const\s+TOOL_METADATA\s*=\s*(\{[\s\S]*?\});\s*$/m;
|
|
2995
|
+
function scanCustomTools(folderPath) {
|
|
2996
|
+
if (!fs6.existsSync(folderPath)) {
|
|
2997
|
+
throw new Error(
|
|
2998
|
+
`Custom tools folder not found: ${folderPath}. Create the directory or check your --custom-tools-folder path.`
|
|
2999
|
+
);
|
|
3000
|
+
}
|
|
3001
|
+
const entries = fs6.readdirSync(folderPath, { withFileTypes: true });
|
|
3002
|
+
const discovered = [];
|
|
3003
|
+
for (const entry of entries) {
|
|
3004
|
+
if (entry.isDirectory()) continue;
|
|
3005
|
+
const ext = path6.extname(entry.name).toLowerCase();
|
|
3006
|
+
if (ext !== ".ts" && ext !== ".js") continue;
|
|
3007
|
+
const filePath = path6.resolve(folderPath, entry.name);
|
|
3008
|
+
const baseName = path6.basename(entry.name, ext);
|
|
3009
|
+
let metadata;
|
|
3010
|
+
try {
|
|
3011
|
+
metadata = extractMetadata(filePath);
|
|
3012
|
+
} catch (err) {
|
|
3013
|
+
throw new Error(
|
|
3014
|
+
`Failed to extract TOOL_METADATA from "${entry.name}": ${err.message}
|
|
3015
|
+
Ensure the file exports \`export const TOOL_METADATA = { name, description, inputs, outputType };\``
|
|
3016
|
+
);
|
|
3017
|
+
}
|
|
3018
|
+
if (metadata.name !== baseName) {
|
|
3019
|
+
throw new Error(
|
|
3020
|
+
`Tool metadata name mismatch in "${entry.name}": file is "${baseName}" but TOOL_METADATA.name is "${metadata.name}". They must match (Convention over Configuration).`
|
|
3021
|
+
);
|
|
3022
|
+
}
|
|
3023
|
+
discovered.push({ filePath, metadata });
|
|
3024
|
+
}
|
|
3025
|
+
return discovered;
|
|
3026
|
+
}
|
|
3027
|
+
function extractMetadata(filePath) {
|
|
3028
|
+
const source = fs6.readFileSync(filePath, "utf8");
|
|
3029
|
+
const match = source.match(METADATA_REGEX);
|
|
3030
|
+
if (!match) {
|
|
3031
|
+
throw new Error(
|
|
3032
|
+
"No `export const TOOL_METADATA = { ... };` block found. Add the metadata export at the bottom of your tool file."
|
|
3033
|
+
);
|
|
3034
|
+
}
|
|
3035
|
+
let parsed;
|
|
3036
|
+
try {
|
|
3037
|
+
parsed = new Function(`"use strict"; return (${match[1]});`)();
|
|
3038
|
+
} catch (err) {
|
|
3039
|
+
throw new Error(
|
|
3040
|
+
`Could not parse TOOL_METADATA object: ${err.message}. Ensure it is a valid JavaScript object literal.`
|
|
3041
|
+
);
|
|
3042
|
+
}
|
|
3043
|
+
if (!parsed.name || typeof parsed.name !== "string") {
|
|
3044
|
+
throw new Error("TOOL_METADATA.name must be a non-empty string.");
|
|
3045
|
+
}
|
|
3046
|
+
if (!parsed.description || typeof parsed.description !== "string") {
|
|
3047
|
+
throw new Error("TOOL_METADATA.description must be a non-empty string.");
|
|
3048
|
+
}
|
|
3049
|
+
if (!parsed.inputs || typeof parsed.inputs !== "object") {
|
|
3050
|
+
throw new Error("TOOL_METADATA.inputs must be an object mapping parameter names to their schemas.");
|
|
3051
|
+
}
|
|
3052
|
+
if (!parsed.outputType || typeof parsed.outputType !== "string") {
|
|
3053
|
+
throw new Error("TOOL_METADATA.outputType must be a non-empty string.");
|
|
3054
|
+
}
|
|
3055
|
+
return parsed;
|
|
3056
|
+
}
|
|
3057
|
+
function loadCustomTools(folderPath) {
|
|
3058
|
+
const discovered = scanCustomTools(folderPath);
|
|
3059
|
+
const tools = /* @__PURE__ */ new Map();
|
|
3060
|
+
for (const { filePath, metadata } of discovered) {
|
|
3061
|
+
const config = {
|
|
3062
|
+
toolPath: filePath,
|
|
3063
|
+
name: metadata.name,
|
|
3064
|
+
description: metadata.description,
|
|
3065
|
+
inputs: metadata.inputs,
|
|
3066
|
+
outputType: metadata.outputType,
|
|
3067
|
+
timeout: metadata.timeout
|
|
3068
|
+
};
|
|
3069
|
+
tools.set(metadata.name, new ProxyTool(config));
|
|
3070
|
+
}
|
|
3071
|
+
return tools;
|
|
3072
|
+
}
|
|
3073
|
+
|
|
2732
3074
|
// src/orchestrator/YAMLLoader.ts
|
|
2733
|
-
import * as
|
|
2734
|
-
import * as
|
|
3075
|
+
import * as fs7 from "fs";
|
|
3076
|
+
import * as path7 from "path";
|
|
2735
3077
|
import YAML from "yaml";
|
|
2736
3078
|
var TOOL_REGISTRY = {
|
|
2737
3079
|
read_file: ReadFileTool,
|
|
@@ -2744,21 +3086,30 @@ var TOOL_REGISTRY = {
|
|
|
2744
3086
|
};
|
|
2745
3087
|
var YAMLLoader = class {
|
|
2746
3088
|
customTools = /* @__PURE__ */ new Map();
|
|
3089
|
+
toolInstances = /* @__PURE__ */ new Map();
|
|
2747
3090
|
/**
|
|
2748
|
-
* Register a custom tool type for use in YAML definitions.
|
|
3091
|
+
* Register a custom tool type (class) for use in YAML definitions.
|
|
2749
3092
|
*/
|
|
2750
3093
|
registerToolType(typeName, toolClass) {
|
|
2751
3094
|
this.customTools.set(typeName, toolClass);
|
|
2752
3095
|
}
|
|
3096
|
+
/**
|
|
3097
|
+
* Register a pre-built tool instance for use in YAML definitions.
|
|
3098
|
+
* Used by the custom tool system to register ProxyTool instances
|
|
3099
|
+
* that are created by the scanner rather than by class instantiation.
|
|
3100
|
+
*/
|
|
3101
|
+
registerToolInstance(typeName, tool) {
|
|
3102
|
+
this.toolInstances.set(typeName, tool);
|
|
3103
|
+
}
|
|
2753
3104
|
/**
|
|
2754
3105
|
* Load a workflow from a YAML file path.
|
|
2755
3106
|
*/
|
|
2756
3107
|
loadFromFile(filePath) {
|
|
2757
|
-
const absolutePath =
|
|
2758
|
-
if (!
|
|
3108
|
+
const absolutePath = path7.isAbsolute(filePath) ? filePath : path7.resolve(process.cwd(), filePath);
|
|
3109
|
+
if (!fs7.existsSync(absolutePath)) {
|
|
2759
3110
|
throw new Error(`Workflow file not found: ${absolutePath}`);
|
|
2760
3111
|
}
|
|
2761
|
-
const content =
|
|
3112
|
+
const content = fs7.readFileSync(absolutePath, "utf-8");
|
|
2762
3113
|
return this.loadFromString(content);
|
|
2763
3114
|
}
|
|
2764
3115
|
/**
|
|
@@ -2833,9 +3184,16 @@ var YAMLLoader = class {
|
|
|
2833
3184
|
* Build a tool instance from a type name and config.
|
|
2834
3185
|
*/
|
|
2835
3186
|
buildTool(name, type, config) {
|
|
3187
|
+
const existingInstance = this.toolInstances.get(type);
|
|
3188
|
+
if (existingInstance) {
|
|
3189
|
+
if (name !== type && name !== existingInstance.name) {
|
|
3190
|
+
Object.defineProperty(existingInstance, "name", { value: name, writable: false });
|
|
3191
|
+
}
|
|
3192
|
+
return existingInstance;
|
|
3193
|
+
}
|
|
2836
3194
|
const ToolClass = TOOL_REGISTRY[type] ?? this.customTools.get(type);
|
|
2837
3195
|
if (!ToolClass) {
|
|
2838
|
-
throw new Error(`Unknown tool type: ${type}. Available types: ${[...Object.keys(TOOL_REGISTRY), ...this.customTools.keys()].join(", ")}`);
|
|
3196
|
+
throw new Error(`Unknown tool type: ${type}. Available types: ${[...Object.keys(TOOL_REGISTRY), ...this.customTools.keys(), ...this.toolInstances.keys()].join(", ")}`);
|
|
2839
3197
|
}
|
|
2840
3198
|
const tool = new ToolClass(config);
|
|
2841
3199
|
if (name !== type && name !== tool.name) {
|
|
@@ -2863,11 +3221,16 @@ var YAMLLoader = class {
|
|
|
2863
3221
|
if (tool) {
|
|
2864
3222
|
agentTools.push(tool);
|
|
2865
3223
|
} else {
|
|
2866
|
-
const
|
|
2867
|
-
if (
|
|
2868
|
-
agentTools.push(
|
|
3224
|
+
const instance = this.toolInstances.get(toolName);
|
|
3225
|
+
if (instance) {
|
|
3226
|
+
agentTools.push(instance);
|
|
2869
3227
|
} else {
|
|
2870
|
-
|
|
3228
|
+
const ToolClass = TOOL_REGISTRY[toolName] ?? this.customTools.get(toolName);
|
|
3229
|
+
if (ToolClass) {
|
|
3230
|
+
agentTools.push(new ToolClass());
|
|
3231
|
+
} else {
|
|
3232
|
+
throw new Error(`Tool "${toolName}" not found for agent "${name}"`);
|
|
3233
|
+
}
|
|
2871
3234
|
}
|
|
2872
3235
|
}
|
|
2873
3236
|
}
|
|
@@ -2918,39 +3281,117 @@ var YAMLLoader = class {
|
|
|
2918
3281
|
|
|
2919
3282
|
// src/orchestrator/Orchestrator.ts
|
|
2920
3283
|
import chalk2 from "chalk";
|
|
3284
|
+
|
|
3285
|
+
// src/output/JSONOutputHandler.ts
|
|
3286
|
+
var JSONOutputHandler = class {
|
|
3287
|
+
runId;
|
|
3288
|
+
startTime;
|
|
3289
|
+
constructor(config) {
|
|
3290
|
+
this.runId = config.runId;
|
|
3291
|
+
this.startTime = Date.now();
|
|
3292
|
+
}
|
|
3293
|
+
emit(type, data, extra = {}) {
|
|
3294
|
+
console.log(JSON.stringify({
|
|
3295
|
+
runId: this.runId,
|
|
3296
|
+
timestamp: Date.now(),
|
|
3297
|
+
type,
|
|
3298
|
+
...extra,
|
|
3299
|
+
data
|
|
3300
|
+
}));
|
|
3301
|
+
}
|
|
3302
|
+
emitRunStart(workflowPath, task, cwd) {
|
|
3303
|
+
this.emit("run_start", { workflowPath, task, cwd });
|
|
3304
|
+
}
|
|
3305
|
+
emitWorkflowLoaded(name, description, agents, tools, entrypoint) {
|
|
3306
|
+
this.emit("workflow_loaded", { name, description, agents, tools, entrypoint });
|
|
3307
|
+
}
|
|
3308
|
+
emitRunEnd(success, output, totalTokens, totalSteps) {
|
|
3309
|
+
this.emit("run_end", {
|
|
3310
|
+
success,
|
|
3311
|
+
output,
|
|
3312
|
+
totalDuration: Date.now() - this.startTime,
|
|
3313
|
+
totalTokens,
|
|
3314
|
+
totalSteps
|
|
3315
|
+
});
|
|
3316
|
+
}
|
|
3317
|
+
emitAgentStart(agentName, depth, task, agentType, maxSteps) {
|
|
3318
|
+
this.emit("agent_start", { task, agentType, maxSteps }, { agentName, depth });
|
|
3319
|
+
}
|
|
3320
|
+
emitAgentEnd(agentName, depth, output, totalSteps, tokenUsage, duration, success) {
|
|
3321
|
+
this.emit("agent_end", { output, totalSteps, tokenUsage, duration, success }, { agentName, depth });
|
|
3322
|
+
}
|
|
3323
|
+
emitAgentStep(agentName, depth, stepNumber, maxSteps, phase) {
|
|
3324
|
+
this.emit("agent_step", { stepNumber, maxSteps, phase }, { agentName, depth });
|
|
3325
|
+
}
|
|
3326
|
+
emitAgentThinking(agentName, depth, stepNumber, content, isPartial) {
|
|
3327
|
+
this.emit("agent_thinking", { stepNumber, content, isPartial }, { agentName, depth });
|
|
3328
|
+
}
|
|
3329
|
+
emitToolCall(agentName, depth, stepNumber, toolCallId, toolName, args) {
|
|
3330
|
+
this.emit("agent_tool_call", { stepNumber, toolCallId, toolName, arguments: args }, { agentName, depth });
|
|
3331
|
+
}
|
|
3332
|
+
emitToolResult(agentName, depth, stepNumber, toolCallId, toolName, result, error, duration) {
|
|
3333
|
+
this.emit("agent_tool_result", { stepNumber, toolCallId, toolName, result, error, duration }, { agentName, depth });
|
|
3334
|
+
}
|
|
3335
|
+
emitObservation(agentName, depth, stepNumber, observation, codeAction, logs) {
|
|
3336
|
+
this.emit("agent_observation", { stepNumber, observation, codeAction, logs }, { agentName, depth });
|
|
3337
|
+
}
|
|
3338
|
+
emitError(message, stack, agentName, depth, stepNumber) {
|
|
3339
|
+
this.emit("error", { message, stack, stepNumber }, {
|
|
3340
|
+
...agentName ? { agentName } : {},
|
|
3341
|
+
...depth !== void 0 ? { depth } : {}
|
|
3342
|
+
});
|
|
3343
|
+
}
|
|
3344
|
+
};
|
|
3345
|
+
|
|
3346
|
+
// src/orchestrator/Orchestrator.ts
|
|
2921
3347
|
var Orchestrator = class {
|
|
2922
3348
|
loader;
|
|
2923
3349
|
config;
|
|
2924
3350
|
activeAgents = /* @__PURE__ */ new Map();
|
|
2925
3351
|
eventLog = [];
|
|
3352
|
+
jsonOutput = null;
|
|
3353
|
+
isJsonMode = false;
|
|
2926
3354
|
constructor(config = {}) {
|
|
2927
3355
|
this.loader = new YAMLLoader();
|
|
3356
|
+
this.isJsonMode = config.outputFormat === "json";
|
|
2928
3357
|
this.config = {
|
|
2929
3358
|
verbose: config.verbose ?? true,
|
|
2930
|
-
onEvent: config.onEvent
|
|
3359
|
+
onEvent: config.onEvent,
|
|
3360
|
+
outputFormat: config.outputFormat ?? "text",
|
|
3361
|
+
runId: config.runId,
|
|
3362
|
+
cwd: config.cwd
|
|
2931
3363
|
};
|
|
3364
|
+
if (this.isJsonMode) {
|
|
3365
|
+
if (!config.runId) {
|
|
3366
|
+
throw new Error("runId is required for JSON output mode");
|
|
3367
|
+
}
|
|
3368
|
+
this.jsonOutput = new JSONOutputHandler({
|
|
3369
|
+
runId: config.runId,
|
|
3370
|
+
verbose: config.verbose ?? true
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
2932
3373
|
}
|
|
2933
3374
|
/**
|
|
2934
3375
|
* Load a workflow from a YAML file.
|
|
2935
3376
|
*/
|
|
2936
3377
|
loadWorkflow(filePath) {
|
|
2937
3378
|
const workflow = this.loader.loadFromFile(filePath);
|
|
2938
|
-
this.displayWorkflowInfo(workflow);
|
|
3379
|
+
this.displayWorkflowInfo(workflow, filePath);
|
|
2939
3380
|
return workflow;
|
|
2940
3381
|
}
|
|
2941
3382
|
/**
|
|
2942
3383
|
* Load a workflow from YAML string.
|
|
2943
3384
|
*/
|
|
2944
|
-
loadWorkflowFromString(yamlContent) {
|
|
3385
|
+
loadWorkflowFromString(yamlContent, sourcePath) {
|
|
2945
3386
|
const workflow = this.loader.loadFromString(yamlContent);
|
|
2946
|
-
this.displayWorkflowInfo(workflow);
|
|
3387
|
+
this.displayWorkflowInfo(workflow, sourcePath);
|
|
2947
3388
|
return workflow;
|
|
2948
3389
|
}
|
|
2949
3390
|
/**
|
|
2950
3391
|
* Run a loaded workflow with a task.
|
|
2951
3392
|
*/
|
|
2952
|
-
async runWorkflow(workflow, task) {
|
|
2953
|
-
this.displayRunStart(workflow.name, task);
|
|
3393
|
+
async runWorkflow(workflow, task, workflowPath) {
|
|
3394
|
+
this.displayRunStart(workflow.name, task, workflowPath);
|
|
2954
3395
|
this.instrumentAgent(workflow.entrypointAgent, workflow.entrypointAgent.getName(), 0);
|
|
2955
3396
|
for (const [name, agent] of workflow.agents) {
|
|
2956
3397
|
if (agent !== workflow.entrypointAgent) {
|
|
@@ -2959,10 +3400,13 @@ var Orchestrator = class {
|
|
|
2959
3400
|
}
|
|
2960
3401
|
try {
|
|
2961
3402
|
const result = await workflow.entrypointAgent.run(task);
|
|
2962
|
-
this.displayRunEnd(result);
|
|
3403
|
+
this.displayRunEnd(result, true);
|
|
2963
3404
|
return result;
|
|
2964
3405
|
} catch (error) {
|
|
2965
3406
|
this.displayError(error);
|
|
3407
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3408
|
+
this.jsonOutput.emitRunEnd(false, null, 0, 0);
|
|
3409
|
+
}
|
|
2966
3410
|
throw error;
|
|
2967
3411
|
}
|
|
2968
3412
|
}
|
|
@@ -2979,11 +3423,128 @@ var Orchestrator = class {
|
|
|
2979
3423
|
*/
|
|
2980
3424
|
instrumentAgent(agent, name, depth) {
|
|
2981
3425
|
this.activeAgents.set(name, { agent, depth });
|
|
3426
|
+
agent.setOnEvent((event) => {
|
|
3427
|
+
const orchestratorEvent = {
|
|
3428
|
+
type: event.type,
|
|
3429
|
+
agentName: name,
|
|
3430
|
+
depth,
|
|
3431
|
+
data: event.data,
|
|
3432
|
+
timestamp: Date.now()
|
|
3433
|
+
};
|
|
3434
|
+
this.logEvent(orchestratorEvent);
|
|
3435
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3436
|
+
this.emitAgentEventAsJSON(event, name, depth);
|
|
3437
|
+
}
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
/**
|
|
3441
|
+
* Emit an agent event as JSON.
|
|
3442
|
+
*/
|
|
3443
|
+
emitAgentEventAsJSON(event, agentName, depth) {
|
|
3444
|
+
if (!this.jsonOutput) return;
|
|
3445
|
+
const data = event.data;
|
|
3446
|
+
switch (event.type) {
|
|
3447
|
+
case "agent_start":
|
|
3448
|
+
this.jsonOutput.emitAgentStart(
|
|
3449
|
+
agentName,
|
|
3450
|
+
depth,
|
|
3451
|
+
data.task,
|
|
3452
|
+
data.name ? "ToolUseAgent" : "CodeAgent",
|
|
3453
|
+
// Will be improved
|
|
3454
|
+
20
|
|
3455
|
+
// Default maxSteps, could be passed
|
|
3456
|
+
);
|
|
3457
|
+
break;
|
|
3458
|
+
case "agent_step":
|
|
3459
|
+
this.jsonOutput.emitAgentStep(
|
|
3460
|
+
agentName,
|
|
3461
|
+
depth,
|
|
3462
|
+
data.step,
|
|
3463
|
+
data.maxSteps,
|
|
3464
|
+
"start"
|
|
3465
|
+
);
|
|
3466
|
+
break;
|
|
3467
|
+
case "agent_thinking":
|
|
3468
|
+
this.jsonOutput.emitAgentThinking(
|
|
3469
|
+
agentName,
|
|
3470
|
+
depth,
|
|
3471
|
+
data.step,
|
|
3472
|
+
data.content,
|
|
3473
|
+
false
|
|
3474
|
+
);
|
|
3475
|
+
break;
|
|
3476
|
+
case "agent_tool_call":
|
|
3477
|
+
this.jsonOutput.emitToolCall(
|
|
3478
|
+
agentName,
|
|
3479
|
+
depth,
|
|
3480
|
+
data.step,
|
|
3481
|
+
data.toolCallId,
|
|
3482
|
+
data.toolName,
|
|
3483
|
+
data.arguments
|
|
3484
|
+
);
|
|
3485
|
+
break;
|
|
3486
|
+
case "agent_tool_result":
|
|
3487
|
+
this.jsonOutput.emitToolResult(
|
|
3488
|
+
agentName,
|
|
3489
|
+
depth,
|
|
3490
|
+
data.step,
|
|
3491
|
+
data.toolCallId,
|
|
3492
|
+
data.toolName,
|
|
3493
|
+
data.result,
|
|
3494
|
+
data.error,
|
|
3495
|
+
data.duration
|
|
3496
|
+
);
|
|
3497
|
+
break;
|
|
3498
|
+
case "agent_observation":
|
|
3499
|
+
this.jsonOutput.emitObservation(
|
|
3500
|
+
agentName,
|
|
3501
|
+
depth,
|
|
3502
|
+
data.step,
|
|
3503
|
+
data.observation,
|
|
3504
|
+
data.codeAction,
|
|
3505
|
+
data.logs
|
|
3506
|
+
);
|
|
3507
|
+
break;
|
|
3508
|
+
case "agent_error":
|
|
3509
|
+
this.jsonOutput.emitError(
|
|
3510
|
+
data.error,
|
|
3511
|
+
void 0,
|
|
3512
|
+
agentName,
|
|
3513
|
+
depth,
|
|
3514
|
+
data.step
|
|
3515
|
+
);
|
|
3516
|
+
break;
|
|
3517
|
+
case "agent_end":
|
|
3518
|
+
this.jsonOutput.emitAgentEnd(
|
|
3519
|
+
agentName,
|
|
3520
|
+
depth,
|
|
3521
|
+
data.output,
|
|
3522
|
+
0,
|
|
3523
|
+
// totalSteps - would need to track
|
|
3524
|
+
data.tokenUsage,
|
|
3525
|
+
data.duration,
|
|
3526
|
+
true
|
|
3527
|
+
);
|
|
3528
|
+
break;
|
|
3529
|
+
}
|
|
2982
3530
|
}
|
|
2983
3531
|
/**
|
|
2984
3532
|
* Display workflow info at startup.
|
|
2985
3533
|
*/
|
|
2986
|
-
displayWorkflowInfo(workflow) {
|
|
3534
|
+
displayWorkflowInfo(workflow, _sourcePath) {
|
|
3535
|
+
const agents = Array.from(workflow.agents.keys());
|
|
3536
|
+
const tools = Array.from(workflow.tools.keys());
|
|
3537
|
+
const entrypoint = workflow.entrypointAgent.getName();
|
|
3538
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3539
|
+
this.jsonOutput.emitWorkflowLoaded(
|
|
3540
|
+
workflow.name,
|
|
3541
|
+
workflow.description,
|
|
3542
|
+
agents,
|
|
3543
|
+
tools,
|
|
3544
|
+
entrypoint
|
|
3545
|
+
);
|
|
3546
|
+
return;
|
|
3547
|
+
}
|
|
2987
3548
|
if (!this.config.verbose) return;
|
|
2988
3549
|
const line = "\u2550".repeat(70);
|
|
2989
3550
|
console.log(chalk2.cyan(line));
|
|
@@ -2991,16 +3552,20 @@ var Orchestrator = class {
|
|
|
2991
3552
|
if (workflow.description) {
|
|
2992
3553
|
console.log(chalk2.cyan(` ${workflow.description}`));
|
|
2993
3554
|
}
|
|
2994
|
-
console.log(chalk2.cyan(` Agents: ${
|
|
2995
|
-
console.log(chalk2.cyan(` Tools: ${
|
|
2996
|
-
console.log(chalk2.cyan(` Entrypoint: ${
|
|
3555
|
+
console.log(chalk2.cyan(` Agents: ${agents.join(", ")}`));
|
|
3556
|
+
console.log(chalk2.cyan(` Tools: ${tools.join(", ") || "(none defined at workflow level)"}`));
|
|
3557
|
+
console.log(chalk2.cyan(` Entrypoint: ${entrypoint}`));
|
|
2997
3558
|
console.log(chalk2.cyan(line));
|
|
2998
3559
|
console.log();
|
|
2999
3560
|
}
|
|
3000
3561
|
/**
|
|
3001
3562
|
* Display run start info.
|
|
3002
3563
|
*/
|
|
3003
|
-
displayRunStart(workflowName, task) {
|
|
3564
|
+
displayRunStart(workflowName, task, workflowPath) {
|
|
3565
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3566
|
+
this.jsonOutput.emitRunStart(workflowPath || workflowName, task, this.config.cwd);
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3004
3569
|
if (!this.config.verbose) return;
|
|
3005
3570
|
console.log(chalk2.green.bold(`
|
|
3006
3571
|
\u25B6 Running workflow "${workflowName}"`));
|
|
@@ -3010,7 +3575,16 @@ var Orchestrator = class {
|
|
|
3010
3575
|
/**
|
|
3011
3576
|
* Display run completion info.
|
|
3012
3577
|
*/
|
|
3013
|
-
displayRunEnd(result) {
|
|
3578
|
+
displayRunEnd(result, success = true) {
|
|
3579
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3580
|
+
this.jsonOutput.emitRunEnd(
|
|
3581
|
+
success,
|
|
3582
|
+
result.output,
|
|
3583
|
+
result.tokenUsage.totalTokens,
|
|
3584
|
+
result.steps.length
|
|
3585
|
+
);
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3014
3588
|
if (!this.config.verbose) return;
|
|
3015
3589
|
console.log(chalk2.gray("\n" + "\u2500".repeat(70)));
|
|
3016
3590
|
console.log(chalk2.green.bold(`
|
|
@@ -3028,6 +3602,10 @@ var Orchestrator = class {
|
|
|
3028
3602
|
* Display an error.
|
|
3029
3603
|
*/
|
|
3030
3604
|
displayError(error) {
|
|
3605
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3606
|
+
this.jsonOutput.emitError(error.message, error.stack);
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3031
3609
|
if (!this.config.verbose) return;
|
|
3032
3610
|
console.error(chalk2.red.bold(`
|
|
3033
3611
|
\u274C Workflow failed: ${error.message}`));
|
|
@@ -3056,6 +3634,24 @@ var Orchestrator = class {
|
|
|
3056
3634
|
getLoader() {
|
|
3057
3635
|
return this.loader;
|
|
3058
3636
|
}
|
|
3637
|
+
/**
|
|
3638
|
+
* Get the JSON output handler (if in JSON mode).
|
|
3639
|
+
*/
|
|
3640
|
+
getJSONOutputHandler() {
|
|
3641
|
+
return this.jsonOutput;
|
|
3642
|
+
}
|
|
3643
|
+
/**
|
|
3644
|
+
* Check if in JSON output mode.
|
|
3645
|
+
*/
|
|
3646
|
+
isJSONOutputMode() {
|
|
3647
|
+
return this.isJsonMode;
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Get the run ID.
|
|
3651
|
+
*/
|
|
3652
|
+
getRunId() {
|
|
3653
|
+
return this.config.runId;
|
|
3654
|
+
}
|
|
3059
3655
|
};
|
|
3060
3656
|
export {
|
|
3061
3657
|
Agent,
|
|
@@ -3069,11 +3665,13 @@ export {
|
|
|
3069
3665
|
ExaSearchTool,
|
|
3070
3666
|
FINAL_ANSWER_PROMPT,
|
|
3071
3667
|
FinalAnswerTool,
|
|
3668
|
+
JSONOutputHandler,
|
|
3072
3669
|
LocalExecutor,
|
|
3073
3670
|
LogLevel,
|
|
3074
3671
|
Model,
|
|
3075
3672
|
OpenAIModel,
|
|
3076
3673
|
Orchestrator,
|
|
3674
|
+
ProxyTool,
|
|
3077
3675
|
ReadFileTool,
|
|
3078
3676
|
Tool,
|
|
3079
3677
|
ToolUseAgent,
|
|
@@ -3086,6 +3684,8 @@ export {
|
|
|
3086
3684
|
formatToolDescriptions,
|
|
3087
3685
|
generateSystemPrompt,
|
|
3088
3686
|
generateToolUseSystemPrompt,
|
|
3089
|
-
getErrorRecoveryPrompt
|
|
3687
|
+
getErrorRecoveryPrompt,
|
|
3688
|
+
loadCustomTools,
|
|
3689
|
+
scanCustomTools
|
|
3090
3690
|
};
|
|
3091
3691
|
//# sourceMappingURL=index.mjs.map
|