@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,357 @@
|
|
|
1
|
+
import type { RuntimeState, ContextEntry, ContextVariable, ContextPrompt, ContextToolCall, FrameEntry } from './types';
|
|
2
|
+
import { resolveValue } from './types';
|
|
3
|
+
|
|
4
|
+
// Types that are filtered from context (config/instructions, not data for AI)
|
|
5
|
+
const FILTERED_TYPES = ['model', 'prompt'];
|
|
6
|
+
|
|
7
|
+
// Build local context - entries from current frame only
|
|
8
|
+
// Pure function: takes full state, returns context array
|
|
9
|
+
// Uses snapshotted values from entries for accurate history
|
|
10
|
+
// Note: Model, prompt, and private variables are filtered out (they are config/instructions, not data for AI context)
|
|
11
|
+
export function buildLocalContext(state: RuntimeState): ContextEntry[] {
|
|
12
|
+
const frameIndex = state.callStack.length - 1;
|
|
13
|
+
const frame = state.callStack[frameIndex];
|
|
14
|
+
if (!frame) return [];
|
|
15
|
+
|
|
16
|
+
return frame.orderedEntries
|
|
17
|
+
.flatMap((entry): ContextEntry[] => {
|
|
18
|
+
if (entry.kind === 'variable') {
|
|
19
|
+
// Filter out model, prompt types, and private variables
|
|
20
|
+
if (FILTERED_TYPES.includes(entry.type ?? '') || entry.isPrivate) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
// Use snapshotted value from entry, resolving VibeValue to its value
|
|
24
|
+
const contextVar: ContextVariable = {
|
|
25
|
+
kind: 'variable',
|
|
26
|
+
name: entry.name,
|
|
27
|
+
value: resolveValue(entry.value), // Resolve VibeValue to primitive
|
|
28
|
+
type: entry.type as 'text' | 'json' | 'boolean' | 'number' | null,
|
|
29
|
+
isConst: entry.isConst, // Use snapshotted isConst
|
|
30
|
+
frameName: frame.name,
|
|
31
|
+
frameDepth: frameIndex,
|
|
32
|
+
source: entry.source ?? null, // Always include source, use null as default
|
|
33
|
+
};
|
|
34
|
+
return [contextVar];
|
|
35
|
+
} else if (entry.kind === 'prompt') {
|
|
36
|
+
const contextPrompt: ContextPrompt = {
|
|
37
|
+
kind: 'prompt',
|
|
38
|
+
aiType: entry.aiType,
|
|
39
|
+
prompt: entry.prompt,
|
|
40
|
+
frameName: frame.name,
|
|
41
|
+
frameDepth: frameIndex,
|
|
42
|
+
};
|
|
43
|
+
// Include toolCalls if present
|
|
44
|
+
if (entry.toolCalls && entry.toolCalls.length > 0) {
|
|
45
|
+
contextPrompt.toolCalls = entry.toolCalls;
|
|
46
|
+
}
|
|
47
|
+
// Only include response if defined
|
|
48
|
+
if (entry.response !== undefined) {
|
|
49
|
+
contextPrompt.response = entry.response;
|
|
50
|
+
}
|
|
51
|
+
return [contextPrompt];
|
|
52
|
+
} else if (entry.kind === 'scope-enter' || entry.kind === 'scope-exit') {
|
|
53
|
+
return [{
|
|
54
|
+
kind: entry.kind,
|
|
55
|
+
scopeType: entry.scopeType,
|
|
56
|
+
label: entry.label,
|
|
57
|
+
frameName: frame.name,
|
|
58
|
+
frameDepth: frameIndex,
|
|
59
|
+
}];
|
|
60
|
+
} else if (entry.kind === 'summary') {
|
|
61
|
+
return [{
|
|
62
|
+
kind: 'summary',
|
|
63
|
+
text: entry.text,
|
|
64
|
+
frameName: frame.name,
|
|
65
|
+
frameDepth: frameIndex,
|
|
66
|
+
}];
|
|
67
|
+
} else if (entry.kind === 'tool-call') {
|
|
68
|
+
const toolEntry: ContextToolCall = {
|
|
69
|
+
kind: 'tool-call',
|
|
70
|
+
toolName: entry.toolName,
|
|
71
|
+
args: entry.args,
|
|
72
|
+
frameName: frame.name,
|
|
73
|
+
frameDepth: frameIndex,
|
|
74
|
+
};
|
|
75
|
+
if (entry.result !== undefined) {
|
|
76
|
+
toolEntry.result = entry.result;
|
|
77
|
+
}
|
|
78
|
+
if (entry.error !== undefined) {
|
|
79
|
+
toolEntry.error = entry.error;
|
|
80
|
+
}
|
|
81
|
+
return [toolEntry];
|
|
82
|
+
}
|
|
83
|
+
return [];
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Build global context - entries from all frames in call stack
|
|
88
|
+
// Pure function: takes full state, returns context array
|
|
89
|
+
// Uses snapshotted values from entries for accurate history
|
|
90
|
+
// Note: Model, prompt, and private variables are filtered out (they are config/instructions, not data for AI context)
|
|
91
|
+
// Entries are returned with frameDepth: 0 = entry frame, higher = deeper in call stack
|
|
92
|
+
export function buildGlobalContext(state: RuntimeState): ContextEntry[] {
|
|
93
|
+
return state.callStack.flatMap((frame, frameIndex) => {
|
|
94
|
+
const frameDepth = frameIndex;
|
|
95
|
+
|
|
96
|
+
return frame.orderedEntries.flatMap((entry): ContextEntry[] => {
|
|
97
|
+
if (entry.kind === 'variable') {
|
|
98
|
+
// Filter out model, prompt types, and private variables
|
|
99
|
+
if (FILTERED_TYPES.includes(entry.type ?? '') || entry.isPrivate) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
// Use snapshotted value from entry, resolving VibeValue to its value
|
|
103
|
+
const contextVar: ContextVariable = {
|
|
104
|
+
kind: 'variable',
|
|
105
|
+
name: entry.name,
|
|
106
|
+
value: resolveValue(entry.value), // Resolve VibeValue to primitive
|
|
107
|
+
type: entry.type as 'text' | 'json' | 'boolean' | 'number' | null,
|
|
108
|
+
isConst: entry.isConst, // Use snapshotted isConst
|
|
109
|
+
frameName: frame.name,
|
|
110
|
+
frameDepth,
|
|
111
|
+
source: entry.source ?? null, // Always include source, use null as default
|
|
112
|
+
};
|
|
113
|
+
return [contextVar];
|
|
114
|
+
} else if (entry.kind === 'prompt') {
|
|
115
|
+
const contextPrompt: ContextPrompt = {
|
|
116
|
+
kind: 'prompt',
|
|
117
|
+
aiType: entry.aiType,
|
|
118
|
+
prompt: entry.prompt,
|
|
119
|
+
frameName: frame.name,
|
|
120
|
+
frameDepth,
|
|
121
|
+
};
|
|
122
|
+
// Include toolCalls if present
|
|
123
|
+
if (entry.toolCalls && entry.toolCalls.length > 0) {
|
|
124
|
+
contextPrompt.toolCalls = entry.toolCalls;
|
|
125
|
+
}
|
|
126
|
+
// Only include response if defined
|
|
127
|
+
if (entry.response !== undefined) {
|
|
128
|
+
contextPrompt.response = entry.response;
|
|
129
|
+
}
|
|
130
|
+
return [contextPrompt];
|
|
131
|
+
} else if (entry.kind === 'scope-enter' || entry.kind === 'scope-exit') {
|
|
132
|
+
return [{
|
|
133
|
+
kind: entry.kind,
|
|
134
|
+
scopeType: entry.scopeType,
|
|
135
|
+
label: entry.label,
|
|
136
|
+
frameName: frame.name,
|
|
137
|
+
frameDepth,
|
|
138
|
+
}];
|
|
139
|
+
} else if (entry.kind === 'summary') {
|
|
140
|
+
return [{
|
|
141
|
+
kind: 'summary',
|
|
142
|
+
text: entry.text,
|
|
143
|
+
frameName: frame.name,
|
|
144
|
+
frameDepth,
|
|
145
|
+
}];
|
|
146
|
+
} else if (entry.kind === 'tool-call') {
|
|
147
|
+
const toolEntry: ContextToolCall = {
|
|
148
|
+
kind: 'tool-call',
|
|
149
|
+
toolName: entry.toolName,
|
|
150
|
+
args: entry.args,
|
|
151
|
+
frameName: frame.name,
|
|
152
|
+
frameDepth,
|
|
153
|
+
};
|
|
154
|
+
if (entry.result !== undefined) {
|
|
155
|
+
toolEntry.result = entry.result;
|
|
156
|
+
}
|
|
157
|
+
if (entry.error !== undefined) {
|
|
158
|
+
toolEntry.error = entry.error;
|
|
159
|
+
}
|
|
160
|
+
return [toolEntry];
|
|
161
|
+
}
|
|
162
|
+
return [];
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Formatted context for AI consumption
|
|
168
|
+
export interface FormattedContext {
|
|
169
|
+
text: string;
|
|
170
|
+
variables: ContextEntry[];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Format context for AI calls with instructional wrapping
|
|
174
|
+
export function formatContextForAI(
|
|
175
|
+
context: ContextEntry[],
|
|
176
|
+
options?: { includeInstructions?: boolean }
|
|
177
|
+
): FormattedContext {
|
|
178
|
+
const text = formatContextText(context, options?.includeInstructions ?? true);
|
|
179
|
+
return { text, variables: context };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Group entries by frame for structured output
|
|
183
|
+
function groupByFrame(entries: ContextEntry[]): Map<string, ContextEntry[]> {
|
|
184
|
+
const groups = new Map<string, ContextEntry[]>();
|
|
185
|
+
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
const key = `${entry.frameDepth}:${entry.frameName}`;
|
|
188
|
+
const existing = groups.get(key) ?? [];
|
|
189
|
+
groups.set(key, [...existing, entry]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return groups;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Format context as text with optional instruction header
|
|
196
|
+
// Entries are grouped by call stack frame in execution order
|
|
197
|
+
function formatContextText(
|
|
198
|
+
entries: ContextEntry[],
|
|
199
|
+
includeInstructions: boolean
|
|
200
|
+
): string {
|
|
201
|
+
const lines: string[] = [];
|
|
202
|
+
|
|
203
|
+
if (includeInstructions) {
|
|
204
|
+
lines.push('## VIBE Program Context');
|
|
205
|
+
lines.push('Variables from the VIBE language call stack.');
|
|
206
|
+
lines.push('');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (entries.length > 0) {
|
|
210
|
+
formatFrameGroups(entries, lines);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return lines.join('\n').trimEnd();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Format entries grouped by frame with indentation
|
|
217
|
+
// Depth 0 = entry (leftmost), higher depth = deeper in call stack (more indented)
|
|
218
|
+
function formatFrameGroups(entries: ContextEntry[], lines: string[]): void {
|
|
219
|
+
const frameGroups = groupByFrame(entries);
|
|
220
|
+
const sortedKeys = [...frameGroups.keys()].sort((a, b) => {
|
|
221
|
+
const depthA = parseInt(a.split(':')[0]);
|
|
222
|
+
const depthB = parseInt(b.split(':')[0]);
|
|
223
|
+
return depthA - depthB; // Lower depth (entry) first
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Find max depth to identify current scope
|
|
227
|
+
const maxDepth = Math.max(...entries.map((e) => e.frameDepth));
|
|
228
|
+
|
|
229
|
+
for (const key of sortedKeys) {
|
|
230
|
+
const frameEntries = frameGroups.get(key) ?? [];
|
|
231
|
+
if (frameEntries.length === 0) continue;
|
|
232
|
+
|
|
233
|
+
const { frameName, frameDepth } = frameEntries[0];
|
|
234
|
+
// Label: entry at depth 0, current scope at max depth, otherwise show depth
|
|
235
|
+
const scopeLabel =
|
|
236
|
+
frameDepth === maxDepth
|
|
237
|
+
? '(current scope)'
|
|
238
|
+
: frameDepth === 0
|
|
239
|
+
? '(entry)'
|
|
240
|
+
: `(depth ${frameDepth})`;
|
|
241
|
+
// Indent: base indent for section + extra indent per frame depth
|
|
242
|
+
const indent = ' ' + ' '.repeat(frameDepth);
|
|
243
|
+
lines.push(`${indent}${frameName} ${scopeLabel}`);
|
|
244
|
+
|
|
245
|
+
for (const entry of frameEntries) {
|
|
246
|
+
if (entry.kind === 'variable') {
|
|
247
|
+
const typeStr = entry.type ? ` (${entry.type})` : '';
|
|
248
|
+
const valueStr =
|
|
249
|
+
typeof entry.value === 'object' ? JSON.stringify(entry.value) : String(entry.value);
|
|
250
|
+
// Use <-- for AI/user responses, - for regular variables
|
|
251
|
+
const prefix = entry.source === 'ai' || entry.source === 'user' ? '<--' : '-';
|
|
252
|
+
lines.push(`${indent} ${prefix} ${entry.name}${typeStr}: ${valueStr}`);
|
|
253
|
+
} else if (entry.kind === 'prompt') {
|
|
254
|
+
// Prompt entry: AI call → tool calls → response
|
|
255
|
+
lines.push(`${indent} --> ${entry.aiType}: "${entry.prompt}"`);
|
|
256
|
+
// Format tool calls in order (between prompt and response)
|
|
257
|
+
if (entry.toolCalls && entry.toolCalls.length > 0) {
|
|
258
|
+
for (const toolCall of entry.toolCalls) {
|
|
259
|
+
const argsStr = JSON.stringify(toolCall.args);
|
|
260
|
+
lines.push(`${indent} [tool] ${toolCall.toolName}(${argsStr})`);
|
|
261
|
+
if (toolCall.error) {
|
|
262
|
+
lines.push(`${indent} [error] ${toolCall.error}`);
|
|
263
|
+
} else if (toolCall.result !== undefined) {
|
|
264
|
+
const resultStr =
|
|
265
|
+
typeof toolCall.result === 'object' ? JSON.stringify(toolCall.result) : String(toolCall.result);
|
|
266
|
+
lines.push(`${indent} [result] ${resultStr}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Note: We don't show the response here because it will be shown
|
|
271
|
+
// with the variable assignment that follows (with source: 'ai')
|
|
272
|
+
// This avoids duplication like:
|
|
273
|
+
// --> do: "prompt"
|
|
274
|
+
// <-- response <- would be duplicate
|
|
275
|
+
// <-- varName: response
|
|
276
|
+
} else if (entry.kind === 'scope-enter') {
|
|
277
|
+
// Scope enter marker
|
|
278
|
+
const labelStr = entry.label ? ` ${entry.label}` : '';
|
|
279
|
+
lines.push(`${indent} ==> ${entry.scopeType}${labelStr}`);
|
|
280
|
+
} else if (entry.kind === 'scope-exit') {
|
|
281
|
+
// Scope exit marker
|
|
282
|
+
const labelStr = entry.label ? ` ${entry.label}` : '';
|
|
283
|
+
lines.push(`${indent} <== ${entry.scopeType}${labelStr}`);
|
|
284
|
+
} else if (entry.kind === 'summary') {
|
|
285
|
+
// Summary from compress mode
|
|
286
|
+
lines.push(`${indent} [summary] ${entry.text}`);
|
|
287
|
+
} else if (entry.kind === 'tool-call') {
|
|
288
|
+
// Tool call with args and result/error
|
|
289
|
+
const argsStr = JSON.stringify(entry.args);
|
|
290
|
+
lines.push(`${indent} [tool] ${entry.toolName}(${argsStr})`);
|
|
291
|
+
if (entry.error) {
|
|
292
|
+
lines.push(`${indent} [error] ${entry.error}`);
|
|
293
|
+
} else if (entry.result !== undefined) {
|
|
294
|
+
const resultStr =
|
|
295
|
+
typeof entry.result === 'object' ? JSON.stringify(entry.result) : String(entry.result);
|
|
296
|
+
lines.push(`${indent} [result] ${resultStr}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
lines.push('');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Format FrameEntries for AI summarization (used by compress mode).
|
|
307
|
+
* Converts frame entries into a human-readable format suitable for AI summarization.
|
|
308
|
+
*/
|
|
309
|
+
export function formatEntriesForSummarization(entries: FrameEntry[]): string {
|
|
310
|
+
const lines: string[] = [];
|
|
311
|
+
|
|
312
|
+
for (const entry of entries) {
|
|
313
|
+
if (entry.kind === 'variable') {
|
|
314
|
+
const typeStr = entry.type ? ` (${entry.type})` : '';
|
|
315
|
+
const valueStr = typeof entry.value === 'object' ? JSON.stringify(entry.value) : String(entry.value);
|
|
316
|
+
const sourceStr = entry.source === 'ai' ? ' [from AI]' : entry.source === 'user' ? ' [from user]' : '';
|
|
317
|
+
lines.push(`Variable ${entry.name}${typeStr} = ${valueStr}${sourceStr}`);
|
|
318
|
+
} else if (entry.kind === 'prompt') {
|
|
319
|
+
lines.push(`AI ${entry.aiType}: "${entry.prompt}"`);
|
|
320
|
+
if (entry.toolCalls && entry.toolCalls.length > 0) {
|
|
321
|
+
for (const toolCall of entry.toolCalls) {
|
|
322
|
+
const argsStr = JSON.stringify(toolCall.args);
|
|
323
|
+
lines.push(` Tool call: ${toolCall.toolName}(${argsStr})`);
|
|
324
|
+
if (toolCall.error) {
|
|
325
|
+
lines.push(` Error: ${toolCall.error}`);
|
|
326
|
+
} else if (toolCall.result !== undefined) {
|
|
327
|
+
const resultStr = typeof toolCall.result === 'object' ? JSON.stringify(toolCall.result) : String(toolCall.result);
|
|
328
|
+
lines.push(` Result: ${resultStr}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (entry.response !== undefined) {
|
|
333
|
+
const responseStr = typeof entry.response === 'object' ? JSON.stringify(entry.response) : String(entry.response);
|
|
334
|
+
lines.push(` Response: ${responseStr}`);
|
|
335
|
+
}
|
|
336
|
+
} else if (entry.kind === 'scope-enter') {
|
|
337
|
+
const labelStr = entry.label ? ` (${entry.label})` : '';
|
|
338
|
+
lines.push(`==> ${entry.scopeType}${labelStr} started`);
|
|
339
|
+
} else if (entry.kind === 'scope-exit') {
|
|
340
|
+
const labelStr = entry.label ? ` (${entry.label})` : '';
|
|
341
|
+
lines.push(`<== ${entry.scopeType}${labelStr} ended`);
|
|
342
|
+
} else if (entry.kind === 'summary') {
|
|
343
|
+
lines.push(`Summary: ${entry.text}`);
|
|
344
|
+
} else if (entry.kind === 'tool-call') {
|
|
345
|
+
const argsStr = JSON.stringify(entry.args);
|
|
346
|
+
lines.push(`Tool call: ${entry.toolName}(${argsStr})`);
|
|
347
|
+
if (entry.error) {
|
|
348
|
+
lines.push(` Error: ${entry.error}`);
|
|
349
|
+
} else if (entry.result !== undefined) {
|
|
350
|
+
const resultStr = typeof entry.result === 'object' ? JSON.stringify(entry.result) : String(entry.result);
|
|
351
|
+
lines.push(` Result: ${resultStr}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return lines.join('\n');
|
|
357
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// AI operations: vibe expression and execution
|
|
2
|
+
|
|
3
|
+
import * as AST from '../../ast';
|
|
4
|
+
import type { RuntimeState } from '../types';
|
|
5
|
+
import { resolveValue } from '../types';
|
|
6
|
+
import { currentFrame } from '../state';
|
|
7
|
+
import { scheduleAsyncOperation, isInAsyncContext } from '../async/scheduling';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract model name from expression (must be an identifier), or null if not provided.
|
|
11
|
+
*/
|
|
12
|
+
export function extractModelName(expr: AST.Expression | null): string | null {
|
|
13
|
+
if (expr === null) return null;
|
|
14
|
+
if (expr.type === 'Identifier') return expr.name;
|
|
15
|
+
throw new Error('Model must be an identifier');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Vibe/Do expression - push instructions for AI call.
|
|
20
|
+
* operationType determines tool loop behavior: 'vibe' = multi-turn, 'do' = single round.
|
|
21
|
+
* Sets inPromptContext flag so string literals use prompt interpolation semantics.
|
|
22
|
+
*/
|
|
23
|
+
export function execVibeExpression(state: RuntimeState, expr: AST.VibeExpression): RuntimeState {
|
|
24
|
+
return {
|
|
25
|
+
...state,
|
|
26
|
+
inPromptContext: true, // Prompt string interpolation mode
|
|
27
|
+
instructionStack: [
|
|
28
|
+
{ op: 'exec_expression', expr: expr.prompt, location: expr.prompt.location },
|
|
29
|
+
{ op: 'clear_prompt_context', location: expr.location }, // Clear flag after prompt eval
|
|
30
|
+
{ op: 'ai_vibe', model: extractModelName(expr.model), context: expr.context, operationType: expr.operationType, location: expr.location },
|
|
31
|
+
...state.instructionStack,
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get context data for AI based on context specifier.
|
|
38
|
+
* If context is null, defaults to 'default' (full execution history).
|
|
39
|
+
*/
|
|
40
|
+
export function getContextForAI(state: RuntimeState, context: AST.ContextSpecifier | null): unknown[] {
|
|
41
|
+
// Default to full execution history if no context specified
|
|
42
|
+
if (context === null) {
|
|
43
|
+
return state.executionLog;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
switch (context.kind) {
|
|
47
|
+
case 'local':
|
|
48
|
+
// Current frame's execution log only
|
|
49
|
+
return state.executionLog.filter((_, i) => {
|
|
50
|
+
// Filter to just recent entries (simplified - could be smarter)
|
|
51
|
+
return i >= state.executionLog.length - 10;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
case 'default':
|
|
55
|
+
// All execution history
|
|
56
|
+
return state.executionLog;
|
|
57
|
+
|
|
58
|
+
case 'variable':
|
|
59
|
+
// Use variable value as context
|
|
60
|
+
if (context.variable) {
|
|
61
|
+
const frame = currentFrame(state);
|
|
62
|
+
const variable = frame.locals[context.variable];
|
|
63
|
+
if (variable && Array.isArray(variable.value)) {
|
|
64
|
+
return variable.value as unknown[];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
|
|
69
|
+
default:
|
|
70
|
+
return state.executionLog;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* AI Vibe/Do - pause for AI response.
|
|
76
|
+
* Note: The prompt is added to orderedEntries in resumeWithAIResponse (after completion),
|
|
77
|
+
* not here, so it doesn't appear in context before the AI call completes.
|
|
78
|
+
*
|
|
79
|
+
* If model is null, uses lastUsedModel from state.
|
|
80
|
+
* If context is null, defaults to 'default' (full execution history).
|
|
81
|
+
*
|
|
82
|
+
* When currentAsyncVarName is set (async declaration), schedules the operation
|
|
83
|
+
* for non-blocking execution instead of pausing.
|
|
84
|
+
*/
|
|
85
|
+
export function execAIVibe(state: RuntimeState, model: string | null, context: AST.ContextSpecifier | null, operationType: 'do' | 'vibe'): RuntimeState {
|
|
86
|
+
// Unwrap VibeValue if needed before converting to string
|
|
87
|
+
const prompt = String(resolveValue(state.lastResult));
|
|
88
|
+
|
|
89
|
+
// Resolve model: use provided model or fall back to lastUsedModel
|
|
90
|
+
const resolvedModel = model ?? state.lastUsedModel;
|
|
91
|
+
if (!resolvedModel) {
|
|
92
|
+
throw new Error('No model specified and no previous model has been used. Please specify a model.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const contextData = getContextForAI(state, context);
|
|
96
|
+
const contextKind = context?.kind ?? 'default';
|
|
97
|
+
|
|
98
|
+
// Check if we're in async context (variable, destructuring, or fire-and-forget)
|
|
99
|
+
if (isInAsyncContext(state)) {
|
|
100
|
+
// Schedule for non-blocking execution using shared helper
|
|
101
|
+
const newState = scheduleAsyncOperation(
|
|
102
|
+
state,
|
|
103
|
+
{
|
|
104
|
+
type: operationType,
|
|
105
|
+
aiDetails: {
|
|
106
|
+
prompt,
|
|
107
|
+
model: resolvedModel,
|
|
108
|
+
context: contextData,
|
|
109
|
+
operationType,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
`async_${operationType}_scheduled`
|
|
113
|
+
);
|
|
114
|
+
// AI operations also update lastUsedModel
|
|
115
|
+
return { ...newState, lastUsedModel: resolvedModel };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Normal blocking execution
|
|
119
|
+
return {
|
|
120
|
+
...state,
|
|
121
|
+
status: 'awaiting_ai',
|
|
122
|
+
// Update lastUsedModel for compress to use
|
|
123
|
+
lastUsedModel: resolvedModel,
|
|
124
|
+
pendingAI: {
|
|
125
|
+
type: operationType, // 'do' = single round, 'vibe' = multi-turn
|
|
126
|
+
prompt,
|
|
127
|
+
model: resolvedModel,
|
|
128
|
+
context: contextData,
|
|
129
|
+
},
|
|
130
|
+
executionLog: [
|
|
131
|
+
...state.executionLog,
|
|
132
|
+
{
|
|
133
|
+
timestamp: Date.now(),
|
|
134
|
+
instructionType: operationType === 'do' ? 'ai_do_request' : 'ai_vibe_request',
|
|
135
|
+
details: { prompt, model: resolvedModel, contextKind },
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
}
|