@vibe-lang/runtime 0.2.5
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 +46 -0
- package/src/ast/index.ts +375 -0
- package/src/ast.ts +2 -0
- package/src/debug/advanced-features.ts +482 -0
- package/src/debug/bun-inspector.ts +424 -0
- package/src/debug/handoff-manager.ts +283 -0
- package/src/debug/index.ts +150 -0
- package/src/debug/runner.ts +365 -0
- package/src/debug/server.ts +565 -0
- package/src/debug/stack-merger.ts +267 -0
- package/src/debug/state.ts +581 -0
- package/src/debug/test/advanced-features.test.ts +300 -0
- package/src/debug/test/e2e.test.ts +218 -0
- package/src/debug/test/handoff-manager.test.ts +256 -0
- package/src/debug/test/runner.test.ts +256 -0
- package/src/debug/test/stack-merger.test.ts +163 -0
- package/src/debug/test/state.test.ts +400 -0
- package/src/debug/test/ts-debug-integration.test.ts +374 -0
- package/src/debug/test/ts-import-tracker.test.ts +125 -0
- package/src/debug/test/ts-source-map.test.ts +169 -0
- package/src/debug/ts-import-tracker.ts +151 -0
- package/src/debug/ts-source-map.ts +171 -0
- package/src/errors/index.ts +124 -0
- package/src/index.ts +358 -0
- package/src/lexer/index.ts +348 -0
- package/src/lexer.ts +2 -0
- package/src/parser/index.ts +792 -0
- package/src/parser/parse.ts +45 -0
- package/src/parser/test/async.test.ts +248 -0
- package/src/parser/test/destructuring.test.ts +167 -0
- package/src/parser/test/do-expression.test.ts +486 -0
- package/src/parser/test/errors/do-expression.test.ts +95 -0
- package/src/parser/test/errors/error-locations.test.ts +230 -0
- package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
- package/src/parser/test/errors/missing-tokens.test.ts +126 -0
- package/src/parser/test/errors/model-declaration.test.ts +185 -0
- package/src/parser/test/errors/nested-blocks.test.ts +226 -0
- package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
- package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
- package/src/parser/test/import-export.test.ts +143 -0
- package/src/parser/test/literals.test.ts +404 -0
- package/src/parser/test/model-declaration.test.ts +161 -0
- package/src/parser/test/nested-blocks.test.ts +402 -0
- package/src/parser/test/parser.test.ts +743 -0
- package/src/parser/test/private.test.ts +136 -0
- package/src/parser/test/template-literal.test.ts +127 -0
- package/src/parser/test/tool-declaration.test.ts +302 -0
- package/src/parser/test/ts-block.test.ts +252 -0
- package/src/parser/test/type-annotations.test.ts +254 -0
- package/src/parser/visitor/helpers.ts +330 -0
- package/src/parser/visitor.ts +794 -0
- package/src/parser.ts +2 -0
- package/src/runtime/ai/cache-chunking.test.ts +69 -0
- package/src/runtime/ai/cache-chunking.ts +73 -0
- package/src/runtime/ai/client.ts +109 -0
- package/src/runtime/ai/context.ts +168 -0
- package/src/runtime/ai/formatters.ts +316 -0
- package/src/runtime/ai/index.ts +38 -0
- package/src/runtime/ai/language-ref.ts +38 -0
- package/src/runtime/ai/providers/anthropic.ts +253 -0
- package/src/runtime/ai/providers/google.ts +201 -0
- package/src/runtime/ai/providers/openai.ts +156 -0
- package/src/runtime/ai/retry.ts +100 -0
- package/src/runtime/ai/return-tools.ts +301 -0
- package/src/runtime/ai/test/client.test.ts +83 -0
- package/src/runtime/ai/test/formatters.test.ts +485 -0
- package/src/runtime/ai/test/retry.test.ts +137 -0
- package/src/runtime/ai/test/return-tools.test.ts +450 -0
- package/src/runtime/ai/test/tool-loop.test.ts +319 -0
- package/src/runtime/ai/test/tool-schema.test.ts +241 -0
- package/src/runtime/ai/tool-loop.ts +203 -0
- package/src/runtime/ai/tool-schema.ts +151 -0
- package/src/runtime/ai/types.ts +113 -0
- package/src/runtime/ai-logger.ts +255 -0
- package/src/runtime/ai-provider.ts +347 -0
- package/src/runtime/async/dependencies.ts +276 -0
- package/src/runtime/async/executor.ts +293 -0
- package/src/runtime/async/index.ts +43 -0
- package/src/runtime/async/scheduling.ts +163 -0
- package/src/runtime/async/test/dependencies.test.ts +284 -0
- package/src/runtime/async/test/executor.test.ts +388 -0
- package/src/runtime/context.ts +357 -0
- package/src/runtime/exec/ai.ts +139 -0
- package/src/runtime/exec/expressions.ts +475 -0
- package/src/runtime/exec/frames.ts +26 -0
- package/src/runtime/exec/functions.ts +305 -0
- package/src/runtime/exec/interpolation.ts +312 -0
- package/src/runtime/exec/statements.ts +604 -0
- package/src/runtime/exec/tools.ts +129 -0
- package/src/runtime/exec/typescript.ts +215 -0
- package/src/runtime/exec/variables.ts +279 -0
- package/src/runtime/index.ts +975 -0
- package/src/runtime/modules.ts +452 -0
- package/src/runtime/serialize.ts +103 -0
- package/src/runtime/state.ts +489 -0
- package/src/runtime/stdlib/core.ts +45 -0
- package/src/runtime/stdlib/directory.test.ts +156 -0
- package/src/runtime/stdlib/edit.test.ts +154 -0
- package/src/runtime/stdlib/fastEdit.test.ts +201 -0
- package/src/runtime/stdlib/glob.test.ts +106 -0
- package/src/runtime/stdlib/grep.test.ts +144 -0
- package/src/runtime/stdlib/index.ts +16 -0
- package/src/runtime/stdlib/readFile.test.ts +123 -0
- package/src/runtime/stdlib/tools/index.ts +707 -0
- package/src/runtime/stdlib/writeFile.test.ts +157 -0
- package/src/runtime/step.ts +969 -0
- package/src/runtime/test/ai-context.test.ts +1086 -0
- package/src/runtime/test/ai-result-object.test.ts +419 -0
- package/src/runtime/test/ai-tool-flow.test.ts +859 -0
- package/src/runtime/test/async-execution-order.test.ts +618 -0
- package/src/runtime/test/async-execution.test.ts +344 -0
- package/src/runtime/test/async-nested.test.ts +660 -0
- package/src/runtime/test/async-parallel-timing.test.ts +546 -0
- package/src/runtime/test/basic1.test.ts +154 -0
- package/src/runtime/test/binary-operators.test.ts +431 -0
- package/src/runtime/test/break-statement.test.ts +257 -0
- package/src/runtime/test/context-modes.test.ts +650 -0
- package/src/runtime/test/context.test.ts +466 -0
- package/src/runtime/test/core-functions.test.ts +228 -0
- package/src/runtime/test/e2e.test.ts +88 -0
- package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
- package/src/runtime/test/error-locations/main-error.vibe +4 -0
- package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
- package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
- package/src/runtime/test/for-in.test.ts +312 -0
- package/src/runtime/test/helpers.ts +69 -0
- package/src/runtime/test/imports.test.ts +334 -0
- package/src/runtime/test/json-expressions.test.ts +232 -0
- package/src/runtime/test/literals.test.ts +372 -0
- package/src/runtime/test/logical-indexing.test.ts +478 -0
- package/src/runtime/test/member-methods.test.ts +324 -0
- package/src/runtime/test/model-config.test.ts +338 -0
- package/src/runtime/test/null-handling.test.ts +342 -0
- package/src/runtime/test/private-visibility.test.ts +332 -0
- package/src/runtime/test/runtime-state.test.ts +514 -0
- package/src/runtime/test/scoping.test.ts +370 -0
- package/src/runtime/test/string-interpolation.test.ts +354 -0
- package/src/runtime/test/template-literal.test.ts +181 -0
- package/src/runtime/test/tool-execution.test.ts +467 -0
- package/src/runtime/test/tool-schema-generation.test.ts +477 -0
- package/src/runtime/test/tostring.test.ts +210 -0
- package/src/runtime/test/ts-block.test.ts +594 -0
- package/src/runtime/test/ts-error-location.test.ts +231 -0
- package/src/runtime/test/types.test.ts +732 -0
- package/src/runtime/test/verbose-logger.test.ts +710 -0
- package/src/runtime/test/vibe-expression.test.ts +54 -0
- package/src/runtime/test/vibe-value-errors.test.ts +541 -0
- package/src/runtime/test/while.test.ts +232 -0
- package/src/runtime/tools/builtin.ts +30 -0
- package/src/runtime/tools/directory-tools.ts +70 -0
- package/src/runtime/tools/file-tools.ts +228 -0
- package/src/runtime/tools/index.ts +5 -0
- package/src/runtime/tools/registry.ts +48 -0
- package/src/runtime/tools/search-tools.ts +134 -0
- package/src/runtime/tools/security.ts +36 -0
- package/src/runtime/tools/system-tools.ts +312 -0
- package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
- package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
- package/src/runtime/tools/test/registry.test.ts +713 -0
- package/src/runtime/tools/test/security.test.ts +86 -0
- package/src/runtime/tools/test/system-tools.test.ts +679 -0
- package/src/runtime/tools/test/ts-schema.test.ts +357 -0
- package/src/runtime/tools/ts-schema.ts +341 -0
- package/src/runtime/tools/types.ts +89 -0
- package/src/runtime/tools/utility-tools.ts +198 -0
- package/src/runtime/ts-eval.ts +126 -0
- package/src/runtime/types.ts +797 -0
- package/src/runtime/validation.ts +160 -0
- package/src/runtime/verbose-logger.ts +459 -0
- package/src/runtime.ts +2 -0
- package/src/semantic/analyzer-context.ts +62 -0
- package/src/semantic/analyzer-validators.ts +575 -0
- package/src/semantic/analyzer-visitors.ts +534 -0
- package/src/semantic/analyzer.ts +83 -0
- package/src/semantic/index.ts +11 -0
- package/src/semantic/symbol-table.ts +58 -0
- package/src/semantic/test/async-validation.test.ts +301 -0
- package/src/semantic/test/compress-validation.test.ts +179 -0
- package/src/semantic/test/const-reassignment.test.ts +111 -0
- package/src/semantic/test/control-flow.test.ts +346 -0
- package/src/semantic/test/destructuring.test.ts +185 -0
- package/src/semantic/test/duplicate-declarations.test.ts +168 -0
- package/src/semantic/test/export-validation.test.ts +111 -0
- package/src/semantic/test/fixtures/math.ts +31 -0
- package/src/semantic/test/imports.test.ts +148 -0
- package/src/semantic/test/json-type.test.ts +68 -0
- package/src/semantic/test/literals.test.ts +127 -0
- package/src/semantic/test/model-validation.test.ts +179 -0
- package/src/semantic/test/prompt-validation.test.ts +343 -0
- package/src/semantic/test/scoping.test.ts +312 -0
- package/src/semantic/test/tool-validation.test.ts +306 -0
- package/src/semantic/test/ts-type-checking.test.ts +563 -0
- package/src/semantic/test/type-constraints.test.ts +111 -0
- package/src/semantic/test/type-inference.test.ts +87 -0
- package/src/semantic/test/type-validation.test.ts +552 -0
- package/src/semantic/test/undefined-variables.test.ts +163 -0
- package/src/semantic/ts-block-checker.ts +204 -0
- package/src/semantic/ts-signatures.ts +194 -0
- package/src/semantic/ts-types.ts +170 -0
- package/src/semantic/types.ts +58 -0
- package/tests/fixtures/conditional-logic.vibe +14 -0
- package/tests/fixtures/function-call.vibe +16 -0
- package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
- package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
- package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
- package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
- package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
- package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
- package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
- package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
- package/tests/fixtures/imports/nested-import/main.vibe +3 -0
- package/tests/fixtures/imports/nested-import/utils.ts +3 -0
- package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
- package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
- package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
- package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
- package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
- package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
- package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
- package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
- package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
- package/tests/fixtures/imports/ts-import/main.vibe +4 -0
- package/tests/fixtures/imports/ts-import/math.ts +9 -0
- package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/data.ts +10 -0
- package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
- package/tests/fixtures/multiple-ai-calls.vibe +10 -0
- package/tests/fixtures/simple-greeting.vibe +6 -0
- package/tests/fixtures/template-literals.vibe +11 -0
- package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
- package/tests/integration/basic-ai/basic-ai.vibe +12 -0
- package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
- package/tests/integration/bug-fix/buggy-code.ts +22 -0
- package/tests/integration/bug-fix/fix-bug.vibe +21 -0
- package/tests/integration/compress/compress.integration.test.ts +206 -0
- package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
- package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
- package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
- package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
- package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
- package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
- package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
- package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { parse } from '../../parser/parse';
|
|
3
|
+
import { Runtime, type AIProvider, type AIExecutionResult } from '../index';
|
|
4
|
+
import { executeWithTools, type ToolRoundResult } from '../ai/tool-loop';
|
|
5
|
+
import type { AIRequest, AIResponse } from '../ai/types';
|
|
6
|
+
import type { VibeToolValue, ToolSchema } from '../tools/types';
|
|
7
|
+
import { formatContextForAI, buildLocalContext } from '../context';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tool calling flow tests with mocked AI responses.
|
|
11
|
+
*
|
|
12
|
+
* These tests verify the tool calling flow works correctly when an AI
|
|
13
|
+
* response includes tool calls. The AI provider is mocked, but real tools
|
|
14
|
+
* are registered and executed.
|
|
15
|
+
*
|
|
16
|
+
* The flow being tested:
|
|
17
|
+
* 1. `do` command is executed
|
|
18
|
+
* 2. Mocked AI provider returns tool calls
|
|
19
|
+
* 3. Registered tools are actually executed
|
|
20
|
+
* 4. Tool results are passed back to AI (mocked followup)
|
|
21
|
+
* 5. Final response is returned to variable
|
|
22
|
+
* 6. Context shows tool call history
|
|
23
|
+
*
|
|
24
|
+
* For tests that call real AI APIs, see tests/integration/
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// Track tool executions for verification
|
|
28
|
+
interface ToolExecution {
|
|
29
|
+
name: string;
|
|
30
|
+
args: Record<string, unknown>;
|
|
31
|
+
result: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create an AI provider that simulates tool calling by using executeWithTools
|
|
36
|
+
* with a controllable mock backend.
|
|
37
|
+
*/
|
|
38
|
+
function createToolCallingAIProvider(
|
|
39
|
+
mockResponses: AIResponse[],
|
|
40
|
+
toolExecutions: ToolExecution[],
|
|
41
|
+
testTools: VibeToolValue[]
|
|
42
|
+
): AIProvider {
|
|
43
|
+
let callIndex = 0;
|
|
44
|
+
|
|
45
|
+
// Wrap tool executors to track executions
|
|
46
|
+
const trackedTools: VibeToolValue[] = testTools.map(tool => ({
|
|
47
|
+
...tool,
|
|
48
|
+
executor: async (args: Record<string, unknown>) => {
|
|
49
|
+
const result = await tool.executor(args, { rootDir: process.cwd() });
|
|
50
|
+
toolExecutions.push({ name: tool.name, args, result });
|
|
51
|
+
return result;
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
async execute(prompt: string): Promise<AIExecutionResult> {
|
|
57
|
+
// Mock provider that returns responses in sequence
|
|
58
|
+
const mockProviderExecutor = async (request: AIRequest): Promise<AIResponse> => {
|
|
59
|
+
const response = mockResponses[callIndex] ?? mockResponses[mockResponses.length - 1];
|
|
60
|
+
callIndex++;
|
|
61
|
+
return response;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Use executeWithTools to actually execute tools
|
|
65
|
+
const { response, rounds } = await executeWithTools(
|
|
66
|
+
{
|
|
67
|
+
prompt,
|
|
68
|
+
model: { name: 'mock', apiKey: 'mock', url: null },
|
|
69
|
+
operationType: 'do',
|
|
70
|
+
contextText: '',
|
|
71
|
+
targetType: null,
|
|
72
|
+
},
|
|
73
|
+
trackedTools,
|
|
74
|
+
process.cwd(), // Test rootDir for path sandboxing
|
|
75
|
+
mockProviderExecutor,
|
|
76
|
+
{ maxRounds: 10 }
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
value: response.parsedValue ?? response.content,
|
|
81
|
+
usage: response.usage,
|
|
82
|
+
toolRounds: rounds.length > 0 ? rounds : undefined,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async generateCode(prompt: string): Promise<AIExecutionResult> {
|
|
87
|
+
return { value: `// Generated for: ${prompt}` };
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
async askUser(prompt: string): Promise<string> {
|
|
91
|
+
throw new Error('User input not implemented in test');
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Simple test tools that don't have side effects
|
|
97
|
+
function createTestTools(): VibeToolValue[] {
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
__vibeTool: true,
|
|
101
|
+
name: 'add',
|
|
102
|
+
schema: {
|
|
103
|
+
name: 'add',
|
|
104
|
+
description: 'Add two numbers',
|
|
105
|
+
parameters: [
|
|
106
|
+
{ name: 'a', type: { type: 'number' }, required: true },
|
|
107
|
+
{ name: 'b', type: { type: 'number' }, required: true },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
executor: async (args) => (args.a as number) + (args.b as number),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
__vibeTool: true,
|
|
114
|
+
name: 'multiply',
|
|
115
|
+
schema: {
|
|
116
|
+
name: 'multiply',
|
|
117
|
+
description: 'Multiply two numbers',
|
|
118
|
+
parameters: [
|
|
119
|
+
{ name: 'a', type: { type: 'number' }, required: true },
|
|
120
|
+
{ name: 'b', type: { type: 'number' }, required: true },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
executor: async (args) => (args.a as number) * (args.b as number),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
__vibeTool: true,
|
|
127
|
+
name: 'getWeather',
|
|
128
|
+
schema: {
|
|
129
|
+
name: 'getWeather',
|
|
130
|
+
description: 'Get weather for a city',
|
|
131
|
+
parameters: [
|
|
132
|
+
{ name: 'city', type: { type: 'string' }, required: true },
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
executor: async (args) => {
|
|
136
|
+
const city = args.city as string;
|
|
137
|
+
// Return mock weather data based on city
|
|
138
|
+
const weatherData: Record<string, { temp: number; condition: string }> = {
|
|
139
|
+
'Seattle': { temp: 55, condition: 'rainy' },
|
|
140
|
+
'San Francisco': { temp: 68, condition: 'sunny' },
|
|
141
|
+
'New York': { temp: 45, condition: 'cloudy' },
|
|
142
|
+
};
|
|
143
|
+
return weatherData[city] ?? { temp: 70, condition: 'unknown' };
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
describe('AI Tool Calling Flow', () => {
|
|
150
|
+
test('single tool call is executed and result returned to AI', async () => {
|
|
151
|
+
const ast = parse(`
|
|
152
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
153
|
+
let result: text = vibe "Calculate 5 + 3" m default
|
|
154
|
+
`);
|
|
155
|
+
|
|
156
|
+
const toolExecutions: ToolExecution[] = [];
|
|
157
|
+
const testTools = createTestTools();
|
|
158
|
+
|
|
159
|
+
// Mock responses: first returns tool call, second returns final answer
|
|
160
|
+
const mockResponses: AIResponse[] = [
|
|
161
|
+
{
|
|
162
|
+
content: '',
|
|
163
|
+
parsedValue: '',
|
|
164
|
+
toolCalls: [
|
|
165
|
+
{ id: 'call_1', toolName: 'add', args: { a: 5, b: 3 } },
|
|
166
|
+
],
|
|
167
|
+
stopReason: 'tool_use',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
content: 'The result of 5 + 3 is 8',
|
|
171
|
+
parsedValue: 'The result of 5 + 3 is 8',
|
|
172
|
+
stopReason: 'end',
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
177
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
178
|
+
const result = await runtime.run();
|
|
179
|
+
|
|
180
|
+
// Verify tool was actually executed
|
|
181
|
+
expect(toolExecutions).toHaveLength(1);
|
|
182
|
+
expect(toolExecutions[0]).toEqual({
|
|
183
|
+
name: 'add',
|
|
184
|
+
args: { a: 5, b: 3 },
|
|
185
|
+
result: 8,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Verify final result
|
|
189
|
+
expect(runtime.getValue('result')).toBe('The result of 5 + 3 is 8');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('multiple tool calls in single response are all executed', async () => {
|
|
193
|
+
const ast = parse(`
|
|
194
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
195
|
+
let weather: text = vibe "What's the weather in Seattle and SF?" m default
|
|
196
|
+
`);
|
|
197
|
+
|
|
198
|
+
const toolExecutions: ToolExecution[] = [];
|
|
199
|
+
const testTools = createTestTools();
|
|
200
|
+
|
|
201
|
+
// Mock responses: first returns two tool calls, second returns final answer
|
|
202
|
+
const mockResponses: AIResponse[] = [
|
|
203
|
+
{
|
|
204
|
+
content: '',
|
|
205
|
+
parsedValue: '',
|
|
206
|
+
toolCalls: [
|
|
207
|
+
{ id: 'call_1', toolName: 'getWeather', args: { city: 'Seattle' } },
|
|
208
|
+
{ id: 'call_2', toolName: 'getWeather', args: { city: 'San Francisco' } },
|
|
209
|
+
],
|
|
210
|
+
stopReason: 'tool_use',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
content: 'Seattle is 55°F and rainy, San Francisco is 68°F and sunny.',
|
|
214
|
+
parsedValue: 'Seattle is 55°F and rainy, San Francisco is 68°F and sunny.',
|
|
215
|
+
stopReason: 'end',
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
220
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
221
|
+
await runtime.run();
|
|
222
|
+
|
|
223
|
+
// Verify both tools were executed
|
|
224
|
+
expect(toolExecutions).toHaveLength(2);
|
|
225
|
+
expect(toolExecutions[0]).toEqual({
|
|
226
|
+
name: 'getWeather',
|
|
227
|
+
args: { city: 'Seattle' },
|
|
228
|
+
result: { temp: 55, condition: 'rainy' },
|
|
229
|
+
});
|
|
230
|
+
expect(toolExecutions[1]).toEqual({
|
|
231
|
+
name: 'getWeather',
|
|
232
|
+
args: { city: 'San Francisco' },
|
|
233
|
+
result: { temp: 68, condition: 'sunny' },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Verify final result
|
|
237
|
+
expect(runtime.getValue('weather')).toBe('Seattle is 55°F and rainy, San Francisco is 68°F and sunny.');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('multiple rounds of tool calls are executed sequentially', async () => {
|
|
241
|
+
const ast = parse(`
|
|
242
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
243
|
+
let result: text = vibe "Calculate (2+3) * 4" m default
|
|
244
|
+
`);
|
|
245
|
+
|
|
246
|
+
const toolExecutions: ToolExecution[] = [];
|
|
247
|
+
const testTools = createTestTools();
|
|
248
|
+
|
|
249
|
+
// Mock responses: first round adds, second round multiplies, third is final
|
|
250
|
+
const mockResponses: AIResponse[] = [
|
|
251
|
+
{
|
|
252
|
+
content: '',
|
|
253
|
+
parsedValue: '',
|
|
254
|
+
toolCalls: [
|
|
255
|
+
{ id: 'call_1', toolName: 'add', args: { a: 2, b: 3 } },
|
|
256
|
+
],
|
|
257
|
+
stopReason: 'tool_use',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
content: '',
|
|
261
|
+
parsedValue: '',
|
|
262
|
+
toolCalls: [
|
|
263
|
+
{ id: 'call_2', toolName: 'multiply', args: { a: 5, b: 4 } },
|
|
264
|
+
],
|
|
265
|
+
stopReason: 'tool_use',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
content: 'The result of (2+3) * 4 is 20',
|
|
269
|
+
parsedValue: 'The result of (2+3) * 4 is 20',
|
|
270
|
+
stopReason: 'end',
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
275
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
276
|
+
await runtime.run();
|
|
277
|
+
|
|
278
|
+
// Verify tools were executed in order
|
|
279
|
+
expect(toolExecutions).toHaveLength(2);
|
|
280
|
+
expect(toolExecutions[0]).toEqual({
|
|
281
|
+
name: 'add',
|
|
282
|
+
args: { a: 2, b: 3 },
|
|
283
|
+
result: 5,
|
|
284
|
+
});
|
|
285
|
+
expect(toolExecutions[1]).toEqual({
|
|
286
|
+
name: 'multiply',
|
|
287
|
+
args: { a: 5, b: 4 },
|
|
288
|
+
result: 20,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Verify final result
|
|
292
|
+
expect(runtime.getValue('result')).toBe('The result of (2+3) * 4 is 20');
|
|
293
|
+
|
|
294
|
+
// Verify formatted context shows all tool calls in order
|
|
295
|
+
const state = runtime.getState();
|
|
296
|
+
const context = buildLocalContext(state);
|
|
297
|
+
const formatted = formatContextForAI(context, { includeInstructions: false });
|
|
298
|
+
|
|
299
|
+
expect(formatted.text).toBe(
|
|
300
|
+
` <entry> (current scope)
|
|
301
|
+
--> vibe: "Calculate (2+3) * 4"
|
|
302
|
+
[tool] add({"a":2,"b":3})
|
|
303
|
+
[result] 5
|
|
304
|
+
[tool] multiply({"a":5,"b":4})
|
|
305
|
+
[result] 20
|
|
306
|
+
<-- result (text): The result of (2+3) * 4 is 20`
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('tool calls appear in context for subsequent AI calls', async () => {
|
|
311
|
+
const ast = parse(`
|
|
312
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
313
|
+
let first: text = vibe "What's 2 + 2?" m default
|
|
314
|
+
let second: text = vibe "What was the previous result?" m default
|
|
315
|
+
`);
|
|
316
|
+
|
|
317
|
+
const toolExecutions: ToolExecution[] = [];
|
|
318
|
+
const testTools = createTestTools();
|
|
319
|
+
|
|
320
|
+
// We need to track if context includes tool calls from first do
|
|
321
|
+
let secondCallContext = '';
|
|
322
|
+
|
|
323
|
+
const mockResponses: AIResponse[] = [
|
|
324
|
+
// First do call - returns tool call
|
|
325
|
+
{
|
|
326
|
+
content: '',
|
|
327
|
+
parsedValue: '',
|
|
328
|
+
toolCalls: [
|
|
329
|
+
{ id: 'call_1', toolName: 'add', args: { a: 2, b: 2 } },
|
|
330
|
+
],
|
|
331
|
+
stopReason: 'tool_use',
|
|
332
|
+
},
|
|
333
|
+
// First do call - final response
|
|
334
|
+
{
|
|
335
|
+
content: 'The result is 4',
|
|
336
|
+
parsedValue: 'The result is 4',
|
|
337
|
+
stopReason: 'end',
|
|
338
|
+
},
|
|
339
|
+
// Second do call - no tool calls
|
|
340
|
+
{
|
|
341
|
+
content: 'The previous result was 4',
|
|
342
|
+
parsedValue: 'The previous result was 4',
|
|
343
|
+
stopReason: 'end',
|
|
344
|
+
},
|
|
345
|
+
];
|
|
346
|
+
|
|
347
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
348
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
349
|
+
await runtime.run();
|
|
350
|
+
|
|
351
|
+
// Verify tool was executed
|
|
352
|
+
expect(toolExecutions).toHaveLength(1);
|
|
353
|
+
expect(toolExecutions[0].result).toBe(4);
|
|
354
|
+
|
|
355
|
+
// Verify both results assigned correctly
|
|
356
|
+
expect(runtime.getValue('first')).toBe('The result is 4');
|
|
357
|
+
expect(runtime.getValue('second')).toBe('The previous result was 4');
|
|
358
|
+
|
|
359
|
+
// Get state and check that tool calls are embedded in prompt entries
|
|
360
|
+
const state = runtime.getState();
|
|
361
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
362
|
+
|
|
363
|
+
// Find prompt entries that have toolCalls
|
|
364
|
+
const promptEntries = frame.orderedEntries.filter(e => e.kind === 'prompt');
|
|
365
|
+
expect(promptEntries).toHaveLength(2); // first and second do calls
|
|
366
|
+
|
|
367
|
+
// First prompt should have the tool call embedded
|
|
368
|
+
const firstPrompt = promptEntries[0];
|
|
369
|
+
expect(firstPrompt.kind).toBe('prompt');
|
|
370
|
+
if (firstPrompt.kind === 'prompt') {
|
|
371
|
+
expect(firstPrompt.toolCalls).toHaveLength(1);
|
|
372
|
+
expect(firstPrompt.toolCalls![0]).toEqual({
|
|
373
|
+
toolName: 'add',
|
|
374
|
+
args: { a: 2, b: 2 },
|
|
375
|
+
result: 4,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('AI call with no tool calls works normally', async () => {
|
|
381
|
+
const ast = parse(`
|
|
382
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
383
|
+
let greeting: text = vibe "Say hello" m default
|
|
384
|
+
`);
|
|
385
|
+
|
|
386
|
+
const toolExecutions: ToolExecution[] = [];
|
|
387
|
+
const testTools = createTestTools();
|
|
388
|
+
|
|
389
|
+
// Mock response with no tool calls
|
|
390
|
+
const mockResponses: AIResponse[] = [
|
|
391
|
+
{
|
|
392
|
+
content: 'Hello, world!',
|
|
393
|
+
parsedValue: 'Hello, world!',
|
|
394
|
+
stopReason: 'end',
|
|
395
|
+
},
|
|
396
|
+
];
|
|
397
|
+
|
|
398
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
399
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
400
|
+
await runtime.run();
|
|
401
|
+
|
|
402
|
+
// No tools should be executed
|
|
403
|
+
expect(toolExecutions).toHaveLength(0);
|
|
404
|
+
|
|
405
|
+
// Verify result
|
|
406
|
+
expect(runtime.getValue('greeting')).toBe('Hello, world!');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('tool call errors are captured and passed to AI', async () => {
|
|
410
|
+
const ast = parse(`
|
|
411
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
412
|
+
let result: text = vibe "Try to do something that fails" m default
|
|
413
|
+
`);
|
|
414
|
+
|
|
415
|
+
const toolExecutions: ToolExecution[] = [];
|
|
416
|
+
|
|
417
|
+
// Create a tool that throws an error
|
|
418
|
+
const testTools: VibeToolValue[] = [
|
|
419
|
+
{
|
|
420
|
+
__vibeTool: true,
|
|
421
|
+
name: 'failingTool',
|
|
422
|
+
schema: {
|
|
423
|
+
name: 'failingTool',
|
|
424
|
+
description: 'A tool that always fails',
|
|
425
|
+
parameters: [],
|
|
426
|
+
},
|
|
427
|
+
executor: async () => {
|
|
428
|
+
throw new Error('Tool execution failed!');
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
// Mock responses: first returns tool call, AI handles error, then final response
|
|
434
|
+
const mockResponses: AIResponse[] = [
|
|
435
|
+
{
|
|
436
|
+
content: '',
|
|
437
|
+
parsedValue: '',
|
|
438
|
+
toolCalls: [
|
|
439
|
+
{ id: 'call_1', toolName: 'failingTool', args: {} },
|
|
440
|
+
],
|
|
441
|
+
stopReason: 'tool_use',
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
content: 'I tried to use a tool but it failed. Let me handle that gracefully.',
|
|
445
|
+
parsedValue: 'I tried to use a tool but it failed. Let me handle that gracefully.',
|
|
446
|
+
stopReason: 'end',
|
|
447
|
+
},
|
|
448
|
+
];
|
|
449
|
+
|
|
450
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
451
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
452
|
+
await runtime.run();
|
|
453
|
+
|
|
454
|
+
// Tool execution was attempted (even though it failed)
|
|
455
|
+
// Note: Our tracking wrapper won't capture failed executions, but the flow should complete
|
|
456
|
+
expect(runtime.getValue('result')).toBe('I tried to use a tool but it failed. Let me handle that gracefully.');
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
describe('AI Tool Calling - Do Command (Single Round)', () => {
|
|
461
|
+
test('do command with tool calls shows tool history in context for second do call', async () => {
|
|
462
|
+
// This test verifies that when using `do`:
|
|
463
|
+
// 1. First `do` call triggers tool execution (single round)
|
|
464
|
+
// 2. Second `do` call can see the tool calls and results from the first `do` in its context
|
|
465
|
+
const ast = parse(`
|
|
466
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
467
|
+
let first: text = do "Calculate 5 + 3 and 2 * 4" m default
|
|
468
|
+
let second: text = do "What were the previous calculations?" m default
|
|
469
|
+
`);
|
|
470
|
+
|
|
471
|
+
const toolExecutions: ToolExecution[] = [];
|
|
472
|
+
const testTools = createTestTools();
|
|
473
|
+
|
|
474
|
+
// Mock responses:
|
|
475
|
+
// First do call - returns two tool calls, then final response (single round for do)
|
|
476
|
+
// Second do call - just returns a response (no tools)
|
|
477
|
+
const mockResponses: AIResponse[] = [
|
|
478
|
+
// First do: AI requests two tool calls
|
|
479
|
+
{
|
|
480
|
+
content: '',
|
|
481
|
+
parsedValue: '',
|
|
482
|
+
toolCalls: [
|
|
483
|
+
{ id: 'call_1', toolName: 'add', args: { a: 5, b: 3 } },
|
|
484
|
+
{ id: 'call_2', toolName: 'multiply', args: { a: 2, b: 4 } },
|
|
485
|
+
],
|
|
486
|
+
stopReason: 'tool_use',
|
|
487
|
+
},
|
|
488
|
+
// First do: Final response after tools execute
|
|
489
|
+
{
|
|
490
|
+
content: '5 + 3 = 8 and 2 * 4 = 8',
|
|
491
|
+
parsedValue: '5 + 3 = 8 and 2 * 4 = 8',
|
|
492
|
+
stopReason: 'end',
|
|
493
|
+
},
|
|
494
|
+
// Second do: Response referencing previous context
|
|
495
|
+
{
|
|
496
|
+
content: 'Previously: add(5,3)=8 and multiply(2,4)=8',
|
|
497
|
+
parsedValue: 'Previously: add(5,3)=8 and multiply(2,4)=8',
|
|
498
|
+
stopReason: 'end',
|
|
499
|
+
},
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
503
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
504
|
+
await runtime.run();
|
|
505
|
+
|
|
506
|
+
// Verify both tools were executed in the first do call
|
|
507
|
+
expect(toolExecutions).toHaveLength(2);
|
|
508
|
+
expect(toolExecutions[0]).toEqual({
|
|
509
|
+
name: 'add',
|
|
510
|
+
args: { a: 5, b: 3 },
|
|
511
|
+
result: 8,
|
|
512
|
+
});
|
|
513
|
+
expect(toolExecutions[1]).toEqual({
|
|
514
|
+
name: 'multiply',
|
|
515
|
+
args: { a: 2, b: 4 },
|
|
516
|
+
result: 8,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// Verify both results assigned correctly
|
|
520
|
+
expect(runtime.getValue('first')).toBe('5 + 3 = 8 and 2 * 4 = 8');
|
|
521
|
+
expect(runtime.getValue('second')).toBe('Previously: add(5,3)=8 and multiply(2,4)=8');
|
|
522
|
+
|
|
523
|
+
// Get state and verify context structure
|
|
524
|
+
const state = runtime.getState();
|
|
525
|
+
const frame = state.callStack[state.callStack.length - 1];
|
|
526
|
+
|
|
527
|
+
// Find prompt entries
|
|
528
|
+
const promptEntries = frame.orderedEntries.filter(e => e.kind === 'prompt');
|
|
529
|
+
expect(promptEntries).toHaveLength(2); // first and second do calls
|
|
530
|
+
|
|
531
|
+
// First prompt should have the two tool calls embedded
|
|
532
|
+
const firstPrompt = promptEntries[0];
|
|
533
|
+
expect(firstPrompt.kind).toBe('prompt');
|
|
534
|
+
if (firstPrompt.kind === 'prompt') {
|
|
535
|
+
expect(firstPrompt.aiType).toBe('do');
|
|
536
|
+
expect(firstPrompt.toolCalls).toHaveLength(2);
|
|
537
|
+
expect(firstPrompt.toolCalls![0]).toEqual({
|
|
538
|
+
toolName: 'add',
|
|
539
|
+
args: { a: 5, b: 3 },
|
|
540
|
+
result: 8,
|
|
541
|
+
});
|
|
542
|
+
expect(firstPrompt.toolCalls![1]).toEqual({
|
|
543
|
+
toolName: 'multiply',
|
|
544
|
+
args: { a: 2, b: 4 },
|
|
545
|
+
result: 8,
|
|
546
|
+
});
|
|
547
|
+
expect(firstPrompt.response).toBe('5 + 3 = 8 and 2 * 4 = 8');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Second prompt should have no tool calls
|
|
551
|
+
const secondPrompt = promptEntries[1];
|
|
552
|
+
if (secondPrompt.kind === 'prompt') {
|
|
553
|
+
expect(secondPrompt.aiType).toBe('do');
|
|
554
|
+
expect(secondPrompt.toolCalls).toBeUndefined();
|
|
555
|
+
expect(secondPrompt.response).toBe('Previously: add(5,3)=8 and multiply(2,4)=8');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Verify formatted context shows the complete history
|
|
559
|
+
const context = buildLocalContext(state);
|
|
560
|
+
const formatted = formatContextForAI(context, { includeInstructions: false });
|
|
561
|
+
|
|
562
|
+
expect(formatted.text).toBe(
|
|
563
|
+
` <entry> (current scope)
|
|
564
|
+
--> do: "Calculate 5 + 3 and 2 * 4"
|
|
565
|
+
[tool] add({"a":5,"b":3})
|
|
566
|
+
[result] 8
|
|
567
|
+
[tool] multiply({"a":2,"b":4})
|
|
568
|
+
[result] 8
|
|
569
|
+
<-- first (text): 5 + 3 = 8 and 2 * 4 = 8
|
|
570
|
+
--> do: "What were the previous calculations?"
|
|
571
|
+
<-- second (text): Previously: add(5,3)=8 and multiply(2,4)=8`
|
|
572
|
+
);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
describe('AI Tool Calling - Formatted Context Output', () => {
|
|
577
|
+
test('formatted context shows tool calls and results', async () => {
|
|
578
|
+
const ast = parse(`
|
|
579
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
580
|
+
let result: text = vibe "Calculate 5 + 3" m default
|
|
581
|
+
`);
|
|
582
|
+
|
|
583
|
+
const toolExecutions: ToolExecution[] = [];
|
|
584
|
+
const testTools = createTestTools();
|
|
585
|
+
|
|
586
|
+
const mockResponses: AIResponse[] = [
|
|
587
|
+
{
|
|
588
|
+
content: '',
|
|
589
|
+
parsedValue: '',
|
|
590
|
+
toolCalls: [
|
|
591
|
+
{ id: 'call_1', toolName: 'add', args: { a: 5, b: 3 } },
|
|
592
|
+
],
|
|
593
|
+
stopReason: 'tool_use',
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
content: 'The answer is 8',
|
|
597
|
+
parsedValue: 'The answer is 8',
|
|
598
|
+
stopReason: 'end',
|
|
599
|
+
},
|
|
600
|
+
];
|
|
601
|
+
|
|
602
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
603
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
604
|
+
await runtime.run();
|
|
605
|
+
|
|
606
|
+
// Get the formatted context
|
|
607
|
+
const state = runtime.getState();
|
|
608
|
+
const context = buildLocalContext(state);
|
|
609
|
+
const formatted = formatContextForAI(context, { includeInstructions: false });
|
|
610
|
+
|
|
611
|
+
// Verify formatted output shows: AI call → tool calls → response (via variable)
|
|
612
|
+
expect(formatted.text).toBe(
|
|
613
|
+
` <entry> (current scope)
|
|
614
|
+
--> vibe: "Calculate 5 + 3"
|
|
615
|
+
[tool] add({"a":5,"b":3})
|
|
616
|
+
[result] 8
|
|
617
|
+
<-- result (text): The answer is 8`
|
|
618
|
+
);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test('formatted context shows multiple tool calls in sequence', async () => {
|
|
622
|
+
const ast = parse(`
|
|
623
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
624
|
+
let weather: text = vibe "Weather in Seattle and NYC?" m default
|
|
625
|
+
`);
|
|
626
|
+
|
|
627
|
+
const toolExecutions: ToolExecution[] = [];
|
|
628
|
+
const testTools = createTestTools();
|
|
629
|
+
|
|
630
|
+
const mockResponses: AIResponse[] = [
|
|
631
|
+
{
|
|
632
|
+
content: '',
|
|
633
|
+
parsedValue: '',
|
|
634
|
+
toolCalls: [
|
|
635
|
+
{ id: 'call_1', toolName: 'getWeather', args: { city: 'Seattle' } },
|
|
636
|
+
{ id: 'call_2', toolName: 'getWeather', args: { city: 'New York' } },
|
|
637
|
+
],
|
|
638
|
+
stopReason: 'tool_use',
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
content: 'Seattle: 55F rainy, NYC: 45F cloudy',
|
|
642
|
+
parsedValue: 'Seattle: 55F rainy, NYC: 45F cloudy',
|
|
643
|
+
stopReason: 'end',
|
|
644
|
+
},
|
|
645
|
+
];
|
|
646
|
+
|
|
647
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
648
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
649
|
+
await runtime.run();
|
|
650
|
+
|
|
651
|
+
const state = runtime.getState();
|
|
652
|
+
const context = buildLocalContext(state);
|
|
653
|
+
const formatted = formatContextForAI(context, { includeInstructions: false });
|
|
654
|
+
|
|
655
|
+
expect(formatted.text).toBe(
|
|
656
|
+
` <entry> (current scope)
|
|
657
|
+
--> vibe: "Weather in Seattle and NYC?"
|
|
658
|
+
[tool] getWeather({"city":"Seattle"})
|
|
659
|
+
[result] {"temp":55,"condition":"rainy"}
|
|
660
|
+
[tool] getWeather({"city":"New York"})
|
|
661
|
+
[result] {"temp":45,"condition":"cloudy"}
|
|
662
|
+
<-- weather (text): Seattle: 55F rainy, NYC: 45F cloudy`
|
|
663
|
+
);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test('formatted context shows tool call error', async () => {
|
|
667
|
+
const ast = parse(`
|
|
668
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
669
|
+
let result: text = vibe "Try the failing tool" m default
|
|
670
|
+
`);
|
|
671
|
+
|
|
672
|
+
const toolExecutions: ToolExecution[] = [];
|
|
673
|
+
const failingTools: VibeToolValue[] = [
|
|
674
|
+
{
|
|
675
|
+
__vibeTool: true,
|
|
676
|
+
name: 'riskyOperation',
|
|
677
|
+
schema: {
|
|
678
|
+
name: 'riskyOperation',
|
|
679
|
+
description: 'A risky operation',
|
|
680
|
+
parameters: [],
|
|
681
|
+
},
|
|
682
|
+
executor: async () => {
|
|
683
|
+
throw new Error('Operation failed: insufficient permissions');
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
];
|
|
687
|
+
|
|
688
|
+
const mockResponses: AIResponse[] = [
|
|
689
|
+
{
|
|
690
|
+
content: '',
|
|
691
|
+
parsedValue: '',
|
|
692
|
+
toolCalls: [
|
|
693
|
+
{ id: 'call_1', toolName: 'riskyOperation', args: {} },
|
|
694
|
+
],
|
|
695
|
+
stopReason: 'tool_use',
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
content: 'The operation failed due to permissions',
|
|
699
|
+
parsedValue: 'The operation failed due to permissions',
|
|
700
|
+
stopReason: 'end',
|
|
701
|
+
},
|
|
702
|
+
];
|
|
703
|
+
|
|
704
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, failingTools);
|
|
705
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
706
|
+
await runtime.run();
|
|
707
|
+
|
|
708
|
+
const state = runtime.getState();
|
|
709
|
+
const context = buildLocalContext(state);
|
|
710
|
+
const formatted = formatContextForAI(context, { includeInstructions: false });
|
|
711
|
+
|
|
712
|
+
expect(formatted.text).toBe(
|
|
713
|
+
` <entry> (current scope)
|
|
714
|
+
--> vibe: "Try the failing tool"
|
|
715
|
+
[tool] riskyOperation({})
|
|
716
|
+
[error] Operation failed: insufficient permissions
|
|
717
|
+
<-- result (text): The operation failed due to permissions`
|
|
718
|
+
);
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
describe('AI Tool Calling - Context Modes (forget/verbose)', () => {
|
|
723
|
+
test('tool calls inside loop with forget mode are removed from context', async () => {
|
|
724
|
+
// Tool calls inside a loop with 'forget' should not appear in context after loop exits
|
|
725
|
+
const ast = parse(`
|
|
726
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
727
|
+
let sum = 0
|
|
728
|
+
for i in [1, 2] {
|
|
729
|
+
let partial: number = vibe "Add {i} to running total" m default
|
|
730
|
+
sum = sum + partial
|
|
731
|
+
} forget
|
|
732
|
+
let final: text = vibe "What is the final sum?" m default
|
|
733
|
+
`);
|
|
734
|
+
|
|
735
|
+
const toolExecutions: ToolExecution[] = [];
|
|
736
|
+
const testTools = createTestTools();
|
|
737
|
+
|
|
738
|
+
// Mock responses for: loop iter 1 (with tool), loop iter 2 (with tool), final do
|
|
739
|
+
const mockResponses: AIResponse[] = [
|
|
740
|
+
// First iteration - AI calls add tool
|
|
741
|
+
{
|
|
742
|
+
content: '',
|
|
743
|
+
parsedValue: '',
|
|
744
|
+
toolCalls: [{ id: 'call_1', toolName: 'add', args: { a: 0, b: 1 } }],
|
|
745
|
+
stopReason: 'tool_use',
|
|
746
|
+
},
|
|
747
|
+
{ content: '1', parsedValue: 1, stopReason: 'end' },
|
|
748
|
+
// Second iteration - AI calls add tool
|
|
749
|
+
{
|
|
750
|
+
content: '',
|
|
751
|
+
parsedValue: '',
|
|
752
|
+
toolCalls: [{ id: 'call_2', toolName: 'add', args: { a: 1, b: 2 } }],
|
|
753
|
+
stopReason: 'tool_use',
|
|
754
|
+
},
|
|
755
|
+
{ content: '3', parsedValue: 3, stopReason: 'end' },
|
|
756
|
+
// Final do call - no tools
|
|
757
|
+
{ content: 'The final sum is 3', parsedValue: 'The final sum is 3', stopReason: 'end' },
|
|
758
|
+
];
|
|
759
|
+
|
|
760
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
761
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
762
|
+
await runtime.run();
|
|
763
|
+
|
|
764
|
+
// Verify tools were executed during the loop
|
|
765
|
+
expect(toolExecutions).toHaveLength(2);
|
|
766
|
+
|
|
767
|
+
// Get context after loop with forget - tool calls should NOT appear
|
|
768
|
+
const state = runtime.getState();
|
|
769
|
+
const context = buildLocalContext(state);
|
|
770
|
+
const formatted = formatContextForAI(context, { includeInstructions: false });
|
|
771
|
+
|
|
772
|
+
// With 'forget', the loop's tool calls, iterations, and scope markers are removed
|
|
773
|
+
// Only variables declared outside the loop and what happens after remain
|
|
774
|
+
expect(formatted.text).toBe(
|
|
775
|
+
` <entry> (current scope)
|
|
776
|
+
- sum (number): 0
|
|
777
|
+
--> vibe: "What is the final sum?"
|
|
778
|
+
<-- final (text): The final sum is 3`
|
|
779
|
+
);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
test('tool calls inside loop with verbose mode are preserved in context', async () => {
|
|
783
|
+
// Tool calls inside a loop with 'verbose' should appear in context
|
|
784
|
+
const ast = parse(`
|
|
785
|
+
model m = { name: "test", apiKey: "key", url: "http://test" }
|
|
786
|
+
let sum = 0
|
|
787
|
+
for i in [1, 2] {
|
|
788
|
+
let partial: number = vibe "Add {i}" m default
|
|
789
|
+
sum = sum + partial
|
|
790
|
+
} verbose
|
|
791
|
+
let final: text = vibe "What is sum?" m default
|
|
792
|
+
`);
|
|
793
|
+
|
|
794
|
+
const toolExecutions: ToolExecution[] = [];
|
|
795
|
+
const testTools = createTestTools();
|
|
796
|
+
|
|
797
|
+
const mockResponses: AIResponse[] = [
|
|
798
|
+
// First iteration with tool
|
|
799
|
+
{
|
|
800
|
+
content: '',
|
|
801
|
+
parsedValue: '',
|
|
802
|
+
toolCalls: [{ id: 'call_1', toolName: 'add', args: { a: 0, b: 1 } }],
|
|
803
|
+
stopReason: 'tool_use',
|
|
804
|
+
},
|
|
805
|
+
{ content: '1', parsedValue: 1, stopReason: 'end' },
|
|
806
|
+
// Second iteration with tool
|
|
807
|
+
{
|
|
808
|
+
content: '',
|
|
809
|
+
parsedValue: '',
|
|
810
|
+
toolCalls: [{ id: 'call_2', toolName: 'add', args: { a: 1, b: 2 } }],
|
|
811
|
+
stopReason: 'tool_use',
|
|
812
|
+
},
|
|
813
|
+
{ content: '3', parsedValue: 3, stopReason: 'end' },
|
|
814
|
+
// Final do
|
|
815
|
+
{ content: 'Sum is 3', parsedValue: 'Sum is 3', stopReason: 'end' },
|
|
816
|
+
];
|
|
817
|
+
|
|
818
|
+
const aiProvider = createToolCallingAIProvider(mockResponses, toolExecutions, testTools);
|
|
819
|
+
const runtime = new Runtime(ast, aiProvider);
|
|
820
|
+
await runtime.run();
|
|
821
|
+
|
|
822
|
+
// Verify tools were executed
|
|
823
|
+
expect(toolExecutions).toHaveLength(2);
|
|
824
|
+
|
|
825
|
+
// Get context - with verbose, tool calls should be preserved
|
|
826
|
+
const state = runtime.getState();
|
|
827
|
+
const context = buildLocalContext(state);
|
|
828
|
+
const formatted = formatContextForAI(context, { includeInstructions: false });
|
|
829
|
+
|
|
830
|
+
// With 'verbose', the loop preserves all history including tool calls and scope markers
|
|
831
|
+
// Note: sum = 0 + 1 = 1, then sum = 1 + 3 = 4
|
|
832
|
+
// Order: AI call → tool calls → response (via variable assignment)
|
|
833
|
+
// With unified interpolation, {i} is left as reference in prompt strings
|
|
834
|
+
expect(formatted.text).toBe(
|
|
835
|
+
` <entry> (current scope)
|
|
836
|
+
- sum (number): 0
|
|
837
|
+
==> for i
|
|
838
|
+
- i (number): 1
|
|
839
|
+
--> vibe: "Add {i}"
|
|
840
|
+
[tool] add({"a":0,"b":1})
|
|
841
|
+
[result] 1
|
|
842
|
+
<-- partial (number): 1
|
|
843
|
+
- sum (number): 1
|
|
844
|
+
- i (number): 2
|
|
845
|
+
--> vibe: "Add {i}"
|
|
846
|
+
[tool] add({"a":1,"b":2})
|
|
847
|
+
[result] 3
|
|
848
|
+
<-- partial (number): 3
|
|
849
|
+
- sum (number): 4
|
|
850
|
+
<== for i
|
|
851
|
+
--> vibe: "What is sum?"
|
|
852
|
+
<-- final (text): Sum is 3`
|
|
853
|
+
);
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// Note: Function context modes (verbose/forget) were removed.
|
|
858
|
+
// Functions always forget their internal context on exit, like traditional callstacks.
|
|
859
|
+
// If you need data visible outside a function, return it and assign to a variable.
|