@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.
Files changed (250) hide show
  1. package/package.json +46 -0
  2. package/src/ast/index.ts +375 -0
  3. package/src/ast.ts +2 -0
  4. package/src/debug/advanced-features.ts +482 -0
  5. package/src/debug/bun-inspector.ts +424 -0
  6. package/src/debug/handoff-manager.ts +283 -0
  7. package/src/debug/index.ts +150 -0
  8. package/src/debug/runner.ts +365 -0
  9. package/src/debug/server.ts +565 -0
  10. package/src/debug/stack-merger.ts +267 -0
  11. package/src/debug/state.ts +581 -0
  12. package/src/debug/test/advanced-features.test.ts +300 -0
  13. package/src/debug/test/e2e.test.ts +218 -0
  14. package/src/debug/test/handoff-manager.test.ts +256 -0
  15. package/src/debug/test/runner.test.ts +256 -0
  16. package/src/debug/test/stack-merger.test.ts +163 -0
  17. package/src/debug/test/state.test.ts +400 -0
  18. package/src/debug/test/ts-debug-integration.test.ts +374 -0
  19. package/src/debug/test/ts-import-tracker.test.ts +125 -0
  20. package/src/debug/test/ts-source-map.test.ts +169 -0
  21. package/src/debug/ts-import-tracker.ts +151 -0
  22. package/src/debug/ts-source-map.ts +171 -0
  23. package/src/errors/index.ts +124 -0
  24. package/src/index.ts +358 -0
  25. package/src/lexer/index.ts +348 -0
  26. package/src/lexer.ts +2 -0
  27. package/src/parser/index.ts +792 -0
  28. package/src/parser/parse.ts +45 -0
  29. package/src/parser/test/async.test.ts +248 -0
  30. package/src/parser/test/destructuring.test.ts +167 -0
  31. package/src/parser/test/do-expression.test.ts +486 -0
  32. package/src/parser/test/errors/do-expression.test.ts +95 -0
  33. package/src/parser/test/errors/error-locations.test.ts +230 -0
  34. package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
  35. package/src/parser/test/errors/missing-tokens.test.ts +126 -0
  36. package/src/parser/test/errors/model-declaration.test.ts +185 -0
  37. package/src/parser/test/errors/nested-blocks.test.ts +226 -0
  38. package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
  39. package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
  40. package/src/parser/test/import-export.test.ts +143 -0
  41. package/src/parser/test/literals.test.ts +404 -0
  42. package/src/parser/test/model-declaration.test.ts +161 -0
  43. package/src/parser/test/nested-blocks.test.ts +402 -0
  44. package/src/parser/test/parser.test.ts +743 -0
  45. package/src/parser/test/private.test.ts +136 -0
  46. package/src/parser/test/template-literal.test.ts +127 -0
  47. package/src/parser/test/tool-declaration.test.ts +302 -0
  48. package/src/parser/test/ts-block.test.ts +252 -0
  49. package/src/parser/test/type-annotations.test.ts +254 -0
  50. package/src/parser/visitor/helpers.ts +330 -0
  51. package/src/parser/visitor.ts +794 -0
  52. package/src/parser.ts +2 -0
  53. package/src/runtime/ai/cache-chunking.test.ts +69 -0
  54. package/src/runtime/ai/cache-chunking.ts +73 -0
  55. package/src/runtime/ai/client.ts +109 -0
  56. package/src/runtime/ai/context.ts +168 -0
  57. package/src/runtime/ai/formatters.ts +316 -0
  58. package/src/runtime/ai/index.ts +38 -0
  59. package/src/runtime/ai/language-ref.ts +38 -0
  60. package/src/runtime/ai/providers/anthropic.ts +253 -0
  61. package/src/runtime/ai/providers/google.ts +201 -0
  62. package/src/runtime/ai/providers/openai.ts +156 -0
  63. package/src/runtime/ai/retry.ts +100 -0
  64. package/src/runtime/ai/return-tools.ts +301 -0
  65. package/src/runtime/ai/test/client.test.ts +83 -0
  66. package/src/runtime/ai/test/formatters.test.ts +485 -0
  67. package/src/runtime/ai/test/retry.test.ts +137 -0
  68. package/src/runtime/ai/test/return-tools.test.ts +450 -0
  69. package/src/runtime/ai/test/tool-loop.test.ts +319 -0
  70. package/src/runtime/ai/test/tool-schema.test.ts +241 -0
  71. package/src/runtime/ai/tool-loop.ts +203 -0
  72. package/src/runtime/ai/tool-schema.ts +151 -0
  73. package/src/runtime/ai/types.ts +113 -0
  74. package/src/runtime/ai-logger.ts +255 -0
  75. package/src/runtime/ai-provider.ts +347 -0
  76. package/src/runtime/async/dependencies.ts +276 -0
  77. package/src/runtime/async/executor.ts +293 -0
  78. package/src/runtime/async/index.ts +43 -0
  79. package/src/runtime/async/scheduling.ts +163 -0
  80. package/src/runtime/async/test/dependencies.test.ts +284 -0
  81. package/src/runtime/async/test/executor.test.ts +388 -0
  82. package/src/runtime/context.ts +357 -0
  83. package/src/runtime/exec/ai.ts +139 -0
  84. package/src/runtime/exec/expressions.ts +475 -0
  85. package/src/runtime/exec/frames.ts +26 -0
  86. package/src/runtime/exec/functions.ts +305 -0
  87. package/src/runtime/exec/interpolation.ts +312 -0
  88. package/src/runtime/exec/statements.ts +604 -0
  89. package/src/runtime/exec/tools.ts +129 -0
  90. package/src/runtime/exec/typescript.ts +215 -0
  91. package/src/runtime/exec/variables.ts +279 -0
  92. package/src/runtime/index.ts +975 -0
  93. package/src/runtime/modules.ts +452 -0
  94. package/src/runtime/serialize.ts +103 -0
  95. package/src/runtime/state.ts +489 -0
  96. package/src/runtime/stdlib/core.ts +45 -0
  97. package/src/runtime/stdlib/directory.test.ts +156 -0
  98. package/src/runtime/stdlib/edit.test.ts +154 -0
  99. package/src/runtime/stdlib/fastEdit.test.ts +201 -0
  100. package/src/runtime/stdlib/glob.test.ts +106 -0
  101. package/src/runtime/stdlib/grep.test.ts +144 -0
  102. package/src/runtime/stdlib/index.ts +16 -0
  103. package/src/runtime/stdlib/readFile.test.ts +123 -0
  104. package/src/runtime/stdlib/tools/index.ts +707 -0
  105. package/src/runtime/stdlib/writeFile.test.ts +157 -0
  106. package/src/runtime/step.ts +969 -0
  107. package/src/runtime/test/ai-context.test.ts +1086 -0
  108. package/src/runtime/test/ai-result-object.test.ts +419 -0
  109. package/src/runtime/test/ai-tool-flow.test.ts +859 -0
  110. package/src/runtime/test/async-execution-order.test.ts +618 -0
  111. package/src/runtime/test/async-execution.test.ts +344 -0
  112. package/src/runtime/test/async-nested.test.ts +660 -0
  113. package/src/runtime/test/async-parallel-timing.test.ts +546 -0
  114. package/src/runtime/test/basic1.test.ts +154 -0
  115. package/src/runtime/test/binary-operators.test.ts +431 -0
  116. package/src/runtime/test/break-statement.test.ts +257 -0
  117. package/src/runtime/test/context-modes.test.ts +650 -0
  118. package/src/runtime/test/context.test.ts +466 -0
  119. package/src/runtime/test/core-functions.test.ts +228 -0
  120. package/src/runtime/test/e2e.test.ts +88 -0
  121. package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
  122. package/src/runtime/test/error-locations/main-error.vibe +4 -0
  123. package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
  124. package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
  125. package/src/runtime/test/for-in.test.ts +312 -0
  126. package/src/runtime/test/helpers.ts +69 -0
  127. package/src/runtime/test/imports.test.ts +334 -0
  128. package/src/runtime/test/json-expressions.test.ts +232 -0
  129. package/src/runtime/test/literals.test.ts +372 -0
  130. package/src/runtime/test/logical-indexing.test.ts +478 -0
  131. package/src/runtime/test/member-methods.test.ts +324 -0
  132. package/src/runtime/test/model-config.test.ts +338 -0
  133. package/src/runtime/test/null-handling.test.ts +342 -0
  134. package/src/runtime/test/private-visibility.test.ts +332 -0
  135. package/src/runtime/test/runtime-state.test.ts +514 -0
  136. package/src/runtime/test/scoping.test.ts +370 -0
  137. package/src/runtime/test/string-interpolation.test.ts +354 -0
  138. package/src/runtime/test/template-literal.test.ts +181 -0
  139. package/src/runtime/test/tool-execution.test.ts +467 -0
  140. package/src/runtime/test/tool-schema-generation.test.ts +477 -0
  141. package/src/runtime/test/tostring.test.ts +210 -0
  142. package/src/runtime/test/ts-block.test.ts +594 -0
  143. package/src/runtime/test/ts-error-location.test.ts +231 -0
  144. package/src/runtime/test/types.test.ts +732 -0
  145. package/src/runtime/test/verbose-logger.test.ts +710 -0
  146. package/src/runtime/test/vibe-expression.test.ts +54 -0
  147. package/src/runtime/test/vibe-value-errors.test.ts +541 -0
  148. package/src/runtime/test/while.test.ts +232 -0
  149. package/src/runtime/tools/builtin.ts +30 -0
  150. package/src/runtime/tools/directory-tools.ts +70 -0
  151. package/src/runtime/tools/file-tools.ts +228 -0
  152. package/src/runtime/tools/index.ts +5 -0
  153. package/src/runtime/tools/registry.ts +48 -0
  154. package/src/runtime/tools/search-tools.ts +134 -0
  155. package/src/runtime/tools/security.ts +36 -0
  156. package/src/runtime/tools/system-tools.ts +312 -0
  157. package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
  158. package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
  159. package/src/runtime/tools/test/registry.test.ts +713 -0
  160. package/src/runtime/tools/test/security.test.ts +86 -0
  161. package/src/runtime/tools/test/system-tools.test.ts +679 -0
  162. package/src/runtime/tools/test/ts-schema.test.ts +357 -0
  163. package/src/runtime/tools/ts-schema.ts +341 -0
  164. package/src/runtime/tools/types.ts +89 -0
  165. package/src/runtime/tools/utility-tools.ts +198 -0
  166. package/src/runtime/ts-eval.ts +126 -0
  167. package/src/runtime/types.ts +797 -0
  168. package/src/runtime/validation.ts +160 -0
  169. package/src/runtime/verbose-logger.ts +459 -0
  170. package/src/runtime.ts +2 -0
  171. package/src/semantic/analyzer-context.ts +62 -0
  172. package/src/semantic/analyzer-validators.ts +575 -0
  173. package/src/semantic/analyzer-visitors.ts +534 -0
  174. package/src/semantic/analyzer.ts +83 -0
  175. package/src/semantic/index.ts +11 -0
  176. package/src/semantic/symbol-table.ts +58 -0
  177. package/src/semantic/test/async-validation.test.ts +301 -0
  178. package/src/semantic/test/compress-validation.test.ts +179 -0
  179. package/src/semantic/test/const-reassignment.test.ts +111 -0
  180. package/src/semantic/test/control-flow.test.ts +346 -0
  181. package/src/semantic/test/destructuring.test.ts +185 -0
  182. package/src/semantic/test/duplicate-declarations.test.ts +168 -0
  183. package/src/semantic/test/export-validation.test.ts +111 -0
  184. package/src/semantic/test/fixtures/math.ts +31 -0
  185. package/src/semantic/test/imports.test.ts +148 -0
  186. package/src/semantic/test/json-type.test.ts +68 -0
  187. package/src/semantic/test/literals.test.ts +127 -0
  188. package/src/semantic/test/model-validation.test.ts +179 -0
  189. package/src/semantic/test/prompt-validation.test.ts +343 -0
  190. package/src/semantic/test/scoping.test.ts +312 -0
  191. package/src/semantic/test/tool-validation.test.ts +306 -0
  192. package/src/semantic/test/ts-type-checking.test.ts +563 -0
  193. package/src/semantic/test/type-constraints.test.ts +111 -0
  194. package/src/semantic/test/type-inference.test.ts +87 -0
  195. package/src/semantic/test/type-validation.test.ts +552 -0
  196. package/src/semantic/test/undefined-variables.test.ts +163 -0
  197. package/src/semantic/ts-block-checker.ts +204 -0
  198. package/src/semantic/ts-signatures.ts +194 -0
  199. package/src/semantic/ts-types.ts +170 -0
  200. package/src/semantic/types.ts +58 -0
  201. package/tests/fixtures/conditional-logic.vibe +14 -0
  202. package/tests/fixtures/function-call.vibe +16 -0
  203. package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
  204. package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
  205. package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
  206. package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
  207. package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
  208. package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
  209. package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
  210. package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
  211. package/tests/fixtures/imports/nested-import/main.vibe +3 -0
  212. package/tests/fixtures/imports/nested-import/utils.ts +3 -0
  213. package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
  214. package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
  215. package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
  216. package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
  217. package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
  218. package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
  219. package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
  220. package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
  221. package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
  222. package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
  223. package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
  224. package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
  225. package/tests/fixtures/imports/ts-import/main.vibe +4 -0
  226. package/tests/fixtures/imports/ts-import/math.ts +9 -0
  227. package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
  228. package/tests/fixtures/imports/ts-variables/data.ts +10 -0
  229. package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
  230. package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
  231. package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
  232. package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
  233. package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
  234. package/tests/fixtures/multiple-ai-calls.vibe +10 -0
  235. package/tests/fixtures/simple-greeting.vibe +6 -0
  236. package/tests/fixtures/template-literals.vibe +11 -0
  237. package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
  238. package/tests/integration/basic-ai/basic-ai.vibe +12 -0
  239. package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
  240. package/tests/integration/bug-fix/buggy-code.ts +22 -0
  241. package/tests/integration/bug-fix/fix-bug.vibe +21 -0
  242. package/tests/integration/compress/compress.integration.test.ts +206 -0
  243. package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
  244. package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
  245. package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
  246. package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
  247. package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
  248. package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
  249. package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
  250. package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
