@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.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((resolve7) => setTimeout(resolve7, 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((resolve7) => {
|
|
1385
1397
|
rl.question(`
|
|
1386
1398
|
[Agent asks]: ${question}
|
|
1387
1399
|
Your response: `, (answer) => {
|
|
1388
1400
|
rl.close();
|
|
1389
|
-
|
|
1401
|
+
resolve7(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,390 @@ 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((resolve7) => setTimeout(resolve7, 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
|
+
import * as path6 from "path";
|
|
2784
|
+
|
|
2785
|
+
// src/utils/bunInstaller.ts
|
|
2786
|
+
import { execSync } from "child_process";
|
|
2787
|
+
import * as path5 from "path";
|
|
2788
|
+
import * as fs5 from "fs";
|
|
2789
|
+
import * as os3 from "os";
|
|
2790
|
+
var cachedBunPath = null;
|
|
2791
|
+
async function ensureBunAvailable() {
|
|
2792
|
+
if (cachedBunPath) return cachedBunPath;
|
|
2793
|
+
const fromPath = whichBun();
|
|
2794
|
+
if (fromPath) {
|
|
2795
|
+
cachedBunPath = fromPath;
|
|
2796
|
+
return fromPath;
|
|
2797
|
+
}
|
|
2798
|
+
const localPath = path5.join(os3.homedir(), ".bun", "bin", "bun");
|
|
2799
|
+
if (fs5.existsSync(localPath)) {
|
|
2800
|
+
cachedBunPath = localPath;
|
|
2801
|
+
return localPath;
|
|
2802
|
+
}
|
|
2803
|
+
console.log(
|
|
2804
|
+
"\n[smol-js] Bun is required to run custom tools but was not found. Installing Bun automatically...\n"
|
|
2805
|
+
);
|
|
2806
|
+
try {
|
|
2807
|
+
execSync("curl --proto =https --tlsv1.2 -sSf https://bun.sh | bash", {
|
|
2808
|
+
stdio: "inherit",
|
|
2809
|
+
shell: "/bin/bash",
|
|
2810
|
+
env: { ...process.env, HOME: os3.homedir() }
|
|
2811
|
+
});
|
|
2812
|
+
} catch (err) {
|
|
2813
|
+
throw new Error(
|
|
2814
|
+
`[smol-js] Failed to auto-install Bun. Please install it manually: https://bun.sh
|
|
2815
|
+
Details: ${err.message}`
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
const afterInstall = whichBun() || (fs5.existsSync(localPath) ? localPath : null);
|
|
2819
|
+
if (!afterInstall) {
|
|
2820
|
+
throw new Error(
|
|
2821
|
+
"[smol-js] Bun installation appeared to succeed but the binary was not found. Please install manually: https://bun.sh"
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2824
|
+
console.log(`[smol-js] Bun installed successfully at: ${afterInstall}
|
|
2825
|
+
`);
|
|
2826
|
+
cachedBunPath = afterInstall;
|
|
2827
|
+
return afterInstall;
|
|
2828
|
+
}
|
|
2829
|
+
function whichBun() {
|
|
2830
|
+
try {
|
|
2831
|
+
const cmd = process.platform === "win32" ? "where bun" : "which bun";
|
|
2832
|
+
const result = execSync(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
|
|
2833
|
+
const first = result.split("\n")[0]?.trim();
|
|
2834
|
+
if (first && fs5.existsSync(first)) return first;
|
|
2835
|
+
return null;
|
|
2836
|
+
} catch {
|
|
2837
|
+
return null;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// src/tools/ProxyTool.ts
|
|
2842
|
+
var TOOL_RESULT_PREFIX = "[TOOL_RESULT]";
|
|
2843
|
+
var TOOL_ERROR_PREFIX = "[TOOL_ERROR]";
|
|
2844
|
+
var DEFAULT_TOOL_TIMEOUT_MS = 6e4;
|
|
2845
|
+
function resolveHarnessPath() {
|
|
2846
|
+
return path6.resolve(__dirname, "..", "toolHarness.ts");
|
|
2847
|
+
}
|
|
2848
|
+
var ProxyTool = class extends Tool {
|
|
2849
|
+
name;
|
|
2850
|
+
description;
|
|
2851
|
+
inputs;
|
|
2852
|
+
outputType;
|
|
2853
|
+
toolPath;
|
|
2854
|
+
timeout;
|
|
2855
|
+
bunPath = null;
|
|
2856
|
+
harnessPath = null;
|
|
2857
|
+
constructor(config) {
|
|
2858
|
+
super();
|
|
2859
|
+
this.name = config.name;
|
|
2860
|
+
this.description = config.description;
|
|
2861
|
+
this.inputs = config.inputs;
|
|
2862
|
+
this.outputType = config.outputType;
|
|
2863
|
+
this.toolPath = config.toolPath;
|
|
2864
|
+
this.timeout = config.timeout ?? DEFAULT_TOOL_TIMEOUT_MS;
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* Ensure Bun is available and locate the harness before first invocation.
|
|
2868
|
+
*/
|
|
2869
|
+
async setup() {
|
|
2870
|
+
this.bunPath = await ensureBunAvailable();
|
|
2871
|
+
this.harnessPath = resolveHarnessPath();
|
|
2872
|
+
this.isSetup = true;
|
|
2873
|
+
}
|
|
2874
|
+
/**
|
|
2875
|
+
* Spawn the harness in a Bun child process. The harness imports the tool,
|
|
2876
|
+
* calls execute(args), and writes the protocol lines. Any console.log from
|
|
2877
|
+
* the tool flows through stdout as plain lines.
|
|
2878
|
+
*/
|
|
2879
|
+
async execute(args) {
|
|
2880
|
+
if (!this.bunPath || !this.harnessPath) {
|
|
2881
|
+
await this.setup();
|
|
2882
|
+
}
|
|
2883
|
+
const serializedArgs = JSON.stringify(args);
|
|
2884
|
+
return new Promise((resolve7, reject) => {
|
|
2885
|
+
const child = spawn(this.bunPath, ["run", this.harnessPath, this.toolPath, serializedArgs], {
|
|
2886
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2887
|
+
env: { ...process.env }
|
|
2888
|
+
});
|
|
2889
|
+
let result = void 0;
|
|
2890
|
+
let resultReceived = false;
|
|
2891
|
+
let errorMessage = null;
|
|
2892
|
+
const logBuffer = [];
|
|
2893
|
+
let stderr = "";
|
|
2894
|
+
let partialLine = "";
|
|
2895
|
+
child.stdout.on("data", (chunk) => {
|
|
2896
|
+
partialLine += chunk.toString("utf8");
|
|
2897
|
+
const lines = partialLine.split("\n");
|
|
2898
|
+
partialLine = lines.pop();
|
|
2899
|
+
for (const line of lines) {
|
|
2900
|
+
this.processLine(line, {
|
|
2901
|
+
onOutput: (msg) => logBuffer.push(msg),
|
|
2902
|
+
onResult: (value) => {
|
|
2903
|
+
result = value;
|
|
2904
|
+
resultReceived = true;
|
|
2905
|
+
},
|
|
2906
|
+
onError: (msg) => {
|
|
2907
|
+
errorMessage = msg;
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
});
|
|
2912
|
+
child.stderr.on("data", (chunk) => {
|
|
2913
|
+
stderr += chunk.toString("utf8");
|
|
2914
|
+
});
|
|
2915
|
+
const timer = setTimeout(() => {
|
|
2916
|
+
child.kill("SIGTERM");
|
|
2917
|
+
reject(new Error(
|
|
2918
|
+
`Custom tool "${this.name}" timed out after ${this.timeout}ms. The process was terminated. Check the tool for infinite loops or slow operations.`
|
|
2919
|
+
));
|
|
2920
|
+
}, this.timeout);
|
|
2921
|
+
timer.unref();
|
|
2922
|
+
child.on("close", (code) => {
|
|
2923
|
+
clearTimeout(timer);
|
|
2924
|
+
if (partialLine.trim()) {
|
|
2925
|
+
this.processLine(partialLine, {
|
|
2926
|
+
onOutput: (msg) => logBuffer.push(msg),
|
|
2927
|
+
onResult: (value) => {
|
|
2928
|
+
result = value;
|
|
2929
|
+
resultReceived = true;
|
|
2930
|
+
},
|
|
2931
|
+
onError: (msg) => {
|
|
2932
|
+
errorMessage = msg;
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
if (errorMessage) {
|
|
2937
|
+
reject(new Error(
|
|
2938
|
+
`Custom tool "${this.name}" reported an error: ${errorMessage}`
|
|
2939
|
+
));
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
if (resultReceived) {
|
|
2943
|
+
if (logBuffer.length > 0) {
|
|
2944
|
+
const logPrefix = `[Tool output logs]
|
|
2945
|
+
${logBuffer.join("\n")}
|
|
2946
|
+
|
|
2947
|
+
[Tool result]
|
|
2948
|
+
`;
|
|
2949
|
+
if (typeof result === "string") {
|
|
2950
|
+
resolve7(logPrefix + result);
|
|
2951
|
+
} else {
|
|
2952
|
+
resolve7({ logs: logBuffer.join("\n"), result });
|
|
2953
|
+
}
|
|
2954
|
+
} else {
|
|
2955
|
+
resolve7(result);
|
|
2956
|
+
}
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
const combined = (logBuffer.join("\n") + "\n" + stderr).trim();
|
|
2960
|
+
if (code !== 0) {
|
|
2961
|
+
reject(new Error(
|
|
2962
|
+
`Custom tool "${this.name}" exited with code ${code}. Output: ${combined || "(none)"}`
|
|
2963
|
+
));
|
|
2964
|
+
} else {
|
|
2965
|
+
resolve7(combined || `Tool "${this.name}" produced no output.`);
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
child.on("error", (err) => {
|
|
2969
|
+
clearTimeout(timer);
|
|
2970
|
+
reject(new Error(
|
|
2971
|
+
`Failed to spawn custom tool "${this.name}": ${err.message}`
|
|
2972
|
+
));
|
|
2973
|
+
});
|
|
2727
2974
|
});
|
|
2728
|
-
|
|
2975
|
+
}
|
|
2976
|
+
// --- line parser: protocol is spoken by harness, interpreted here ---
|
|
2977
|
+
processLine(line, handlers) {
|
|
2978
|
+
const trimmed = line.trimEnd();
|
|
2979
|
+
if (!trimmed) return;
|
|
2980
|
+
if (trimmed.startsWith(TOOL_RESULT_PREFIX)) {
|
|
2981
|
+
const json = trimmed.slice(TOOL_RESULT_PREFIX.length).trim();
|
|
2982
|
+
try {
|
|
2983
|
+
handlers.onResult(JSON.parse(json));
|
|
2984
|
+
} catch {
|
|
2985
|
+
handlers.onResult(json);
|
|
2986
|
+
}
|
|
2987
|
+
} else if (trimmed.startsWith(TOOL_ERROR_PREFIX)) {
|
|
2988
|
+
handlers.onError(trimmed.slice(TOOL_ERROR_PREFIX.length).trim());
|
|
2989
|
+
} else {
|
|
2990
|
+
handlers.onOutput(trimmed);
|
|
2991
|
+
}
|
|
2729
2992
|
}
|
|
2730
2993
|
};
|
|
2731
2994
|
|
|
2995
|
+
// src/tools/CustomToolScanner.ts
|
|
2996
|
+
import * as fs6 from "fs";
|
|
2997
|
+
import * as path7 from "path";
|
|
2998
|
+
var METADATA_REGEX = /export\s+const\s+TOOL_METADATA\s*=\s*(\{[\s\S]*?\});\s*$/m;
|
|
2999
|
+
function scanCustomTools(folderPath) {
|
|
3000
|
+
if (!fs6.existsSync(folderPath)) {
|
|
3001
|
+
throw new Error(
|
|
3002
|
+
`Custom tools folder not found: ${folderPath}. Create the directory or check your --custom-tools-folder path.`
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
const entries = fs6.readdirSync(folderPath, { withFileTypes: true });
|
|
3006
|
+
const discovered = [];
|
|
3007
|
+
for (const entry of entries) {
|
|
3008
|
+
if (entry.isDirectory()) continue;
|
|
3009
|
+
const ext = path7.extname(entry.name).toLowerCase();
|
|
3010
|
+
if (ext !== ".ts" && ext !== ".js") continue;
|
|
3011
|
+
const filePath = path7.resolve(folderPath, entry.name);
|
|
3012
|
+
const baseName = path7.basename(entry.name, ext);
|
|
3013
|
+
let metadata;
|
|
3014
|
+
try {
|
|
3015
|
+
metadata = extractMetadata(filePath);
|
|
3016
|
+
} catch (err) {
|
|
3017
|
+
throw new Error(
|
|
3018
|
+
`Failed to extract TOOL_METADATA from "${entry.name}": ${err.message}
|
|
3019
|
+
Ensure the file exports \`export const TOOL_METADATA = { name, description, inputs, outputType };\``
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
if (metadata.name !== baseName) {
|
|
3023
|
+
throw new Error(
|
|
3024
|
+
`Tool metadata name mismatch in "${entry.name}": file is "${baseName}" but TOOL_METADATA.name is "${metadata.name}". They must match (Convention over Configuration).`
|
|
3025
|
+
);
|
|
3026
|
+
}
|
|
3027
|
+
discovered.push({ filePath, metadata });
|
|
3028
|
+
}
|
|
3029
|
+
return discovered;
|
|
3030
|
+
}
|
|
3031
|
+
function extractMetadata(filePath) {
|
|
3032
|
+
const source = fs6.readFileSync(filePath, "utf8");
|
|
3033
|
+
const match = source.match(METADATA_REGEX);
|
|
3034
|
+
if (!match) {
|
|
3035
|
+
throw new Error(
|
|
3036
|
+
"No `export const TOOL_METADATA = { ... };` block found. Add the metadata export at the bottom of your tool file."
|
|
3037
|
+
);
|
|
3038
|
+
}
|
|
3039
|
+
let parsed;
|
|
3040
|
+
try {
|
|
3041
|
+
parsed = new Function(`"use strict"; return (${match[1]});`)();
|
|
3042
|
+
} catch (err) {
|
|
3043
|
+
throw new Error(
|
|
3044
|
+
`Could not parse TOOL_METADATA object: ${err.message}. Ensure it is a valid JavaScript object literal.`
|
|
3045
|
+
);
|
|
3046
|
+
}
|
|
3047
|
+
if (!parsed.name || typeof parsed.name !== "string") {
|
|
3048
|
+
throw new Error("TOOL_METADATA.name must be a non-empty string.");
|
|
3049
|
+
}
|
|
3050
|
+
if (!parsed.description || typeof parsed.description !== "string") {
|
|
3051
|
+
throw new Error("TOOL_METADATA.description must be a non-empty string.");
|
|
3052
|
+
}
|
|
3053
|
+
if (!parsed.inputs || typeof parsed.inputs !== "object") {
|
|
3054
|
+
throw new Error("TOOL_METADATA.inputs must be an object mapping parameter names to their schemas.");
|
|
3055
|
+
}
|
|
3056
|
+
if (!parsed.outputType || typeof parsed.outputType !== "string") {
|
|
3057
|
+
throw new Error("TOOL_METADATA.outputType must be a non-empty string.");
|
|
3058
|
+
}
|
|
3059
|
+
return parsed;
|
|
3060
|
+
}
|
|
3061
|
+
function loadCustomTools(folderPath) {
|
|
3062
|
+
const discovered = scanCustomTools(folderPath);
|
|
3063
|
+
const tools = /* @__PURE__ */ new Map();
|
|
3064
|
+
for (const { filePath, metadata } of discovered) {
|
|
3065
|
+
const config = {
|
|
3066
|
+
toolPath: filePath,
|
|
3067
|
+
name: metadata.name,
|
|
3068
|
+
description: metadata.description,
|
|
3069
|
+
inputs: metadata.inputs,
|
|
3070
|
+
outputType: metadata.outputType,
|
|
3071
|
+
timeout: metadata.timeout
|
|
3072
|
+
};
|
|
3073
|
+
tools.set(metadata.name, new ProxyTool(config));
|
|
3074
|
+
}
|
|
3075
|
+
return tools;
|
|
3076
|
+
}
|
|
3077
|
+
|
|
2732
3078
|
// src/orchestrator/YAMLLoader.ts
|
|
2733
|
-
import * as
|
|
2734
|
-
import * as
|
|
3079
|
+
import * as fs7 from "fs";
|
|
3080
|
+
import * as path8 from "path";
|
|
2735
3081
|
import YAML from "yaml";
|
|
2736
3082
|
var TOOL_REGISTRY = {
|
|
2737
3083
|
read_file: ReadFileTool,
|
|
@@ -2744,21 +3090,30 @@ var TOOL_REGISTRY = {
|
|
|
2744
3090
|
};
|
|
2745
3091
|
var YAMLLoader = class {
|
|
2746
3092
|
customTools = /* @__PURE__ */ new Map();
|
|
3093
|
+
toolInstances = /* @__PURE__ */ new Map();
|
|
2747
3094
|
/**
|
|
2748
|
-
* Register a custom tool type for use in YAML definitions.
|
|
3095
|
+
* Register a custom tool type (class) for use in YAML definitions.
|
|
2749
3096
|
*/
|
|
2750
3097
|
registerToolType(typeName, toolClass) {
|
|
2751
3098
|
this.customTools.set(typeName, toolClass);
|
|
2752
3099
|
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Register a pre-built tool instance for use in YAML definitions.
|
|
3102
|
+
* Used by the custom tool system to register ProxyTool instances
|
|
3103
|
+
* that are created by the scanner rather than by class instantiation.
|
|
3104
|
+
*/
|
|
3105
|
+
registerToolInstance(typeName, tool) {
|
|
3106
|
+
this.toolInstances.set(typeName, tool);
|
|
3107
|
+
}
|
|
2753
3108
|
/**
|
|
2754
3109
|
* Load a workflow from a YAML file path.
|
|
2755
3110
|
*/
|
|
2756
3111
|
loadFromFile(filePath) {
|
|
2757
|
-
const absolutePath =
|
|
2758
|
-
if (!
|
|
3112
|
+
const absolutePath = path8.isAbsolute(filePath) ? filePath : path8.resolve(process.cwd(), filePath);
|
|
3113
|
+
if (!fs7.existsSync(absolutePath)) {
|
|
2759
3114
|
throw new Error(`Workflow file not found: ${absolutePath}`);
|
|
2760
3115
|
}
|
|
2761
|
-
const content =
|
|
3116
|
+
const content = fs7.readFileSync(absolutePath, "utf-8");
|
|
2762
3117
|
return this.loadFromString(content);
|
|
2763
3118
|
}
|
|
2764
3119
|
/**
|
|
@@ -2833,9 +3188,16 @@ var YAMLLoader = class {
|
|
|
2833
3188
|
* Build a tool instance from a type name and config.
|
|
2834
3189
|
*/
|
|
2835
3190
|
buildTool(name, type, config) {
|
|
3191
|
+
const existingInstance = this.toolInstances.get(type);
|
|
3192
|
+
if (existingInstance) {
|
|
3193
|
+
if (name !== type && name !== existingInstance.name) {
|
|
3194
|
+
Object.defineProperty(existingInstance, "name", { value: name, writable: false });
|
|
3195
|
+
}
|
|
3196
|
+
return existingInstance;
|
|
3197
|
+
}
|
|
2836
3198
|
const ToolClass = TOOL_REGISTRY[type] ?? this.customTools.get(type);
|
|
2837
3199
|
if (!ToolClass) {
|
|
2838
|
-
throw new Error(`Unknown tool type: ${type}. Available types: ${[...Object.keys(TOOL_REGISTRY), ...this.customTools.keys()].join(", ")}`);
|
|
3200
|
+
throw new Error(`Unknown tool type: ${type}. Available types: ${[...Object.keys(TOOL_REGISTRY), ...this.customTools.keys(), ...this.toolInstances.keys()].join(", ")}`);
|
|
2839
3201
|
}
|
|
2840
3202
|
const tool = new ToolClass(config);
|
|
2841
3203
|
if (name !== type && name !== tool.name) {
|
|
@@ -2863,11 +3225,16 @@ var YAMLLoader = class {
|
|
|
2863
3225
|
if (tool) {
|
|
2864
3226
|
agentTools.push(tool);
|
|
2865
3227
|
} else {
|
|
2866
|
-
const
|
|
2867
|
-
if (
|
|
2868
|
-
agentTools.push(
|
|
3228
|
+
const instance = this.toolInstances.get(toolName);
|
|
3229
|
+
if (instance) {
|
|
3230
|
+
agentTools.push(instance);
|
|
2869
3231
|
} else {
|
|
2870
|
-
|
|
3232
|
+
const ToolClass = TOOL_REGISTRY[toolName] ?? this.customTools.get(toolName);
|
|
3233
|
+
if (ToolClass) {
|
|
3234
|
+
agentTools.push(new ToolClass());
|
|
3235
|
+
} else {
|
|
3236
|
+
throw new Error(`Tool "${toolName}" not found for agent "${name}"`);
|
|
3237
|
+
}
|
|
2871
3238
|
}
|
|
2872
3239
|
}
|
|
2873
3240
|
}
|
|
@@ -2918,39 +3285,117 @@ var YAMLLoader = class {
|
|
|
2918
3285
|
|
|
2919
3286
|
// src/orchestrator/Orchestrator.ts
|
|
2920
3287
|
import chalk2 from "chalk";
|
|
3288
|
+
|
|
3289
|
+
// src/output/JSONOutputHandler.ts
|
|
3290
|
+
var JSONOutputHandler = class {
|
|
3291
|
+
runId;
|
|
3292
|
+
startTime;
|
|
3293
|
+
constructor(config) {
|
|
3294
|
+
this.runId = config.runId;
|
|
3295
|
+
this.startTime = Date.now();
|
|
3296
|
+
}
|
|
3297
|
+
emit(type, data, extra = {}) {
|
|
3298
|
+
console.log(JSON.stringify({
|
|
3299
|
+
runId: this.runId,
|
|
3300
|
+
timestamp: Date.now(),
|
|
3301
|
+
type,
|
|
3302
|
+
...extra,
|
|
3303
|
+
data
|
|
3304
|
+
}));
|
|
3305
|
+
}
|
|
3306
|
+
emitRunStart(workflowPath, task, cwd) {
|
|
3307
|
+
this.emit("run_start", { workflowPath, task, cwd });
|
|
3308
|
+
}
|
|
3309
|
+
emitWorkflowLoaded(name, description, agents, tools, entrypoint) {
|
|
3310
|
+
this.emit("workflow_loaded", { name, description, agents, tools, entrypoint });
|
|
3311
|
+
}
|
|
3312
|
+
emitRunEnd(success, output, totalTokens, totalSteps) {
|
|
3313
|
+
this.emit("run_end", {
|
|
3314
|
+
success,
|
|
3315
|
+
output,
|
|
3316
|
+
totalDuration: Date.now() - this.startTime,
|
|
3317
|
+
totalTokens,
|
|
3318
|
+
totalSteps
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
emitAgentStart(agentName, depth, task, agentType, maxSteps) {
|
|
3322
|
+
this.emit("agent_start", { task, agentType, maxSteps }, { agentName, depth });
|
|
3323
|
+
}
|
|
3324
|
+
emitAgentEnd(agentName, depth, output, totalSteps, tokenUsage, duration, success) {
|
|
3325
|
+
this.emit("agent_end", { output, totalSteps, tokenUsage, duration, success }, { agentName, depth });
|
|
3326
|
+
}
|
|
3327
|
+
emitAgentStep(agentName, depth, stepNumber, maxSteps, phase) {
|
|
3328
|
+
this.emit("agent_step", { stepNumber, maxSteps, phase }, { agentName, depth });
|
|
3329
|
+
}
|
|
3330
|
+
emitAgentThinking(agentName, depth, stepNumber, content, isPartial) {
|
|
3331
|
+
this.emit("agent_thinking", { stepNumber, content, isPartial }, { agentName, depth });
|
|
3332
|
+
}
|
|
3333
|
+
emitToolCall(agentName, depth, stepNumber, toolCallId, toolName, args) {
|
|
3334
|
+
this.emit("agent_tool_call", { stepNumber, toolCallId, toolName, arguments: args }, { agentName, depth });
|
|
3335
|
+
}
|
|
3336
|
+
emitToolResult(agentName, depth, stepNumber, toolCallId, toolName, result, error, duration) {
|
|
3337
|
+
this.emit("agent_tool_result", { stepNumber, toolCallId, toolName, result, error, duration }, { agentName, depth });
|
|
3338
|
+
}
|
|
3339
|
+
emitObservation(agentName, depth, stepNumber, observation, codeAction, logs) {
|
|
3340
|
+
this.emit("agent_observation", { stepNumber, observation, codeAction, logs }, { agentName, depth });
|
|
3341
|
+
}
|
|
3342
|
+
emitError(message, stack, agentName, depth, stepNumber) {
|
|
3343
|
+
this.emit("error", { message, stack, stepNumber }, {
|
|
3344
|
+
...agentName ? { agentName } : {},
|
|
3345
|
+
...depth !== void 0 ? { depth } : {}
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
};
|
|
3349
|
+
|
|
3350
|
+
// src/orchestrator/Orchestrator.ts
|
|
2921
3351
|
var Orchestrator = class {
|
|
2922
3352
|
loader;
|
|
2923
3353
|
config;
|
|
2924
3354
|
activeAgents = /* @__PURE__ */ new Map();
|
|
2925
3355
|
eventLog = [];
|
|
3356
|
+
jsonOutput = null;
|
|
3357
|
+
isJsonMode = false;
|
|
2926
3358
|
constructor(config = {}) {
|
|
2927
3359
|
this.loader = new YAMLLoader();
|
|
3360
|
+
this.isJsonMode = config.outputFormat === "json";
|
|
2928
3361
|
this.config = {
|
|
2929
3362
|
verbose: config.verbose ?? true,
|
|
2930
|
-
onEvent: config.onEvent
|
|
3363
|
+
onEvent: config.onEvent,
|
|
3364
|
+
outputFormat: config.outputFormat ?? "text",
|
|
3365
|
+
runId: config.runId,
|
|
3366
|
+
cwd: config.cwd
|
|
2931
3367
|
};
|
|
3368
|
+
if (this.isJsonMode) {
|
|
3369
|
+
if (!config.runId) {
|
|
3370
|
+
throw new Error("runId is required for JSON output mode");
|
|
3371
|
+
}
|
|
3372
|
+
this.jsonOutput = new JSONOutputHandler({
|
|
3373
|
+
runId: config.runId,
|
|
3374
|
+
verbose: config.verbose ?? true
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
2932
3377
|
}
|
|
2933
3378
|
/**
|
|
2934
3379
|
* Load a workflow from a YAML file.
|
|
2935
3380
|
*/
|
|
2936
3381
|
loadWorkflow(filePath) {
|
|
2937
3382
|
const workflow = this.loader.loadFromFile(filePath);
|
|
2938
|
-
this.displayWorkflowInfo(workflow);
|
|
3383
|
+
this.displayWorkflowInfo(workflow, filePath);
|
|
2939
3384
|
return workflow;
|
|
2940
3385
|
}
|
|
2941
3386
|
/**
|
|
2942
3387
|
* Load a workflow from YAML string.
|
|
2943
3388
|
*/
|
|
2944
|
-
loadWorkflowFromString(yamlContent) {
|
|
3389
|
+
loadWorkflowFromString(yamlContent, sourcePath) {
|
|
2945
3390
|
const workflow = this.loader.loadFromString(yamlContent);
|
|
2946
|
-
this.displayWorkflowInfo(workflow);
|
|
3391
|
+
this.displayWorkflowInfo(workflow, sourcePath);
|
|
2947
3392
|
return workflow;
|
|
2948
3393
|
}
|
|
2949
3394
|
/**
|
|
2950
3395
|
* Run a loaded workflow with a task.
|
|
2951
3396
|
*/
|
|
2952
|
-
async runWorkflow(workflow, task) {
|
|
2953
|
-
this.displayRunStart(workflow.name, task);
|
|
3397
|
+
async runWorkflow(workflow, task, workflowPath) {
|
|
3398
|
+
this.displayRunStart(workflow.name, task, workflowPath);
|
|
2954
3399
|
this.instrumentAgent(workflow.entrypointAgent, workflow.entrypointAgent.getName(), 0);
|
|
2955
3400
|
for (const [name, agent] of workflow.agents) {
|
|
2956
3401
|
if (agent !== workflow.entrypointAgent) {
|
|
@@ -2959,10 +3404,13 @@ var Orchestrator = class {
|
|
|
2959
3404
|
}
|
|
2960
3405
|
try {
|
|
2961
3406
|
const result = await workflow.entrypointAgent.run(task);
|
|
2962
|
-
this.displayRunEnd(result);
|
|
3407
|
+
this.displayRunEnd(result, true);
|
|
2963
3408
|
return result;
|
|
2964
3409
|
} catch (error) {
|
|
2965
3410
|
this.displayError(error);
|
|
3411
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3412
|
+
this.jsonOutput.emitRunEnd(false, null, 0, 0);
|
|
3413
|
+
}
|
|
2966
3414
|
throw error;
|
|
2967
3415
|
}
|
|
2968
3416
|
}
|
|
@@ -2979,11 +3427,128 @@ var Orchestrator = class {
|
|
|
2979
3427
|
*/
|
|
2980
3428
|
instrumentAgent(agent, name, depth) {
|
|
2981
3429
|
this.activeAgents.set(name, { agent, depth });
|
|
3430
|
+
agent.setOnEvent((event) => {
|
|
3431
|
+
const orchestratorEvent = {
|
|
3432
|
+
type: event.type,
|
|
3433
|
+
agentName: name,
|
|
3434
|
+
depth,
|
|
3435
|
+
data: event.data,
|
|
3436
|
+
timestamp: Date.now()
|
|
3437
|
+
};
|
|
3438
|
+
this.logEvent(orchestratorEvent);
|
|
3439
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3440
|
+
this.emitAgentEventAsJSON(event, name, depth);
|
|
3441
|
+
}
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3444
|
+
/**
|
|
3445
|
+
* Emit an agent event as JSON.
|
|
3446
|
+
*/
|
|
3447
|
+
emitAgentEventAsJSON(event, agentName, depth) {
|
|
3448
|
+
if (!this.jsonOutput) return;
|
|
3449
|
+
const data = event.data;
|
|
3450
|
+
switch (event.type) {
|
|
3451
|
+
case "agent_start":
|
|
3452
|
+
this.jsonOutput.emitAgentStart(
|
|
3453
|
+
agentName,
|
|
3454
|
+
depth,
|
|
3455
|
+
data.task,
|
|
3456
|
+
data.name ? "ToolUseAgent" : "CodeAgent",
|
|
3457
|
+
// Will be improved
|
|
3458
|
+
20
|
|
3459
|
+
// Default maxSteps, could be passed
|
|
3460
|
+
);
|
|
3461
|
+
break;
|
|
3462
|
+
case "agent_step":
|
|
3463
|
+
this.jsonOutput.emitAgentStep(
|
|
3464
|
+
agentName,
|
|
3465
|
+
depth,
|
|
3466
|
+
data.step,
|
|
3467
|
+
data.maxSteps,
|
|
3468
|
+
"start"
|
|
3469
|
+
);
|
|
3470
|
+
break;
|
|
3471
|
+
case "agent_thinking":
|
|
3472
|
+
this.jsonOutput.emitAgentThinking(
|
|
3473
|
+
agentName,
|
|
3474
|
+
depth,
|
|
3475
|
+
data.step,
|
|
3476
|
+
data.content,
|
|
3477
|
+
false
|
|
3478
|
+
);
|
|
3479
|
+
break;
|
|
3480
|
+
case "agent_tool_call":
|
|
3481
|
+
this.jsonOutput.emitToolCall(
|
|
3482
|
+
agentName,
|
|
3483
|
+
depth,
|
|
3484
|
+
data.step,
|
|
3485
|
+
data.toolCallId,
|
|
3486
|
+
data.toolName,
|
|
3487
|
+
data.arguments
|
|
3488
|
+
);
|
|
3489
|
+
break;
|
|
3490
|
+
case "agent_tool_result":
|
|
3491
|
+
this.jsonOutput.emitToolResult(
|
|
3492
|
+
agentName,
|
|
3493
|
+
depth,
|
|
3494
|
+
data.step,
|
|
3495
|
+
data.toolCallId,
|
|
3496
|
+
data.toolName,
|
|
3497
|
+
data.result,
|
|
3498
|
+
data.error,
|
|
3499
|
+
data.duration
|
|
3500
|
+
);
|
|
3501
|
+
break;
|
|
3502
|
+
case "agent_observation":
|
|
3503
|
+
this.jsonOutput.emitObservation(
|
|
3504
|
+
agentName,
|
|
3505
|
+
depth,
|
|
3506
|
+
data.step,
|
|
3507
|
+
data.observation,
|
|
3508
|
+
data.codeAction,
|
|
3509
|
+
data.logs
|
|
3510
|
+
);
|
|
3511
|
+
break;
|
|
3512
|
+
case "agent_error":
|
|
3513
|
+
this.jsonOutput.emitError(
|
|
3514
|
+
data.error,
|
|
3515
|
+
void 0,
|
|
3516
|
+
agentName,
|
|
3517
|
+
depth,
|
|
3518
|
+
data.step
|
|
3519
|
+
);
|
|
3520
|
+
break;
|
|
3521
|
+
case "agent_end":
|
|
3522
|
+
this.jsonOutput.emitAgentEnd(
|
|
3523
|
+
agentName,
|
|
3524
|
+
depth,
|
|
3525
|
+
data.output,
|
|
3526
|
+
0,
|
|
3527
|
+
// totalSteps - would need to track
|
|
3528
|
+
data.tokenUsage,
|
|
3529
|
+
data.duration,
|
|
3530
|
+
true
|
|
3531
|
+
);
|
|
3532
|
+
break;
|
|
3533
|
+
}
|
|
2982
3534
|
}
|
|
2983
3535
|
/**
|
|
2984
3536
|
* Display workflow info at startup.
|
|
2985
3537
|
*/
|
|
2986
|
-
displayWorkflowInfo(workflow) {
|
|
3538
|
+
displayWorkflowInfo(workflow, _sourcePath) {
|
|
3539
|
+
const agents = Array.from(workflow.agents.keys());
|
|
3540
|
+
const tools = Array.from(workflow.tools.keys());
|
|
3541
|
+
const entrypoint = workflow.entrypointAgent.getName();
|
|
3542
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3543
|
+
this.jsonOutput.emitWorkflowLoaded(
|
|
3544
|
+
workflow.name,
|
|
3545
|
+
workflow.description,
|
|
3546
|
+
agents,
|
|
3547
|
+
tools,
|
|
3548
|
+
entrypoint
|
|
3549
|
+
);
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
2987
3552
|
if (!this.config.verbose) return;
|
|
2988
3553
|
const line = "\u2550".repeat(70);
|
|
2989
3554
|
console.log(chalk2.cyan(line));
|
|
@@ -2991,16 +3556,20 @@ var Orchestrator = class {
|
|
|
2991
3556
|
if (workflow.description) {
|
|
2992
3557
|
console.log(chalk2.cyan(` ${workflow.description}`));
|
|
2993
3558
|
}
|
|
2994
|
-
console.log(chalk2.cyan(` Agents: ${
|
|
2995
|
-
console.log(chalk2.cyan(` Tools: ${
|
|
2996
|
-
console.log(chalk2.cyan(` Entrypoint: ${
|
|
3559
|
+
console.log(chalk2.cyan(` Agents: ${agents.join(", ")}`));
|
|
3560
|
+
console.log(chalk2.cyan(` Tools: ${tools.join(", ") || "(none defined at workflow level)"}`));
|
|
3561
|
+
console.log(chalk2.cyan(` Entrypoint: ${entrypoint}`));
|
|
2997
3562
|
console.log(chalk2.cyan(line));
|
|
2998
3563
|
console.log();
|
|
2999
3564
|
}
|
|
3000
3565
|
/**
|
|
3001
3566
|
* Display run start info.
|
|
3002
3567
|
*/
|
|
3003
|
-
displayRunStart(workflowName, task) {
|
|
3568
|
+
displayRunStart(workflowName, task, workflowPath) {
|
|
3569
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3570
|
+
this.jsonOutput.emitRunStart(workflowPath || workflowName, task, this.config.cwd);
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3004
3573
|
if (!this.config.verbose) return;
|
|
3005
3574
|
console.log(chalk2.green.bold(`
|
|
3006
3575
|
\u25B6 Running workflow "${workflowName}"`));
|
|
@@ -3010,7 +3579,16 @@ var Orchestrator = class {
|
|
|
3010
3579
|
/**
|
|
3011
3580
|
* Display run completion info.
|
|
3012
3581
|
*/
|
|
3013
|
-
displayRunEnd(result) {
|
|
3582
|
+
displayRunEnd(result, success = true) {
|
|
3583
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3584
|
+
this.jsonOutput.emitRunEnd(
|
|
3585
|
+
success,
|
|
3586
|
+
result.output,
|
|
3587
|
+
result.tokenUsage.totalTokens,
|
|
3588
|
+
result.steps.length
|
|
3589
|
+
);
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3014
3592
|
if (!this.config.verbose) return;
|
|
3015
3593
|
console.log(chalk2.gray("\n" + "\u2500".repeat(70)));
|
|
3016
3594
|
console.log(chalk2.green.bold(`
|
|
@@ -3028,6 +3606,10 @@ var Orchestrator = class {
|
|
|
3028
3606
|
* Display an error.
|
|
3029
3607
|
*/
|
|
3030
3608
|
displayError(error) {
|
|
3609
|
+
if (this.isJsonMode && this.jsonOutput) {
|
|
3610
|
+
this.jsonOutput.emitError(error.message, error.stack);
|
|
3611
|
+
return;
|
|
3612
|
+
}
|
|
3031
3613
|
if (!this.config.verbose) return;
|
|
3032
3614
|
console.error(chalk2.red.bold(`
|
|
3033
3615
|
\u274C Workflow failed: ${error.message}`));
|
|
@@ -3056,6 +3638,24 @@ var Orchestrator = class {
|
|
|
3056
3638
|
getLoader() {
|
|
3057
3639
|
return this.loader;
|
|
3058
3640
|
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Get the JSON output handler (if in JSON mode).
|
|
3643
|
+
*/
|
|
3644
|
+
getJSONOutputHandler() {
|
|
3645
|
+
return this.jsonOutput;
|
|
3646
|
+
}
|
|
3647
|
+
/**
|
|
3648
|
+
* Check if in JSON output mode.
|
|
3649
|
+
*/
|
|
3650
|
+
isJSONOutputMode() {
|
|
3651
|
+
return this.isJsonMode;
|
|
3652
|
+
}
|
|
3653
|
+
/**
|
|
3654
|
+
* Get the run ID.
|
|
3655
|
+
*/
|
|
3656
|
+
getRunId() {
|
|
3657
|
+
return this.config.runId;
|
|
3658
|
+
}
|
|
3059
3659
|
};
|
|
3060
3660
|
export {
|
|
3061
3661
|
Agent,
|
|
@@ -3069,11 +3669,13 @@ export {
|
|
|
3069
3669
|
ExaSearchTool,
|
|
3070
3670
|
FINAL_ANSWER_PROMPT,
|
|
3071
3671
|
FinalAnswerTool,
|
|
3672
|
+
JSONOutputHandler,
|
|
3072
3673
|
LocalExecutor,
|
|
3073
3674
|
LogLevel,
|
|
3074
3675
|
Model,
|
|
3075
3676
|
OpenAIModel,
|
|
3076
3677
|
Orchestrator,
|
|
3678
|
+
ProxyTool,
|
|
3077
3679
|
ReadFileTool,
|
|
3078
3680
|
Tool,
|
|
3079
3681
|
ToolUseAgent,
|
|
@@ -3086,6 +3688,8 @@ export {
|
|
|
3086
3688
|
formatToolDescriptions,
|
|
3087
3689
|
generateSystemPrompt,
|
|
3088
3690
|
generateToolUseSystemPrompt,
|
|
3089
|
-
getErrorRecoveryPrompt
|
|
3691
|
+
getErrorRecoveryPrompt,
|
|
3692
|
+
loadCustomTools,
|
|
3693
|
+
scanCustomTools
|
|
3090
3694
|
};
|
|
3091
3695
|
//# sourceMappingURL=index.mjs.map
|