@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,203 @@
|
|
|
1
|
+
// Tool Execution Loop for AI-Initiated Tool Calling
|
|
2
|
+
// Handles multi-turn conversations where AI calls tools
|
|
3
|
+
|
|
4
|
+
import type { AIRequest, AIResponse, AIToolCall, AIToolResult } from './types';
|
|
5
|
+
import type { VibeToolValue } from '../tools/types';
|
|
6
|
+
import { isReturnToolCall } from './return-tools';
|
|
7
|
+
|
|
8
|
+
/** Options for the tool execution loop */
|
|
9
|
+
export interface ToolLoopOptions {
|
|
10
|
+
/** Maximum number of tool calling rounds (default: 10) */
|
|
11
|
+
maxRounds?: number;
|
|
12
|
+
/** Callback when a tool call is executed */
|
|
13
|
+
onToolCall?: (call: AIToolCall, result: unknown, error?: string) => void;
|
|
14
|
+
/** Expected return tool name (if set, AI must call this tool to return a value) */
|
|
15
|
+
expectedReturnTool?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Result of the tool execution loop */
|
|
19
|
+
export interface ToolLoopResult {
|
|
20
|
+
/** Final AI response */
|
|
21
|
+
response: AIResponse;
|
|
22
|
+
/** Tool execution rounds */
|
|
23
|
+
rounds: ToolRoundResult[];
|
|
24
|
+
/** All values from return tool calls (for multi-field destructuring) */
|
|
25
|
+
returnFieldResults?: unknown[];
|
|
26
|
+
/** Whether execution completed via return tool */
|
|
27
|
+
completedViaReturnTool?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Result of a tool execution round */
|
|
31
|
+
export interface ToolRoundResult {
|
|
32
|
+
/** The tool calls that were executed */
|
|
33
|
+
toolCalls: AIToolCall[];
|
|
34
|
+
/** The results of each tool call */
|
|
35
|
+
results: AIToolResult[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Execute a single round of tool calls.
|
|
40
|
+
* Takes the tool calls from an AI response and executes them.
|
|
41
|
+
*
|
|
42
|
+
* @param toolCalls - Tool calls from the AI response
|
|
43
|
+
* @param tools - Array of available tools
|
|
44
|
+
* @param rootDir - Root directory for file operation sandboxing
|
|
45
|
+
* @param onToolCall - Optional callback for each tool execution
|
|
46
|
+
* @returns Results of all tool calls
|
|
47
|
+
*/
|
|
48
|
+
export async function executeToolCalls(
|
|
49
|
+
toolCalls: AIToolCall[],
|
|
50
|
+
tools: VibeToolValue[],
|
|
51
|
+
rootDir: string,
|
|
52
|
+
onToolCall?: (call: AIToolCall, result: unknown, error?: string) => void
|
|
53
|
+
): Promise<AIToolResult[]> {
|
|
54
|
+
const results: AIToolResult[] = [];
|
|
55
|
+
const context = { rootDir };
|
|
56
|
+
|
|
57
|
+
// Build a lookup map for quick tool access
|
|
58
|
+
const toolMap = new Map(tools.map(t => [t.name, t]));
|
|
59
|
+
|
|
60
|
+
for (const call of toolCalls) {
|
|
61
|
+
const tool = toolMap.get(call.toolName);
|
|
62
|
+
|
|
63
|
+
if (!tool) {
|
|
64
|
+
const error = `Tool '${call.toolName}' not found`;
|
|
65
|
+
results.push({ toolCallId: call.id, error, duration: 0 });
|
|
66
|
+
onToolCall?.(call, undefined, error);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const startTime = Date.now();
|
|
71
|
+
try {
|
|
72
|
+
const result = await tool.executor(call.args, context);
|
|
73
|
+
const duration = Date.now() - startTime;
|
|
74
|
+
results.push({ toolCallId: call.id, result, duration });
|
|
75
|
+
onToolCall?.(call, result);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
const duration = Date.now() - startTime;
|
|
78
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
79
|
+
results.push({ toolCallId: call.id, error, duration });
|
|
80
|
+
onToolCall?.(call, undefined, error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Execute an AI request with tool calling support.
|
|
89
|
+
* Continues making follow-up calls until the AI returns a final response.
|
|
90
|
+
*
|
|
91
|
+
* @param initialRequest - The initial AI request
|
|
92
|
+
* @param tools - Array of available tools (from model)
|
|
93
|
+
* @param rootDir - Root directory for file operation sandboxing
|
|
94
|
+
* @param executeProvider - Function to execute AI requests
|
|
95
|
+
* @param options - Tool loop options
|
|
96
|
+
* @returns Final AI response after all tool calls are complete
|
|
97
|
+
*/
|
|
98
|
+
export async function executeWithTools(
|
|
99
|
+
initialRequest: AIRequest,
|
|
100
|
+
tools: VibeToolValue[],
|
|
101
|
+
rootDir: string,
|
|
102
|
+
executeProvider: (request: AIRequest) => Promise<AIResponse>,
|
|
103
|
+
options: ToolLoopOptions = {}
|
|
104
|
+
): Promise<ToolLoopResult> {
|
|
105
|
+
const { maxRounds = 10, onToolCall, expectedReturnTool } = options;
|
|
106
|
+
const rounds: ToolRoundResult[] = [];
|
|
107
|
+
|
|
108
|
+
let request = initialRequest;
|
|
109
|
+
let response = await executeProvider(request);
|
|
110
|
+
let roundCount = 0;
|
|
111
|
+
let returnFieldResults: unknown[] = [];
|
|
112
|
+
let completedViaReturnTool = false;
|
|
113
|
+
|
|
114
|
+
// Continue while there are tool calls to execute (or we need to retry for missing return tool)
|
|
115
|
+
while (roundCount < maxRounds) {
|
|
116
|
+
// Case 1: AI called tools
|
|
117
|
+
if (response.toolCalls?.length) {
|
|
118
|
+
roundCount++;
|
|
119
|
+
|
|
120
|
+
// Execute all tool calls in this round
|
|
121
|
+
const results = await executeToolCalls(response.toolCalls, tools, rootDir, onToolCall);
|
|
122
|
+
|
|
123
|
+
// Record this round
|
|
124
|
+
rounds.push({
|
|
125
|
+
toolCalls: response.toolCalls,
|
|
126
|
+
results,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Collect all return tool results from this round
|
|
130
|
+
for (const call of response.toolCalls) {
|
|
131
|
+
if (isReturnToolCall(call.toolName)) {
|
|
132
|
+
const result = results.find((r) => r.toolCallId === call.id);
|
|
133
|
+
if (result && result.error === undefined) {
|
|
134
|
+
// Return tool succeeded - collect result
|
|
135
|
+
returnFieldResults.push(result.result);
|
|
136
|
+
completedViaReturnTool = true;
|
|
137
|
+
}
|
|
138
|
+
// If return tool errored, error flows back to AI for retry
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// If we got return tool results, we're done (AI returns all fields in one round)
|
|
143
|
+
if (completedViaReturnTool) {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Make follow-up request with tool results
|
|
148
|
+
request = {
|
|
149
|
+
...request,
|
|
150
|
+
previousToolCalls: response.toolCalls,
|
|
151
|
+
toolResults: results,
|
|
152
|
+
};
|
|
153
|
+
response = await executeProvider(request);
|
|
154
|
+
}
|
|
155
|
+
// Case 2: AI didn't call any tools but we expected a return tool
|
|
156
|
+
else if (expectedReturnTool && !completedViaReturnTool) {
|
|
157
|
+
roundCount++;
|
|
158
|
+
|
|
159
|
+
// Synthesize error to send back to AI
|
|
160
|
+
const errorResult: AIToolResult = {
|
|
161
|
+
toolCallId: 'missing-return-tool',
|
|
162
|
+
error: `You must call the ${expectedReturnTool} tool to return your answer. Do not respond with plain text.`,
|
|
163
|
+
duration: 0,
|
|
164
|
+
};
|
|
165
|
+
rounds.push({ toolCalls: [], results: [errorResult] });
|
|
166
|
+
|
|
167
|
+
// Make follow-up request with error
|
|
168
|
+
request = {
|
|
169
|
+
...request,
|
|
170
|
+
previousToolCalls: [],
|
|
171
|
+
toolResults: [errorResult],
|
|
172
|
+
};
|
|
173
|
+
response = await executeProvider(request);
|
|
174
|
+
}
|
|
175
|
+
// Case 3: No tool calls and no expected return tool - we're done
|
|
176
|
+
else {
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Warn if we hit max rounds
|
|
182
|
+
if (roundCount >= maxRounds && !completedViaReturnTool && expectedReturnTool) {
|
|
183
|
+
console.warn(
|
|
184
|
+
`Tool loop hit max rounds (${maxRounds}) without receiving expected return tool call`
|
|
185
|
+
);
|
|
186
|
+
} else if (roundCount >= maxRounds && response.toolCalls?.length) {
|
|
187
|
+
console.warn(`Tool loop hit max rounds (${maxRounds}), stopping with pending tool calls`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
response,
|
|
192
|
+
rounds,
|
|
193
|
+
returnFieldResults: returnFieldResults.length > 0 ? returnFieldResults : undefined,
|
|
194
|
+
completedViaReturnTool,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Check if an AI response requires tool execution.
|
|
200
|
+
*/
|
|
201
|
+
export function requiresToolExecution(response: AIResponse): boolean {
|
|
202
|
+
return (response.toolCalls?.length ?? 0) > 0;
|
|
203
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Tool Schema Converters for AI Providers
|
|
2
|
+
// Converts internal ToolSchema to provider-specific formats
|
|
3
|
+
|
|
4
|
+
import type { ToolSchema, ToolParameterSchema, JsonSchema } from '../tools/types';
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Provider-specific tool types
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
/** OpenAI tool format */
|
|
11
|
+
export interface OpenAITool {
|
|
12
|
+
type: 'function';
|
|
13
|
+
function: {
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
parameters: {
|
|
17
|
+
type: 'object';
|
|
18
|
+
properties: Record<string, JsonSchema>;
|
|
19
|
+
required?: string[];
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Anthropic tool format */
|
|
25
|
+
export interface AnthropicTool {
|
|
26
|
+
name: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
input_schema: {
|
|
29
|
+
type: 'object';
|
|
30
|
+
properties: Record<string, JsonSchema>;
|
|
31
|
+
required?: string[];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Google function declaration format */
|
|
36
|
+
export interface GoogleFunctionDeclaration {
|
|
37
|
+
name: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
parameters: {
|
|
40
|
+
type: 'object';
|
|
41
|
+
properties: Record<string, JsonSchema>;
|
|
42
|
+
required?: string[];
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Conversion helpers
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Convert tool parameters to JSON Schema properties object.
|
|
52
|
+
*/
|
|
53
|
+
function parametersToProperties(
|
|
54
|
+
params: ToolParameterSchema[]
|
|
55
|
+
): { properties: Record<string, JsonSchema>; required: string[] } {
|
|
56
|
+
const properties: Record<string, JsonSchema> = {};
|
|
57
|
+
const required: string[] = [];
|
|
58
|
+
|
|
59
|
+
for (const param of params) {
|
|
60
|
+
const schema: JsonSchema = { ...param.type };
|
|
61
|
+
if (param.description) {
|
|
62
|
+
schema.description = param.description;
|
|
63
|
+
}
|
|
64
|
+
properties[param.name] = schema;
|
|
65
|
+
|
|
66
|
+
if (param.required) {
|
|
67
|
+
required.push(param.name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { properties, required };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Provider converters
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Convert internal ToolSchema to OpenAI tool format.
|
|
80
|
+
*/
|
|
81
|
+
export function toOpenAITool(schema: ToolSchema): OpenAITool {
|
|
82
|
+
const { properties, required } = parametersToProperties(schema.parameters);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
type: 'function',
|
|
86
|
+
function: {
|
|
87
|
+
name: schema.name,
|
|
88
|
+
description: schema.description,
|
|
89
|
+
parameters: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties,
|
|
92
|
+
required: required.length > 0 ? required : undefined,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Convert array of ToolSchema to OpenAI tools array.
|
|
100
|
+
*/
|
|
101
|
+
export function toOpenAITools(schemas: ToolSchema[]): OpenAITool[] {
|
|
102
|
+
return schemas.map(toOpenAITool);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Convert internal ToolSchema to Anthropic tool format.
|
|
107
|
+
*/
|
|
108
|
+
export function toAnthropicTool(schema: ToolSchema): AnthropicTool {
|
|
109
|
+
const { properties, required } = parametersToProperties(schema.parameters);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
name: schema.name,
|
|
113
|
+
description: schema.description,
|
|
114
|
+
input_schema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties,
|
|
117
|
+
required: required.length > 0 ? required : undefined,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Convert array of ToolSchema to Anthropic tools array.
|
|
124
|
+
*/
|
|
125
|
+
export function toAnthropicTools(schemas: ToolSchema[]): AnthropicTool[] {
|
|
126
|
+
return schemas.map(toAnthropicTool);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Convert internal ToolSchema to Google function declaration format.
|
|
131
|
+
*/
|
|
132
|
+
export function toGoogleFunctionDeclaration(schema: ToolSchema): GoogleFunctionDeclaration {
|
|
133
|
+
const { properties, required } = parametersToProperties(schema.parameters);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
name: schema.name,
|
|
137
|
+
description: schema.description,
|
|
138
|
+
parameters: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties,
|
|
141
|
+
required: required.length > 0 ? required : undefined,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Convert array of ToolSchema to Google function declarations.
|
|
148
|
+
*/
|
|
149
|
+
export function toGoogleFunctionDeclarations(schemas: ToolSchema[]): GoogleFunctionDeclaration[] {
|
|
150
|
+
return schemas.map(toGoogleFunctionDeclaration);
|
|
151
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// AI Module Type Definitions
|
|
2
|
+
|
|
3
|
+
import type { AIProviderType } from '../../ast';
|
|
4
|
+
import type { ToolSchema } from '../tools/types';
|
|
5
|
+
|
|
6
|
+
// Re-export for convenience
|
|
7
|
+
export type { AIProviderType };
|
|
8
|
+
|
|
9
|
+
/** Target types for AI responses */
|
|
10
|
+
export type TargetType = 'text' | 'json' | 'boolean' | 'number' | 'text[]' | 'json[]' | 'boolean[]' | 'number[]' | null;
|
|
11
|
+
|
|
12
|
+
/** Thinking level for extended reasoning */
|
|
13
|
+
export type ThinkingLevel = 'none' | 'low' | 'medium' | 'high' | 'max';
|
|
14
|
+
|
|
15
|
+
/** Model configuration from Vibe model declaration */
|
|
16
|
+
export interface ModelConfig {
|
|
17
|
+
name: string;
|
|
18
|
+
apiKey: string;
|
|
19
|
+
url: string | null;
|
|
20
|
+
provider?: AIProviderType;
|
|
21
|
+
maxRetriesOnError?: number;
|
|
22
|
+
thinkingLevel?: ThinkingLevel;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** AI request for all providers */
|
|
26
|
+
export interface AIRequest {
|
|
27
|
+
operationType: 'do' | 'vibe';
|
|
28
|
+
prompt: string;
|
|
29
|
+
contextText: string;
|
|
30
|
+
targetType: TargetType;
|
|
31
|
+
model: ModelConfig;
|
|
32
|
+
/** Available tools for function calling */
|
|
33
|
+
tools?: ToolSchema[];
|
|
34
|
+
/** Previous tool calls from AI response (for multi-turn) */
|
|
35
|
+
previousToolCalls?: AIToolCall[];
|
|
36
|
+
/** Tool results from previous call (for multi-turn) */
|
|
37
|
+
toolResults?: AIToolResult[];
|
|
38
|
+
/** Override messages (used by vibe for custom system prompt) */
|
|
39
|
+
messages?: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Detailed token usage from AI providers */
|
|
43
|
+
export interface TokenUsage {
|
|
44
|
+
inputTokens: number;
|
|
45
|
+
outputTokens: number;
|
|
46
|
+
// Cached tokens (prompt caching)
|
|
47
|
+
cachedInputTokens?: number;
|
|
48
|
+
// Tokens used to create cache (Anthropic)
|
|
49
|
+
cacheCreationTokens?: number;
|
|
50
|
+
// Reasoning/thinking tokens (OpenAI o1, Claude extended thinking)
|
|
51
|
+
thinkingTokens?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Tool call parsed from AI response */
|
|
55
|
+
export interface AIToolCall {
|
|
56
|
+
/** Provider-assigned ID for the tool call */
|
|
57
|
+
id: string;
|
|
58
|
+
/** Name of the tool to call */
|
|
59
|
+
toolName: string;
|
|
60
|
+
/** Arguments parsed from the tool call */
|
|
61
|
+
args: Record<string, unknown>;
|
|
62
|
+
/** Thought signature for Gemini 3 models (must be echoed back in follow-up) */
|
|
63
|
+
thoughtSignature?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Tool result to send back in follow-up request */
|
|
67
|
+
export interface AIToolResult {
|
|
68
|
+
/** ID of the tool call this result is for */
|
|
69
|
+
toolCallId: string;
|
|
70
|
+
/** Successful result value */
|
|
71
|
+
result?: unknown;
|
|
72
|
+
/** Error message if the tool call failed */
|
|
73
|
+
error?: string;
|
|
74
|
+
/** Execution duration in milliseconds */
|
|
75
|
+
duration?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Why the model stopped generating */
|
|
79
|
+
export type AIStopReason = 'end' | 'tool_use' | 'length' | 'content_filter';
|
|
80
|
+
|
|
81
|
+
/** AI response from all providers */
|
|
82
|
+
export interface AIResponse {
|
|
83
|
+
content: string;
|
|
84
|
+
parsedValue: unknown;
|
|
85
|
+
usage?: TokenUsage;
|
|
86
|
+
/** Tool calls from the model (if any) */
|
|
87
|
+
toolCalls?: AIToolCall[];
|
|
88
|
+
/** Why the model stopped generating */
|
|
89
|
+
stopReason?: AIStopReason;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Custom error for AI operations */
|
|
93
|
+
export class AIError extends Error {
|
|
94
|
+
readonly statusCode?: number;
|
|
95
|
+
readonly isRetryable: boolean;
|
|
96
|
+
|
|
97
|
+
constructor(message: string, statusCode?: number, isRetryable = false) {
|
|
98
|
+
super(message);
|
|
99
|
+
this.name = 'AIError';
|
|
100
|
+
this.statusCode = statusCode;
|
|
101
|
+
this.isRetryable = isRetryable;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Provider executor function signature */
|
|
106
|
+
export type ProviderExecutor = (request: AIRequest) => Promise<AIResponse>;
|
|
107
|
+
|
|
108
|
+
/** Retry options */
|
|
109
|
+
export interface RetryOptions {
|
|
110
|
+
maxRetries: number;
|
|
111
|
+
baseDelayMs?: number;
|
|
112
|
+
maxDelayMs?: number;
|
|
113
|
+
}
|