@@ -0,0 +1,316 @@
1
+ // Message formatting utilities for AI providers
2
+
3
+ import type { ToolSchema } from '../tools/types';
4
+
5
+ /** Message structure for AI providers */
6
+ export interface Message {
7
+ role: 'system' | 'user' | 'assistant';
8
+ content: string;
9
+ }
10
+
11
+ /**
12
+ * Build system message with Vibe processing instructions.
13
+ */
14
+ export function buildSystemMessage(): string {
15
+ return `You are an AI assistant integrated into the Vibe programming language runtime.
16
+ Your responses will be used programmatically in the execution flow.
17
+ Be concise and precise. Follow any type constraints exactly.
18
+ When context is provided, use it to inform your response.`;
19
+ }
20
+
21
+ /**
22
+ * Build context message from formatted context text.
23
+ * Returns null if context is empty.
24
+ */
25
+ export function buildContextMessage(contextText: string): string | null {
26
+ const trimmed = contextText.trim();
27
+ if (!trimmed) return null;
28
+
29
+ return `Here is the current program context:\n\n${trimmed}`;
30
+ }
31
+
32
+ /**
33
+ * Format a JsonSchema as a readable type string.
34
+ * Examples: "string", "number", "{name: string, age: number}", "string[]"
35
+ */
36
+ function formatJsonSchemaType(schema: ToolSchema['parameters'][0]['type']): string {
37
+ if (schema.type === 'array') {
38
+ const itemType = schema.items ? formatJsonSchemaType(schema.items) : 'any';
39
+ return `${itemType}[]`;
40
+ }
41
+
42
+ if (schema.type === 'object' && schema.properties) {
43
+ const props = Object.entries(schema.properties)
44
+ .map(([name, propSchema]) => `${name}: ${formatJsonSchemaType(propSchema)}`)
45
+ .join(', ');
46
+ return `{${props}}`;
47
+ }
48
+
49
+ return schema.type;
50
+ }
51
+
52
+ /**
53
+ * Build system message describing available tools.
54
+ * Returns null if no tools are provided.
55
+ */
56
+ export function buildToolSystemMessage(tools: ToolSchema[]): string | null {
57
+ if (!tools.length) return null;
58
+
59
+ const toolList = tools
60
+ .map((t) => {
61
+ // Format: name(param: type, param: type)
62
+ const signature = t.parameters
63
+ .map((p) => `${p.name}: ${formatJsonSchemaType(p.type)}`)
64
+ .join(', ');
65
+
66
+ const lines: string[] = [`- ${t.name}(${signature})`];
67
+
68
+ // Add tool description
69
+ if (t.description) {
70
+ lines.push(` ${t.description}`);
71
+ }
72
+
73
+ // Add parameter descriptions if any exist
74
+ const paramDescs = t.parameters.filter((p) => p.description);
75
+ if (paramDescs.length > 0) {
76
+ lines.push(' Parameters:');
77
+ for (const p of paramDescs) {
78
+ lines.push(` ${p.name}: ${p.description}`);
79
+ }
80
+ }
81
+
82
+ return lines.join('\n');
83
+ })
84
+ .join('\n');
85
+
86
+ return `You have access to the following tools:\n${toolList}\n\nCall tools when needed to complete the task.`;
87
+ }
88
+
89
+ /**
90
+ * Build the prompt message.
91
+ * Type instructions are no longer needed since we use tool-based returns.
92
+ */
93
+ export function buildPromptMessage(prompt: string): string {
94
+ return prompt;
95
+ }
96
+
97
+ /**
98
+ * Build messages array for chat-style APIs (OpenAI, Anthropic).
99
+ * Returns: [system, tools?, context?, prompt]
100
+ */
101
+ export function buildMessages(
102
+ prompt: string,
103
+ contextText: string,
104
+ tools?: ToolSchema[]
105
+ ): Message[] {
106
+ const messages: Message[] = [
107
+ { role: 'system', content: buildSystemMessage() },
108
+ ];
109
+
110
+ // Add tool descriptions as second system message
111
+ if (tools?.length) {
112
+ const toolMessage = buildToolSystemMessage(tools);
113
+ if (toolMessage) {
114
+ messages.push({ role: 'system', content: toolMessage });
115
+ }
116
+ }
117
+
118
+ const contextMessage = buildContextMessage(contextText);
119
+ if (contextMessage) {
120
+ messages.push({ role: 'user', content: contextMessage });
121
+ }
122
+
123
+ messages.push({ role: 'user', content: prompt });
124
+
125
+ return messages;
126
+ }
127
+
128
+ /**
129
+ * Extract text content from various API response formats.
130
+ */
131
+ export function extractTextContent(response: unknown): string {
132
+ // Anthropic format: { content: [{ type: "text", text: "..." }] }
133
+ if (hasProperty(response, 'content') && Array.isArray(response.content)) {
134
+ const textBlock = response.content.find(
135
+ (block: unknown) => hasProperty(block, 'type') && block.type === 'text'
136
+ );
137
+ if (textBlock && hasProperty(textBlock, 'text')) {
138
+ return String(textBlock.text);
139
+ }
140
+ }
141
+
142
+ // OpenAI format: { choices: [{ message: { content: "..." } }] }
143
+ if (hasProperty(response, 'choices') && Array.isArray(response.choices)) {
144
+ const first = response.choices[0];
145
+ if (hasProperty(first, 'message') && hasProperty(first.message, 'content')) {
146
+ return String(first.message.content);
147
+ }
148
+ }
149
+
150
+ // Google format: { candidates: [{ content: { parts: [{ text: "..." }] } }] }
151
+ if (hasProperty(response, 'candidates') && Array.isArray(response.candidates)) {
152
+ const first = response.candidates[0];
153
+ if (hasProperty(first, 'content') && hasProperty(first.content, 'parts')) {
154
+ const parts = first.content.parts as unknown[];
155
+ const textPart = parts.find((p: unknown) => hasProperty(p, 'text'));
156
+ if (textPart && hasProperty(textPart, 'text')) {
157
+ return String(textPart.text);
158
+ }
159
+ }
160
+ }
161
+
162
+ throw new Error('Unable to extract text content from response');
163
+ }
164
+
165
+ /**
166
+ * Type guard for checking if an object has a property.
167
+ */
168
+ function hasProperty<K extends string>(
169
+ obj: unknown,
170
+ key: K
171
+ ): obj is Record<K, unknown> {
172
+ return typeof obj === 'object' && obj !== null && key in obj;
173
+ }
174
+
175
+ /**
176
+ * Extract usage information from various API response formats.
177
+ */
178
+ export function extractUsage(
179
+ response: unknown
180
+ ): { inputTokens: number; outputTokens: number } | undefined {
181
+ // Anthropic format: { usage: { input_tokens, output_tokens } }
182
+ if (hasProperty(response, 'usage') && hasProperty(response.usage, 'input_tokens')) {
183
+ const usage = response.usage as Record<string, unknown>;
184
+ return {
185
+ inputTokens: Number(usage.input_tokens),
186
+ outputTokens: Number(usage.output_tokens ?? 0),
187
+ };
188
+ }
189
+
190
+ // OpenAI format: { usage: { prompt_tokens, completion_tokens } }
191
+ if (hasProperty(response, 'usage') && hasProperty(response.usage, 'prompt_tokens')) {
192
+ const usage = response.usage as Record<string, unknown>;
193
+ return {
194
+ inputTokens: Number(usage.prompt_tokens),
195
+ outputTokens: Number(usage.completion_tokens ?? 0),
196
+ };
197
+ }
198
+
199
+ // Google format: { usageMetadata: { promptTokenCount, candidatesTokenCount } }
200
+ if (hasProperty(response, 'usageMetadata')) {
201
+ const meta = response.usageMetadata as Record<string, unknown>;
202
+ return {
203
+ inputTokens: Number(meta.promptTokenCount ?? 0),
204
+ outputTokens: Number(meta.candidatesTokenCount ?? 0),
205
+ };
206
+ }
207
+
208
+ return undefined;
209
+ }
210
+
211
+ /**
212
+ * Scope parameter for vibe code generation.
213
+ */
214
+ export interface VibeScopeParam {
215
+ name: string;
216
+ type: string;
217
+ value: unknown;
218
+ }
219
+
220
+ /**
221
+ * Build system message for vibe code generation.
222
+ * Instructs the AI to generate a valid Vibe function.
223
+ */
224
+ export function buildVibeSystemMessage(scopeParams: VibeScopeParam[]): string {
225
+ const paramList = scopeParams
226
+ .map(p => ` - ${p.name}: ${p.type}`)
227
+ .join('\n');
228
+
229
+ return `You are generating a Vibe programming language function.
230
+
231
+ REQUIREMENTS:
232
+ 1. Generate EXACTLY ONE function declaration
233
+ 2. No markdown, no explanations, no code fences - ONLY the function code
234
+ 3. The function will receive these parameters from the calling scope:
235
+ ${paramList || ' (none)'}
236
+
237
+ 4. Use ONLY these parameters - no other variables are accessible
238
+ 5. The function must have a return statement
239
+
240
+ VIBE SYNTAX REFERENCE:
241
+ - Types: text, number, boolean, json, arrays (text[], number[])
242
+ - Variables: let x = value, const x = value
243
+ - Control flow: if/else, for x in array, while (condition)
244
+ - AI calls: vibe "prompt" model default (use the model parameter)
245
+ - TS blocks: ts(params) { return jsCode; }
246
+ - Return: return value
247
+
248
+ IMPORTANT - FUNCTION SIGNATURE:
249
+ - The FIRST parameter must always be 'model' (the AI model to use)
250
+ - Additional parameters are the scope variables listed above
251
+ - Example: function myFunc(model, x, y): text { ... }
252
+
253
+ RESTRICTIONS:
254
+ - You can ONLY use function parameters and locally-created variables
255
+ - No access to external functions or global variables
256
+ - For AI calls, use the 'model' parameter: vibe "prompt" model default
257
+
258
+ EXAMPLE:
259
+ function processData(model, items: text[], count: number): text {
260
+ let result = ""
261
+ for item in items {
262
+ result = result + item
263
+ }
264
+ return result
265
+ }
266
+
267
+ Generate ONLY the function code.`;
268
+ }
269
+
270
+ /**
271
+ * Build user prompt for vibe code generation.
272
+ */
273
+ export function buildVibePromptMessage(userPrompt: string, scopeParams: VibeScopeParam[]): string {
274
+ const paramContext = scopeParams
275
+ .map(p => `${p.name} (${p.type}) = ${formatVibeValue(p.value)}`)
276
+ .join('\n');
277
+
278
+ const contextSection = paramContext
279
+ ? `\nAvailable parameters and their current values:\n${paramContext}\n`
280
+ : '';
281
+
282
+ return `Task: ${userPrompt}${contextSection}
283
+ Generate a function that accomplishes this task using only the available parameters.`;
284
+ }
285
+
286
+ /**
287
+ * Format a value for display in vibe context.
288
+ */
289
+ function formatVibeValue(value: unknown): string {
290
+ if (value === null || value === undefined) return 'null';
291
+ if (typeof value === 'string') {
292
+ if (value.length > 50) return `"${value.substring(0, 50)}..."`;
293
+ return `"${value}"`;
294
+ }
295
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
296
+ if (Array.isArray(value)) {
297
+ if (value.length === 0) return '[]';
298
+ if (value.length > 3) return `[${formatVibeValue(value[0])}, ... (${value.length} items)]`;
299
+ return `[${value.map(formatVibeValue).join(', ')}]`;
300
+ }
301
+ return JSON.stringify(value).substring(0, 50);
302
+ }
303
+
304
+ /**
305
+ * Build messages array for vibe code generation.
306
+ * Returns: [system, prompt]
307
+ */
308
+ export function buildVibeMessages(
309
+ userPrompt: string,
310
+ scopeParams: VibeScopeParam[]
311
+ ): Message[] {
312
+ return [
313
+ { role: 'system', content: buildVibeSystemMessage(scopeParams) },
314
+ { role: 'user', content: buildVibePromptMessage(userPrompt, scopeParams) },
315
+ ];
316
+ }
@@ -0,0 +1,38 @@
1
+ // AI Module - Re-exports
2
+
3
+ // Types
4
+ export type {
5
+ AIProviderType,
6
+ TargetType,
7
+ ModelConfig,
8
+ AIRequest,
9
+ AIResponse,
10
+ ProviderExecutor,
11
+ RetryOptions,
12
+ } from './types';
13
+ export { AIError } from './types';
14
+
15
+ // Client
16
+ export {
17
+ executeAI,
18
+ detectProvider,
19
+ getProviderExecutor,
20
+ buildAIRequest,
21
+ type VibeModelValue,
22
+ } from './client';
23
+
24
+ // Providers
25
+ export { executeOpenAI, OPENAI_CONFIG } from './providers/openai';
26
+ export { executeAnthropic, ANTHROPIC_CONFIG } from './providers/anthropic';
27
+ export { executeGoogle } from './providers/google';
28
+
29
+ // Utilities
30
+ export { withRetry, isRetryableError, calculateDelay, createAIErrorFromResponse } from './retry';
31
+ export {
32
+ buildSystemMessage,
33
+ buildContextMessage,
34
+ buildPromptMessage,
35
+ buildMessages,
36
+ extractTextContent,
37
+ extractUsage,
38
+ } from './formatters';
@@ -0,0 +1,38 @@
1
+ // Language reference loader for AI context
2
+ // Loads and caches the Vibe language reference document
3
+
4
+ import { readFileSync } from 'fs';
5
+ import { join, dirname } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ let cachedLanguageRef: string | null = null;
9
+
10
+ /**
11
+ * Get the Vibe language reference document content.
12
+ * Cached after first load.
13
+ */
14
+ export function getLanguageReference(): string {
15
+ if (cachedLanguageRef !== null) {
16
+ return cachedLanguageRef;
17
+ }
18
+
19
+ try {
20
+ // Navigate from src/runtime/ai/ to docs/
21
+ const currentDir = dirname(fileURLToPath(import.meta.url));
22
+ const docsPath = join(currentDir, '..', '..', '..', 'docs', 'language-reference.md');
23
+ cachedLanguageRef = readFileSync(docsPath, 'utf-8');
24
+ return cachedLanguageRef;
25
+ } catch {
26
+ // If file not found (e.g., in bundled distribution), return empty
27
+ cachedLanguageRef = '';
28
+ return cachedLanguageRef;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Clear the cached language reference.
34
+ * Useful for testing.
35
+ */
36
+ export function clearLanguageRefCache(): void {
37
+ cachedLanguageRef = null;
38
+ }
@@ -0,0 +1,253 @@
1
+ // Anthropic Provider Implementation using official SDK
2
+
3
+ import Anthropic from '@anthropic-ai/sdk';
4
+ import type { AIRequest, AIResponse, AIToolCall, ThinkingLevel } from '../types';
5
+ import { AIError } from '../types';
6
+ import { buildSystemMessage, buildPromptMessage, buildToolSystemMessage } from '../formatters';
7
+ import { chunkContextForCaching } from '../cache-chunking';
8
+ import { toAnthropicTools } from '../tool-schema';
9
+
10
+ /** Anthropic provider configuration */
11
+ export const ANTHROPIC_CONFIG = {
12
+ defaultUrl: 'https://api.anthropic.com',
13
+ };
14
+
15
+ /** Beta headers */
16
+ const EXTENDED_THINKING_BETA = 'interleaved-thinking-2025-05-14';
17
+
18
+ /** Map thinking level to Anthropic budget_tokens */
19
+ const THINKING_BUDGET_MAP: Record<ThinkingLevel, number> = {
20
+ none: 0,
21
+ low: 1024, // Minimum required
22
+ medium: 4096,
23
+ high: 10240,
24
+ max: 32768,
25
+ };
26
+
27
+ /**
28
+ * Execute an AI request using the Anthropic SDK.
29
+ */
30
+ export async function executeAnthropic(request: AIRequest): Promise<AIResponse> {
31
+ const { prompt, contextText, model, tools, previousToolCalls, toolResults, messages: overrideMessages } = request;
32
+
33
+ // Create Anthropic client
34
+ const client = new Anthropic({
35
+ apiKey: model.apiKey,
36
+ baseURL: model.url ?? ANTHROPIC_CONFIG.defaultUrl,
37
+ });
38
+
39
+ // If messages are provided (e.g., for vibe), use simplified path
40
+ if (overrideMessages) {
41
+ return executeWithOverrideMessages(client, model, overrideMessages);
42
+ }
43
+
44
+ // Build system messages
45
+ const systemMessage = buildSystemMessage();
46
+ const toolSystemMessage = tools?.length ? buildToolSystemMessage(tools) : null;
47
+
48
+ // Build prompt message
49
+ const promptMessage = buildPromptMessage(prompt);
50
+
51
+ // Chunk context for progressive caching
52
+ const { chunks, cacheBreakpointIndex } = chunkContextForCaching(contextText);
53
+
54
+ // Build initial user messages: context chunks + prompt
55
+ const initialUserMessages: Record<string, unknown>[] = [
56
+ // Context chunks as separate messages (if any)
57
+ ...chunks.map((chunk, i) => ({
58
+ role: 'user' as const,
59
+ content: i === 0 ? `Here is the current program context:\n\n${chunk.content}` : chunk.content,
60
+ // Cache control on 2nd-to-last chunk to allow latest chunk to change
61
+ ...(i === cacheBreakpointIndex ? { cache_control: { type: 'ephemeral' as const } } : {}),
62
+ })),
63
+ // Prompt is always last (never cached)
64
+ { role: 'user' as const, content: promptMessage },
65
+ ];
66
+
67
+ // Build conversation messages including tool results if this is a follow-up
68
+ let allMessages = initialUserMessages;
69
+ if (previousToolCalls?.length && toolResults?.length) {
70
+ // Add assistant message with tool_use blocks
71
+ const assistantContent = previousToolCalls.map(call => ({
72
+ type: 'tool_use' as const,
73
+ id: call.id,
74
+ name: call.toolName,
75
+ input: call.args,
76
+ }));
77
+
78
+ // Add user message with tool_result blocks
79
+ const toolResultContent = toolResults.map(result => ({
80
+ type: 'tool_result' as const,
81
+ tool_use_id: result.toolCallId,
82
+ content: result.error
83
+ ? `Error: ${result.error}`
84
+ : (typeof result.result === 'string' ? result.result : JSON.stringify(result.result)),
85
+ }));
86
+
87
+ allMessages = [
88
+ ...initialUserMessages,
89
+ { role: 'assistant' as const, content: assistantContent },
90
+ { role: 'user' as const, content: toolResultContent },
91
+ ];
92
+ }
93
+
94
+ try {
95
+ // Build system array with optional tool descriptions
96
+ const systemBlocks = [
97
+ {
98
+ type: 'text',
99
+ text: systemMessage,
100
+ cache_control: { type: 'ephemeral' },
101
+ },
102
+ ];
103
+ if (toolSystemMessage) {
104
+ systemBlocks.push({
105
+ type: 'text',
106
+ text: toolSystemMessage,
107
+ cache_control: { type: 'ephemeral' },
108
+ });
109
+ }
110
+
111
+ // Build request params with cache control on system message
112
+ const params: Record<string, unknown> = {
113
+ model: model.name,
114
+ max_tokens: 16384, // Increased to support thinking tokens
115
+ system: systemBlocks,
116
+ messages: allMessages,
117
+ };
118
+
119
+ // Add tools if provided
120
+ if (tools?.length) {
121
+ params.tools = toAnthropicTools(tools);
122
+ }
123
+
124
+ // Add extended thinking if level specified and not 'none'
125
+ const thinkingLevel = model.thinkingLevel as ThinkingLevel | undefined;
126
+ const thinkingBudget = thinkingLevel ? THINKING_BUDGET_MAP[thinkingLevel] : 0;
127
+ if (thinkingBudget > 0) {
128
+ params.thinking = {
129
+ type: 'enabled',
130
+ budget_tokens: thinkingBudget,
131
+ };
132
+ }
133
+
134
+ // Make API request - use beta endpoint for extended thinking
135
+ // Response type is Message (non-streaming since we don't pass stream: true)
136
+ const response = thinkingBudget > 0
137
+ ? await client.beta.messages.create({
138
+ ...params,
139
+ betas: [EXTENDED_THINKING_BETA],
140
+ } as Parameters<typeof client.beta.messages.create>[0])
141
+ : await client.messages.create(params as unknown as Parameters<typeof client.messages.create>[0]);
142
+
143
+ // Cast to Message type (we know it's not streaming)
144
+ const message = response as Anthropic.Message;
145
+
146
+ // Extract text content from response
147
+ const textBlock = message.content.find((block): block is Anthropic.TextBlock => block.type === 'text');
148
+ const content = textBlock?.text ?? '';
149
+
150
+ // Extract tool_use blocks
151
+ const toolUseBlocks = message.content.filter(
152
+ (block): block is Anthropic.ToolUseBlock => block.type === 'tool_use'
153
+ );
154
+ let toolCalls: AIToolCall[] | undefined;
155
+ if (toolUseBlocks.length > 0) {
156
+ toolCalls = toolUseBlocks.map((block) => ({
157
+ id: block.id,
158
+ toolName: block.name,
159
+ args: block.input as Record<string, unknown>,
160
+ }));
161
+ }
162
+
163
+ // Determine stop reason
164
+ const stopReason =
165
+ message.stop_reason === 'tool_use'
166
+ ? 'tool_use'
167
+ : message.stop_reason === 'max_tokens'
168
+ ? 'length'
169
+ : 'end';
170
+
171
+ // Extract usage including cache and thinking tokens
172
+ const rawUsage = message.usage as unknown as Record<string, unknown> | undefined;
173
+ const usage = rawUsage
174
+ ? {
175
+ inputTokens: Number(rawUsage.input_tokens ?? 0),
176
+ outputTokens: Number(rawUsage.output_tokens ?? 0),
177
+ cachedInputTokens: rawUsage.cache_read_input_tokens ? Number(rawUsage.cache_read_input_tokens) : undefined,
178
+ cacheCreationTokens: rawUsage.cache_creation_input_tokens ? Number(rawUsage.cache_creation_input_tokens) : undefined,
179
+ thinkingTokens: rawUsage.thinking_tokens ? Number(rawUsage.thinking_tokens) : undefined,
180
+ }
181
+ : undefined;
182
+
183
+ // For text responses, parsedValue is just the content
184
+ // For typed responses, the value comes from return tool calls (handled by tool-loop)
185
+ return { content, parsedValue: content, usage, toolCalls, stopReason };
186
+ } catch (error) {
187
+ if (error instanceof Anthropic.APIError) {
188
+ const isRetryable = error.status === 429 || (error.status ?? 0) >= 500;
189
+ throw new AIError(
190
+ `Anthropic API error (${error.status}): ${error.message}`,
191
+ error.status,
192
+ isRetryable
193
+ );
194
+ }
195
+ throw error;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Execute with override messages (used by vibe for custom system prompt).
201
+ * Simpler path without context chunking, caching, or structured output.
202
+ */
203
+ async function executeWithOverrideMessages(
204
+ client: Anthropic,
205
+ model: AIRequest['model'],
206
+ messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>
207
+ ): Promise<AIResponse> {
208
+ // Separate system messages from user/assistant messages
209
+ const systemMessages = messages.filter(m => m.role === 'system');
210
+ const conversationMessages = messages.filter(m => m.role !== 'system');
211
+
212
+ // Build system content
213
+ const systemContent = systemMessages.map(m => m.content).join('\n\n');
214
+
215
+ try {
216
+ const response = await client.messages.create({
217
+ model: model.name,
218
+ max_tokens: 4096,
219
+ system: systemContent,
220
+ messages: conversationMessages.map(m => ({
221
+ role: m.role as 'user' | 'assistant',
222
+ content: m.content,
223
+ })),
224
+ });
225
+
226
+ // Extract text content
227
+ const textBlock = response.content.find(
228
+ (block): block is Anthropic.TextBlock => block.type === 'text'
229
+ );
230
+ const content = textBlock?.text ?? '';
231
+
232
+ // Extract usage
233
+ const rawUsage = response.usage as unknown as Record<string, unknown> | undefined;
234
+ const usage = rawUsage
235
+ ? {
236
+ inputTokens: Number(rawUsage.input_tokens ?? 0),
237
+ outputTokens: Number(rawUsage.output_tokens ?? 0),
238
+ }
239
+ : undefined;
240
+
241
+ return { content, parsedValue: content, usage, toolCalls: [], stopReason: 'end' };
242
+ } catch (error) {
243
+ if (error instanceof Anthropic.APIError) {
244
+ const isRetryable = error.status === 429 || (error.status ?? 0) >= 500;
245
+ throw new AIError(
246
+ `Anthropic API error (${error.status}): ${error.message}`,
247
+ error.status,
248
+ isRetryable
249
+ );
250
+ }
251
+ throw error;
252
+ }
253
+ }