@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,975 @@
|
|
|
1
|
+
// Re-export types (RuntimeStatus exported as enum below for backward compatibility)
|
|
2
|
+
export type {
|
|
3
|
+
RuntimeState,
|
|
4
|
+
StackFrame,
|
|
5
|
+
Variable,
|
|
6
|
+
ContextVariable,
|
|
7
|
+
ContextEntry,
|
|
8
|
+
AIOperation,
|
|
9
|
+
AIInteraction,
|
|
10
|
+
ExecutionEntry,
|
|
11
|
+
PendingAI,
|
|
12
|
+
PendingCompress,
|
|
13
|
+
PendingTS,
|
|
14
|
+
PendingToolCall,
|
|
15
|
+
TsModule,
|
|
16
|
+
VibeModule,
|
|
17
|
+
ExportedItem,
|
|
18
|
+
Instruction,
|
|
19
|
+
} from './types';
|
|
20
|
+
|
|
21
|
+
// Re-export module functions
|
|
22
|
+
export {
|
|
23
|
+
loadImports,
|
|
24
|
+
getImportedValue,
|
|
25
|
+
isImportedTsFunction,
|
|
26
|
+
isImportedVibeFunction,
|
|
27
|
+
getImportedVibeFunction,
|
|
28
|
+
getImportedTsFunction,
|
|
29
|
+
} from './modules';
|
|
30
|
+
|
|
31
|
+
// Re-export state functions
|
|
32
|
+
export {
|
|
33
|
+
createInitialState,
|
|
34
|
+
createFrame,
|
|
35
|
+
resumeWithAIResponse,
|
|
36
|
+
resumeWithUserInput,
|
|
37
|
+
resumeWithTsResult,
|
|
38
|
+
resumeWithImportedTsResult,
|
|
39
|
+
resumeWithToolResult,
|
|
40
|
+
resumeWithCompressResult,
|
|
41
|
+
pauseExecution,
|
|
42
|
+
resumeExecution,
|
|
43
|
+
currentFrame,
|
|
44
|
+
getVariable,
|
|
45
|
+
} from './state';
|
|
46
|
+
|
|
47
|
+
// Re-export TypeScript evaluation functions
|
|
48
|
+
export { evalTsBlock, validateReturnType, clearFunctionCache, getFunctionCacheSize, TsBlockError } from './ts-eval';
|
|
49
|
+
|
|
50
|
+
// Re-export step functions
|
|
51
|
+
export {
|
|
52
|
+
step,
|
|
53
|
+
stepN,
|
|
54
|
+
runUntilPause,
|
|
55
|
+
getNextInstruction,
|
|
56
|
+
stepUntilCondition,
|
|
57
|
+
stepUntilStatement,
|
|
58
|
+
stepUntilOp,
|
|
59
|
+
} from './step';
|
|
60
|
+
|
|
61
|
+
// Re-export context functions
|
|
62
|
+
export {
|
|
63
|
+
buildLocalContext,
|
|
64
|
+
buildGlobalContext,
|
|
65
|
+
formatContextForAI,
|
|
66
|
+
formatEntriesForSummarization,
|
|
67
|
+
type FormattedContext,
|
|
68
|
+
} from './context';
|
|
69
|
+
|
|
70
|
+
// Re-export serialization
|
|
71
|
+
export {
|
|
72
|
+
serializeState,
|
|
73
|
+
deserializeState,
|
|
74
|
+
cloneState,
|
|
75
|
+
deepCloneState,
|
|
76
|
+
getStateSummary,
|
|
77
|
+
} from './serialize';
|
|
78
|
+
|
|
79
|
+
// Re-export AI provider implementations
|
|
80
|
+
export { createRealAIProvider, createMockAIProvider } from './ai-provider';
|
|
81
|
+
|
|
82
|
+
// Re-export AI module
|
|
83
|
+
export * from './ai';
|
|
84
|
+
|
|
85
|
+
// Re-export AI interaction logging utilities
|
|
86
|
+
export { formatAIInteractions, dumpAIInteractions, saveAIInteractions } from './ai-logger';
|
|
87
|
+
|
|
88
|
+
// Re-export verbose logging
|
|
89
|
+
export { VerboseLogger, type VerboseLoggerOptions } from './verbose-logger';
|
|
90
|
+
export type { LogEvent, AILogMessage, TokenUsage } from './types';
|
|
91
|
+
|
|
92
|
+
// Legacy imports for backward compatibility
|
|
93
|
+
import * as AST from '../ast';
|
|
94
|
+
import { dirname } from 'path';
|
|
95
|
+
import type { SourceLocation } from '../errors';
|
|
96
|
+
import type { RuntimeState, AIInteraction } from './types';
|
|
97
|
+
import { resolveValue } from './types';
|
|
98
|
+
import { createInitialState, resumeWithAIResponse, resumeWithUserInput, resumeWithTsResult, resumeWithImportedTsResult, resumeWithToolResult, resumeWithCompressResult, resumeWithAsyncResults } from './state';
|
|
99
|
+
import type { VibeValue, PendingAsyncStart } from './types';
|
|
100
|
+
import { createVibeValue, isVibeValue } from './types';
|
|
101
|
+
import { awaitOperations, completeAsyncOperation, failAsyncOperation, createAsyncVibeError, startAsyncOperation } from './async';
|
|
102
|
+
import { step, runUntilPause } from './step';
|
|
103
|
+
import { evalTsBlock, TsBlockError } from './ts-eval';
|
|
104
|
+
import { loadImports, getImportedTsFunction, getImportedVibeFunction } from './modules';
|
|
105
|
+
import { createFunctionFrame } from './exec/functions';
|
|
106
|
+
import { buildLocalContext, buildGlobalContext, formatContextForAI, formatEntriesForSummarization } from './context';
|
|
107
|
+
import { saveAIInteractions } from './ai-logger';
|
|
108
|
+
import { deepCloneState } from './serialize';
|
|
109
|
+
|
|
110
|
+
// Token usage from AI providers
|
|
111
|
+
import type { TokenUsage } from './ai/types';
|
|
112
|
+
import type { ToolRoundResult } from './ai/tool-loop';
|
|
113
|
+
import type { AILogMessage, ContextEntry, PromptToolCall, LogEvent } from './types';
|
|
114
|
+
import { VerboseLogger } from './verbose-logger';
|
|
115
|
+
|
|
116
|
+
// AI execution result with optional usage and tool rounds
|
|
117
|
+
export interface AIExecutionResult {
|
|
118
|
+
value: unknown;
|
|
119
|
+
usage?: TokenUsage;
|
|
120
|
+
toolRounds?: ToolRoundResult[]; // Tool calling rounds that occurred during execution
|
|
121
|
+
// Context for logging (single source of truth)
|
|
122
|
+
messages?: AILogMessage[]; // Complete message sequence sent to model
|
|
123
|
+
executionContext?: ContextEntry[]; // Structured execution context
|
|
124
|
+
interactionToolCalls?: PromptToolCall[]; // Tool calls made during interaction
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// AI provider interface (for external callers)
|
|
128
|
+
export interface AIProvider {
|
|
129
|
+
execute(prompt: string): Promise<AIExecutionResult>;
|
|
130
|
+
generateCode(prompt: string): Promise<AIExecutionResult>;
|
|
131
|
+
askUser(prompt: string): Promise<string>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Runtime options
|
|
135
|
+
export interface RuntimeOptions {
|
|
136
|
+
basePath?: string; // Base path for resolving imports (defaults to cwd)
|
|
137
|
+
logAiInteractions?: boolean; // Capture detailed AI interaction logs for debugging (DEPRECATED: use verbose)
|
|
138
|
+
rootDir?: string; // Root directory for file operation sandboxing (defaults to cwd)
|
|
139
|
+
verbose?: boolean; // Enable verbose logging (JSONL events + context files)
|
|
140
|
+
logDir?: string; // Directory for verbose logs (default: .vibe-logs)
|
|
141
|
+
printToConsole?: boolean; // Print verbose logs to console (default: true)
|
|
142
|
+
writeToFile?: boolean; // Write verbose logs to file (default: true)
|
|
143
|
+
maxParallel?: number; // Max concurrent async operations (default: 4)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Legacy Runtime class - convenience wrapper around functional API
|
|
147
|
+
export class Runtime {
|
|
148
|
+
private state: RuntimeState;
|
|
149
|
+
private aiProvider: AIProvider;
|
|
150
|
+
private basePath: string;
|
|
151
|
+
private importsLoaded: boolean = false;
|
|
152
|
+
private logAiInteractions: boolean;
|
|
153
|
+
private verboseLogger: VerboseLogger | null = null;
|
|
154
|
+
|
|
155
|
+
constructor(program: AST.Program, aiProvider: AIProvider, options?: RuntimeOptions) {
|
|
156
|
+
this.logAiInteractions = options?.logAiInteractions ?? false;
|
|
157
|
+
this.state = createInitialState(program, {
|
|
158
|
+
logAiInteractions: this.logAiInteractions,
|
|
159
|
+
rootDir: options?.rootDir,
|
|
160
|
+
maxParallel: options?.maxParallel,
|
|
161
|
+
});
|
|
162
|
+
this.aiProvider = aiProvider;
|
|
163
|
+
this.basePath = options?.basePath ?? process.cwd() + '/main.vibe';
|
|
164
|
+
|
|
165
|
+
// Initialize verbose logger if enabled
|
|
166
|
+
if (options?.verbose) {
|
|
167
|
+
this.verboseLogger = new VerboseLogger({
|
|
168
|
+
logDir: options.logDir,
|
|
169
|
+
printToConsole: options.printToConsole,
|
|
170
|
+
writeToFile: options.writeToFile,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getState(): RuntimeState {
|
|
176
|
+
return { ...this.state };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
getValue(name: string): unknown {
|
|
180
|
+
const frame = this.state.callStack[this.state.callStack.length - 1];
|
|
181
|
+
if (!frame) return undefined;
|
|
182
|
+
|
|
183
|
+
const variable = frame.locals[name];
|
|
184
|
+
// Resolve VibeValue to its value for easier testing
|
|
185
|
+
return resolveValue(variable?.value);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Get raw value including VibeValue wrapper if present
|
|
189
|
+
getRawValue(name: string): unknown {
|
|
190
|
+
const frame = this.state.callStack[this.state.callStack.length - 1];
|
|
191
|
+
if (!frame) return undefined;
|
|
192
|
+
|
|
193
|
+
const variable = frame.locals[name];
|
|
194
|
+
return variable?.value;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Get all AI interactions (for debugging)
|
|
198
|
+
getAIInteractions(): AIInteraction[] {
|
|
199
|
+
return [...this.state.aiInteractions];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Run the program to completion, handling AI calls and TS evaluation
|
|
203
|
+
async run(): Promise<unknown> {
|
|
204
|
+
// Log run start
|
|
205
|
+
this.verboseLogger?.start(this.basePath);
|
|
206
|
+
|
|
207
|
+
// Load imports if not already loaded
|
|
208
|
+
if (!this.importsLoaded) {
|
|
209
|
+
this.state = await loadImports(this.state, this.basePath);
|
|
210
|
+
this.importsLoaded = true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Run until pause or complete
|
|
214
|
+
this.state = runUntilPause(this.state);
|
|
215
|
+
|
|
216
|
+
// Start any scheduled async operations (non-blocking)
|
|
217
|
+
await this.startScheduledAsyncOps();
|
|
218
|
+
|
|
219
|
+
// Handle AI calls, TS evaluation, tool calls, compress, and async in a loop
|
|
220
|
+
while (
|
|
221
|
+
this.state.status === 'awaiting_ai' ||
|
|
222
|
+
this.state.status === 'awaiting_user' ||
|
|
223
|
+
this.state.status === 'awaiting_ts' ||
|
|
224
|
+
this.state.status === 'awaiting_tool' ||
|
|
225
|
+
this.state.status === 'awaiting_compress' ||
|
|
226
|
+
this.state.status === 'awaiting_async'
|
|
227
|
+
) {
|
|
228
|
+
// Handle awaiting_async - wait for pending async operations
|
|
229
|
+
if (this.state.status === 'awaiting_async') {
|
|
230
|
+
// IMPORTANT: Start scheduled async ops BEFORE awaiting them
|
|
231
|
+
// Operations are scheduled (in pendingAsyncStarts) but their promises
|
|
232
|
+
// are only created when started
|
|
233
|
+
await this.startScheduledAsyncOps();
|
|
234
|
+
|
|
235
|
+
const results = await awaitOperations(
|
|
236
|
+
this.state.awaitingAsyncIds,
|
|
237
|
+
this.state.asyncOperations
|
|
238
|
+
);
|
|
239
|
+
this.state = resumeWithAsyncResults(this.state, results);
|
|
240
|
+
this.state = runUntilPause(this.state);
|
|
241
|
+
await this.startScheduledAsyncOps();
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (this.state.status === 'awaiting_ts') {
|
|
245
|
+
if (this.state.pendingTS) {
|
|
246
|
+
// Handle inline ts block evaluation
|
|
247
|
+
const { params, body, paramValues, location } = this.state.pendingTS;
|
|
248
|
+
|
|
249
|
+
// Log TS block start
|
|
250
|
+
const tsId = this.verboseLogger?.tsBlockStart(
|
|
251
|
+
params,
|
|
252
|
+
paramValues,
|
|
253
|
+
body,
|
|
254
|
+
{ file: location.file ?? this.basePath, line: location.line }
|
|
255
|
+
);
|
|
256
|
+
const tsStartTime = Date.now();
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const result = await evalTsBlock(params, body, paramValues, location);
|
|
260
|
+
this.state = resumeWithTsResult(this.state, result);
|
|
261
|
+
|
|
262
|
+
// Log TS block complete
|
|
263
|
+
if (tsId) {
|
|
264
|
+
this.verboseLogger?.tsBlockComplete(tsId, Date.now() - tsStartTime);
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
// Log TS block error
|
|
268
|
+
if (tsId) {
|
|
269
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
270
|
+
this.verboseLogger?.tsBlockComplete(tsId, Date.now() - tsStartTime, errMsg);
|
|
271
|
+
}
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
} else if (this.state.pendingImportedTsCall) {
|
|
275
|
+
// Handle imported TS function call
|
|
276
|
+
const { funcName, args, location } = this.state.pendingImportedTsCall;
|
|
277
|
+
const fn = getImportedTsFunction(this.state, funcName);
|
|
278
|
+
if (!fn) {
|
|
279
|
+
throw new Error(`Import error: Function '${funcName}' not found`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Log TS function start
|
|
283
|
+
const tsfId = this.verboseLogger?.tsFunctionStart(
|
|
284
|
+
funcName,
|
|
285
|
+
args,
|
|
286
|
+
{ file: location.file ?? this.basePath, line: location.line }
|
|
287
|
+
);
|
|
288
|
+
const tsfStartTime = Date.now();
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const result = await fn(...args);
|
|
292
|
+
this.state = resumeWithImportedTsResult(this.state, result);
|
|
293
|
+
|
|
294
|
+
// Log TS function complete
|
|
295
|
+
if (tsfId) {
|
|
296
|
+
this.verboseLogger?.tsFunctionComplete(tsfId, Date.now() - tsfStartTime);
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
// Log TS function error
|
|
300
|
+
if (tsfId) {
|
|
301
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
302
|
+
this.verboseLogger?.tsFunctionComplete(tsfId, Date.now() - tsfStartTime, errMsg);
|
|
303
|
+
}
|
|
304
|
+
// Wrap imported TS function error with Vibe location info
|
|
305
|
+
const originalError = error instanceof Error ? error : new Error(String(error));
|
|
306
|
+
throw new TsBlockError(
|
|
307
|
+
`Error in imported function '${funcName}': ${originalError.message}`,
|
|
308
|
+
[], // no params for imported functions
|
|
309
|
+
`/* imported function: ${funcName} */`,
|
|
310
|
+
originalError,
|
|
311
|
+
location
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
throw new Error('State awaiting TS but no pending TS request');
|
|
316
|
+
}
|
|
317
|
+
} else if (this.state.status === 'awaiting_ai') {
|
|
318
|
+
// Handle AI calls
|
|
319
|
+
if (!this.state.pendingAI) {
|
|
320
|
+
throw new Error('State awaiting AI but no pending AI request');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const startTime = Date.now();
|
|
324
|
+
const pendingAI = this.state.pendingAI;
|
|
325
|
+
|
|
326
|
+
// Get target type from next instruction
|
|
327
|
+
let targetType: string | null = null;
|
|
328
|
+
const nextInstruction = this.state.instructionStack[0];
|
|
329
|
+
if (nextInstruction?.op === 'declare_var' && nextInstruction.type) {
|
|
330
|
+
targetType = nextInstruction.type;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Get model details from state for logging
|
|
334
|
+
let modelDetails: AIInteraction['modelDetails'];
|
|
335
|
+
const modelVar = this.state.callStack[0]?.locals?.[pendingAI.model];
|
|
336
|
+
if (modelVar?.value && typeof modelVar.value === 'object') {
|
|
337
|
+
const mv = modelVar.value as Record<string, unknown>;
|
|
338
|
+
modelDetails = {
|
|
339
|
+
name: String(mv.name ?? ''),
|
|
340
|
+
provider: String(mv.provider ?? ''),
|
|
341
|
+
url: mv.url ? String(mv.url) : undefined,
|
|
342
|
+
thinkingLevel: mv.thinkingLevel ? String(mv.thinkingLevel) : undefined,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Log AI start (verbose logger)
|
|
347
|
+
const aiId = this.verboseLogger?.aiStart(
|
|
348
|
+
pendingAI.type,
|
|
349
|
+
pendingAI.model,
|
|
350
|
+
pendingAI.prompt,
|
|
351
|
+
{
|
|
352
|
+
model: pendingAI.model,
|
|
353
|
+
modelDetails,
|
|
354
|
+
type: pendingAI.type,
|
|
355
|
+
targetType,
|
|
356
|
+
messages: [], // Will be updated after execution
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
// vibe is the only AI expression type now
|
|
361
|
+
let result: AIExecutionResult;
|
|
362
|
+
let aiError: string | undefined;
|
|
363
|
+
try {
|
|
364
|
+
result = await this.aiProvider.execute(pendingAI.prompt);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
aiError = error instanceof Error ? error.message : String(error);
|
|
367
|
+
// Log AI error
|
|
368
|
+
if (aiId) {
|
|
369
|
+
this.verboseLogger?.aiComplete(aiId, Date.now() - startTime, undefined, 0, aiError);
|
|
370
|
+
}
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Log AI complete (verbose logger)
|
|
375
|
+
if (aiId) {
|
|
376
|
+
const toolCallCount = result.toolRounds?.reduce((sum, r) => sum + r.toolCalls.length, 0) ?? 0;
|
|
377
|
+
this.verboseLogger?.aiComplete(aiId, Date.now() - startTime, result.usage, toolCallCount);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Create interaction record if logging (legacy)
|
|
381
|
+
// Uses context from result (single source of truth from ai-provider)
|
|
382
|
+
let interaction: AIInteraction | undefined;
|
|
383
|
+
if (this.logAiInteractions) {
|
|
384
|
+
interaction = {
|
|
385
|
+
type: pendingAI.type,
|
|
386
|
+
prompt: pendingAI.prompt,
|
|
387
|
+
response: result.value,
|
|
388
|
+
timestamp: startTime,
|
|
389
|
+
model: pendingAI.model,
|
|
390
|
+
modelDetails,
|
|
391
|
+
targetType,
|
|
392
|
+
usage: result.usage,
|
|
393
|
+
durationMs: Date.now() - startTime,
|
|
394
|
+
// Context from ai-provider (single source of truth)
|
|
395
|
+
messages: result.messages ?? [],
|
|
396
|
+
executionContext: result.executionContext ?? [],
|
|
397
|
+
interactionToolCalls: result.interactionToolCalls,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
this.state = resumeWithAIResponse(this.state, result.value, interaction, result.toolRounds);
|
|
402
|
+
} else if (this.state.status === 'awaiting_tool') {
|
|
403
|
+
// Handle tool calls
|
|
404
|
+
if (!this.state.pendingToolCall) {
|
|
405
|
+
throw new Error('State awaiting tool but no pending tool call');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const { toolName, args, executor } = this.state.pendingToolCall;
|
|
409
|
+
|
|
410
|
+
// Log tool start (we don't have a parent AI ID here, use empty string)
|
|
411
|
+
this.verboseLogger?.toolStart('', toolName ?? 'unknown', args as Record<string, unknown>);
|
|
412
|
+
const toolStartTime = Date.now();
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
// Execute the tool with context - let errors propagate
|
|
416
|
+
const context = { rootDir: this.state.rootDir };
|
|
417
|
+
const result = await executor(args, context);
|
|
418
|
+
this.state = resumeWithToolResult(this.state, result);
|
|
419
|
+
|
|
420
|
+
// Log tool complete
|
|
421
|
+
this.verboseLogger?.toolComplete('', toolName ?? 'unknown', Date.now() - toolStartTime, true);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
424
|
+
this.verboseLogger?.toolComplete('', toolName ?? 'unknown', Date.now() - toolStartTime, false, errMsg);
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
} else if (this.state.status === 'awaiting_compress') {
|
|
428
|
+
// Handle compress AI summarization
|
|
429
|
+
if (!this.state.pendingCompress) {
|
|
430
|
+
throw new Error('State awaiting compress but no pending compress request');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const { prompt, scopeType } = this.state.pendingCompress;
|
|
434
|
+
|
|
435
|
+
// Build local context at end of loop (before we discard entries)
|
|
436
|
+
const localContext = buildLocalContext(this.state);
|
|
437
|
+
const contextFormatted = formatContextForAI(localContext, { includeInstructions: false });
|
|
438
|
+
|
|
439
|
+
// Build summarization prompt
|
|
440
|
+
const defaultPrompt = `Provide a concise summary of the most recent ${scopeType} loop execution. Focus on key results, state changes, and outcomes.`;
|
|
441
|
+
const summaryPrompt = `${prompt ?? defaultPrompt}\n\n${contextFormatted.text}`;
|
|
442
|
+
|
|
443
|
+
// Execute AI call for summarization (uses pendingCompress.model)
|
|
444
|
+
const result = await this.aiProvider.execute(summaryPrompt);
|
|
445
|
+
const summary = typeof result.value === 'string' ? result.value : String(result.value);
|
|
446
|
+
|
|
447
|
+
this.state = resumeWithCompressResult(this.state, summary);
|
|
448
|
+
} else {
|
|
449
|
+
// Handle user input
|
|
450
|
+
if (!this.state.pendingAI) {
|
|
451
|
+
throw new Error('State awaiting user but no pending AI request');
|
|
452
|
+
}
|
|
453
|
+
const response = await this.aiProvider.askUser(this.state.pendingAI.prompt);
|
|
454
|
+
this.state = resumeWithUserInput(this.state, response);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Continue running
|
|
458
|
+
this.state = runUntilPause(this.state);
|
|
459
|
+
|
|
460
|
+
// Start any newly scheduled async operations
|
|
461
|
+
await this.startScheduledAsyncOps();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (this.state.status === 'error') {
|
|
465
|
+
// Log run error
|
|
466
|
+
this.verboseLogger?.complete('error', this.state.error ?? 'Unknown error');
|
|
467
|
+
|
|
468
|
+
// Save logs even on error if logging enabled
|
|
469
|
+
this.saveLogsIfEnabled();
|
|
470
|
+
// Throw the original error object to preserve location info
|
|
471
|
+
throw this.state.errorObject ?? new Error(this.state.error ?? 'Unknown runtime error');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// At program completion, await any remaining pending async operations
|
|
475
|
+
// This implements "await at block boundary" for the main program
|
|
476
|
+
if (this.state.pendingAsyncIds.size > 0) {
|
|
477
|
+
const pendingIds = Array.from(this.state.pendingAsyncIds);
|
|
478
|
+
const results = await awaitOperations(pendingIds, this.state.asyncOperations);
|
|
479
|
+
this.state = resumeWithAsyncResults(
|
|
480
|
+
{ ...this.state, status: 'awaiting_async', awaitingAsyncIds: pendingIds },
|
|
481
|
+
results
|
|
482
|
+
);
|
|
483
|
+
// resumeWithAsyncResults sets status to 'running', so run until completion again
|
|
484
|
+
this.state = runUntilPause(this.state);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Log run complete
|
|
488
|
+
this.verboseLogger?.complete('completed');
|
|
489
|
+
|
|
490
|
+
// Save logs on successful completion
|
|
491
|
+
this.saveLogsIfEnabled();
|
|
492
|
+
|
|
493
|
+
// Resolve VibeValue to its value for the final return
|
|
494
|
+
return resolveValue(this.state.lastResult);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Save AI interaction logs if logging is enabled
|
|
498
|
+
private saveLogsIfEnabled(): void {
|
|
499
|
+
if (this.logAiInteractions && this.state.aiInteractions.length > 0) {
|
|
500
|
+
const projectRoot = dirname(this.basePath);
|
|
501
|
+
saveAIInteractions(this.state, projectRoot);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Start scheduled async operations as background Promises
|
|
506
|
+
private async startScheduledAsyncOps(): Promise<void> {
|
|
507
|
+
const pending = this.state.pendingAsyncStarts;
|
|
508
|
+
if (pending.length === 0) return;
|
|
509
|
+
|
|
510
|
+
// Clear pending starts
|
|
511
|
+
this.state = { ...this.state, pendingAsyncStarts: [] };
|
|
512
|
+
|
|
513
|
+
// Start each operation as a Promise (non-blocking)
|
|
514
|
+
for (const start of pending) {
|
|
515
|
+
const operation = this.state.asyncOperations.get(start.operationId);
|
|
516
|
+
if (!operation) continue;
|
|
517
|
+
|
|
518
|
+
if (start.aiDetails) {
|
|
519
|
+
// Start AI operation
|
|
520
|
+
const { prompt } = start.aiDetails;
|
|
521
|
+
|
|
522
|
+
// Create the Promise for this AI call
|
|
523
|
+
const promise = this.executeAIAsync(start.operationId, prompt);
|
|
524
|
+
|
|
525
|
+
// Store the promise in the operation (so awaiting_async can wait on it)
|
|
526
|
+
startAsyncOperation(operation, promise);
|
|
527
|
+
} else if (start.tsDetails) {
|
|
528
|
+
// Start TS block operation
|
|
529
|
+
const { params, body, paramValues, location } = start.tsDetails;
|
|
530
|
+
|
|
531
|
+
// Create the Promise for this TS eval
|
|
532
|
+
const promise = this.executeTsAsync(start.operationId, params, body, paramValues, location);
|
|
533
|
+
|
|
534
|
+
startAsyncOperation(operation, promise);
|
|
535
|
+
} else if (start.tsFuncDetails) {
|
|
536
|
+
// Start imported TS function operation
|
|
537
|
+
const { funcName, args, location } = start.tsFuncDetails;
|
|
538
|
+
|
|
539
|
+
// Create the Promise for this TS function call
|
|
540
|
+
const promise = this.executeTsFuncAsync(start.operationId, funcName, args, location);
|
|
541
|
+
|
|
542
|
+
startAsyncOperation(operation, promise);
|
|
543
|
+
} else if (start.vibeFuncDetails) {
|
|
544
|
+
// Start Vibe function operation
|
|
545
|
+
const { funcName, args, modulePath } = start.vibeFuncDetails;
|
|
546
|
+
|
|
547
|
+
// Create the Promise for this Vibe function call
|
|
548
|
+
const promise = this.executeVibeFuncAsync(start.operationId, funcName, args, modulePath);
|
|
549
|
+
|
|
550
|
+
startAsyncOperation(operation, promise);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Execute an AI call asynchronously (returns Promise that resolves to VibeValue)
|
|
556
|
+
private async executeAIAsync(
|
|
557
|
+
operationId: string,
|
|
558
|
+
prompt: string
|
|
559
|
+
): Promise<VibeValue> {
|
|
560
|
+
try {
|
|
561
|
+
const result = await this.aiProvider.execute(prompt);
|
|
562
|
+
|
|
563
|
+
// Create VibeValue with the result
|
|
564
|
+
const vibeValue = createVibeValue(result.value, {
|
|
565
|
+
source: 'ai',
|
|
566
|
+
toolCalls: result.toolRounds?.flatMap((round) =>
|
|
567
|
+
round.toolCalls.map((call, i) => {
|
|
568
|
+
const error = round.results[i]?.error;
|
|
569
|
+
return {
|
|
570
|
+
toolName: call.toolName,
|
|
571
|
+
args: call.args,
|
|
572
|
+
result: error ? null : String(round.results[i]?.result ?? ''),
|
|
573
|
+
err: !!error,
|
|
574
|
+
errDetails: error ? { message: error } : null,
|
|
575
|
+
duration: round.results[i]?.duration ?? 0,
|
|
576
|
+
};
|
|
577
|
+
})
|
|
578
|
+
) ?? [],
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// Mark operation as complete
|
|
582
|
+
completeAsyncOperation(this.state, operationId, vibeValue);
|
|
583
|
+
|
|
584
|
+
return vibeValue;
|
|
585
|
+
} catch (error) {
|
|
586
|
+
const vibeError = createAsyncVibeError(error);
|
|
587
|
+
failAsyncOperation(this.state, operationId, vibeError);
|
|
588
|
+
return createVibeValue(null, { source: 'ai', err: true, errDetails: vibeError });
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Execute a TS block asynchronously (returns Promise that resolves to VibeValue)
|
|
593
|
+
private async executeTsAsync(
|
|
594
|
+
operationId: string,
|
|
595
|
+
params: string[],
|
|
596
|
+
body: string,
|
|
597
|
+
paramValues: unknown[],
|
|
598
|
+
location: SourceLocation
|
|
599
|
+
): Promise<VibeValue> {
|
|
600
|
+
try {
|
|
601
|
+
const result = await evalTsBlock(params, body, paramValues, location);
|
|
602
|
+
|
|
603
|
+
// Create VibeValue with the result
|
|
604
|
+
const vibeValue = createVibeValue(result, { source: 'ts' });
|
|
605
|
+
|
|
606
|
+
// Mark operation as complete
|
|
607
|
+
completeAsyncOperation(this.state, operationId, vibeValue);
|
|
608
|
+
|
|
609
|
+
return vibeValue;
|
|
610
|
+
} catch (error) {
|
|
611
|
+
const vibeError = createAsyncVibeError(error, location);
|
|
612
|
+
failAsyncOperation(this.state, operationId, vibeError);
|
|
613
|
+
return createVibeValue(null, { source: 'ts', err: true, errDetails: vibeError });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Execute an imported TS function asynchronously (returns Promise that resolves to VibeValue)
|
|
618
|
+
private async executeTsFuncAsync(
|
|
619
|
+
operationId: string,
|
|
620
|
+
funcName: string,
|
|
621
|
+
args: unknown[],
|
|
622
|
+
location: SourceLocation
|
|
623
|
+
): Promise<VibeValue> {
|
|
624
|
+
try {
|
|
625
|
+
const fn = getImportedTsFunction(this.state, funcName);
|
|
626
|
+
if (!fn) {
|
|
627
|
+
throw new Error(`Import error: Function '${funcName}' not found`);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const result = await fn(...args);
|
|
631
|
+
|
|
632
|
+
// Create VibeValue with the result
|
|
633
|
+
const vibeValue = createVibeValue(result, { source: 'ts' });
|
|
634
|
+
|
|
635
|
+
// Mark operation as complete
|
|
636
|
+
completeAsyncOperation(this.state, operationId, vibeValue);
|
|
637
|
+
|
|
638
|
+
return vibeValue;
|
|
639
|
+
} catch (error) {
|
|
640
|
+
const vibeError = createAsyncVibeError(error, location);
|
|
641
|
+
failAsyncOperation(this.state, operationId, vibeError);
|
|
642
|
+
return createVibeValue(null, { source: 'ts', err: true, errDetails: vibeError });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Core helper to run a Vibe function in isolated state.
|
|
648
|
+
* Used by both top-level async calls and nested calls within isolated execution.
|
|
649
|
+
* The function runs to completion in isolation - only the return value persists.
|
|
650
|
+
*/
|
|
651
|
+
private async runVibeFuncIsolated(
|
|
652
|
+
sourceState: RuntimeState,
|
|
653
|
+
funcName: string,
|
|
654
|
+
args: unknown[],
|
|
655
|
+
modulePath?: string
|
|
656
|
+
): Promise<unknown> {
|
|
657
|
+
// Get the function declaration
|
|
658
|
+
let func: AST.FunctionDeclaration | undefined;
|
|
659
|
+
if (modulePath) {
|
|
660
|
+
func = getImportedVibeFunction(sourceState, funcName);
|
|
661
|
+
} else {
|
|
662
|
+
func = sourceState.functions[funcName];
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (!func) {
|
|
666
|
+
throw new Error(`ReferenceError: '${funcName}' is not defined`);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Deep clone the state for isolated execution
|
|
670
|
+
let isolatedState = deepCloneState(sourceState);
|
|
671
|
+
|
|
672
|
+
// Reset async-related state for the isolated execution
|
|
673
|
+
isolatedState.asyncOperations = new Map();
|
|
674
|
+
isolatedState.pendingAsyncIds = new Set();
|
|
675
|
+
isolatedState.asyncVarToOpId = new Map();
|
|
676
|
+
isolatedState.pendingAsyncStarts = [];
|
|
677
|
+
isolatedState.awaitingAsyncIds = [];
|
|
678
|
+
|
|
679
|
+
// Create the function frame
|
|
680
|
+
const newFrame = createFunctionFrame(funcName, func.params, args, modulePath);
|
|
681
|
+
|
|
682
|
+
// Set up the function call
|
|
683
|
+
const bodyInstructions = func.body.body.map((s) => ({
|
|
684
|
+
op: 'exec_statement' as const,
|
|
685
|
+
stmt: s,
|
|
686
|
+
location: s.location,
|
|
687
|
+
}));
|
|
688
|
+
|
|
689
|
+
isolatedState = {
|
|
690
|
+
...isolatedState,
|
|
691
|
+
status: 'running',
|
|
692
|
+
callStack: [...isolatedState.callStack, newFrame],
|
|
693
|
+
instructionStack: [
|
|
694
|
+
...bodyInstructions,
|
|
695
|
+
{ op: 'pop_frame' as const, location: func.body.location },
|
|
696
|
+
],
|
|
697
|
+
lastResult: null,
|
|
698
|
+
isInAsyncIsolation: true,
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
// Run the isolated state to completion
|
|
702
|
+
return this.runIsolatedState(isolatedState);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Execute a Vibe function asynchronously from main state.
|
|
707
|
+
*/
|
|
708
|
+
private async executeVibeFuncAsync(
|
|
709
|
+
operationId: string,
|
|
710
|
+
funcName: string,
|
|
711
|
+
args: unknown[],
|
|
712
|
+
modulePath?: string
|
|
713
|
+
): Promise<VibeValue> {
|
|
714
|
+
try {
|
|
715
|
+
const result = await this.runVibeFuncIsolated(this.state, funcName, args, modulePath);
|
|
716
|
+
|
|
717
|
+
// If result is already a VibeValue with error (from function returning error value), preserve it
|
|
718
|
+
if (isVibeValue(result) && result.err) {
|
|
719
|
+
completeAsyncOperation(this.state, operationId, result);
|
|
720
|
+
return result;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Otherwise wrap in a new VibeValue
|
|
724
|
+
const vibeValue = createVibeValue(result, { source: 'vibe-function' });
|
|
725
|
+
completeAsyncOperation(this.state, operationId, vibeValue);
|
|
726
|
+
return vibeValue;
|
|
727
|
+
} catch (error) {
|
|
728
|
+
const vibeError = createAsyncVibeError(error);
|
|
729
|
+
failAsyncOperation(this.state, operationId, vibeError);
|
|
730
|
+
return createVibeValue(null, { source: 'vibe-function', err: true, errDetails: vibeError });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Run an isolated state to completion, handling AI calls, TS blocks, etc.
|
|
736
|
+
* Returns the final lastResult value.
|
|
737
|
+
*/
|
|
738
|
+
private async runIsolatedState(state: RuntimeState): Promise<unknown> {
|
|
739
|
+
// Run until pause or complete
|
|
740
|
+
state = runUntilPause(state);
|
|
741
|
+
|
|
742
|
+
// Start any scheduled async operations in the isolated state
|
|
743
|
+
state = await this.startIsolatedAsyncOps(state);
|
|
744
|
+
|
|
745
|
+
// Handle AI calls, TS evaluation, async awaits, etc. in a loop
|
|
746
|
+
while (
|
|
747
|
+
state.status === 'awaiting_ai' ||
|
|
748
|
+
state.status === 'awaiting_ts' ||
|
|
749
|
+
state.status === 'awaiting_tool' ||
|
|
750
|
+
state.status === 'awaiting_async'
|
|
751
|
+
) {
|
|
752
|
+
if (state.status === 'awaiting_async') {
|
|
753
|
+
// Wait for pending async operations in isolated state
|
|
754
|
+
const results = await awaitOperations(
|
|
755
|
+
state.awaitingAsyncIds,
|
|
756
|
+
state.asyncOperations
|
|
757
|
+
);
|
|
758
|
+
state = resumeWithAsyncResults(state, results);
|
|
759
|
+
state = runUntilPause(state);
|
|
760
|
+
state = await this.startIsolatedAsyncOps(state);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (state.status === 'awaiting_ts') {
|
|
765
|
+
if (state.pendingTS) {
|
|
766
|
+
const { params, body, paramValues, location } = state.pendingTS;
|
|
767
|
+
const result = await evalTsBlock(params, body, paramValues, location);
|
|
768
|
+
state = resumeWithTsResult(state, result);
|
|
769
|
+
} else if (state.pendingImportedTsCall) {
|
|
770
|
+
const { funcName, args } = state.pendingImportedTsCall;
|
|
771
|
+
const fn = getImportedTsFunction(state, funcName);
|
|
772
|
+
if (!fn) {
|
|
773
|
+
throw new Error(`Import error: Function '${funcName}' not found`);
|
|
774
|
+
}
|
|
775
|
+
const result = await fn(...args);
|
|
776
|
+
state = resumeWithImportedTsResult(state, result);
|
|
777
|
+
}
|
|
778
|
+
} else if (state.status === 'awaiting_ai') {
|
|
779
|
+
if (!state.pendingAI) {
|
|
780
|
+
throw new Error('State awaiting AI but no pending AI request');
|
|
781
|
+
}
|
|
782
|
+
const result = await this.aiProvider.execute(state.pendingAI.prompt);
|
|
783
|
+
state = resumeWithAIResponse(state, result.value);
|
|
784
|
+
} else if (state.status === 'awaiting_tool') {
|
|
785
|
+
if (!state.pendingToolCall) {
|
|
786
|
+
throw new Error('State awaiting tool but no pending tool call');
|
|
787
|
+
}
|
|
788
|
+
const { args, executor } = state.pendingToolCall;
|
|
789
|
+
const context = { rootDir: state.rootDir };
|
|
790
|
+
const result = await executor(args, context);
|
|
791
|
+
state = resumeWithToolResult(state, result);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Continue running
|
|
795
|
+
state = runUntilPause(state);
|
|
796
|
+
|
|
797
|
+
// Start any newly scheduled async operations
|
|
798
|
+
state = await this.startIsolatedAsyncOps(state);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (state.status === 'error') {
|
|
802
|
+
throw state.errorObject ?? new Error(state.error ?? 'Unknown runtime error');
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Await any remaining pending async operations at function exit
|
|
806
|
+
if (state.pendingAsyncIds.size > 0) {
|
|
807
|
+
const pendingIds = Array.from(state.pendingAsyncIds);
|
|
808
|
+
const results = await awaitOperations(pendingIds, state.asyncOperations);
|
|
809
|
+
state = resumeWithAsyncResults(
|
|
810
|
+
{ ...state, status: 'awaiting_async', awaitingAsyncIds: pendingIds },
|
|
811
|
+
results
|
|
812
|
+
);
|
|
813
|
+
state = runUntilPause(state);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// If lastResult is a VibeValue with error, return it as-is to preserve error info
|
|
817
|
+
if (isVibeValue(state.lastResult) && state.lastResult.err) {
|
|
818
|
+
return state.lastResult;
|
|
819
|
+
}
|
|
820
|
+
return resolveValue(state.lastResult);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Start scheduled async operations in an isolated state.
|
|
825
|
+
* Similar to startScheduledAsyncOps but operates on passed state.
|
|
826
|
+
*/
|
|
827
|
+
private async startIsolatedAsyncOps(state: RuntimeState): Promise<RuntimeState> {
|
|
828
|
+
const pending = state.pendingAsyncStarts;
|
|
829
|
+
if (pending.length === 0) return state;
|
|
830
|
+
|
|
831
|
+
// Clear pending starts
|
|
832
|
+
state = { ...state, pendingAsyncStarts: [] };
|
|
833
|
+
|
|
834
|
+
// Start each operation as a Promise
|
|
835
|
+
for (const start of pending) {
|
|
836
|
+
const operation = state.asyncOperations.get(start.operationId);
|
|
837
|
+
if (!operation) continue;
|
|
838
|
+
|
|
839
|
+
if (start.aiDetails) {
|
|
840
|
+
// Start AI operation
|
|
841
|
+
const { prompt } = start.aiDetails;
|
|
842
|
+
|
|
843
|
+
// Create the Promise for this AI call (simpler version for isolated state)
|
|
844
|
+
const promise = (async () => {
|
|
845
|
+
try {
|
|
846
|
+
const result = await this.aiProvider.execute(prompt);
|
|
847
|
+
const vibeValue = createVibeValue(result.value, { source: 'ai' });
|
|
848
|
+
completeAsyncOperation(state, start.operationId, vibeValue);
|
|
849
|
+
return vibeValue;
|
|
850
|
+
} catch (error) {
|
|
851
|
+
const vibeError = createAsyncVibeError(error);
|
|
852
|
+
failAsyncOperation(state, start.operationId, vibeError);
|
|
853
|
+
return createVibeValue(null, { source: 'ai', err: true, errDetails: vibeError });
|
|
854
|
+
}
|
|
855
|
+
})();
|
|
856
|
+
|
|
857
|
+
startAsyncOperation(operation, promise);
|
|
858
|
+
} else if (start.tsDetails) {
|
|
859
|
+
// Start TS block operation
|
|
860
|
+
const { params, body, paramValues, location } = start.tsDetails;
|
|
861
|
+
|
|
862
|
+
const promise = (async () => {
|
|
863
|
+
try {
|
|
864
|
+
const result = await evalTsBlock(params, body, paramValues, location);
|
|
865
|
+
const vibeValue = createVibeValue(result, { source: 'ts' });
|
|
866
|
+
completeAsyncOperation(state, start.operationId, vibeValue);
|
|
867
|
+
return vibeValue;
|
|
868
|
+
} catch (error) {
|
|
869
|
+
const vibeError = createAsyncVibeError(error, location);
|
|
870
|
+
failAsyncOperation(state, start.operationId, vibeError);
|
|
871
|
+
return createVibeValue(null, { source: 'ts', err: true, errDetails: vibeError });
|
|
872
|
+
}
|
|
873
|
+
})();
|
|
874
|
+
|
|
875
|
+
startAsyncOperation(operation, promise);
|
|
876
|
+
} else if (start.tsFuncDetails) {
|
|
877
|
+
// Start imported TS function operation
|
|
878
|
+
const { funcName, args, location } = start.tsFuncDetails;
|
|
879
|
+
|
|
880
|
+
const promise = (async () => {
|
|
881
|
+
try {
|
|
882
|
+
const fn = getImportedTsFunction(state, funcName);
|
|
883
|
+
if (!fn) {
|
|
884
|
+
throw new Error(`Import error: Function '${funcName}' not found`);
|
|
885
|
+
}
|
|
886
|
+
const result = await fn(...args);
|
|
887
|
+
const vibeValue = createVibeValue(result, { source: 'ts' });
|
|
888
|
+
completeAsyncOperation(state, start.operationId, vibeValue);
|
|
889
|
+
return vibeValue;
|
|
890
|
+
} catch (error) {
|
|
891
|
+
const vibeError = createAsyncVibeError(error, location);
|
|
892
|
+
failAsyncOperation(state, start.operationId, vibeError);
|
|
893
|
+
return createVibeValue(null, { source: 'ts', err: true, errDetails: vibeError });
|
|
894
|
+
}
|
|
895
|
+
})();
|
|
896
|
+
|
|
897
|
+
startAsyncOperation(operation, promise);
|
|
898
|
+
} else if (start.vibeFuncDetails) {
|
|
899
|
+
// Nested Vibe function call - reuse shared helper
|
|
900
|
+
const { funcName, args, modulePath } = start.vibeFuncDetails;
|
|
901
|
+
|
|
902
|
+
const promise = (async () => {
|
|
903
|
+
try {
|
|
904
|
+
const result = await this.runVibeFuncIsolated(state, funcName, args, modulePath);
|
|
905
|
+
const vibeValue = createVibeValue(result, { source: 'vibe-function' });
|
|
906
|
+
completeAsyncOperation(state, start.operationId, vibeValue);
|
|
907
|
+
return vibeValue;
|
|
908
|
+
} catch (error) {
|
|
909
|
+
const vibeError = createAsyncVibeError(error);
|
|
910
|
+
failAsyncOperation(state, start.operationId, vibeError);
|
|
911
|
+
return createVibeValue(null, { source: 'vibe-function', err: true, errDetails: vibeError });
|
|
912
|
+
}
|
|
913
|
+
})();
|
|
914
|
+
|
|
915
|
+
startAsyncOperation(operation, promise);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return state;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Step through one instruction at a time
|
|
923
|
+
step(): RuntimeState {
|
|
924
|
+
this.state = step(this.state);
|
|
925
|
+
return this.state;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Run until pause point (AI call, user input, or completion)
|
|
929
|
+
runUntilPause(): RuntimeState {
|
|
930
|
+
this.state = runUntilPause(this.state);
|
|
931
|
+
return this.state;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Resume after providing AI response
|
|
935
|
+
resumeWithAIResponse(response: string): RuntimeState {
|
|
936
|
+
this.state = resumeWithAIResponse(this.state, response);
|
|
937
|
+
return this.state;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Resume after providing user input
|
|
941
|
+
resumeWithUserInput(input: string): RuntimeState {
|
|
942
|
+
this.state = resumeWithUserInput(this.state, input);
|
|
943
|
+
return this.state;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Get all logged events (for programmatic access / testing)
|
|
947
|
+
getLogEvents(): LogEvent[] {
|
|
948
|
+
return this.verboseLogger?.getEvents() ?? [];
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Get the main log file path (for debugging)
|
|
952
|
+
getMainLogPath(): string | null {
|
|
953
|
+
return this.verboseLogger?.getMainLogPath() ?? null;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Get the context directory path (for debugging)
|
|
957
|
+
getContextDir(): string | null {
|
|
958
|
+
return this.verboseLogger?.getContextDir() ?? null;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// Legacy enum for backward compatibility
|
|
963
|
+
export enum RuntimeStatus {
|
|
964
|
+
RUNNING = 'running',
|
|
965
|
+
AWAITING_AI_RESPONSE = 'awaiting_ai',
|
|
966
|
+
AWAITING_COMPRESS = 'awaiting_compress',
|
|
967
|
+
AWAITING_USER_INPUT = 'awaiting_user',
|
|
968
|
+
AWAITING_TS = 'awaiting_ts',
|
|
969
|
+
AWAITING_TOOL = 'awaiting_tool',
|
|
970
|
+
COMPLETED = 'completed',
|
|
971
|
+
ERROR = 'error',
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Re-export tool system
|
|
975
|
+
export * from './tools';
|