@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,160 @@
|
|
|
1
|
+
// Type validation and coercion utilities
|
|
2
|
+
|
|
3
|
+
import type { VibeType, VibeTypeRequired } from '../ast';
|
|
4
|
+
import { RuntimeError, type SourceLocation } from '../errors';
|
|
5
|
+
import { isVibeValue, resolveValue } from './types';
|
|
6
|
+
|
|
7
|
+
// Map array types to their element types (type-safe alternative to string slicing)
|
|
8
|
+
const ARRAY_ELEMENT_TYPES: Record<string, VibeTypeRequired> = {
|
|
9
|
+
'text[]': 'text',
|
|
10
|
+
'json[]': 'json',
|
|
11
|
+
'boolean[]': 'boolean',
|
|
12
|
+
'number[]': 'number',
|
|
13
|
+
'prompt[]': 'prompt',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validates a value against a type annotation and coerces if necessary.
|
|
18
|
+
* Returns { value, inferredType } where inferredType is set when no explicit type was given.
|
|
19
|
+
*/
|
|
20
|
+
export function validateAndCoerce(
|
|
21
|
+
value: unknown,
|
|
22
|
+
type: VibeType,
|
|
23
|
+
varName: string,
|
|
24
|
+
location?: SourceLocation,
|
|
25
|
+
source?: 'ai' | 'user'
|
|
26
|
+
): { value: unknown; inferredType: VibeType } {
|
|
27
|
+
// Resolve VibeValue unless this is a direct AI result to an UNTYPED variable
|
|
28
|
+
// (source === 'ai' means the value came directly from an AI call)
|
|
29
|
+
// For typed variables, always resolve so type validation can work on the primitive value
|
|
30
|
+
const keepVibeValueWrapper = source === 'ai' && type === null;
|
|
31
|
+
if (!keepVibeValueWrapper) {
|
|
32
|
+
value = resolveValue(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// null is valid for any typed variable
|
|
36
|
+
if (value === null) {
|
|
37
|
+
return { value: null, inferredType: type };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// If no type annotation, infer from JavaScript type
|
|
41
|
+
if (!type) {
|
|
42
|
+
// For VibeValue, infer type from the underlying value, not the wrapper
|
|
43
|
+
const valueToInfer = resolveValue(value);
|
|
44
|
+
|
|
45
|
+
if (typeof valueToInfer === 'string') {
|
|
46
|
+
return { value, inferredType: 'text' };
|
|
47
|
+
}
|
|
48
|
+
if (typeof valueToInfer === 'boolean') {
|
|
49
|
+
return { value, inferredType: 'boolean' };
|
|
50
|
+
}
|
|
51
|
+
if (typeof valueToInfer === 'number') {
|
|
52
|
+
return { value, inferredType: 'number' };
|
|
53
|
+
}
|
|
54
|
+
if (typeof valueToInfer === 'object' && valueToInfer !== null) {
|
|
55
|
+
return { value, inferredType: 'json' };
|
|
56
|
+
}
|
|
57
|
+
// For other types (null, undefined), no type inference
|
|
58
|
+
return { value, inferredType: null };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Validate array types (text[], json[], boolean[], number[], prompt[])
|
|
62
|
+
const elementType = ARRAY_ELEMENT_TYPES[type];
|
|
63
|
+
if (elementType) {
|
|
64
|
+
let arrayValue = value;
|
|
65
|
+
|
|
66
|
+
// If string, try to parse as JSON array
|
|
67
|
+
if (typeof value === 'string') {
|
|
68
|
+
try {
|
|
69
|
+
arrayValue = JSON.parse(value);
|
|
70
|
+
} catch {
|
|
71
|
+
throw new RuntimeError(`Variable '${varName}': invalid JSON array string`, location);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!Array.isArray(arrayValue)) {
|
|
76
|
+
throw new RuntimeError(`Variable '${varName}': expected ${type} (array), got ${typeof value}`, location);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Validate each element recursively
|
|
80
|
+
const validatedElements = arrayValue.map((elem, i) => {
|
|
81
|
+
const { value: validated } = validateAndCoerce(elem, elementType, `${varName}[${i}]`, location);
|
|
82
|
+
return validated;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return { value: validatedElements, inferredType: type };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Validate text type - must be a string
|
|
89
|
+
if (type === 'text') {
|
|
90
|
+
if (typeof value !== 'string') {
|
|
91
|
+
throw new RuntimeError(`Variable '${varName}': expected text (string), got ${typeof value}`, location);
|
|
92
|
+
}
|
|
93
|
+
return { value, inferredType: 'text' };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate json type - must be object (not array)
|
|
97
|
+
if (type === 'json') {
|
|
98
|
+
let result = value;
|
|
99
|
+
|
|
100
|
+
// If string, try to parse as JSON
|
|
101
|
+
if (typeof value === 'string') {
|
|
102
|
+
try {
|
|
103
|
+
result = JSON.parse(value);
|
|
104
|
+
} catch {
|
|
105
|
+
throw new RuntimeError(`Variable '${varName}': invalid JSON string`, location);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Validate the result is an object (not array, not primitive)
|
|
110
|
+
if (typeof result !== 'object' || result === null) {
|
|
111
|
+
throw new RuntimeError(`Variable '${varName}': expected json (object), got ${typeof value}`, location);
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(result)) {
|
|
114
|
+
throw new RuntimeError(`Variable '${varName}': json type expects an object, not an array. Use json[] for arrays.`, location);
|
|
115
|
+
}
|
|
116
|
+
return { value: result, inferredType: 'json' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Validate boolean type - must be a boolean
|
|
120
|
+
if (type === 'boolean') {
|
|
121
|
+
if (typeof value !== 'boolean') {
|
|
122
|
+
throw new RuntimeError(`Variable '${varName}': expected boolean, got ${typeof value}`, location);
|
|
123
|
+
}
|
|
124
|
+
return { value, inferredType: 'boolean' };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Validate number type - must be a finite number
|
|
128
|
+
if (type === 'number') {
|
|
129
|
+
if (typeof value !== 'number') {
|
|
130
|
+
throw new RuntimeError(`Variable '${varName}': expected number, got ${typeof value}`, location);
|
|
131
|
+
}
|
|
132
|
+
if (!Number.isFinite(value)) {
|
|
133
|
+
throw new RuntimeError(`Variable '${varName}': number must be finite, got ${value}`, location);
|
|
134
|
+
}
|
|
135
|
+
return { value, inferredType: 'number' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// For prompt type, accept string values as-is
|
|
139
|
+
return { value, inferredType: type };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Strict boolean check - no truthy coercion allowed.
|
|
144
|
+
* Throws if value is not a boolean.
|
|
145
|
+
*/
|
|
146
|
+
export function requireBoolean(value: unknown, context: string): boolean {
|
|
147
|
+
// Handle VibeValue with error - throw the error
|
|
148
|
+
if (isVibeValue(value) && value.err && value.errDetails) {
|
|
149
|
+
throw new Error(`${value.errDetails.type}: ${value.errDetails.message}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Auto-unwrap VibeValue
|
|
153
|
+
const unwrapped = resolveValue(value);
|
|
154
|
+
|
|
155
|
+
if (typeof unwrapped !== 'boolean') {
|
|
156
|
+
const valueType = unwrapped === null ? 'null' : typeof unwrapped;
|
|
157
|
+
throw new Error(`TypeError: ${context} must be a boolean, got ${valueType}`);
|
|
158
|
+
}
|
|
159
|
+
return unwrapped;
|
|
160
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verbose Logger - JSONL logging for AI interactions, TS executions, and tool calls
|
|
3
|
+
*
|
|
4
|
+
* Outputs:
|
|
5
|
+
* - Main log: JSONL events to console and .vibe-logs/run-{timestamp}.jsonl
|
|
6
|
+
* - Context files: .vibe-logs/run-{timestamp}/do-000001.txt, etc.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdirSync, writeFileSync, appendFileSync, existsSync } from 'fs';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import type {
|
|
12
|
+
LogEvent,
|
|
13
|
+
RunStartEvent,
|
|
14
|
+
RunCompleteEvent,
|
|
15
|
+
AIStartEvent,
|
|
16
|
+
AICompleteEvent,
|
|
17
|
+
ToolStartEvent,
|
|
18
|
+
ToolCompleteEvent,
|
|
19
|
+
TSStartEvent,
|
|
20
|
+
TSCompleteEvent,
|
|
21
|
+
AILogMessage,
|
|
22
|
+
TokenUsage,
|
|
23
|
+
} from './types';
|
|
24
|
+
|
|
25
|
+
export interface VerboseLoggerOptions {
|
|
26
|
+
logDir?: string; // Base directory for logs (default: .vibe-logs)
|
|
27
|
+
printToConsole?: boolean; // Print events to console (default: true)
|
|
28
|
+
writeToFile?: boolean; // Write events to file (default: true)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface AICallContext {
|
|
32
|
+
model: string;
|
|
33
|
+
modelDetails?: { name: string; provider: string; url?: string };
|
|
34
|
+
type: 'do' | 'vibe';
|
|
35
|
+
targetType: string | null;
|
|
36
|
+
messages: AILogMessage[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* VerboseLogger - Manages structured logging for Vibe runtime
|
|
41
|
+
*/
|
|
42
|
+
export class VerboseLogger {
|
|
43
|
+
private logDir: string;
|
|
44
|
+
private runTimestamp: string;
|
|
45
|
+
private mainLogPath: string;
|
|
46
|
+
private contextDir: string;
|
|
47
|
+
private printToConsole: boolean;
|
|
48
|
+
private writeToFile: boolean;
|
|
49
|
+
|
|
50
|
+
private seq = 0;
|
|
51
|
+
private counters = { do: 0, vibe: 0, ts: 0, tsf: 0 };
|
|
52
|
+
private events: LogEvent[] = [];
|
|
53
|
+
private startTime: number = 0;
|
|
54
|
+
|
|
55
|
+
constructor(options: VerboseLoggerOptions = {}) {
|
|
56
|
+
this.logDir = options.logDir ?? '.vibe-logs';
|
|
57
|
+
this.printToConsole = options.printToConsole ?? true;
|
|
58
|
+
this.writeToFile = options.writeToFile ?? true;
|
|
59
|
+
|
|
60
|
+
// Generate timestamp for this run
|
|
61
|
+
this.runTimestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
62
|
+
this.mainLogPath = join(this.logDir, `run-${this.runTimestamp}.jsonl`);
|
|
63
|
+
this.contextDir = join(this.logDir, `run-${this.runTimestamp}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Initialize the logger (create directories)
|
|
68
|
+
*/
|
|
69
|
+
private ensureDirectories(): void {
|
|
70
|
+
if (this.writeToFile) {
|
|
71
|
+
if (!existsSync(this.logDir)) {
|
|
72
|
+
mkdirSync(this.logDir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
if (!existsSync(this.contextDir)) {
|
|
75
|
+
mkdirSync(this.contextDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generate next ID for a given type
|
|
82
|
+
*/
|
|
83
|
+
private nextId(type: 'do' | 'vibe' | 'ts' | 'tsf'): string {
|
|
84
|
+
this.counters[type]++;
|
|
85
|
+
return `${type}-${String(this.counters[type]).padStart(6, '0')}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Log an event (console + file + in-memory)
|
|
90
|
+
*/
|
|
91
|
+
private logEvent(event: LogEvent): void {
|
|
92
|
+
this.events.push(event);
|
|
93
|
+
|
|
94
|
+
const jsonLine = JSON.stringify(event);
|
|
95
|
+
|
|
96
|
+
if (this.printToConsole) {
|
|
97
|
+
console.log(jsonLine);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (this.writeToFile) {
|
|
101
|
+
this.ensureDirectories();
|
|
102
|
+
appendFileSync(this.mainLogPath, jsonLine + '\n');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Write context file for an AI call
|
|
108
|
+
*/
|
|
109
|
+
private writeContextFile(id: string, context: AICallContext): void {
|
|
110
|
+
if (!this.writeToFile) return;
|
|
111
|
+
|
|
112
|
+
this.ensureDirectories();
|
|
113
|
+
|
|
114
|
+
const lines: string[] = [
|
|
115
|
+
`=== AI Call: ${id} ===`,
|
|
116
|
+
`Model: ${context.modelDetails?.name ?? context.model} (${context.modelDetails?.provider ?? 'unknown'})`,
|
|
117
|
+
`Type: ${context.type}`,
|
|
118
|
+
`Target: ${context.targetType ?? 'text'}`,
|
|
119
|
+
`Timestamp: ${new Date().toISOString()}`,
|
|
120
|
+
'',
|
|
121
|
+
'=== MESSAGES ===',
|
|
122
|
+
'',
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
for (const msg of context.messages) {
|
|
126
|
+
lines.push(`[${msg.role}]`);
|
|
127
|
+
lines.push(msg.content);
|
|
128
|
+
|
|
129
|
+
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
130
|
+
lines.push('');
|
|
131
|
+
lines.push('Tool calls:');
|
|
132
|
+
for (const tc of msg.toolCalls) {
|
|
133
|
+
lines.push(` - ${tc.toolName}(${JSON.stringify(tc.args)})`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (msg.toolResults && msg.toolResults.length > 0) {
|
|
138
|
+
lines.push('');
|
|
139
|
+
lines.push('Tool results:');
|
|
140
|
+
for (const tr of msg.toolResults) {
|
|
141
|
+
if (tr.error) {
|
|
142
|
+
lines.push(` - Error: ${tr.error}`);
|
|
143
|
+
} else {
|
|
144
|
+
lines.push(` - Result: ${JSON.stringify(tr.result)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
lines.push('');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
lines.push('=== END MESSAGES ===');
|
|
153
|
+
|
|
154
|
+
const filePath = join(this.contextDir, `${id}.txt`);
|
|
155
|
+
writeFileSync(filePath, lines.join('\n'));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Write context file for a TS block
|
|
160
|
+
*/
|
|
161
|
+
private writeTSContextFile(id: string, params: string[], paramValues: unknown[], body: string, location: { file: string; line: number }): void {
|
|
162
|
+
if (!this.writeToFile) return;
|
|
163
|
+
|
|
164
|
+
this.ensureDirectories();
|
|
165
|
+
|
|
166
|
+
const lines: string[] = [
|
|
167
|
+
`// TS Block: ${id}`,
|
|
168
|
+
`// Location: ${location.file}:${location.line}`,
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
if (params.length > 0) {
|
|
172
|
+
const paramStr = params.map((p, i) => `${p} = ${JSON.stringify(paramValues[i])}`).join(', ');
|
|
173
|
+
lines.push(`// Params: ${paramStr}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
lines.push('');
|
|
177
|
+
lines.push(body);
|
|
178
|
+
|
|
179
|
+
const filePath = join(this.contextDir, `${id}.ts`);
|
|
180
|
+
writeFileSync(filePath, lines.join('\n'));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Write context file for an imported TS function call
|
|
185
|
+
*/
|
|
186
|
+
private writeTSFunctionContextFile(id: string, funcName: string, args: unknown[], location: { file: string; line: number }): void {
|
|
187
|
+
if (!this.writeToFile) return;
|
|
188
|
+
|
|
189
|
+
this.ensureDirectories();
|
|
190
|
+
|
|
191
|
+
const lines: string[] = [
|
|
192
|
+
`// TS Function Call: ${id}`,
|
|
193
|
+
`// Function: ${funcName}`,
|
|
194
|
+
`// Location: ${location.file}:${location.line}`,
|
|
195
|
+
'',
|
|
196
|
+
`${funcName}(`,
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
for (let i = 0; i < args.length; i++) {
|
|
200
|
+
const comma = i < args.length - 1 ? ',' : '';
|
|
201
|
+
lines.push(` ${JSON.stringify(args[i], null, 2).split('\n').join('\n ')}${comma}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
lines.push(')');
|
|
205
|
+
|
|
206
|
+
const filePath = join(this.contextDir, `${id}.ts`);
|
|
207
|
+
writeFileSync(filePath, lines.join('\n'));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Public API
|
|
212
|
+
// ============================================================================
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Log run start
|
|
216
|
+
*/
|
|
217
|
+
start(file: string): void {
|
|
218
|
+
this.startTime = Date.now();
|
|
219
|
+
|
|
220
|
+
const event: RunStartEvent = {
|
|
221
|
+
seq: ++this.seq,
|
|
222
|
+
ts: new Date().toISOString(),
|
|
223
|
+
event: 'run_start',
|
|
224
|
+
file,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
this.logEvent(event);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Log run completion
|
|
232
|
+
*/
|
|
233
|
+
complete(status: 'completed' | 'error', error?: string): void {
|
|
234
|
+
const event: RunCompleteEvent = {
|
|
235
|
+
seq: ++this.seq,
|
|
236
|
+
ts: new Date().toISOString(),
|
|
237
|
+
event: 'run_complete',
|
|
238
|
+
durationMs: Date.now() - this.startTime,
|
|
239
|
+
status,
|
|
240
|
+
...(error && { error }),
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
this.logEvent(event);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Log AI call start - returns the ID for this call
|
|
248
|
+
*/
|
|
249
|
+
aiStart(
|
|
250
|
+
type: 'do' | 'vibe',
|
|
251
|
+
model: string,
|
|
252
|
+
prompt: string,
|
|
253
|
+
context: AICallContext
|
|
254
|
+
): string {
|
|
255
|
+
const id = this.nextId(type);
|
|
256
|
+
|
|
257
|
+
const event: AIStartEvent = {
|
|
258
|
+
seq: ++this.seq,
|
|
259
|
+
ts: new Date().toISOString(),
|
|
260
|
+
event: 'ai_start',
|
|
261
|
+
id,
|
|
262
|
+
type,
|
|
263
|
+
model,
|
|
264
|
+
prompt: prompt.length > 100 ? prompt.slice(0, 100) + '...' : prompt,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
this.logEvent(event);
|
|
268
|
+
|
|
269
|
+
// Write full context to file
|
|
270
|
+
this.writeContextFile(id, context);
|
|
271
|
+
|
|
272
|
+
return id;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Log AI call completion
|
|
277
|
+
*/
|
|
278
|
+
aiComplete(
|
|
279
|
+
id: string,
|
|
280
|
+
durationMs: number,
|
|
281
|
+
usage?: TokenUsage,
|
|
282
|
+
toolCallCount = 0,
|
|
283
|
+
error?: string
|
|
284
|
+
): void {
|
|
285
|
+
const event: AICompleteEvent = {
|
|
286
|
+
seq: ++this.seq,
|
|
287
|
+
ts: new Date().toISOString(),
|
|
288
|
+
event: 'ai_complete',
|
|
289
|
+
id,
|
|
290
|
+
durationMs,
|
|
291
|
+
...(usage && { tokens: { in: usage.inputTokens, out: usage.outputTokens } }),
|
|
292
|
+
toolCalls: toolCallCount,
|
|
293
|
+
...(error && { error }),
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
this.logEvent(event);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Log tool call start
|
|
301
|
+
*/
|
|
302
|
+
toolStart(parentId: string, tool: string, args: Record<string, unknown>): void {
|
|
303
|
+
const event: ToolStartEvent = {
|
|
304
|
+
seq: ++this.seq,
|
|
305
|
+
ts: new Date().toISOString(),
|
|
306
|
+
event: 'tool_start',
|
|
307
|
+
parentId,
|
|
308
|
+
tool,
|
|
309
|
+
args,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
this.logEvent(event);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Log tool call completion
|
|
317
|
+
*/
|
|
318
|
+
toolComplete(
|
|
319
|
+
parentId: string,
|
|
320
|
+
tool: string,
|
|
321
|
+
durationMs: number,
|
|
322
|
+
success: boolean,
|
|
323
|
+
error?: string
|
|
324
|
+
): void {
|
|
325
|
+
const event: ToolCompleteEvent = {
|
|
326
|
+
seq: ++this.seq,
|
|
327
|
+
ts: new Date().toISOString(),
|
|
328
|
+
event: 'tool_complete',
|
|
329
|
+
parentId,
|
|
330
|
+
tool,
|
|
331
|
+
durationMs,
|
|
332
|
+
success,
|
|
333
|
+
...(error && { error }),
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
this.logEvent(event);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Log TS block start - returns the ID
|
|
341
|
+
*/
|
|
342
|
+
tsBlockStart(
|
|
343
|
+
params: string[],
|
|
344
|
+
paramValues: unknown[],
|
|
345
|
+
body: string,
|
|
346
|
+
location: { file: string; line: number }
|
|
347
|
+
): string {
|
|
348
|
+
const id = this.nextId('ts');
|
|
349
|
+
|
|
350
|
+
const event: TSStartEvent = {
|
|
351
|
+
seq: ++this.seq,
|
|
352
|
+
ts: new Date().toISOString(),
|
|
353
|
+
event: 'ts_start',
|
|
354
|
+
id,
|
|
355
|
+
tsType: 'block',
|
|
356
|
+
params,
|
|
357
|
+
location,
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
this.logEvent(event);
|
|
361
|
+
|
|
362
|
+
// Write TS code to context file
|
|
363
|
+
this.writeTSContextFile(id, params, paramValues, body, location);
|
|
364
|
+
|
|
365
|
+
return id;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Log imported TS function start - returns the ID
|
|
370
|
+
*/
|
|
371
|
+
tsFunctionStart(
|
|
372
|
+
funcName: string,
|
|
373
|
+
args: unknown[],
|
|
374
|
+
location: { file: string; line: number }
|
|
375
|
+
): string {
|
|
376
|
+
const id = this.nextId('tsf');
|
|
377
|
+
|
|
378
|
+
const event: TSStartEvent = {
|
|
379
|
+
seq: ++this.seq,
|
|
380
|
+
ts: new Date().toISOString(),
|
|
381
|
+
event: 'ts_start',
|
|
382
|
+
id,
|
|
383
|
+
tsType: 'function',
|
|
384
|
+
name: funcName,
|
|
385
|
+
params: [], // We don't have param names for imported functions
|
|
386
|
+
location,
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
this.logEvent(event);
|
|
390
|
+
|
|
391
|
+
// Write function call to context file
|
|
392
|
+
this.writeTSFunctionContextFile(id, funcName, args, location);
|
|
393
|
+
|
|
394
|
+
return id;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Log TS block completion
|
|
399
|
+
*/
|
|
400
|
+
tsBlockComplete(id: string, durationMs: number, error?: string): void {
|
|
401
|
+
const event: TSCompleteEvent = {
|
|
402
|
+
seq: ++this.seq,
|
|
403
|
+
ts: new Date().toISOString(),
|
|
404
|
+
event: 'ts_complete',
|
|
405
|
+
id,
|
|
406
|
+
tsType: 'block',
|
|
407
|
+
durationMs,
|
|
408
|
+
...(error && { error }),
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
this.logEvent(event);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Log imported TS function completion
|
|
416
|
+
*/
|
|
417
|
+
tsFunctionComplete(id: string, durationMs: number, error?: string): void {
|
|
418
|
+
const event: TSCompleteEvent = {
|
|
419
|
+
seq: ++this.seq,
|
|
420
|
+
ts: new Date().toISOString(),
|
|
421
|
+
event: 'ts_complete',
|
|
422
|
+
id,
|
|
423
|
+
tsType: 'function',
|
|
424
|
+
durationMs,
|
|
425
|
+
...(error && { error }),
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
this.logEvent(event);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get all logged events (for testing/inspection)
|
|
433
|
+
*/
|
|
434
|
+
getEvents(): LogEvent[] {
|
|
435
|
+
return [...this.events];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Get the main log file path
|
|
440
|
+
*/
|
|
441
|
+
getMainLogPath(): string {
|
|
442
|
+
return this.mainLogPath;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Get the context directory path
|
|
447
|
+
*/
|
|
448
|
+
getContextDir(): string {
|
|
449
|
+
return this.contextDir;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Create a no-op logger (when verbose is disabled)
|
|
455
|
+
* Returns null - callers should check for null before logging
|
|
456
|
+
*/
|
|
457
|
+
export function createNoOpLogger(): null {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyzer Context
|
|
3
|
+
*
|
|
4
|
+
* Shared interface and types for semantic analyzer modules.
|
|
5
|
+
* Provides a clean way to pass analyzer state to helper functions.
|
|
6
|
+
*/
|
|
7
|
+
import type { SourceLocation } from '../errors';
|
|
8
|
+
import type { SymbolTable, SymbolKind } from './symbol-table';
|
|
9
|
+
import type { TsFunctionSignature } from './ts-signatures';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Context interface passed to analyzer helper functions.
|
|
13
|
+
* Contains the shared state needed for semantic analysis.
|
|
14
|
+
*/
|
|
15
|
+
export interface AnalyzerContext {
|
|
16
|
+
/** Symbol table for scope management */
|
|
17
|
+
symbols: SymbolTable;
|
|
18
|
+
|
|
19
|
+
/** Map of imported TS function names to their signatures */
|
|
20
|
+
tsImportSignatures: Map<string, TsFunctionSignature>;
|
|
21
|
+
|
|
22
|
+
/** Base path for resolving imports */
|
|
23
|
+
basePath?: string;
|
|
24
|
+
|
|
25
|
+
/** Original source code (for error messages) */
|
|
26
|
+
source?: string;
|
|
27
|
+
|
|
28
|
+
/** Whether currently inside a function body */
|
|
29
|
+
inFunction: boolean;
|
|
30
|
+
|
|
31
|
+
/** Whether at top level (not in a block) */
|
|
32
|
+
atTopLevel: boolean;
|
|
33
|
+
|
|
34
|
+
/** Current loop nesting depth (0 = not in a loop) */
|
|
35
|
+
loopDepth: number;
|
|
36
|
+
|
|
37
|
+
/** Report an error at the given location */
|
|
38
|
+
error(message: string, location: SourceLocation): void;
|
|
39
|
+
|
|
40
|
+
/** Declare a symbol in the current scope */
|
|
41
|
+
declare(
|
|
42
|
+
name: string,
|
|
43
|
+
kind: SymbolKind,
|
|
44
|
+
location: SourceLocation,
|
|
45
|
+
options?: {
|
|
46
|
+
paramCount?: number;
|
|
47
|
+
typeAnnotation?: string | null;
|
|
48
|
+
paramTypes?: string[];
|
|
49
|
+
returnType?: string | null;
|
|
50
|
+
}
|
|
51
|
+
): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Mutable context state that can be modified during analysis.
|
|
56
|
+
* Separate from AnalyzerContext to make mutation explicit.
|
|
57
|
+
*/
|
|
58
|
+
export interface AnalyzerState {
|
|
59
|
+
inFunction: boolean;
|
|
60
|
+
atTopLevel: boolean;
|
|
61
|
+
loopDepth: number;
|
|
62
|
+
}
|