@samrahimi/smol-js 0.6.5 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +407 -154
- package/dist/cli.js +830 -133
- package/dist/cli.js.map +1 -1
- package/dist/index.d.mts +326 -9
- package/dist/index.d.ts +326 -9
- package/dist/index.js +731 -123
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +726 -122
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/toolHarness.ts +73 -0
package/dist/index.js
CHANGED
|
@@ -41,11 +41,13 @@ __export(index_exports, {
|
|
|
41
41
|
ExaSearchTool: () => ExaSearchTool,
|
|
42
42
|
FINAL_ANSWER_PROMPT: () => FINAL_ANSWER_PROMPT,
|
|
43
43
|
FinalAnswerTool: () => FinalAnswerTool,
|
|
44
|
+
JSONOutputHandler: () => JSONOutputHandler,
|
|
44
45
|
LocalExecutor: () => LocalExecutor,
|
|
45
46
|
LogLevel: () => LogLevel,
|
|
46
47
|
Model: () => Model,
|
|
47
48
|
OpenAIModel: () => OpenAIModel,
|
|
48
49
|
Orchestrator: () => Orchestrator,
|
|
50
|
+
ProxyTool: () => ProxyTool,
|
|
49
51
|
ReadFileTool: () => ReadFileTool,
|
|
50
52
|
Tool: () => Tool,
|
|
51
53
|
ToolUseAgent: () => ToolUseAgent,
|
|
@@ -58,7 +60,9 @@ __export(index_exports, {
|
|
|
58
60
|
formatToolDescriptions: () => formatToolDescriptions,
|
|
59
61
|
generateSystemPrompt: () => generateSystemPrompt,
|
|
60
62
|
generateToolUseSystemPrompt: () => generateToolUseSystemPrompt,
|
|
61
|
-
getErrorRecoveryPrompt: () => getErrorRecoveryPrompt
|
|
63
|
+
getErrorRecoveryPrompt: () => getErrorRecoveryPrompt,
|
|
64
|
+
loadCustomTools: () => loadCustomTools,
|
|
65
|
+
scanCustomTools: () => scanCustomTools
|
|
62
66
|
});
|
|
63
67
|
module.exports = __toCommonJS(index_exports);
|
|
64
68
|
|
|
@@ -774,9 +778,21 @@ Total time: ${(duration / 1e3).toFixed(2)}s`);
|
|
|
774
778
|
getName() {
|
|
775
779
|
return this.config.name;
|
|
776
780
|
}
|
|
781
|
+
/** Get agent type identifier */
|
|
782
|
+
getType() {
|
|
783
|
+
return this.constructor.name;
|
|
784
|
+
}
|
|
785
|
+
/** Get max steps configuration */
|
|
786
|
+
getMaxSteps() {
|
|
787
|
+
return this.config.maxSteps;
|
|
788
|
+
}
|
|
789
|
+
/** Set the event callback (useful for orchestration) */
|
|
790
|
+
setOnEvent(callback) {
|
|
791
|
+
this.config.onEvent = callback;
|
|
792
|
+
}
|
|
777
793
|
/** Sleep for a specified duration */
|
|
778
794
|
sleep(ms) {
|
|
779
|
-
return new Promise((
|
|
795
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
780
796
|
}
|
|
781
797
|
};
|
|
782
798
|
|
|
@@ -1445,12 +1461,12 @@ var UserInputTool = class extends Tool {
|
|
|
1445
1461
|
input: process.stdin,
|
|
1446
1462
|
output: process.stdout
|
|
1447
1463
|
});
|
|
1448
|
-
return new Promise((
|
|
1464
|
+
return new Promise((resolve7) => {
|
|
1449
1465
|
rl.question(`
|
|
1450
1466
|
[Agent asks]: ${question}
|
|
1451
1467
|
Your response: `, (answer) => {
|
|
1452
1468
|
rl.close();
|
|
1453
|
-
|
|
1469
|
+
resolve7(answer);
|
|
1454
1470
|
});
|
|
1455
1471
|
});
|
|
1456
1472
|
}
|
|
@@ -1929,6 +1945,10 @@ Please try a different approach.`
|
|
|
1929
1945
|
memoryStep.tokenUsage = response.tokenUsage;
|
|
1930
1946
|
if (response.content && response.content.trim()) {
|
|
1931
1947
|
this.logger.reasoning(response.content.trim());
|
|
1948
|
+
this.emitEvent("agent_thinking", {
|
|
1949
|
+
step: this.currentStep,
|
|
1950
|
+
content: response.content.trim()
|
|
1951
|
+
});
|
|
1932
1952
|
}
|
|
1933
1953
|
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
1934
1954
|
this.logger.warn("No tool calls in response. Prompting model to use tools.");
|
|
@@ -1961,42 +1981,84 @@ Please try a different approach.`
|
|
|
1961
1981
|
async processToolCalls(toolCalls) {
|
|
1962
1982
|
const results = [];
|
|
1963
1983
|
const executeTool = async (tc) => {
|
|
1984
|
+
const startTime = Date.now();
|
|
1964
1985
|
const toolName = tc.function.name;
|
|
1965
1986
|
const tool = this.tools.get(toolName);
|
|
1966
1987
|
if (!tool) {
|
|
1988
|
+
const error = `Unknown tool: ${toolName}. Available tools: ${Array.from(this.tools.keys()).join(", ")}`;
|
|
1989
|
+
this.emitEvent("agent_tool_result", {
|
|
1990
|
+
step: this.currentStep,
|
|
1991
|
+
toolCallId: tc.id,
|
|
1992
|
+
toolName,
|
|
1993
|
+
result: null,
|
|
1994
|
+
error,
|
|
1995
|
+
duration: Date.now() - startTime
|
|
1996
|
+
});
|
|
1967
1997
|
return {
|
|
1968
1998
|
toolCallId: tc.id,
|
|
1969
1999
|
toolName,
|
|
1970
2000
|
result: null,
|
|
1971
|
-
error
|
|
2001
|
+
error
|
|
1972
2002
|
};
|
|
1973
2003
|
}
|
|
1974
2004
|
let args;
|
|
1975
2005
|
try {
|
|
1976
2006
|
args = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
|
|
1977
2007
|
} catch {
|
|
2008
|
+
const error = `Failed to parse tool arguments: ${tc.function.arguments}`;
|
|
2009
|
+
this.emitEvent("agent_tool_result", {
|
|
2010
|
+
step: this.currentStep,
|
|
2011
|
+
toolCallId: tc.id,
|
|
2012
|
+
toolName,
|
|
2013
|
+
result: null,
|
|
2014
|
+
error,
|
|
2015
|
+
duration: Date.now() - startTime
|
|
2016
|
+
});
|
|
1978
2017
|
return {
|
|
1979
2018
|
toolCallId: tc.id,
|
|
1980
2019
|
toolName,
|
|
1981
2020
|
result: null,
|
|
1982
|
-
error
|
|
2021
|
+
error
|
|
1983
2022
|
};
|
|
1984
2023
|
}
|
|
1985
2024
|
this.logger.info(` Calling tool: ${toolName}(${JSON.stringify(args).slice(0, 100)}...)`);
|
|
1986
|
-
this.emitEvent("agent_tool_call", {
|
|
2025
|
+
this.emitEvent("agent_tool_call", {
|
|
2026
|
+
step: this.currentStep,
|
|
2027
|
+
toolCallId: tc.id,
|
|
2028
|
+
toolName,
|
|
2029
|
+
arguments: args
|
|
2030
|
+
});
|
|
1987
2031
|
try {
|
|
1988
2032
|
const result = await tool.call(args);
|
|
2033
|
+
const duration = Date.now() - startTime;
|
|
2034
|
+
this.emitEvent("agent_tool_result", {
|
|
2035
|
+
step: this.currentStep,
|
|
2036
|
+
toolCallId: tc.id,
|
|
2037
|
+
toolName,
|
|
2038
|
+
result,
|
|
2039
|
+
duration
|
|
2040
|
+
});
|
|
1989
2041
|
return {
|
|
1990
2042
|
toolCallId: tc.id,
|
|
1991
2043
|
toolName,
|
|
1992
2044
|
result
|
|
1993
2045
|
};
|
|
1994
2046
|
} catch (error) {
|
|
2047
|
+
const errorMsg = `Tool execution error: ${error.message}`;
|
|
2048
|
+
const duration = Date.now() - startTime;
|
|
2049
|
+
this.emitEvent("agent_tool_result", {
|
|
2050
|
+
step: this.currentStep,
|
|
2051
|
+
toolCallId: tc.id,
|
|
2052
|
+
toolName,
|
|
2053
|
+
result: null,
|
|
2054
|
+
error: errorMsg,
|
|
2055
|
+
duration
|
|
2056
|
+
});
|
|
1995
2057
|
return {
|
|
1996
2058
|
toolCallId: tc.id,
|
|
1997
2059
|
toolName,
|
|
1998
2060
|
result: null,
|
|
1999
|
-
error:
|
|
2061
|
+
error: errorMsg
|
|
2000
2062
|
};
|
|
2001
2063
|
}
|
|
2002
2064
|
};
|
|
@@ -2074,7 +2136,7 @@ var Model = class {
|
|
|
2074
2136
|
};
|
|
2075
2137
|
|
|
2076
2138
|
// src/models/OpenAIModel.ts
|
|
2077
|
-
var import_openai = __toESM(require("openai"));
|
|
2139
|
+
var import_openai = __toESM(require("openai/index.mjs"));
|
|
2078
2140
|
var DEFAULT_MODEL_ID = "anthropic/claude-sonnet-4.5";
|
|
2079
2141
|
var DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
|
|
2080
2142
|
var DEFAULT_TIMEOUT = 12e4;
|
|
@@ -2663,40 +2725,35 @@ var ExaGetContentsTool = class extends Tool {
|
|
|
2663
2725
|
// src/tools/ExaResearchTool.ts
|
|
2664
2726
|
var ExaResearchTool = class extends Tool {
|
|
2665
2727
|
name = "exa_research";
|
|
2666
|
-
description = "Perform
|
|
2728
|
+
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.";
|
|
2667
2729
|
inputs = {
|
|
2668
|
-
|
|
2730
|
+
instructions: {
|
|
2669
2731
|
type: "string",
|
|
2670
|
-
description: "
|
|
2732
|
+
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.",
|
|
2671
2733
|
required: true
|
|
2672
2734
|
},
|
|
2673
|
-
|
|
2674
|
-
type: "number",
|
|
2675
|
-
description: "Number of primary sources to retrieve (default: 5, max: 10)",
|
|
2676
|
-
required: false,
|
|
2677
|
-
default: 5
|
|
2678
|
-
},
|
|
2679
|
-
category: {
|
|
2735
|
+
model: {
|
|
2680
2736
|
type: "string",
|
|
2681
|
-
description: '
|
|
2682
|
-
required: false
|
|
2683
|
-
},
|
|
2684
|
-
includeDomains: {
|
|
2685
|
-
type: "array",
|
|
2686
|
-
description: "Only include results from these domains",
|
|
2737
|
+
description: 'Research model: "exa-research-fast" (faster, cheaper), "exa-research" (balanced, default), or "exa-research-pro" (most thorough)',
|
|
2687
2738
|
required: false
|
|
2688
2739
|
},
|
|
2689
|
-
|
|
2690
|
-
type: "
|
|
2691
|
-
description: "
|
|
2740
|
+
outputSchema: {
|
|
2741
|
+
type: "object",
|
|
2742
|
+
description: "Optional JSON Schema to enforce structured output format (max 8 root fields, 5 levels deep). If omitted, returns markdown report.",
|
|
2692
2743
|
required: false
|
|
2693
2744
|
}
|
|
2694
2745
|
};
|
|
2695
2746
|
outputType = "string";
|
|
2696
2747
|
apiKey;
|
|
2748
|
+
defaultModel;
|
|
2749
|
+
pollInterval;
|
|
2750
|
+
maxPollTime;
|
|
2697
2751
|
constructor(config) {
|
|
2698
2752
|
super();
|
|
2699
2753
|
this.apiKey = config?.apiKey ?? process.env.EXA_API_KEY ?? "";
|
|
2754
|
+
this.defaultModel = config?.model ?? "exa-research";
|
|
2755
|
+
this.pollInterval = config?.pollInterval ?? 2e3;
|
|
2756
|
+
this.maxPollTime = config?.maxPollTime ?? 3e5;
|
|
2700
2757
|
}
|
|
2701
2758
|
async setup() {
|
|
2702
2759
|
if (!this.apiKey) {
|
|
@@ -2705,97 +2762,390 @@ var ExaResearchTool = class extends Tool {
|
|
|
2705
2762
|
this.isSetup = true;
|
|
2706
2763
|
}
|
|
2707
2764
|
async execute(args) {
|
|
2708
|
-
const
|
|
2709
|
-
const
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2765
|
+
const instructions = args.instructions;
|
|
2766
|
+
const model = args.model ?? this.defaultModel;
|
|
2767
|
+
const outputSchema = args.outputSchema;
|
|
2768
|
+
if (!instructions || instructions.length > 4096) {
|
|
2769
|
+
throw new Error("Instructions are required and must be <= 4096 characters");
|
|
2770
|
+
}
|
|
2771
|
+
const createBody = {
|
|
2772
|
+
instructions,
|
|
2773
|
+
model
|
|
2715
2774
|
};
|
|
2716
|
-
if (
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
const
|
|
2775
|
+
if (outputSchema) {
|
|
2776
|
+
createBody.outputSchema = outputSchema;
|
|
2777
|
+
}
|
|
2778
|
+
const createResponse = await fetch("https://api.exa.ai/research/v1", {
|
|
2720
2779
|
method: "POST",
|
|
2721
2780
|
headers: {
|
|
2722
2781
|
"x-api-key": this.apiKey,
|
|
2723
2782
|
"Content-Type": "application/json"
|
|
2724
2783
|
},
|
|
2725
|
-
body: JSON.stringify(
|
|
2784
|
+
body: JSON.stringify(createBody)
|
|
2726
2785
|
});
|
|
2727
|
-
if (!
|
|
2728
|
-
const errorText = await
|
|
2729
|
-
throw new Error(`
|
|
2786
|
+
if (!createResponse.ok) {
|
|
2787
|
+
const errorText = await createResponse.text();
|
|
2788
|
+
throw new Error(`Failed to create research task (${createResponse.status}): ${errorText}`);
|
|
2730
2789
|
}
|
|
2731
|
-
const
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
let
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
headers: {
|
|
2746
|
-
"x-api-key": this.apiKey,
|
|
2747
|
-
"Content-Type": "application/json"
|
|
2748
|
-
},
|
|
2749
|
-
body: JSON.stringify(similarBody)
|
|
2750
|
-
});
|
|
2751
|
-
if (similarResponse.ok) {
|
|
2752
|
-
const similarData = await similarResponse.json();
|
|
2753
|
-
similarResults = similarData.results ?? [];
|
|
2790
|
+
const createData = await createResponse.json();
|
|
2791
|
+
const researchId = createData.researchId;
|
|
2792
|
+
console.log(`Research task created: ${researchId}. Polling for results...`);
|
|
2793
|
+
const startTime = Date.now();
|
|
2794
|
+
let attempts = 0;
|
|
2795
|
+
while (Date.now() - startTime < this.maxPollTime) {
|
|
2796
|
+
attempts++;
|
|
2797
|
+
if (attempts > 1) {
|
|
2798
|
+
await new Promise((resolve7) => setTimeout(resolve7, this.pollInterval));
|
|
2799
|
+
}
|
|
2800
|
+
const statusResponse = await fetch(`https://api.exa.ai/research/v1/${researchId}`, {
|
|
2801
|
+
method: "GET",
|
|
2802
|
+
headers: {
|
|
2803
|
+
"x-api-key": this.apiKey
|
|
2754
2804
|
}
|
|
2755
|
-
}
|
|
2805
|
+
});
|
|
2806
|
+
if (!statusResponse.ok) {
|
|
2807
|
+
const errorText = await statusResponse.text();
|
|
2808
|
+
throw new Error(`Failed to check research status (${statusResponse.status}): ${errorText}`);
|
|
2756
2809
|
}
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2810
|
+
const statusData = await statusResponse.json();
|
|
2811
|
+
if (statusData.status === "completed") {
|
|
2812
|
+
const output = statusData.output;
|
|
2813
|
+
if (!output) {
|
|
2814
|
+
throw new Error("Research completed but no output was returned");
|
|
2815
|
+
}
|
|
2816
|
+
const sections = [];
|
|
2817
|
+
if (outputSchema && output.parsed) {
|
|
2818
|
+
sections.push("# Research Results (Structured)\n");
|
|
2819
|
+
sections.push(JSON.stringify(output.parsed, null, 2));
|
|
2820
|
+
} else {
|
|
2821
|
+
sections.push(output.content);
|
|
2822
|
+
}
|
|
2823
|
+
if (statusData.costDollars) {
|
|
2824
|
+
const cost = statusData.costDollars;
|
|
2825
|
+
sections.push("\n---\n");
|
|
2826
|
+
sections.push("## Research Metrics\n");
|
|
2827
|
+
sections.push(`- **Cost**: $${cost.total.toFixed(4)}`);
|
|
2828
|
+
sections.push(`- **Searches**: ${cost.numSearches}`);
|
|
2829
|
+
sections.push(`- **Pages analyzed**: ${cost.numPages}`);
|
|
2830
|
+
sections.push(`- **Reasoning tokens**: ${cost.reasoningTokens.toLocaleString()}`);
|
|
2831
|
+
}
|
|
2832
|
+
console.log(`Research completed in ${attempts} polls (${((Date.now() - startTime) / 1e3).toFixed(1)}s)`);
|
|
2833
|
+
return sections.join("\n");
|
|
2834
|
+
}
|
|
2835
|
+
if (statusData.status === "failed") {
|
|
2836
|
+
throw new Error(`Research failed: ${statusData.error ?? "Unknown error"}`);
|
|
2777
2837
|
}
|
|
2778
|
-
if ("
|
|
2779
|
-
|
|
2838
|
+
if (statusData.status === "canceled") {
|
|
2839
|
+
throw new Error("Research was canceled");
|
|
2780
2840
|
}
|
|
2781
|
-
if (
|
|
2782
|
-
|
|
2783
|
-
Content:
|
|
2784
|
-
${source.text.slice(0, 2e3)}`);
|
|
2841
|
+
if (attempts % 10 === 0) {
|
|
2842
|
+
console.log(`Still researching... (${attempts} polls, ${((Date.now() - startTime) / 1e3).toFixed(0)}s elapsed)`);
|
|
2785
2843
|
}
|
|
2786
|
-
sections.push("");
|
|
2787
2844
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2845
|
+
throw new Error(`Research timed out after ${this.maxPollTime / 1e3}s. Task ID: ${researchId}`);
|
|
2846
|
+
}
|
|
2847
|
+
};
|
|
2848
|
+
|
|
2849
|
+
// src/tools/ProxyTool.ts
|
|
2850
|
+
var import_child_process2 = require("child_process");
|
|
2851
|
+
var path6 = __toESM(require("path"));
|
|
2852
|
+
|
|
2853
|
+
// src/utils/bunInstaller.ts
|
|
2854
|
+
var import_child_process = require("child_process");
|
|
2855
|
+
var path5 = __toESM(require("path"));
|
|
2856
|
+
var fs5 = __toESM(require("fs"));
|
|
2857
|
+
var os3 = __toESM(require("os"));
|
|
2858
|
+
var cachedBunPath = null;
|
|
2859
|
+
async function ensureBunAvailable() {
|
|
2860
|
+
if (cachedBunPath) return cachedBunPath;
|
|
2861
|
+
const fromPath = whichBun();
|
|
2862
|
+
if (fromPath) {
|
|
2863
|
+
cachedBunPath = fromPath;
|
|
2864
|
+
return fromPath;
|
|
2865
|
+
}
|
|
2866
|
+
const localPath = path5.join(os3.homedir(), ".bun", "bin", "bun");
|
|
2867
|
+
if (fs5.existsSync(localPath)) {
|
|
2868
|
+
cachedBunPath = localPath;
|
|
2869
|
+
return localPath;
|
|
2870
|
+
}
|
|
2871
|
+
console.log(
|
|
2872
|
+
"\n[smol-js] Bun is required to run custom tools but was not found. Installing Bun automatically...\n"
|
|
2873
|
+
);
|
|
2874
|
+
try {
|
|
2875
|
+
(0, import_child_process.execSync)("curl --proto =https --tlsv1.2 -sSf https://bun.sh | bash", {
|
|
2876
|
+
stdio: "inherit",
|
|
2877
|
+
shell: "/bin/bash",
|
|
2878
|
+
env: { ...process.env, HOME: os3.homedir() }
|
|
2879
|
+
});
|
|
2880
|
+
} catch (err) {
|
|
2881
|
+
throw new Error(
|
|
2882
|
+
`[smol-js] Failed to auto-install Bun. Please install it manually: https://bun.sh
|
|
2883
|
+
Details: ${err.message}`
|
|
2884
|
+
);
|
|
2885
|
+
}
|
|
2886
|
+
const afterInstall = whichBun() || (fs5.existsSync(localPath) ? localPath : null);
|
|
2887
|
+
if (!afterInstall) {
|
|
2888
|
+
throw new Error(
|
|
2889
|
+
"[smol-js] Bun installation appeared to succeed but the binary was not found. Please install manually: https://bun.sh"
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
console.log(`[smol-js] Bun installed successfully at: ${afterInstall}
|
|
2893
|
+
`);
|
|
2894
|
+
cachedBunPath = afterInstall;
|
|
2895
|
+
return afterInstall;
|
|
2896
|
+
}
|
|
2897
|
+
function whichBun() {
|
|
2898
|
+
try {
|
|
2899
|
+
const cmd = process.platform === "win32" ? "where bun" : "which bun";
|
|
2900
|
+
const result = (0, import_child_process.execSync)(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
|
|
2901
|
+
const first = result.split("\n")[0]?.trim();
|
|
2902
|
+
if (first && fs5.existsSync(first)) return first;
|
|
2903
|
+
return null;
|
|
2904
|
+
} catch {
|
|
2905
|
+
return null;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
// src/tools/ProxyTool.ts
|
|
2910
|
+
var TOOL_RESULT_PREFIX = "[TOOL_RESULT]";
|
|
2911
|
+
var TOOL_ERROR_PREFIX = "[TOOL_ERROR]";
|
|
2912
|
+
var DEFAULT_TOOL_TIMEOUT_MS = 6e4;
|
|
2913
|
+
function resolveHarnessPath() {
|
|
2914
|
+
return path6.resolve(__dirname, "..", "toolHarness.ts");
|
|
2915
|
+
}
|
|
2916
|
+
var ProxyTool = class extends Tool {
|
|
2917
|
+
name;
|
|
2918
|
+
description;
|
|
2919
|
+
inputs;
|
|
2920
|
+
outputType;
|
|
2921
|
+
toolPath;
|
|
2922
|
+
timeout;
|
|
2923
|
+
bunPath = null;
|
|
2924
|
+
harnessPath = null;
|
|
2925
|
+
constructor(config) {
|
|
2926
|
+
super();
|
|
2927
|
+
this.name = config.name;
|
|
2928
|
+
this.description = config.description;
|
|
2929
|
+
this.inputs = config.inputs;
|
|
2930
|
+
this.outputType = config.outputType;
|
|
2931
|
+
this.toolPath = config.toolPath;
|
|
2932
|
+
this.timeout = config.timeout ?? DEFAULT_TOOL_TIMEOUT_MS;
|
|
2933
|
+
}
|
|
2934
|
+
/**
|
|
2935
|
+
* Ensure Bun is available and locate the harness before first invocation.
|
|
2936
|
+
*/
|
|
2937
|
+
async setup() {
|
|
2938
|
+
this.bunPath = await ensureBunAvailable();
|
|
2939
|
+
this.harnessPath = resolveHarnessPath();
|
|
2940
|
+
this.isSetup = true;
|
|
2941
|
+
}
|
|
2942
|
+
/**
|
|
2943
|
+
* Spawn the harness in a Bun child process. The harness imports the tool,
|
|
2944
|
+
* calls execute(args), and writes the protocol lines. Any console.log from
|
|
2945
|
+
* the tool flows through stdout as plain lines.
|
|
2946
|
+
*/
|
|
2947
|
+
async execute(args) {
|
|
2948
|
+
if (!this.bunPath || !this.harnessPath) {
|
|
2949
|
+
await this.setup();
|
|
2950
|
+
}
|
|
2951
|
+
const serializedArgs = JSON.stringify(args);
|
|
2952
|
+
return new Promise((resolve7, reject) => {
|
|
2953
|
+
const child = (0, import_child_process2.spawn)(this.bunPath, ["run", this.harnessPath, this.toolPath, serializedArgs], {
|
|
2954
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2955
|
+
env: { ...process.env }
|
|
2956
|
+
});
|
|
2957
|
+
let result = void 0;
|
|
2958
|
+
let resultReceived = false;
|
|
2959
|
+
let errorMessage = null;
|
|
2960
|
+
const logBuffer = [];
|
|
2961
|
+
let stderr = "";
|
|
2962
|
+
let partialLine = "";
|
|
2963
|
+
child.stdout.on("data", (chunk) => {
|
|
2964
|
+
partialLine += chunk.toString("utf8");
|
|
2965
|
+
const lines = partialLine.split("\n");
|
|
2966
|
+
partialLine = lines.pop();
|
|
2967
|
+
for (const line of lines) {
|
|
2968
|
+
this.processLine(line, {
|
|
2969
|
+
onOutput: (msg) => logBuffer.push(msg),
|
|
2970
|
+
onResult: (value) => {
|
|
2971
|
+
result = value;
|
|
2972
|
+
resultReceived = true;
|
|
2973
|
+
},
|
|
2974
|
+
onError: (msg) => {
|
|
2975
|
+
errorMessage = msg;
|
|
2976
|
+
}
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
child.stderr.on("data", (chunk) => {
|
|
2981
|
+
stderr += chunk.toString("utf8");
|
|
2982
|
+
});
|
|
2983
|
+
const timer = setTimeout(() => {
|
|
2984
|
+
child.kill("SIGTERM");
|
|
2985
|
+
reject(new Error(
|
|
2986
|
+
`Custom tool "${this.name}" timed out after ${this.timeout}ms. The process was terminated. Check the tool for infinite loops or slow operations.`
|
|
2987
|
+
));
|
|
2988
|
+
}, this.timeout);
|
|
2989
|
+
timer.unref();
|
|
2990
|
+
child.on("close", (code) => {
|
|
2991
|
+
clearTimeout(timer);
|
|
2992
|
+
if (partialLine.trim()) {
|
|
2993
|
+
this.processLine(partialLine, {
|
|
2994
|
+
onOutput: (msg) => logBuffer.push(msg),
|
|
2995
|
+
onResult: (value) => {
|
|
2996
|
+
result = value;
|
|
2997
|
+
resultReceived = true;
|
|
2998
|
+
},
|
|
2999
|
+
onError: (msg) => {
|
|
3000
|
+
errorMessage = msg;
|
|
3001
|
+
}
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
if (errorMessage) {
|
|
3005
|
+
reject(new Error(
|
|
3006
|
+
`Custom tool "${this.name}" reported an error: ${errorMessage}`
|
|
3007
|
+
));
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
if (resultReceived) {
|
|
3011
|
+
if (logBuffer.length > 0) {
|
|
3012
|
+
const logPrefix = `[Tool output logs]
|
|
3013
|
+
${logBuffer.join("\n")}
|
|
3014
|
+
|
|
3015
|
+
[Tool result]
|
|
3016
|
+
`;
|
|
3017
|
+
if (typeof result === "string") {
|
|
3018
|
+
resolve7(logPrefix + result);
|
|
3019
|
+
} else {
|
|
3020
|
+
resolve7({ logs: logBuffer.join("\n"), result });
|
|
3021
|
+
}
|
|
3022
|
+
} else {
|
|
3023
|
+
resolve7(result);
|
|
3024
|
+
}
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
const combined = (logBuffer.join("\n") + "\n" + stderr).trim();
|
|
3028
|
+
if (code !== 0) {
|
|
3029
|
+
reject(new Error(
|
|
3030
|
+
`Custom tool "${this.name}" exited with code ${code}. Output: ${combined || "(none)"}`
|
|
3031
|
+
));
|
|
3032
|
+
} else {
|
|
3033
|
+
resolve7(combined || `Tool "${this.name}" produced no output.`);
|
|
3034
|
+
}
|
|
3035
|
+
});
|
|
3036
|
+
child.on("error", (err) => {
|
|
3037
|
+
clearTimeout(timer);
|
|
3038
|
+
reject(new Error(
|
|
3039
|
+
`Failed to spawn custom tool "${this.name}": ${err.message}`
|
|
3040
|
+
));
|
|
3041
|
+
});
|
|
2791
3042
|
});
|
|
2792
|
-
|
|
3043
|
+
}
|
|
3044
|
+
// --- line parser: protocol is spoken by harness, interpreted here ---
|
|
3045
|
+
processLine(line, handlers) {
|
|
3046
|
+
const trimmed = line.trimEnd();
|
|
3047
|
+
if (!trimmed) return;
|
|
3048
|
+
if (trimmed.startsWith(TOOL_RESULT_PREFIX)) {
|
|
3049
|
+
const json = trimmed.slice(TOOL_RESULT_PREFIX.length).trim();
|
|
3050
|
+
try {
|
|
3051
|
+
handlers.onResult(JSON.parse(json));
|
|
3052
|
+
} catch {
|
|
3053
|
+
handlers.onResult(json);
|
|
3054
|
+
}
|
|
3055
|
+
} else if (trimmed.startsWith(TOOL_ERROR_PREFIX)) {
|
|
3056
|
+
handlers.onError(trimmed.slice(TOOL_ERROR_PREFIX.length).trim());
|
|
3057
|
+
} else {
|
|
3058
|
+
handlers.onOutput(trimmed);
|
|
3059
|
+
}
|
|
2793
3060
|
}
|
|
2794
3061
|
};
|
|
2795
3062
|
|
|
3063
|
+
// src/tools/CustomToolScanner.ts
|
|
3064
|
+
var fs6 = __toESM(require("fs"));
|
|
3065
|
+
var path7 = __toESM(require("path"));
|
|
3066
|
+
var METADATA_REGEX = /export\s+const\s+TOOL_METADATA\s*=\s*(\{[\s\S]*?\});\s*$/m;
|
|
3067
|
+
function scanCustomTools(folderPath) {
|
|
3068
|
+
if (!fs6.existsSync(folderPath)) {
|
|
3069
|
+
throw new Error(
|
|
3070
|
+
`Custom tools folder not found: ${folderPath}. Create the directory or check your --custom-tools-folder path.`
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3073
|
+
const entries = fs6.readdirSync(folderPath, { withFileTypes: true });
|
|
3074
|
+
const discovered = [];
|
|
3075
|
+
for (const entry of entries) {
|
|
3076
|
+
if (entry.isDirectory()) continue;
|
|
3077
|
+
const ext = path7.extname(entry.name).toLowerCase();
|
|
3078
|
+
if (ext !== ".ts" && ext !== ".js") continue;
|
|
3079
|
+
const filePath = path7.resolve(folderPath, entry.name);
|
|
3080
|
+
const baseName = path7.basename(entry.name, ext);
|
|
3081
|
+
let metadata;
|
|
3082
|
+
try {
|
|
3083
|
+
metadata = extractMetadata(filePath);
|
|
3084
|
+
} catch (err) {
|
|
3085
|
+
throw new Error(
|
|
3086
|
+
`Failed to extract TOOL_METADATA from "${entry.name}": ${err.message}
|
|
3087
|
+
Ensure the file exports \`export const TOOL_METADATA = { name, description, inputs, outputType };\``
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3090
|
+
if (metadata.name !== baseName) {
|
|
3091
|
+
throw new Error(
|
|
3092
|
+
`Tool metadata name mismatch in "${entry.name}": file is "${baseName}" but TOOL_METADATA.name is "${metadata.name}". They must match (Convention over Configuration).`
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
discovered.push({ filePath, metadata });
|
|
3096
|
+
}
|
|
3097
|
+
return discovered;
|
|
3098
|
+
}
|
|
3099
|
+
function extractMetadata(filePath) {
|
|
3100
|
+
const source = fs6.readFileSync(filePath, "utf8");
|
|
3101
|
+
const match = source.match(METADATA_REGEX);
|
|
3102
|
+
if (!match) {
|
|
3103
|
+
throw new Error(
|
|
3104
|
+
"No `export const TOOL_METADATA = { ... };` block found. Add the metadata export at the bottom of your tool file."
|
|
3105
|
+
);
|
|
3106
|
+
}
|
|
3107
|
+
let parsed;
|
|
3108
|
+
try {
|
|
3109
|
+
parsed = new Function(`"use strict"; return (${match[1]});`)();
|
|
3110
|
+
} catch (err) {
|
|
3111
|
+
throw new Error(
|
|
3112
|
+
`Could not parse TOOL_METADATA object: ${err.message}. Ensure it is a valid JavaScript object literal.`
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
if (!parsed.name || typeof parsed.name !== "string") {
|
|
3116
|
+
throw new Error("TOOL_METADATA.name must be a non-empty string.");
|
|
3117
|
+
}
|
|
3118
|
+
if (!parsed.description || typeof parsed.description !== "string") {
|
|
3119
|
+
throw new Error("TOOL_METADATA.description must be a non-empty string.");
|
|
3120
|
+
}
|
|
3121
|
+
if (!parsed.inputs || typeof parsed.inputs !== "object") {
|
|
3122
|
+
throw new Error("TOOL_METADATA.inputs must be an object mapping parameter names to their schemas.");
|
|
3123
|
+
}
|
|
3124
|
+
if (!parsed.outputType || typeof parsed.outputType !== "string") {
|
|
3125
|
+
throw new Error("TOOL_METADATA.outputType must be a non-empty string.");
|
|
3126
|
+
}
|
|
3127
|
+
return parsed;
|
|
3128
|
+
}
|
|
3129
|
+
function loadCustomTools(folderPath) {
|
|
3130
|
+
const discovered = scanCustomTools(folderPath);
|
|
3131
|
+
const tools = /* @__PURE__ */ new Map();
|
|
3132
|
+
for (const { filePath, metadata } of discovered) {
|
|
3133
|
+
const config = {
|
|
3134
|
+
toolPath: filePath,
|
|
3135
|
+
name: metadata.name,
|
|
3136
|
+
description: metadata.description,
|
|
3137
|
+
inputs: metadata.inputs,
|
|
3138
|
+
outputType: metadata.outputType,
|
|
3139
|
+
timeout: metadata.timeout
|
|
3140
|
+
};
|
|
3141
|
+
tools.set(metadata.name, new ProxyTool(config));
|
|
3142
|
+
}
|
|
3143
|
+
return tools;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
2796
3146
|
// src/orchestrator/YAMLLoader.ts
|
|
2797
|
-
var
|
|
2798
|
-
var
|
|
3147
|
+
var fs7 = __toESM(require("fs"));
|
|
3148
|
+
var path8 = __toESM(require("path"));
|
|
2799
3149
|
var import_yaml = __toESM(require("yaml"));
|
|
2800
3150
|
var TOOL_REGISTRY = {
|
|
2801
3151
|
read_file: ReadFileTool,
|
|
@@ -2808,21 +3158,30 @@ var TOOL_REGISTRY = {
|
|
|
2808
3158
|
};
|
|
2809
3159
|
var YAMLLoader = class {
|
|
2810
3160
|
customTools = /* @__PURE__ */ new Map();
|
|
3161
|
+
toolInstances = /* @__PURE__ */ new Map();
|
|
2811
3162
|
/**
|
|
2812
|
-
* Register a custom tool type for use in YAML definitions.
|
|
3163
|
+
* Register a custom tool type (class) for use in YAML definitions.
|
|
2813
3164
|
*/
|
|
2814
3165
|
registerToolType(typeName, toolClass) {
|
|
2815
3166
|
this.customTools.set(typeName, toolClass);
|
|
2816
3167
|
}
|
|
3168
|
+
/**
|
|
3169
|
+
* Register a pre-built tool instance for use in YAML definitions.
|
|
3170
|
+
* Used by the custom tool system to register ProxyTool instances
|
|
3171
|
+
* that are created by the scanner rather than by class instantiation.
|
|
3172
|
+
*/
|
|
3173
|
+
registerToolInstance(typeName, tool) {
|
|
3174
|
+
this.toolInstances.set(typeName, tool);
|
|
3175
|
+
}
|
|
2817
3176
|
/**
|
|
2818
3177
|
* Load a workflow from a YAML file path.
|
|
2819
3178
|
*/
|
|
2820
3179
|
loadFromFile(filePath) {
|
|
2821
|
-
const absolutePath =
|
|
2822
|
-
if (!
|
|
3180
|
+
const absolutePath = path8.isAbsolute(filePath) ? filePath : path8.resolve(process.cwd(), filePath);
|
|
3181
|
+
if (!fs7.existsSync(absolutePath)) {
|
|
2823
3182
|
throw new Error(`Workflow file not found: ${absolutePath}`);
|
|
2824
3183
|
}
|
|
2825
|
-
const content =
|
|
3184
|
+
const content = fs7.readFileSync(absolutePath, "utf-8");
|
|
2826
3185
|
return this.loadFromString(content);
|
|
2827
3186
|
}
|
|
2828
3187
|
/**
|
|
@@ -2897,9 +3256,16 @@ var YAMLLoader = class {
|
|
|
2897
3256
|
* Build a tool instance from a type name and config.
|
|
2898
3257
|
*/
|
|
2899
3258
|
buildTool(name, type, config) {
|
|
3259
|
+
const existingInstance = this.toolInstances.get(type);
|
|
3260
|
+
if (existingInstance) {
|
|
3261
|
+
if (name !== type && name !== existingInstance.name) {
|
|
3262
|
+
Object.defineProperty(existingInstance, "name", { value: name, writable: false });
|
|
3263
|
+
}
|
|
3264
|
+
return existingInstance;
|
|
3265
|
+
}
|
|
2900
3266
|
const ToolClass = TOOL_REGISTRY[type] ?? this.customTools.get(type);
|
|
2901
3267
|
if (!ToolClass) {
|
|
2902
|
-
throw new Error(`Unknown tool type: ${type}. Available types: ${[...Object.keys(TOOL_REGISTRY), ...this.customTools.keys()].join(", ")}`);
|
|
3268
|
+
throw new Error(`Unknown tool type: ${type}. Available types: ${[...Object.keys(TOOL_REGISTRY), ...this.customTools.keys(), ...this.toolInstances.keys()].join(", ")}`);
|
|
2903
3269
|
}
|
|
2904
3270
|
const tool = new ToolClass(config);
|
|
2905
3271
|
if (name !== type && name !== tool.name) {
|
|
@@ -2927,11 +3293,16 @@ var YAMLLoader = class {
|
|
|
2927
3293
|
if (tool) {
|
|
2928
3294
|
agentTools.push(tool);
|
|
2929
3295
|
} else {
|
|
2930
|
-
const
|
|
2931
|
-
if (
|
|
2932
|
-
agentTools.push(
|
|
3296
|
+
const instance = this.toolInstances.get(toolName);
|
|
3297
|
+
if (instance) {
|
|
3298
|
+
agentTools.push(instance);
|
|
2933
3299
|
} else {
|
|
2934
|
-
|
|
3300
|
+
const ToolClass = TOOL_REGISTRY[toolName] ?? this.customTools.get(toolName);
|
|
3301
|
+
if (ToolClass) {
|
|
3302
|
+
agentTools.push(new ToolClass());
|
|
3303
|
+
} else {
|
|
3304
|
+
throw new Error(`Tool "${toolName}" not found for agent "${name}"`);
|
|
3305
|
+
}
|
|
2935
3306
|
}
|
|
2936
3307
|
}
|
|
2937
3308
|
}
|
|
@@ -2982,39 +3353,117 @@ var YAMLLoader = class {
|
|
|
2982
3353
|
|
|
2983
3354
|
// src/orchestrator/Orchestrator.ts
|
|
2984
3355
|
var import_chalk2 = __toESM(require("chalk"));
|
|
3356
|
+
|
|
3357
|
+
// src/output/JSONOutputHandler.ts
|
|
3358
|
+
var JSONOutputHandler = class {
|
|
3359
|
+
runId;
|
|
3360
|
+
startTime;
|
|
3361
|
+
constructor(config) {
|
|
3362
|
+
this.runId = config.runId;
|
|
3363
|
+
this.startTime = Date.now();
|
|
3364
|
+
}
|
|
3365
|
+
emit(type, data, extra = {}) {
|
|
3366
|
+
console.log(JSON.stringify({
|
|
3367
|
+
runId: this.runId,
|
|
3368
|
+
timestamp: Date.now(),
|
|
3369
|
+
type,
|
|
3370
|
+
...extra,
|
|
3371
|
+
data
|
|
3372
|
+
}));
|
|
3373
|
+
}
|
|
3374
|
+
emitRunStart(workflowPath, task, cwd) {
|
|
3375
|
+
this.emit("run_start", { workflowPath, task, cwd });
|
|
3376
|
+
}
|
|
3377
|
+
emitWorkflowLoaded(name, description, agents, tools, entrypoint) {
|
|
3378
|
+
this.emit("workflow_loaded", { name, description, agents, tools, entrypoint });
|
|
3379
|
+
}
|
|
3380
|
+
emitRunEnd(success, output, totalTokens, totalSteps) {
|
|
3381
|
+
this.emit("run_end", {
|
|
3382
|
+
success,
|
|
3383
|
+
output,
|
|
3384
|
+
totalDuration: Date.now() - this.startTime,
|
|
3385
|
+
totalTokens,
|
|
3386
|
+
totalSteps
|
|
3387
|
+
});
|
|
3388
|
+
}
|
|
3389
|
+
emitAgentStart(agentName, depth, task, agentType, maxSteps) {
|
|
3390
|
+
this.emit("agent_start", { task, agentType, maxSteps }, { agentName, depth });
|
|
3391
|
+
}
|
|
3392
|
+
emitAgentEnd(agentName, depth, output, totalSteps, tokenUsage, duration, success) {
|
|
3393
|
+
this.emit("agent_end", { output, totalSteps, tokenUsage, duration, success }, { agentName, depth });
|
|
3394
|
+
}
|
|
3395
|
+
emitAgentStep(agentName, depth, stepNumber, maxSteps, phase) {
|
|
3396
|
+
this.emit("agent_step", { stepNumber, maxSteps, phase }, { agentName, depth });
|
|
3397
|
+
}
|
|
3398
|
+
emitAgentThinking(agentName, depth, stepNumber, content, isPartial) {
|
|
3399
|
+
this.emit("agent_thinking", { stepNumber, content, isPartial }, { agentName, depth });
|
|
3400
|
+
}
|
|
3401
|
+
emitToolCall(agentName, depth, stepNumber, toolCallId, toolName, args) {
|
|
3402
|
+
this.emit("agent_tool_call", { stepNumber, toolCallId, toolName, arguments: args }, { agentName, depth });
|
|
3403
|
+
}
|
|
3404
|
+
emitToolResult(agentName, depth, stepNumber, toolCallId, toolName, result, error, duration) {
|
|
3405
|
+
this.emit("agent_tool_result", { stepNumber, toolCallId, toolName, result, error, duration }, { agentName, depth });
|
|
3406
|
+
}
|
|
3407
|
+
emitObservation(agentName, depth, stepNumber, observation, codeAction, logs) {
|
|
3408
|
+
this.emit("agent_observation", { stepNumber, observation, codeAction, logs }, { agentName, depth });
|
|
3409
|
+
}
|
|
3410
|
+
emitError(message, stack, agentName, depth, stepNumber) {
|
|
3411
|
+
this.emit("error", { message, stack, stepNumber }, {
|
|
3412
|
+
...agentName ? { agentName } : {},
|
|
3413
|
+
...depth !== void 0 ? { depth } : {}
|
|
3414
|
+
});
|
|
3415
|
+
}
|
|
3416
|
+
};
|
|
3417
|
+
|
|
3418
|
+
// src/orchestrator/Orchestrator.ts
|
|
2985
3419
|
var Orchestrator = class {
|
|
2986
3420
|
loader;
|
|
2987
3421
|
config;
|
|
2988
3422
|
activeAgents = /* @__PURE__ */ new Map();
|
|
2989
3423
|
eventLog = [];
|
|
3424
|
+
jsonOutput = null;
|
|
3425
|
+
isJsonMode = false;
|
|
2990
3426
|
constructor(config = {}) {
|
|
2991
3427
|
this.loader = new YAMLLoader();
|
|
3428
|
+
this.isJsonMode = config.outputFormat === "json";
|
|
2992
3429
|
this.config = {
|
|
2993
3430
|
verbose: config.verbose ?? true,
|
|
2994
|
-
onEvent: config.onEvent
|
|
3431
|
+
onEvent: config.onEvent,
|
|
3432
|
+
outputFormat: config.outputFormat ?? "text",
|
|
3433
|
+
runId: config.runId,
|
|
3434
|
+
cwd: config.cwd
|
|
2995
3435
|
};
|
|
3436
|
+
if (this.isJsonMode) {
|
|
3437
|
+
if (!config.runId) {
|
|
3438
|
+
throw new Error("runId is required for JSON output mode");
|
|
3439
|
+
}
|
|
3440
|
+
this.jsonOutput = new JSONOutputHandler({
|
|
3441
|
+
runId: config.runId,
|
|
3442
|
+
verbose: config.verbose ?? true
|
|
3443
|
+
});
|
|
3444
|
+
}
|
|
2996
3445
|
}
|
|
2997
3446
|
/**
|
|
2998
3447
|
* Load a workflow from a YAML file.
|
|
2999
3448
|
*/
|
|
3000
3449
|
loadWorkflow(filePath) {
|
|
3001
3450
|
const workflow = this.loader.loadFromFile(filePath);
|
|
3002
|
-
this.displayWorkflowInfo(workflow);
|
|
3451
|
+
this.displayWorkflowInfo(workflow, filePath);
|
|
3003
3452
|
return workflow;
|
|
3004
3453
|
}
|
|
3005
3454
|
/**
|
|
3006
3455
|
* Load a workflow from YAML string.
|
|
3007
3456
|
*/
|
|
3008
|
-
loadWorkflowFromString(yamlContent) {
|
|
3457
|
+
loadWorkflowFromString(yamlContent, sourcePath) {
|
|
3009
3458
|
const workflow = this.loader.loadFromString(yamlContent);
|
|
3010
|
-
this.displayWorkflowInfo(workflow);
|
|
3459
|
+
this.displayWorkflowInfo(workflow, sourcePath);
|
|
3011
3460
|
return workflow;
|
|
3012
3461
|
}
|
|
3013
3462
|
/**
|
|
3014
3463
|
* Run a loaded workflow with a task.
|
|
3015
3464
|
*/
|
|
3016
|
-
async runWorkflow(workflow, task) {
|
|
3017
|
-
this.displayRunStart(workflow.name, task);
|
|
3465
|
+
async runWorkflow(workflow, task, workflowPath) {
|
|
3466
|
+
this.displayRunStart(workflow.name, task, workflowPath);
|
|
3018
3467
|
this.instrumentAgent(workflow.entrypointAgent, workflow.entrypointAgent.getName(), 0);
|
|
3019
3468
|
for (const [name, agent] of workflow.agents) {
|
|
3020
3469
|
if (agent !== workflow.entrypointAgent) {
|
|
@@ -3023,10 +3472,13 @@ var Orchestrator = class {
|
|
|
3023
3472
|
}
|
|
3024
3473
|
try {
|
|
3025
3474
|
const result = await workflow.entrypointAgent.run(task);
|
|
3026
|
-
this.displayRunEnd(result);
|
|
3475
|
+
this.displayRunEnd(result, true);
|
|
3027
3476
|
return result;
|
|
3028
3477
|
} catch (error) {
|
|
3029
3478
|
this.displayError(error);
|
|
3479
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3480
|
+
this.jsonOutput.emitRunEnd(false, null, 0, 0);
|
|
3481
|
+
}
|
|
3030
3482
|
throw error;
|
|
3031
3483
|
}
|
|
3032
3484
|
}
|
|
@@ -3043,11 +3495,128 @@ var Orchestrator = class {
|
|
|
3043
3495
|
*/
|
|
3044
3496
|
instrumentAgent(agent, name, depth) {
|
|
3045
3497
|
this.activeAgents.set(name, { agent, depth });
|
|
3498
|
+
agent.setOnEvent((event) => {
|
|
3499
|
+
const orchestratorEvent = {
|
|
3500
|
+
type: event.type,
|
|
3501
|
+
agentName: name,
|
|
3502
|
+
depth,
|
|
3503
|
+
data: event.data,
|
|
3504
|
+
timestamp: Date.now()
|
|
3505
|
+
};
|
|
3506
|
+
this.logEvent(orchestratorEvent);
|
|
3507
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3508
|
+
this.emitAgentEventAsJSON(event, name, depth);
|
|
3509
|
+
}
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
/**
|
|
3513
|
+
* Emit an agent event as JSON.
|
|
3514
|
+
*/
|
|
3515
|
+
emitAgentEventAsJSON(event, agentName, depth) {
|
|
3516
|
+
if (!this.jsonOutput) return;
|
|
3517
|
+
const data = event.data;
|
|
3518
|
+
switch (event.type) {
|
|
3519
|
+
case "agent_start":
|
|
3520
|
+
this.jsonOutput.emitAgentStart(
|
|
3521
|
+
agentName,
|
|
3522
|
+
depth,
|
|
3523
|
+
data.task,
|
|
3524
|
+
data.name ? "ToolUseAgent" : "CodeAgent",
|
|
3525
|
+
// Will be improved
|
|
3526
|
+
20
|
|
3527
|
+
// Default maxSteps, could be passed
|
|
3528
|
+
);
|
|
3529
|
+
break;
|
|
3530
|
+
case "agent_step":
|
|
3531
|
+
this.jsonOutput.emitAgentStep(
|
|
3532
|
+
agentName,
|
|
3533
|
+
depth,
|
|
3534
|
+
data.step,
|
|
3535
|
+
data.maxSteps,
|
|
3536
|
+
"start"
|
|
3537
|
+
);
|
|
3538
|
+
break;
|
|
3539
|
+
case "agent_thinking":
|
|
3540
|
+
this.jsonOutput.emitAgentThinking(
|
|
3541
|
+
agentName,
|
|
3542
|
+
depth,
|
|
3543
|
+
data.step,
|
|
3544
|
+
data.content,
|
|
3545
|
+
false
|
|
3546
|
+
);
|
|
3547
|
+
break;
|
|
3548
|
+
case "agent_tool_call":
|
|
3549
|
+
this.jsonOutput.emitToolCall(
|
|
3550
|
+
agentName,
|
|
3551
|
+
depth,
|
|
3552
|
+
data.step,
|
|
3553
|
+
data.toolCallId,
|
|
3554
|
+
data.toolName,
|
|
3555
|
+
data.arguments
|
|
3556
|
+
);
|
|
3557
|
+
break;
|
|
3558
|
+
case "agent_tool_result":
|
|
3559
|
+
this.jsonOutput.emitToolResult(
|
|
3560
|
+
agentName,
|
|
3561
|
+
depth,
|
|
3562
|
+
data.step,
|
|
3563
|
+
data.toolCallId,
|
|
3564
|
+
data.toolName,
|
|
3565
|
+
data.result,
|
|
3566
|
+
data.error,
|
|
3567
|
+
data.duration
|
|
3568
|
+
);
|
|
3569
|
+
break;
|
|
3570
|
+
case "agent_observation":
|
|
3571
|
+
this.jsonOutput.emitObservation(
|
|
3572
|
+
agentName,
|
|
3573
|
+
depth,
|
|
3574
|
+
data.step,
|
|
3575
|
+
data.observation,
|
|
3576
|
+
data.codeAction,
|
|
3577
|
+
data.logs
|
|
3578
|
+
);
|
|
3579
|
+
break;
|
|
3580
|
+
case "agent_error":
|
|
3581
|
+
this.jsonOutput.emitError(
|
|
3582
|
+
data.error,
|
|
3583
|
+
void 0,
|
|
3584
|
+
agentName,
|
|
3585
|
+
depth,
|
|
3586
|
+
data.step
|
|
3587
|
+
);
|
|
3588
|
+
break;
|
|
3589
|
+
case "agent_end":
|
|
3590
|
+
this.jsonOutput.emitAgentEnd(
|
|
3591
|
+
agentName,
|
|
3592
|
+
depth,
|
|
3593
|
+
data.output,
|
|
3594
|
+
0,
|
|
3595
|
+
// totalSteps - would need to track
|
|
3596
|
+
data.tokenUsage,
|
|
3597
|
+
data.duration,
|
|
3598
|
+
true
|
|
3599
|
+
);
|
|
3600
|
+
break;
|
|
3601
|
+
}
|
|
3046
3602
|
}
|
|
3047
3603
|
/**
|
|
3048
3604
|
* Display workflow info at startup.
|
|
3049
3605
|
*/
|
|
3050
|
-
displayWorkflowInfo(workflow) {
|
|
3606
|
+
displayWorkflowInfo(workflow, _sourcePath) {
|
|
3607
|
+
const agents = Array.from(workflow.agents.keys());
|
|
3608
|
+
const tools = Array.from(workflow.tools.keys());
|
|
3609
|
+
const entrypoint = workflow.entrypointAgent.getName();
|
|
3610
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3611
|
+
this.jsonOutput.emitWorkflowLoaded(
|
|
3612
|
+
workflow.name,
|
|
3613
|
+
workflow.description,
|
|
3614
|
+
agents,
|
|
3615
|
+
tools,
|
|
3616
|
+
entrypoint
|
|
3617
|
+
);
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3051
3620
|
if (!this.config.verbose) return;
|
|
3052
3621
|
const line = "\u2550".repeat(70);
|
|
3053
3622
|
console.log(import_chalk2.default.cyan(line));
|
|
@@ -3055,16 +3624,20 @@ var Orchestrator = class {
|
|
|
3055
3624
|
if (workflow.description) {
|
|
3056
3625
|
console.log(import_chalk2.default.cyan(` ${workflow.description}`));
|
|
3057
3626
|
}
|
|
3058
|
-
console.log(import_chalk2.default.cyan(` Agents: ${
|
|
3059
|
-
console.log(import_chalk2.default.cyan(` Tools: ${
|
|
3060
|
-
console.log(import_chalk2.default.cyan(` Entrypoint: ${
|
|
3627
|
+
console.log(import_chalk2.default.cyan(` Agents: ${agents.join(", ")}`));
|
|
3628
|
+
console.log(import_chalk2.default.cyan(` Tools: ${tools.join(", ") || "(none defined at workflow level)"}`));
|
|
3629
|
+
console.log(import_chalk2.default.cyan(` Entrypoint: ${entrypoint}`));
|
|
3061
3630
|
console.log(import_chalk2.default.cyan(line));
|
|
3062
3631
|
console.log();
|
|
3063
3632
|
}
|
|
3064
3633
|
/**
|
|
3065
3634
|
* Display run start info.
|
|
3066
3635
|
*/
|
|
3067
|
-
displayRunStart(workflowName, task) {
|
|
3636
|
+
displayRunStart(workflowName, task, workflowPath) {
|
|
3637
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3638
|
+
this.jsonOutput.emitRunStart(workflowPath || workflowName, task, this.config.cwd);
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
3068
3641
|
if (!this.config.verbose) return;
|
|
3069
3642
|
console.log(import_chalk2.default.green.bold(`
|
|
3070
3643
|
\u25B6 Running workflow "${workflowName}"`));
|
|
@@ -3074,7 +3647,16 @@ var Orchestrator = class {
|
|
|
3074
3647
|
/**
|
|
3075
3648
|
* Display run completion info.
|
|
3076
3649
|
*/
|
|
3077
|
-
displayRunEnd(result) {
|
|
3650
|
+
displayRunEnd(result, success = true) {
|
|
3651
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3652
|
+
this.jsonOutput.emitRunEnd(
|
|
3653
|
+
success,
|
|
3654
|
+
result.output,
|
|
3655
|
+
result.tokenUsage.totalTokens,
|
|
3656
|
+
result.steps.length
|
|
3657
|
+
);
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3078
3660
|
if (!this.config.verbose) return;
|
|
3079
3661
|
console.log(import_chalk2.default.gray("\n" + "\u2500".repeat(70)));
|
|
3080
3662
|
console.log(import_chalk2.default.green.bold(`
|
|
@@ -3092,6 +3674,10 @@ var Orchestrator = class {
|
|
|
3092
3674
|
* Display an error.
|
|
3093
3675
|
*/
|
|
3094
3676
|
displayError(error) {
|
|
3677
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3678
|
+
this.jsonOutput.emitError(error.message, error.stack);
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3095
3681
|
if (!this.config.verbose) return;
|
|
3096
3682
|
console.error(import_chalk2.default.red.bold(`
|
|
3097
3683
|
\u274C Workflow failed: ${error.message}`));
|
|
@@ -3120,6 +3706,24 @@ var Orchestrator = class {
|
|
|
3120
3706
|
getLoader() {
|
|
3121
3707
|
return this.loader;
|
|
3122
3708
|
}
|
|
3709
|
+
/**
|
|
3710
|
+
* Get the JSON output handler (if in JSON mode).
|
|
3711
|
+
*/
|
|
3712
|
+
getJSONOutputHandler() {
|
|
3713
|
+
return this.jsonOutput;
|
|
3714
|
+
}
|
|
3715
|
+
/**
|
|
3716
|
+
* Check if in JSON output mode.
|
|
3717
|
+
*/
|
|
3718
|
+
isJSONOutputMode() {
|
|
3719
|
+
return this.isJsonMode;
|
|
3720
|
+
}
|
|
3721
|
+
/**
|
|
3722
|
+
* Get the run ID.
|
|
3723
|
+
*/
|
|
3724
|
+
getRunId() {
|
|
3725
|
+
return this.config.runId;
|
|
3726
|
+
}
|
|
3123
3727
|
};
|
|
3124
3728
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3125
3729
|
0 && (module.exports = {
|
|
@@ -3134,11 +3738,13 @@ var Orchestrator = class {
|
|
|
3134
3738
|
ExaSearchTool,
|
|
3135
3739
|
FINAL_ANSWER_PROMPT,
|
|
3136
3740
|
FinalAnswerTool,
|
|
3741
|
+
JSONOutputHandler,
|
|
3137
3742
|
LocalExecutor,
|
|
3138
3743
|
LogLevel,
|
|
3139
3744
|
Model,
|
|
3140
3745
|
OpenAIModel,
|
|
3141
3746
|
Orchestrator,
|
|
3747
|
+
ProxyTool,
|
|
3142
3748
|
ReadFileTool,
|
|
3143
3749
|
Tool,
|
|
3144
3750
|
ToolUseAgent,
|
|
@@ -3151,6 +3757,8 @@ var Orchestrator = class {
|
|
|
3151
3757
|
formatToolDescriptions,
|
|
3152
3758
|
generateSystemPrompt,
|
|
3153
3759
|
generateToolUseSystemPrompt,
|
|
3154
|
-
getErrorRecoveryPrompt
|
|
3760
|
+
getErrorRecoveryPrompt,
|
|
3761
|
+
loadCustomTools,
|
|
3762
|
+
scanCustomTools
|
|
3155
3763
|
});
|
|
3156
3764
|
//# sourceMappingURL=index.js.map
|