@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.
Files changed (69) hide show
  1. package/package.json +3 -1
  2. package/src/agents/tools/executeScript/README.md +78 -0
  3. package/src/agents/tools/executeScript/definition.ts +73 -0
  4. package/src/agents/tools/executeScript/examples/quick-test.ts +80 -0
  5. package/src/agents/tools/executeScript/examples/serialization-test.ts +309 -0
  6. package/src/agents/tools/executeScript/examples/test-runner.ts +204 -0
  7. package/src/agents/tools/executeScript/index.ts +74 -0
  8. package/src/agents/tools/index.ts +1 -0
  9. package/src/agents/tools/list.ts +2 -1
  10. package/src/cli.ts +2 -6
  11. package/src/clients/index.ts +23 -9
  12. package/src/services/Tools.ts +19 -3
  13. package/src/services/script-execution/SandboxContext.ts +278 -0
  14. package/src/services/script-execution/ScriptExecutor.ts +337 -0
  15. package/src/services/script-execution/ScriptPolicy.ts +236 -0
  16. package/src/services/script-execution/ScriptTracer.ts +249 -0
  17. package/src/services/script-execution/types.ts +134 -0
  18. package/ts_build/src/agents/tools/executeScript/definition.d.ts +2 -0
  19. package/ts_build/src/agents/tools/executeScript/definition.js +70 -0
  20. package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -0
  21. package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +3 -0
  22. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +68 -0
  23. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +1 -0
  24. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +15 -0
  25. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +267 -0
  26. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +1 -0
  27. package/ts_build/src/agents/tools/executeScript/examples/simple-example.d.ts +20 -0
  28. package/ts_build/src/agents/tools/executeScript/examples/simple-example.js +35 -0
  29. package/ts_build/src/agents/tools/executeScript/examples/simple-example.js.map +1 -0
  30. package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +4 -0
  31. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +202 -0
  32. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +1 -0
  33. package/ts_build/src/agents/tools/executeScript/handler.d.ts +27 -0
  34. package/ts_build/src/agents/tools/executeScript/handler.js +64 -0
  35. package/ts_build/src/agents/tools/executeScript/handler.js.map +1 -0
  36. package/ts_build/src/agents/tools/executeScript/index.d.ts +27 -0
  37. package/ts_build/src/agents/tools/executeScript/index.js +64 -0
  38. package/ts_build/src/agents/tools/executeScript/index.js.map +1 -0
  39. package/ts_build/src/agents/tools/executeScript.d.ts +29 -0
  40. package/ts_build/src/agents/tools/executeScript.js +124 -0
  41. package/ts_build/src/agents/tools/executeScript.js.map +1 -0
  42. package/ts_build/src/agents/tools/index.d.ts +1 -0
  43. package/ts_build/src/agents/tools/index.js +1 -0
  44. package/ts_build/src/agents/tools/index.js.map +1 -1
  45. package/ts_build/src/agents/tools/list.js +2 -0
  46. package/ts_build/src/agents/tools/list.js.map +1 -1
  47. package/ts_build/src/cli.js +2 -6
  48. package/ts_build/src/cli.js.map +1 -1
  49. package/ts_build/src/clients/index.d.ts +9 -2
  50. package/ts_build/src/clients/index.js +17 -4
  51. package/ts_build/src/clients/index.js.map +1 -1
  52. package/ts_build/src/services/Tools.d.ts +3 -0
  53. package/ts_build/src/services/Tools.js +10 -2
  54. package/ts_build/src/services/Tools.js.map +1 -1
  55. package/ts_build/src/services/script-execution/SandboxContext.d.ts +34 -0
  56. package/ts_build/src/services/script-execution/SandboxContext.js +188 -0
  57. package/ts_build/src/services/script-execution/SandboxContext.js.map +1 -0
  58. package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +17 -0
  59. package/ts_build/src/services/script-execution/ScriptExecutor.js +207 -0
  60. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -0
  61. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +27 -0
  62. package/ts_build/src/services/script-execution/ScriptPolicy.js +150 -0
  63. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -0
  64. package/ts_build/src/services/script-execution/ScriptTracer.d.ts +19 -0
  65. package/ts_build/src/services/script-execution/ScriptTracer.js +186 -0
  66. package/ts_build/src/services/script-execution/ScriptTracer.js.map +1 -0
  67. package/ts_build/src/services/script-execution/types.d.ts +108 -0
  68. package/ts_build/src/services/script-execution/types.js +3 -0
  69. 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
+ };
@@ -23,3 +23,4 @@ export * from "./aiClient";
23
23
  export * from "./googleSearch";
24
24
  export * from "./loadWebpage";
25
25
  export * from "./stringReplace";
26
+ export * from "./executeScript";
@@ -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/index";
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
- const toolFunctions = Object.entries(allTools)
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();
@@ -153,13 +153,24 @@ export class AIClient {
153
153
  );
154
154
  }
155
155
 
156
- private providerHasModel(provider: string, model: string): boolean {
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
- private detectProviderModel(provider: string, model?: string) {
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
- const providers = Object.keys(this.clientModels);
179
- const foundProvider = providers.find((p) =>
180
- this.providerHasModel(p, model)
181
- );
189
+ // Starts with match
190
+ const foundBySplit = this.findModel(inferredModel);
191
+ if (foundBySplit) {
192
+ return foundBySplit;
193
+ }
194
+ }
182
195
 
183
- if (foundProvider) {
184
- return { provider: foundProvider, model };
196
+ const foundByModel = this.findModel(model);
197
+ if (foundByModel) {
198
+ return foundByModel;
185
199
  }
186
200
 
187
201
  return { provider, model };
@@ -97,23 +97,39 @@ export class ToolsService {
97
97
  }
98
98
 
99
99
  addTools(tools: Tool[]) {
100
- this.tools.push(...tools);
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
+ }