@tyvm/knowhow 0.0.21 → 0.0.22
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/package.json +3 -1
- package/src/agents/tools/executeScript/README.md +78 -0
- package/src/agents/tools/executeScript/definition.ts +73 -0
- package/src/agents/tools/executeScript/examples/quick-test.ts +80 -0
- package/src/agents/tools/executeScript/examples/serialization-test.ts +309 -0
- package/src/agents/tools/executeScript/examples/test-runner.ts +204 -0
- package/src/agents/tools/executeScript/index.ts +74 -0
- package/src/agents/tools/index.ts +1 -0
- package/src/agents/tools/list.ts +2 -1
- package/src/cli.ts +2 -6
- package/src/clients/index.ts +23 -9
- package/src/services/Tools.ts +19 -3
- package/src/services/script-execution/SandboxContext.ts +278 -0
- package/src/services/script-execution/ScriptExecutor.ts +337 -0
- package/src/services/script-execution/ScriptPolicy.ts +236 -0
- package/src/services/script-execution/ScriptTracer.ts +249 -0
- package/src/services/script-execution/types.ts +134 -0
- package/ts_build/src/agents/tools/executeScript/definition.d.ts +2 -0
- package/ts_build/src/agents/tools/executeScript/definition.js +70 -0
- package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +3 -0
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +68 -0
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +15 -0
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +267 -0
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/simple-example.d.ts +20 -0
- package/ts_build/src/agents/tools/executeScript/examples/simple-example.js +35 -0
- package/ts_build/src/agents/tools/executeScript/examples/simple-example.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +4 -0
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +202 -0
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/handler.d.ts +27 -0
- package/ts_build/src/agents/tools/executeScript/handler.js +64 -0
- package/ts_build/src/agents/tools/executeScript/handler.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/index.d.ts +27 -0
- package/ts_build/src/agents/tools/executeScript/index.js +64 -0
- package/ts_build/src/agents/tools/executeScript/index.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript.d.ts +29 -0
- package/ts_build/src/agents/tools/executeScript.js +124 -0
- package/ts_build/src/agents/tools/executeScript.js.map +1 -0
- package/ts_build/src/agents/tools/index.d.ts +1 -0
- package/ts_build/src/agents/tools/index.js +1 -0
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/cli.js +2 -6
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +9 -2
- package/ts_build/src/clients/index.js +17 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/services/Tools.d.ts +3 -0
- package/ts_build/src/services/Tools.js +10 -2
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/script-execution/SandboxContext.d.ts +34 -0
- package/ts_build/src/services/script-execution/SandboxContext.js +188 -0
- package/ts_build/src/services/script-execution/SandboxContext.js.map +1 -0
- package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +17 -0
- package/ts_build/src/services/script-execution/ScriptExecutor.js +207 -0
- package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -0
- package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +27 -0
- package/ts_build/src/services/script-execution/ScriptPolicy.js +150 -0
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -0
- package/ts_build/src/services/script-execution/ScriptTracer.d.ts +19 -0
- package/ts_build/src/services/script-execution/ScriptTracer.js +186 -0
- package/ts_build/src/services/script-execution/ScriptTracer.js.map +1 -0
- package/ts_build/src/services/script-execution/types.d.ts +108 -0
- package/ts_build/src/services/script-execution/types.js +3 -0
- package/ts_build/src/services/script-execution/types.js.map +1 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
/**
|
|
3
|
+
* Test runner for the executeScript tool
|
|
4
|
+
* Usage: npx ts-node src/agents/tools/executeScript/examples/test-runner.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { executeScript } from "../../executeScript";
|
|
8
|
+
import { Tools } from "../../../../services";
|
|
9
|
+
import { Clients } from "../../../../clients";
|
|
10
|
+
import { includedTools } from "../../../tools/list";
|
|
11
|
+
import * as allTools from "../../../tools";
|
|
12
|
+
|
|
13
|
+
// Sample script to test with
|
|
14
|
+
const testScript = `
|
|
15
|
+
// Test script that demonstrates various executeScript capabilities
|
|
16
|
+
console.log("Starting test script execution...");
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
// Test 1: Simple console output
|
|
20
|
+
console.log("Test 1: Basic logging works");
|
|
21
|
+
|
|
22
|
+
// Test 2: Call a tool (file search)
|
|
23
|
+
try {
|
|
24
|
+
console.log("Test 2: Calling fileSearch tool...");
|
|
25
|
+
const searchResult = await callTool("fileSearch", {
|
|
26
|
+
searchTerm: "package.json"
|
|
27
|
+
});
|
|
28
|
+
console.log("File search result:", searchResult);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error("Tool call failed:", error.message);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Test 3: Call another tool (text search)
|
|
34
|
+
try {
|
|
35
|
+
console.log("Test 3: Calling textSearch tool...");
|
|
36
|
+
const textResult = await callTool("textSearch", {
|
|
37
|
+
searchTerm: "executeScript"
|
|
38
|
+
});
|
|
39
|
+
console.log("Text search found", textResult?.length || 0, "matches");
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error("Text search failed:", error.message);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Test 4: Make an LLM call
|
|
45
|
+
try {
|
|
46
|
+
console.log("Test 4: Making LLM call...");
|
|
47
|
+
const llmResponse = await llm([
|
|
48
|
+
{
|
|
49
|
+
role: "system",
|
|
50
|
+
content: "You are a helpful assistant. Respond with exactly one sentence."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
role: "user",
|
|
54
|
+
content: "What is 2+2? Just give the answer briefly."
|
|
55
|
+
}
|
|
56
|
+
], {
|
|
57
|
+
model: "gpt-4o-mini",
|
|
58
|
+
max_tokens: 50
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log("LLM Response:", llmResponse.choices[0].message.content);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error("LLM call failed:", error.message);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Test 5: Create an artifact
|
|
67
|
+
try {
|
|
68
|
+
console.log("Test 5: Creating artifact...");
|
|
69
|
+
createArtifact("test-results.md", \`# Test Results
|
|
70
|
+
|
|
71
|
+
Script executed successfully at: \${new Date().toISOString()}
|
|
72
|
+
|
|
73
|
+
This is a test artifact created by the executeScript tool.
|
|
74
|
+
|
|
75
|
+
## Test Summary
|
|
76
|
+
- Console logging: ✓
|
|
77
|
+
- Tool calls: ✓
|
|
78
|
+
- LLM calls: ✓
|
|
79
|
+
- Artifact creation: ✓
|
|
80
|
+
\`, "markdown");
|
|
81
|
+
console.log("Artifact created successfully");
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("Artifact creation failed:", error.message);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Return final result
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
message: "All tests completed successfully",
|
|
90
|
+
timestamp: new Date().toISOString(),
|
|
91
|
+
testsRun: 5
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Execute the main function
|
|
96
|
+
await main().then(result => {
|
|
97
|
+
console.log("=== SCRIPT COMPLETED ===");
|
|
98
|
+
console.log("Final result:", JSON.stringify(result, null, 2));
|
|
99
|
+
}).catch(error => {
|
|
100
|
+
console.error("=== SCRIPT FAILED ===");
|
|
101
|
+
console.error("Error:", error);
|
|
102
|
+
throw error;
|
|
103
|
+
});
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
async function runTest() {
|
|
107
|
+
console.log("🚀 Starting executeScript test...\n");
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
Tools.defineTools(includedTools, allTools);
|
|
111
|
+
|
|
112
|
+
const context = {
|
|
113
|
+
tools: Tools,
|
|
114
|
+
clients: Clients,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
console.log("📋 Test Parameters:");
|
|
118
|
+
console.log("- Max Tool Calls: 10");
|
|
119
|
+
console.log("- Max Tokens: 1000");
|
|
120
|
+
console.log("- Max Execution Time: 60s");
|
|
121
|
+
console.log("- Max Cost: $0.50\n");
|
|
122
|
+
|
|
123
|
+
const startTime = Date.now();
|
|
124
|
+
|
|
125
|
+
// Execute the test script
|
|
126
|
+
const result = await executeScript(
|
|
127
|
+
{
|
|
128
|
+
script: testScript,
|
|
129
|
+
maxToolCalls: 10,
|
|
130
|
+
maxTokens: 1000,
|
|
131
|
+
maxExecutionTimeMs: 60000,
|
|
132
|
+
maxCostUsd: 0.5,
|
|
133
|
+
},
|
|
134
|
+
context
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const executionTime = Date.now() - startTime;
|
|
138
|
+
|
|
139
|
+
console.log("\n" + "=".repeat(60));
|
|
140
|
+
console.log("🎯 TEST RESULTS");
|
|
141
|
+
console.log("=".repeat(60));
|
|
142
|
+
console.log(`⏱️ Execution Time: ${executionTime}ms`);
|
|
143
|
+
console.log(`✅ Success: ${result.success}`);
|
|
144
|
+
|
|
145
|
+
if (result.success) {
|
|
146
|
+
console.log(`📊 Result:`, result.result);
|
|
147
|
+
console.log(`🔧 Tool Calls Made: ${result.quotaUsage.toolCalls}`);
|
|
148
|
+
console.log(`🎯 Tokens Used: ${result.quotaUsage.tokens}`);
|
|
149
|
+
console.log(`💰 Cost: $${result.quotaUsage.costUsd.toFixed(4)}`);
|
|
150
|
+
|
|
151
|
+
if (result.artifacts.length > 0) {
|
|
152
|
+
console.log(`📁 Artifacts Created: ${result.artifacts.length}`);
|
|
153
|
+
result.artifacts.forEach((artifact) => {
|
|
154
|
+
console.log(
|
|
155
|
+
` - ${artifact.name} (${artifact.type}, ${artifact.contentLength} bytes)`
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (result.consoleOutput.length > 0) {
|
|
161
|
+
console.log(
|
|
162
|
+
`\n📝 Console Output (${result.consoleOutput.length} entries):`
|
|
163
|
+
);
|
|
164
|
+
result.consoleOutput.forEach((entry) => {
|
|
165
|
+
console.log(` ${entry}`);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (result.violations.length > 0) {
|
|
170
|
+
console.log(`\n⚠️ Policy Violations: ${result.violations.length}`);
|
|
171
|
+
result.violations.forEach((violation) => {
|
|
172
|
+
console.log(` - ${JSON.stringify(violation)}`);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
console.log(`❌ Error: ${result.error}`);
|
|
177
|
+
|
|
178
|
+
if (result.consoleOutput.length > 0) {
|
|
179
|
+
console.log(`\n📝 Console Output Before Failure:`);
|
|
180
|
+
result.consoleOutput.forEach((entry) => {
|
|
181
|
+
console.log(` ${entry}`);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log("\n" + "=".repeat(60));
|
|
187
|
+
console.log(result.success ? "🎉 TEST PASSED!" : "💥 TEST FAILED!");
|
|
188
|
+
console.log("=".repeat(60));
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error("\n💥 TEST RUNNER ERROR:");
|
|
191
|
+
console.error(error);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Run the test if this file is executed directly
|
|
197
|
+
if (require.main === module) {
|
|
198
|
+
runTest().catch((error) => {
|
|
199
|
+
console.error("Unhandled error:", error);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { runTest, testScript };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ScriptExecutor } from "../../../services/script-execution/ScriptExecutor";
|
|
2
|
+
import { Tools } from "../../../services";
|
|
3
|
+
import { Clients } from "../../../clients";
|
|
4
|
+
import {
|
|
5
|
+
ExecutionRequest,
|
|
6
|
+
ExecutionResult,
|
|
7
|
+
} from "../../../services/script-execution/types";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const executeScript = async (
|
|
11
|
+
{ script, maxToolCalls, maxTokens, maxExecutionTimeMs, maxCostUsd },
|
|
12
|
+
context
|
|
13
|
+
) => {
|
|
14
|
+
try {
|
|
15
|
+
// Create script executor with access to tools and clients
|
|
16
|
+
const executor = new ScriptExecutor(Tools, Clients);
|
|
17
|
+
|
|
18
|
+
// Execute the script
|
|
19
|
+
const result = await executor.execute({
|
|
20
|
+
script,
|
|
21
|
+
quotas: {
|
|
22
|
+
maxToolCalls: maxToolCalls || 50,
|
|
23
|
+
maxTokens: maxTokens || 10000,
|
|
24
|
+
maxExecutionTimeMs: maxExecutionTimeMs || 30000,
|
|
25
|
+
maxCostUsd: maxCostUsd || 1.0,
|
|
26
|
+
maxMemoryMb: 100,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// If there were policy violations, include them in the response
|
|
31
|
+
const violations = result.trace.events
|
|
32
|
+
.filter((e) => e.type.includes("violation") || e.type.includes("error"))
|
|
33
|
+
.map((e) => e.data);
|
|
34
|
+
|
|
35
|
+
// Format the response
|
|
36
|
+
return {
|
|
37
|
+
success: result.success,
|
|
38
|
+
result: result.result,
|
|
39
|
+
error: result.error,
|
|
40
|
+
artifacts: result.artifacts.map((a) => ({
|
|
41
|
+
id: a.id,
|
|
42
|
+
name: a.name,
|
|
43
|
+
type: a.type,
|
|
44
|
+
contentLength: a.content.length,
|
|
45
|
+
createdAt: a.createdAt,
|
|
46
|
+
})),
|
|
47
|
+
consoleOutput: result.consoleOutput,
|
|
48
|
+
metrics: result.trace.metrics,
|
|
49
|
+
violations,
|
|
50
|
+
executionTimeMs: result.trace.endTime - result.trace.startTime,
|
|
51
|
+
quotaUsage: {
|
|
52
|
+
toolCalls: result.trace.metrics.toolCallCount,
|
|
53
|
+
tokens: result.trace.metrics.tokenUsage.total,
|
|
54
|
+
costUsd: result.trace.metrics.costUsd,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: error instanceof Error ? error.message : String(error),
|
|
61
|
+
result: null,
|
|
62
|
+
artifacts: [],
|
|
63
|
+
consoleOutput: [],
|
|
64
|
+
metrics: null,
|
|
65
|
+
violations: [],
|
|
66
|
+
executionTimeMs: 0,
|
|
67
|
+
quotaUsage: {
|
|
68
|
+
toolCalls: 0,
|
|
69
|
+
tokens: 0,
|
|
70
|
+
costUsd: 0,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
package/src/agents/tools/list.ts
CHANGED
|
@@ -7,6 +7,7 @@ import * as github from "./github/definitions";
|
|
|
7
7
|
import * as asana from "./asana/definitions";
|
|
8
8
|
import * as language from "./language/definitions";
|
|
9
9
|
import { googleSearchDefinition } from "./googleSearch";
|
|
10
|
+
import { executeScriptDefinition } from "./executeScript/definition";
|
|
10
11
|
|
|
11
12
|
export const includedTools = [
|
|
12
13
|
{
|
|
@@ -552,7 +553,7 @@ export const includedTools = [
|
|
|
552
553
|
},
|
|
553
554
|
},
|
|
554
555
|
},
|
|
555
|
-
|
|
556
|
+
executeScriptDefinition,
|
|
556
557
|
googleSearchDefinition,
|
|
557
558
|
...asana.definitions,
|
|
558
559
|
...github.definitions,
|
package/src/cli.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Vimmer } from "./agents/vim/vim";
|
|
|
11
11
|
import { Developer } from "./agents/developer/developer";
|
|
12
12
|
import { Tools } from "./services";
|
|
13
13
|
import { includedTools } from "./agents/tools/list";
|
|
14
|
-
import * as allTools from "./agents/tools
|
|
14
|
+
import * as allTools from "./agents/tools";
|
|
15
15
|
import { Mcp } from "./services/Mcp";
|
|
16
16
|
import { login } from "./login";
|
|
17
17
|
import { worker } from "./worker";
|
|
@@ -24,12 +24,8 @@ async function main() {
|
|
|
24
24
|
Agents.registerAgent(Patcher);
|
|
25
25
|
Agents.registerAgent(Developer);
|
|
26
26
|
Agents.loadAgentsFromConfig();
|
|
27
|
-
Tools.addTools(includedTools);
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
.filter(([_, value]) => typeof value === 'function')
|
|
31
|
-
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
|
|
32
|
-
Tools.addFunctions(toolFunctions);
|
|
28
|
+
Tools.defineTools(includedTools, allTools);
|
|
33
29
|
|
|
34
30
|
await Mcp.connectToConfigured(Tools);
|
|
35
31
|
await Clients.registerConfiguredModels();
|
package/src/clients/index.ts
CHANGED
|
@@ -153,13 +153,24 @@ export class AIClient {
|
|
|
153
153
|
);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
providerHasModel(provider: string, model: string): boolean {
|
|
157
157
|
const models = this.clientModels[provider];
|
|
158
158
|
if (!models) return false;
|
|
159
159
|
return models.includes(model);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
findModel(modelPrefix: string) {
|
|
163
|
+
for (const provider of Object.keys(this.clientModels)) {
|
|
164
|
+
const models = this.clientModels[provider];
|
|
165
|
+
const foundModel = models.find((m) => m.startsWith(modelPrefix));
|
|
166
|
+
if (foundModel) {
|
|
167
|
+
return { provider, model: foundModel };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
detectProviderModel(provider: string, model?: string) {
|
|
163
174
|
if (this.providerHasModel(provider, model)) {
|
|
164
175
|
return { provider, model };
|
|
165
176
|
}
|
|
@@ -170,18 +181,21 @@ export class AIClient {
|
|
|
170
181
|
const inferredProvider = split[0];
|
|
171
182
|
const inferredModel = split.slice(1).join("/");
|
|
172
183
|
|
|
184
|
+
// Exact match
|
|
173
185
|
if (this.providerHasModel(inferredProvider, inferredModel)) {
|
|
174
186
|
return { provider: inferredProvider, model: inferredModel };
|
|
175
187
|
}
|
|
176
|
-
}
|
|
177
188
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
189
|
+
// Starts with match
|
|
190
|
+
const foundBySplit = this.findModel(inferredModel);
|
|
191
|
+
if (foundBySplit) {
|
|
192
|
+
return foundBySplit;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
182
195
|
|
|
183
|
-
|
|
184
|
-
|
|
196
|
+
const foundByModel = this.findModel(model);
|
|
197
|
+
if (foundByModel) {
|
|
198
|
+
return foundByModel;
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
return { provider, model };
|
package/src/services/Tools.ts
CHANGED
|
@@ -97,23 +97,39 @@ export class ToolsService {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
addTools(tools: Tool[]) {
|
|
100
|
-
|
|
100
|
+
// Prevent duplicate tool names
|
|
101
|
+
const existingTools = this.getToolNames();
|
|
102
|
+
const filteredTools = tools.filter(
|
|
103
|
+
(tool) => !existingTools.includes(tool.function.name)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
this.tools.push(...filteredTools);
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
addFunctions(fns: { [fnName: string]: (...args: any) => any }) {
|
|
104
110
|
for (const fnName of Object.keys(fns)) {
|
|
111
|
+
if (typeof fns[fnName] !== "function") {
|
|
112
|
+
// Skip non-function entries
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
105
115
|
this.setFunction(fnName, fns[fnName]);
|
|
106
116
|
}
|
|
107
117
|
}
|
|
108
118
|
|
|
119
|
+
defineTools(
|
|
120
|
+
tools: Tool[],
|
|
121
|
+
functions: { [fnName: string]: ((...args: any) => any) | any }
|
|
122
|
+
) {
|
|
123
|
+
this.addTools(tools);
|
|
124
|
+
this.addFunctions(functions);
|
|
125
|
+
}
|
|
126
|
+
|
|
109
127
|
async callTool(toolCall: ToolCall, enabledTools = this.getToolNames()) {
|
|
110
128
|
const functionName = toolCall.function.name;
|
|
111
129
|
const functionArgs = JSON.parse(
|
|
112
130
|
restoreEscapedNewLines(toolCall.function.arguments)
|
|
113
131
|
);
|
|
114
132
|
|
|
115
|
-
console.log(toolCall);
|
|
116
|
-
|
|
117
133
|
try {
|
|
118
134
|
// Check if tool is enabled
|
|
119
135
|
if (!enabledTools.includes(functionName)) {
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { Tools } from "../../services";
|
|
2
|
+
import { Clients } from "../../clients";
|
|
3
|
+
import { ScriptTracer } from "./ScriptTracer";
|
|
4
|
+
import { ScriptPolicyEnforcer } from "./ScriptPolicy";
|
|
5
|
+
import { Artifact, QuotaUsage } from "./types";
|
|
6
|
+
import { Message } from "../../clients/types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Provides the execution context for scripts with controlled access to tools and AI
|
|
10
|
+
*/
|
|
11
|
+
export class SandboxContext {
|
|
12
|
+
private artifacts: Artifact[] = [];
|
|
13
|
+
private consoleOutput: string[] = [];
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private toolsService: typeof Tools = Tools,
|
|
17
|
+
private clients: typeof Clients = Clients,
|
|
18
|
+
private tracer: ScriptTracer,
|
|
19
|
+
private policyEnforcer: ScriptPolicyEnforcer
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Console implementation that captures output
|
|
24
|
+
*/
|
|
25
|
+
console = {
|
|
26
|
+
log: (...args: any[]) => {
|
|
27
|
+
const message = args
|
|
28
|
+
.map((arg) =>
|
|
29
|
+
typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
30
|
+
)
|
|
31
|
+
.join(" ");
|
|
32
|
+
this.consoleOutput.push(`[LOG] ${message}`);
|
|
33
|
+
this.tracer.emitEvent("console_log", { message, args });
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
error: (...args: any[]) => {
|
|
37
|
+
const message = args
|
|
38
|
+
.map((arg) =>
|
|
39
|
+
typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
40
|
+
)
|
|
41
|
+
.join(" ");
|
|
42
|
+
this.consoleOutput.push(`[ERROR] ${message}`);
|
|
43
|
+
this.tracer.emitEvent("console_error", { message, args });
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
warn: (...args: any[]) => {
|
|
47
|
+
const message = args
|
|
48
|
+
.map((arg) =>
|
|
49
|
+
typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
50
|
+
)
|
|
51
|
+
.join(" ");
|
|
52
|
+
this.consoleOutput.push(`[WARN] ${message}`);
|
|
53
|
+
this.tracer.emitEvent("console_warn", { message, args });
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
info: (...args: any[]) => {
|
|
57
|
+
const message = args
|
|
58
|
+
.map((arg) =>
|
|
59
|
+
typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
60
|
+
)
|
|
61
|
+
.join(" ");
|
|
62
|
+
this.consoleOutput.push(`[INFO] ${message}`);
|
|
63
|
+
this.tracer.emitEvent("console_info", { message, args });
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Call a tool through the tools service
|
|
69
|
+
*/
|
|
70
|
+
async callTool(toolName: string, parameters: any): Promise<any> {
|
|
71
|
+
// Check policy first
|
|
72
|
+
if (!this.policyEnforcer.checkToolCall(toolName)) {
|
|
73
|
+
throw new Error(`Tool call '${toolName}' blocked by policy`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.tracer.emitEvent("tool_call_start", {
|
|
77
|
+
toolName,
|
|
78
|
+
parameters: this.sanitizeForLogging(parameters),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Record the tool call
|
|
83
|
+
this.policyEnforcer.recordToolCall();
|
|
84
|
+
|
|
85
|
+
// Create a proper ToolCall object
|
|
86
|
+
const toolCall = {
|
|
87
|
+
id: `script-tool-${Date.now()}-${Math.random()
|
|
88
|
+
.toString(36)
|
|
89
|
+
.substr(2, 9)}`,
|
|
90
|
+
type: "function" as const,
|
|
91
|
+
function: {
|
|
92
|
+
name: toolName,
|
|
93
|
+
arguments: JSON.stringify(parameters),
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Call the actual tool through the Tools service
|
|
98
|
+
const result = await this.toolsService.callTool(toolCall);
|
|
99
|
+
|
|
100
|
+
this.tracer.emitEvent("tool_call_success", {
|
|
101
|
+
toolName,
|
|
102
|
+
result: this.sanitizeForLogging(result),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
this.tracer.emitEvent("tool_call_error", {
|
|
108
|
+
toolName,
|
|
109
|
+
error: error instanceof Error ? error.message : String(error),
|
|
110
|
+
});
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Call LLM through the clients service
|
|
117
|
+
*/
|
|
118
|
+
async llm(
|
|
119
|
+
messages: Message[],
|
|
120
|
+
options: {
|
|
121
|
+
model?: string;
|
|
122
|
+
maxTokens?: number;
|
|
123
|
+
temperature?: number;
|
|
124
|
+
} = {}
|
|
125
|
+
) {
|
|
126
|
+
const estimatedTokens = this.estimateTokens(messages);
|
|
127
|
+
|
|
128
|
+
// Check token quota
|
|
129
|
+
if (!this.policyEnforcer.checkTokenUsage(estimatedTokens)) {
|
|
130
|
+
throw new Error("Token quota would be exceeded");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.tracer.emitEvent("llm_call_start", {
|
|
134
|
+
messageCount: messages.length,
|
|
135
|
+
estimatedTokens,
|
|
136
|
+
model: options.model,
|
|
137
|
+
options: this.sanitizeForLogging(options),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// Record token usage
|
|
142
|
+
this.policyEnforcer.recordTokenUsage(estimatedTokens);
|
|
143
|
+
|
|
144
|
+
// Use the actual Clients service to make LLM calls
|
|
145
|
+
const completionOptions = {
|
|
146
|
+
model: options.model,
|
|
147
|
+
messages,
|
|
148
|
+
max_tokens: options.maxTokens,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Detect provider from model or use default
|
|
152
|
+
const response = await this.clients.createCompletion(
|
|
153
|
+
"",
|
|
154
|
+
completionOptions
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
this.tracer.emitEvent("llm_call_success", {
|
|
158
|
+
model: response.model,
|
|
159
|
+
usage: response.usage,
|
|
160
|
+
usdCost: response.usd_cost,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return response;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
this.tracer.emitEvent("llm_call_error", {
|
|
166
|
+
error: error instanceof Error ? error.message : String(error),
|
|
167
|
+
});
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get current quota usage
|
|
174
|
+
*/
|
|
175
|
+
getQuotaUsage(): QuotaUsage {
|
|
176
|
+
return this.policyEnforcer.getUsage();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create an artifact
|
|
181
|
+
*/
|
|
182
|
+
async createArtifact(
|
|
183
|
+
name: string,
|
|
184
|
+
content: string,
|
|
185
|
+
type: "text" | "json" | "csv" | "html" | "markdown" = "text"
|
|
186
|
+
): Promise<Artifact> {
|
|
187
|
+
const artifact: Artifact = {
|
|
188
|
+
id: `artifact-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
189
|
+
name,
|
|
190
|
+
type,
|
|
191
|
+
content,
|
|
192
|
+
createdAt: new Date().toISOString(),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
this.artifacts.push(artifact);
|
|
196
|
+
|
|
197
|
+
this.tracer.emitEvent("artifact_created", {
|
|
198
|
+
artifactId: artifact.id,
|
|
199
|
+
name,
|
|
200
|
+
type,
|
|
201
|
+
contentLength: content.length,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return artifact;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async sleep(ms: number): Promise<void> {
|
|
208
|
+
if (typeof ms !== "number" || ms < 0 || ms > 2000) {
|
|
209
|
+
throw new Error("Invalid sleep duration");
|
|
210
|
+
}
|
|
211
|
+
await new Promise((res) => setTimeout(res, ms));
|
|
212
|
+
this.tracer.emitEvent("sleep", { durationMs: ms });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get all created artifacts
|
|
217
|
+
*/
|
|
218
|
+
getArtifacts(): Artifact[] {
|
|
219
|
+
return [...this.artifacts];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get console output
|
|
224
|
+
*/
|
|
225
|
+
getConsoleOutput(): string[] {
|
|
226
|
+
return [...this.consoleOutput];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Estimate tokens for text (rough approximation)
|
|
231
|
+
*/
|
|
232
|
+
private estimateTokens(messages: any[]): number {
|
|
233
|
+
let totalText = "";
|
|
234
|
+
for (const message of messages) {
|
|
235
|
+
if (typeof message === "string") {
|
|
236
|
+
totalText += message;
|
|
237
|
+
} else if (message && typeof message.content === "string") {
|
|
238
|
+
totalText += message.content;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Rough estimation: ~4 characters per token
|
|
242
|
+
return Math.ceil(totalText.length / 4);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Sanitize data for logging (remove sensitive information)
|
|
247
|
+
*/
|
|
248
|
+
private sanitizeForLogging(data: any): any {
|
|
249
|
+
if (data === null || data === undefined) {
|
|
250
|
+
return data;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (typeof data === "string") {
|
|
254
|
+
// Truncate very long strings
|
|
255
|
+
return data.length > 500 ? data.substring(0, 500) + "..." : data;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (typeof data === "object") {
|
|
259
|
+
const sanitized: any = {};
|
|
260
|
+
for (const [key, value] of Object.entries(data)) {
|
|
261
|
+
// Skip potentially sensitive keys
|
|
262
|
+
if (
|
|
263
|
+
key.toLowerCase().includes("password") ||
|
|
264
|
+
key.toLowerCase().includes("token") ||
|
|
265
|
+
key.toLowerCase().includes("secret") ||
|
|
266
|
+
key.toLowerCase().includes("key")
|
|
267
|
+
) {
|
|
268
|
+
sanitized[key] = "[REDACTED]";
|
|
269
|
+
} else {
|
|
270
|
+
sanitized[key] = this.sanitizeForLogging(value);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return sanitized;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return data;
|
|
277
|
+
}
|
|
278
|
+
}
|