@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,129 @@
|
|
|
1
|
+
// Tool execution handlers
|
|
2
|
+
|
|
3
|
+
import * as AST from '../../ast';
|
|
4
|
+
import type { RuntimeState } from '../types';
|
|
5
|
+
import { createVibeValue } from '../types';
|
|
6
|
+
import type { ToolSchema, ToolParameterSchema, VibeToolValue } from '../tools/types';
|
|
7
|
+
import { vibeTypeToJsonSchema } from '../tools/ts-schema';
|
|
8
|
+
import { currentFrame } from '../state';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Execute a tool declaration - stores the tool as a variable in frame locals.
|
|
12
|
+
*/
|
|
13
|
+
export function execToolDeclaration(
|
|
14
|
+
state: RuntimeState,
|
|
15
|
+
decl: AST.ToolDeclaration
|
|
16
|
+
): RuntimeState {
|
|
17
|
+
// Build tool schema from declaration
|
|
18
|
+
const schema = buildToolSchema(state, decl);
|
|
19
|
+
|
|
20
|
+
// Create executor that wraps the tool body
|
|
21
|
+
const executor = createToolExecutor(state, decl);
|
|
22
|
+
|
|
23
|
+
// Create the tool value (like a model value)
|
|
24
|
+
const toolValue: VibeToolValue = {
|
|
25
|
+
__vibeTool: true,
|
|
26
|
+
name: decl.name,
|
|
27
|
+
schema,
|
|
28
|
+
executor,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Store as a variable in frame locals (like model declarations)
|
|
32
|
+
const frame = currentFrame(state);
|
|
33
|
+
const newLocals = {
|
|
34
|
+
...frame.locals,
|
|
35
|
+
[decl.name]: createVibeValue(toolValue, { isConst: true, typeAnnotation: null }),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
...state,
|
|
40
|
+
callStack: [
|
|
41
|
+
...state.callStack.slice(0, -1),
|
|
42
|
+
{ ...frame, locals: newLocals },
|
|
43
|
+
],
|
|
44
|
+
executionLog: [
|
|
45
|
+
...state.executionLog,
|
|
46
|
+
{
|
|
47
|
+
timestamp: Date.now(),
|
|
48
|
+
instructionType: 'tool_declaration',
|
|
49
|
+
details: { toolName: decl.name },
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build a tool schema from the AST declaration.
|
|
57
|
+
* Converts Vibe types to JSON Schema, merging in @param descriptions.
|
|
58
|
+
*/
|
|
59
|
+
function buildToolSchema(
|
|
60
|
+
state: RuntimeState,
|
|
61
|
+
decl: AST.ToolDeclaration
|
|
62
|
+
): ToolSchema {
|
|
63
|
+
// Build map of imported types for resolving TS type references
|
|
64
|
+
const importedTypes = new Map<string, string>();
|
|
65
|
+
for (const [name, info] of Object.entries(state.importedNames)) {
|
|
66
|
+
if (info.sourceType === 'ts') {
|
|
67
|
+
importedTypes.set(name, info.source);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const parameters: ToolParameterSchema[] = decl.params.map((param) => ({
|
|
72
|
+
name: param.name,
|
|
73
|
+
type: vibeTypeToJsonSchema(param.typeAnnotation, importedTypes),
|
|
74
|
+
description: param.description,
|
|
75
|
+
required: true, // All tool parameters are required for now
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
name: decl.name,
|
|
80
|
+
description: decl.description,
|
|
81
|
+
parameters,
|
|
82
|
+
returns: decl.returnType
|
|
83
|
+
? vibeTypeToJsonSchema(decl.returnType, importedTypes)
|
|
84
|
+
: undefined,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create an executor function for a user-defined tool.
|
|
90
|
+
* The executor extracts the TS block from the tool body and runs it.
|
|
91
|
+
*/
|
|
92
|
+
function createToolExecutor(
|
|
93
|
+
_state: RuntimeState,
|
|
94
|
+
decl: AST.ToolDeclaration
|
|
95
|
+
): (args: Record<string, unknown>) => Promise<unknown> {
|
|
96
|
+
// Find the TsBlock in the tool body
|
|
97
|
+
const tsBlock = findTsBlock(decl.body);
|
|
98
|
+
|
|
99
|
+
if (!tsBlock) {
|
|
100
|
+
throw new Error(`Tool '${decl.name}' must have a ts block as its body`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Return an executor that runs the TS code with the provided args
|
|
104
|
+
return async (args: Record<string, unknown>): Promise<unknown> => {
|
|
105
|
+
// Build parameter values in the order they appear in the ts block params
|
|
106
|
+
const paramValues = tsBlock.params.map((paramName) => args[paramName]);
|
|
107
|
+
|
|
108
|
+
// Create async function from the TS body
|
|
109
|
+
const asyncFn = new Function(
|
|
110
|
+
...tsBlock.params,
|
|
111
|
+
`return (async () => { ${tsBlock.body} })()`
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return await asyncFn(...paramValues);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Find the first TsBlock expression in a block statement.
|
|
120
|
+
*/
|
|
121
|
+
function findTsBlock(block: AST.BlockStatement): AST.TsBlock | null {
|
|
122
|
+
for (const stmt of block.body) {
|
|
123
|
+
if (stmt.type === 'ExpressionStatement' && stmt.expression.type === 'TsBlock') {
|
|
124
|
+
return stmt.expression;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// TypeScript execution: interpolation, ts blocks, ts eval
|
|
2
|
+
|
|
3
|
+
import * as AST from '../../ast';
|
|
4
|
+
import type { SourceLocation } from '../../errors';
|
|
5
|
+
import type { RuntimeState } from '../types';
|
|
6
|
+
import { resolveValue, isVibeValue } from '../types';
|
|
7
|
+
import { lookupVariable } from './variables';
|
|
8
|
+
import { getImportedValue } from '../modules';
|
|
9
|
+
import { scheduleAsyncOperation, isInAsyncContext } from '../async/scheduling';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Deep freeze an object to prevent any mutation.
|
|
13
|
+
* Used to enforce const semantics for objects passed to ts blocks.
|
|
14
|
+
*/
|
|
15
|
+
function deepFreeze<T>(obj: T): T {
|
|
16
|
+
if (obj === null || typeof obj !== 'object') {
|
|
17
|
+
return obj;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Freeze the object itself
|
|
21
|
+
Object.freeze(obj);
|
|
22
|
+
|
|
23
|
+
// Recursively freeze all properties
|
|
24
|
+
for (const key of Object.keys(obj)) {
|
|
25
|
+
const value = (obj as Record<string, unknown>)[key];
|
|
26
|
+
if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
27
|
+
deepFreeze(value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return obj;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* String interpolation - {varName} syntax.
|
|
36
|
+
* Resolves AIResultObject to its value for string conversion.
|
|
37
|
+
* If any interpolated variable is a pending async operation, triggers awaiting_async.
|
|
38
|
+
*/
|
|
39
|
+
export function execInterpolateString(state: RuntimeState, template: string, location?: SourceLocation): RuntimeState {
|
|
40
|
+
// First pass: check for pending async variables
|
|
41
|
+
const varPattern = /\{(\w+)\}/g;
|
|
42
|
+
const pendingAsyncIds: string[] = [];
|
|
43
|
+
let match;
|
|
44
|
+
|
|
45
|
+
while ((match = varPattern.exec(template)) !== null) {
|
|
46
|
+
const name = match[1];
|
|
47
|
+
const found = lookupVariable(state, name);
|
|
48
|
+
if (found) {
|
|
49
|
+
const variable = found.variable;
|
|
50
|
+
if (isVibeValue(variable) && variable.asyncOperationId) {
|
|
51
|
+
const opId = variable.asyncOperationId;
|
|
52
|
+
const operation = state.asyncOperations.get(opId);
|
|
53
|
+
if (operation && (operation.status === 'pending' || operation.status === 'running')) {
|
|
54
|
+
pendingAsyncIds.push(opId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// If there are pending async variables, wait for them
|
|
61
|
+
if (pendingAsyncIds.length > 0) {
|
|
62
|
+
return {
|
|
63
|
+
...state,
|
|
64
|
+
status: 'awaiting_async',
|
|
65
|
+
awaitingAsyncIds: pendingAsyncIds,
|
|
66
|
+
instructionStack: [
|
|
67
|
+
{ op: 'interpolate_string', template, location: location ?? { line: 0, column: 0 } },
|
|
68
|
+
...state.instructionStack,
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// All variables ready, do the interpolation
|
|
74
|
+
const result = template.replace(/\{(\w+)\}/g, (_, name) => {
|
|
75
|
+
const found = lookupVariable(state, name);
|
|
76
|
+
if (found) {
|
|
77
|
+
const value = resolveValue(found.variable.value);
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
return `{${name}}`;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return { ...state, lastResult: result };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Template literal interpolation - ${varName} syntax.
|
|
88
|
+
* Resolves AIResultObject to its value for string conversion.
|
|
89
|
+
* If any interpolated variable is a pending async operation, triggers awaiting_async.
|
|
90
|
+
*/
|
|
91
|
+
export function execInterpolateTemplate(state: RuntimeState, template: string, location?: SourceLocation): RuntimeState {
|
|
92
|
+
// First pass: check for pending async variables
|
|
93
|
+
const varPattern = /\$\{(\w+)\}/g;
|
|
94
|
+
const pendingAsyncIds: string[] = [];
|
|
95
|
+
let match;
|
|
96
|
+
|
|
97
|
+
while ((match = varPattern.exec(template)) !== null) {
|
|
98
|
+
const name = match[1];
|
|
99
|
+
const found = lookupVariable(state, name);
|
|
100
|
+
if (found) {
|
|
101
|
+
const variable = found.variable;
|
|
102
|
+
if (isVibeValue(variable) && variable.asyncOperationId) {
|
|
103
|
+
const opId = variable.asyncOperationId;
|
|
104
|
+
const operation = state.asyncOperations.get(opId);
|
|
105
|
+
if (operation && (operation.status === 'pending' || operation.status === 'running')) {
|
|
106
|
+
pendingAsyncIds.push(opId);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// If there are pending async variables, wait for them
|
|
113
|
+
if (pendingAsyncIds.length > 0) {
|
|
114
|
+
return {
|
|
115
|
+
...state,
|
|
116
|
+
status: 'awaiting_async',
|
|
117
|
+
awaitingAsyncIds: pendingAsyncIds,
|
|
118
|
+
instructionStack: [
|
|
119
|
+
{ op: 'interpolate_template', template, location: location ?? { line: 0, column: 0 } },
|
|
120
|
+
...state.instructionStack,
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// All variables ready, do the interpolation
|
|
126
|
+
const result = template.replace(/\$\{(\w+)\}/g, (_, name) => {
|
|
127
|
+
const found = lookupVariable(state, name);
|
|
128
|
+
if (found) {
|
|
129
|
+
const value = resolveValue(found.variable.value);
|
|
130
|
+
return String(value);
|
|
131
|
+
}
|
|
132
|
+
return `\${${name}}`;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return { ...state, lastResult: result };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* TypeScript block - push ts_eval instruction.
|
|
140
|
+
*/
|
|
141
|
+
export function execTsBlock(state: RuntimeState, expr: AST.TsBlock): RuntimeState {
|
|
142
|
+
return {
|
|
143
|
+
...state,
|
|
144
|
+
instructionStack: [
|
|
145
|
+
{ op: 'ts_eval', params: expr.params, body: expr.body, location: expr.location },
|
|
146
|
+
...state.instructionStack,
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* TypeScript eval - pause for async evaluation.
|
|
153
|
+
* When currentAsyncVarName is set (async declaration), schedules the operation
|
|
154
|
+
* for non-blocking execution instead of pausing.
|
|
155
|
+
*/
|
|
156
|
+
export function execTsEval(state: RuntimeState, params: string[], body: string, location: SourceLocation): RuntimeState {
|
|
157
|
+
// Look up parameter values from scope or imports
|
|
158
|
+
const paramValues = params.map((name) => {
|
|
159
|
+
// First try regular variables
|
|
160
|
+
const found = lookupVariable(state, name);
|
|
161
|
+
if (found) {
|
|
162
|
+
// Resolve AIResultObject to primitive value for ts blocks
|
|
163
|
+
const value = resolveValue(found.variable.value);
|
|
164
|
+
// Freeze const objects to prevent mutation in ts blocks
|
|
165
|
+
if (found.variable.isConst && value !== null && typeof value === 'object') {
|
|
166
|
+
return deepFreeze(value);
|
|
167
|
+
}
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
// Then try imported values
|
|
171
|
+
const imported = getImportedValue(state, name);
|
|
172
|
+
if (imported !== undefined) {
|
|
173
|
+
return imported;
|
|
174
|
+
}
|
|
175
|
+
throw new Error(`ReferenceError: '${name}' is not defined`);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Check if we're in async context (variable, destructuring, or fire-and-forget)
|
|
179
|
+
if (isInAsyncContext(state)) {
|
|
180
|
+
// Schedule for non-blocking execution using shared helper
|
|
181
|
+
return scheduleAsyncOperation(
|
|
182
|
+
state,
|
|
183
|
+
{
|
|
184
|
+
type: 'ts',
|
|
185
|
+
tsDetails: {
|
|
186
|
+
params,
|
|
187
|
+
body,
|
|
188
|
+
paramValues,
|
|
189
|
+
location,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
'async_ts_scheduled'
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Normal blocking execution
|
|
197
|
+
return {
|
|
198
|
+
...state,
|
|
199
|
+
status: 'awaiting_ts',
|
|
200
|
+
pendingTS: {
|
|
201
|
+
params,
|
|
202
|
+
body,
|
|
203
|
+
paramValues,
|
|
204
|
+
location, // Include source location for error reporting
|
|
205
|
+
},
|
|
206
|
+
executionLog: [
|
|
207
|
+
...state.executionLog,
|
|
208
|
+
{
|
|
209
|
+
timestamp: Date.now(),
|
|
210
|
+
instructionType: 'ts_eval_request',
|
|
211
|
+
details: { params, body: body.slice(0, 100) }, // Truncate body for log
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// Variable handling: lookup, declare, assign
|
|
2
|
+
|
|
3
|
+
import type { VibeType } from '../../ast';
|
|
4
|
+
import type { SourceLocation } from '../../errors';
|
|
5
|
+
import type { RuntimeState, VibeValue, StackFrame, ToolCallRecord } from '../types';
|
|
6
|
+
import { createVibeValue, isVibeValue } from '../types';
|
|
7
|
+
import { currentFrame } from '../state';
|
|
8
|
+
import { validateAndCoerce } from '../validation';
|
|
9
|
+
import { getModuleGlobals } from '../modules';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Look up a variable by walking the scope chain.
|
|
13
|
+
* Returns the variable and its frame index, or null if not found.
|
|
14
|
+
*
|
|
15
|
+
* Module isolation: If we're in an imported function (frame has modulePath),
|
|
16
|
+
* we check that module's globals instead of walking up to the main program.
|
|
17
|
+
*/
|
|
18
|
+
export function lookupVariable(state: RuntimeState, name: string): { variable: VibeValue; frameIndex: number } | null {
|
|
19
|
+
let frameIndex: number | null = state.callStack.length - 1;
|
|
20
|
+
let modulePath: string | undefined;
|
|
21
|
+
|
|
22
|
+
while (frameIndex !== null && frameIndex >= 0) {
|
|
23
|
+
const frame: StackFrame = state.callStack[frameIndex];
|
|
24
|
+
|
|
25
|
+
// Check frame locals
|
|
26
|
+
if (frame.locals[name]) {
|
|
27
|
+
return { variable: frame.locals[name], frameIndex };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Track the module path from any frame in the chain
|
|
31
|
+
if (frame.modulePath && !modulePath) {
|
|
32
|
+
modulePath = frame.modulePath;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If we're in a module context and this is the module's root frame,
|
|
36
|
+
// don't walk up to the caller's frames - check module globals instead
|
|
37
|
+
if (modulePath && frame.modulePath === modulePath) {
|
|
38
|
+
// Check module globals before walking to parent (caller's context)
|
|
39
|
+
const moduleGlobals = getModuleGlobals(state, modulePath);
|
|
40
|
+
if (moduleGlobals?.[name]) {
|
|
41
|
+
return { variable: moduleGlobals[name], frameIndex: -1 };
|
|
42
|
+
}
|
|
43
|
+
// Don't continue to caller's frames - module isolation
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
frameIndex = frame.parentFrameIndex;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Not in module context - variable not found in main program
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Declare a variable with value from lastResult (or explicit initialValue).
|
|
56
|
+
*/
|
|
57
|
+
export function execDeclareVar(
|
|
58
|
+
state: RuntimeState,
|
|
59
|
+
name: string,
|
|
60
|
+
isConst: boolean,
|
|
61
|
+
type: VibeType,
|
|
62
|
+
initialValue?: unknown,
|
|
63
|
+
isPrivate?: boolean,
|
|
64
|
+
location?: SourceLocation
|
|
65
|
+
): RuntimeState {
|
|
66
|
+
const frame = currentFrame(state);
|
|
67
|
+
|
|
68
|
+
if (frame.locals[name]) {
|
|
69
|
+
throw new Error(`Variable '${name}' is already declared`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const rawValue = initialValue !== undefined ? initialValue : state.lastResult;
|
|
73
|
+
|
|
74
|
+
// If value is already a VibeValue (e.g., from AI response or error), extract its properties
|
|
75
|
+
let innerValue: unknown;
|
|
76
|
+
let toolCalls: ToolCallRecord[] = [];
|
|
77
|
+
let source = initialValue !== undefined ? null : state.lastResultSource;
|
|
78
|
+
let err: VibeValue['err'] = false;
|
|
79
|
+
let errDetails: VibeValue['errDetails'] = null;
|
|
80
|
+
let asyncOperationId: string | undefined;
|
|
81
|
+
|
|
82
|
+
if (isVibeValue(rawValue)) {
|
|
83
|
+
innerValue = rawValue.value;
|
|
84
|
+
toolCalls = rawValue.toolCalls;
|
|
85
|
+
source = rawValue.source ?? source;
|
|
86
|
+
err = rawValue.err; // Preserve error boolean from operations like null arithmetic
|
|
87
|
+
errDetails = rawValue.errDetails; // Preserve error details
|
|
88
|
+
asyncOperationId = rawValue.asyncOperationId; // Preserve async operation ID for pending async
|
|
89
|
+
} else {
|
|
90
|
+
innerValue = rawValue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { value: validatedValue, inferredType } = validateAndCoerce(innerValue, type, name, location, source);
|
|
94
|
+
|
|
95
|
+
// Use explicit type if provided, otherwise use inferred type
|
|
96
|
+
const finalType = type ?? inferredType;
|
|
97
|
+
|
|
98
|
+
const newLocals = {
|
|
99
|
+
...frame.locals,
|
|
100
|
+
[name]: createVibeValue(validatedValue, { isConst, typeAnnotation: finalType, source, toolCalls, err, errDetails, isPrivate, asyncOperationId }),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Add variable to ordered entries with snapshotted value for context tracking
|
|
104
|
+
const newOrderedEntries = [
|
|
105
|
+
...frame.orderedEntries,
|
|
106
|
+
{
|
|
107
|
+
kind: 'variable' as const,
|
|
108
|
+
name,
|
|
109
|
+
value: validatedValue, // Snapshot at assignment time
|
|
110
|
+
type: finalType,
|
|
111
|
+
isConst,
|
|
112
|
+
source,
|
|
113
|
+
...(isPrivate ? { isPrivate: true } : {}),
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const newState: RuntimeState = {
|
|
118
|
+
...state,
|
|
119
|
+
lastResultSource: null, // Clear after consuming
|
|
120
|
+
callStack: [
|
|
121
|
+
...state.callStack.slice(0, -1),
|
|
122
|
+
{ ...frame, locals: newLocals, orderedEntries: newOrderedEntries },
|
|
123
|
+
],
|
|
124
|
+
executionLog: [
|
|
125
|
+
...state.executionLog,
|
|
126
|
+
{
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
instructionType: isConst ? 'const_declaration' : 'let_declaration',
|
|
129
|
+
details: { name, type, isConst },
|
|
130
|
+
result: validatedValue,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return newState;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Assign a value to an existing variable (from lastResult).
|
|
140
|
+
*/
|
|
141
|
+
export function execAssignVar(state: RuntimeState, name: string, location?: SourceLocation): RuntimeState {
|
|
142
|
+
// Walk scope chain to find the variable
|
|
143
|
+
const found = lookupVariable(state, name);
|
|
144
|
+
|
|
145
|
+
if (!found) {
|
|
146
|
+
throw new Error(`ReferenceError: '${name}' is not defined`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const { variable, frameIndex } = found;
|
|
150
|
+
|
|
151
|
+
if (variable.isConst) {
|
|
152
|
+
throw new Error(`TypeError: Cannot assign to constant '${name}'`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Warn if modifying non-local variable in async isolation
|
|
156
|
+
// (modifications won't persist after the async function returns)
|
|
157
|
+
const currentFrameIndex = state.callStack.length - 1;
|
|
158
|
+
if (state.isInAsyncIsolation && frameIndex !== currentFrameIndex && frameIndex >= 0) {
|
|
159
|
+
console.warn(
|
|
160
|
+
`Warning: Modifying non-local variable '${name}' in async function. ` +
|
|
161
|
+
`This modification will not persist after the function returns.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const rawValue = state.lastResult;
|
|
166
|
+
|
|
167
|
+
// If value is already a VibeValue (e.g., from AI response or error), extract its properties
|
|
168
|
+
let innerValue: unknown;
|
|
169
|
+
let toolCalls: ToolCallRecord[] = [];
|
|
170
|
+
let source = state.lastResultSource;
|
|
171
|
+
let err: VibeValue['err'] = false;
|
|
172
|
+
let errDetails: VibeValue['errDetails'] = null;
|
|
173
|
+
|
|
174
|
+
if (isVibeValue(rawValue)) {
|
|
175
|
+
innerValue = rawValue.value;
|
|
176
|
+
toolCalls = rawValue.toolCalls;
|
|
177
|
+
source = rawValue.source ?? source;
|
|
178
|
+
err = rawValue.err; // Preserve error boolean from operations like null arithmetic
|
|
179
|
+
errDetails = rawValue.errDetails; // Preserve error details
|
|
180
|
+
} else {
|
|
181
|
+
innerValue = rawValue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const { value: validatedValue } = validateAndCoerce(innerValue, variable.typeAnnotation, name, location, source);
|
|
185
|
+
|
|
186
|
+
// Handle module global assignment (frameIndex -1)
|
|
187
|
+
if (frameIndex === -1) {
|
|
188
|
+
// Find which module this belongs to by checking current frame's modulePath
|
|
189
|
+
const currentModulePath = getCurrentModulePath(state);
|
|
190
|
+
if (!currentModulePath) {
|
|
191
|
+
throw new Error(`Internal error: module global assignment without module context`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const module = state.vibeModules[currentModulePath];
|
|
195
|
+
if (!module) {
|
|
196
|
+
throw new Error(`Internal error: module not found: ${currentModulePath}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const newGlobals = {
|
|
200
|
+
...module.globals,
|
|
201
|
+
[name]: { ...variable, value: validatedValue, source, toolCalls, err, errDetails },
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
...state,
|
|
206
|
+
lastResultSource: null,
|
|
207
|
+
vibeModules: {
|
|
208
|
+
...state.vibeModules,
|
|
209
|
+
[currentModulePath]: { ...module, globals: newGlobals },
|
|
210
|
+
},
|
|
211
|
+
executionLog: [
|
|
212
|
+
...state.executionLog,
|
|
213
|
+
{
|
|
214
|
+
timestamp: Date.now(),
|
|
215
|
+
instructionType: 'assignment',
|
|
216
|
+
details: { name, moduleGlobal: true },
|
|
217
|
+
result: validatedValue,
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Regular frame local assignment
|
|
224
|
+
const frame = state.callStack[frameIndex];
|
|
225
|
+
const newLocals = {
|
|
226
|
+
...frame.locals,
|
|
227
|
+
[name]: { ...variable, value: validatedValue, source, toolCalls, err, errDetails },
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Add assignment to ordered entries with snapshotted value for context tracking
|
|
231
|
+
// This captures the history of value changes
|
|
232
|
+
const newOrderedEntries = [
|
|
233
|
+
...frame.orderedEntries,
|
|
234
|
+
{
|
|
235
|
+
kind: 'variable' as const,
|
|
236
|
+
name,
|
|
237
|
+
value: validatedValue, // Snapshot at assignment time
|
|
238
|
+
type: variable.typeAnnotation,
|
|
239
|
+
isConst: false, // Assignments only happen to non-const variables
|
|
240
|
+
source,
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
// Update the correct frame in the call stack
|
|
245
|
+
return {
|
|
246
|
+
...state,
|
|
247
|
+
lastResultSource: null, // Clear after consuming
|
|
248
|
+
callStack: [
|
|
249
|
+
...state.callStack.slice(0, frameIndex),
|
|
250
|
+
{ ...frame, locals: newLocals, orderedEntries: newOrderedEntries },
|
|
251
|
+
...state.callStack.slice(frameIndex + 1),
|
|
252
|
+
],
|
|
253
|
+
executionLog: [
|
|
254
|
+
...state.executionLog,
|
|
255
|
+
{
|
|
256
|
+
timestamp: Date.now(),
|
|
257
|
+
instructionType: 'assignment',
|
|
258
|
+
details: { name },
|
|
259
|
+
result: validatedValue,
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get the module path from the current execution context.
|
|
267
|
+
* Walks up the call stack to find a frame with modulePath.
|
|
268
|
+
*/
|
|
269
|
+
function getCurrentModulePath(state: RuntimeState): string | undefined {
|
|
270
|
+
let frameIndex: number | null = state.callStack.length - 1;
|
|
271
|
+
while (frameIndex !== null && frameIndex >= 0) {
|
|
272
|
+
const frame: StackFrame = state.callStack[frameIndex];
|
|
273
|
+
if (frame.modulePath) {
|
|
274
|
+
return frame.modulePath;
|
|
275
|
+
}
|
|
276
|
+
frameIndex = frame.parentFrameIndex;
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|