@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,201 @@
1
+ // Google Generative AI Provider Implementation using official SDK
2
+
3
+ import { GoogleGenAI } from '@google/genai';
4
+ import type { AIRequest, AIResponse, AIToolCall, ThinkingLevel } from '../types';
5
+ import { AIError } from '../types';
6
+ import { buildSystemMessage, buildContextMessage, buildPromptMessage, buildToolSystemMessage } from '../formatters';
7
+ import { toGoogleFunctionDeclarations } from '../tool-schema';
8
+
9
+ /** Map thinking level to Google Gemini 3 thinkingLevel */
10
+ const THINKING_LEVEL_MAP: Record<ThinkingLevel, string | null> = {
11
+ none: null, // Don't set thinkingConfig
12
+ low: 'low',
13
+ medium: 'medium',
14
+ high: 'high',
15
+ max: 'high', // Gemini 3 Flash max is 'high'
16
+ };
17
+
18
+ /** Generate a unique ID for tool calls (Google doesn't provide one) */
19
+ function generateToolCallId(): string {
20
+ return `call_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
21
+ }
22
+
23
+ /**
24
+ * Execute an AI request using the Google Gen AI SDK.
25
+ */
26
+ export async function executeGoogle(request: AIRequest): Promise<AIResponse> {
27
+ const { prompt, contextText, targetType, model, tools, previousToolCalls, toolResults } = request;
28
+
29
+ // Create Google Gen AI client
30
+ const client = new GoogleGenAI({ apiKey: model.apiKey });
31
+
32
+ // Build combined prompt (Google uses a simpler message format)
33
+ const baseSystemInstruction = buildSystemMessage();
34
+ const toolSystemMessage = tools?.length ? buildToolSystemMessage(tools) : null;
35
+ const systemInstruction = toolSystemMessage
36
+ ? `${baseSystemInstruction}\n\n${toolSystemMessage}`
37
+ : baseSystemInstruction;
38
+
39
+ const contextMessage = buildContextMessage(contextText);
40
+ const promptMessage = buildPromptMessage(prompt);
41
+
42
+ // Combine into single prompt for Google
43
+ const parts: string[] = [];
44
+ if (contextMessage) parts.push(contextMessage);
45
+ parts.push(promptMessage);
46
+ const combinedPrompt = parts.join('\n\n');
47
+
48
+ // Build conversation contents - either simple prompt or multi-turn with tool results
49
+ type ContentPart = { text: string } | { functionCall: { name: string; args: Record<string, unknown> } } | { functionResponse: { name: string; response: unknown } };
50
+ type Content = { role: 'user' | 'model'; parts: ContentPart[] };
51
+
52
+ let contents: string | Content[];
53
+
54
+ if (previousToolCalls?.length && toolResults?.length) {
55
+ // Multi-turn conversation with tool results
56
+ // 1. Original user message
57
+ const userMessage: Content = {
58
+ role: 'user',
59
+ parts: [{ text: combinedPrompt }],
60
+ };
61
+
62
+ // 2. Model message with function calls (including thoughtSignature for Gemini 3)
63
+ const modelParts: ContentPart[] = previousToolCalls.map(call => {
64
+ const part: ContentPart = {
65
+ functionCall: {
66
+ name: call.toolName,
67
+ args: call.args,
68
+ },
69
+ };
70
+ // Include thoughtSignature if present (required for Gemini 3)
71
+ if (call.thoughtSignature) {
72
+ (part as Record<string, unknown>).thoughtSignature = call.thoughtSignature;
73
+ }
74
+ return part;
75
+ });
76
+ const modelMessage: Content = {
77
+ role: 'model',
78
+ parts: modelParts,
79
+ };
80
+
81
+ // 3. User message with function responses
82
+ const responseParts: ContentPart[] = toolResults.map((result, i) => ({
83
+ functionResponse: {
84
+ name: previousToolCalls[i].toolName,
85
+ response: result.error
86
+ ? { error: result.error }
87
+ : { result: result.result },
88
+ },
89
+ }));
90
+ const responseMessage: Content = {
91
+ role: 'user',
92
+ parts: responseParts,
93
+ };
94
+
95
+ contents = [userMessage, modelMessage, responseMessage];
96
+ } else {
97
+ // Simple single prompt
98
+ contents = combinedPrompt;
99
+ }
100
+
101
+ try {
102
+ // Build generation config
103
+ const generationConfig: Record<string, unknown> = {};
104
+
105
+ // Add thinking config if level specified
106
+ const thinkingLevel = model.thinkingLevel as ThinkingLevel | undefined;
107
+ const googleThinkingLevel = thinkingLevel ? THINKING_LEVEL_MAP[thinkingLevel] : null;
108
+ if (googleThinkingLevel) {
109
+ generationConfig.thinkingConfig = {
110
+ thinkingLevel: googleThinkingLevel,
111
+ };
112
+ }
113
+
114
+ // Build config with optional tools
115
+ const config: Record<string, unknown> = {
116
+ systemInstruction,
117
+ ...generationConfig,
118
+ };
119
+
120
+ // Add tools if provided
121
+ if (tools?.length) {
122
+ config.tools = [{ functionDeclarations: toGoogleFunctionDeclarations(tools) }];
123
+ }
124
+
125
+ // Make API request
126
+ // Cast contents to unknown to avoid strict SDK type checking (we build valid content)
127
+ const response = await client.models.generateContent({
128
+ model: model.name,
129
+ contents: contents as unknown as Parameters<typeof client.models.generateContent>[0]['contents'],
130
+ config,
131
+ });
132
+
133
+ // Extract text content
134
+ const content = response.text ?? '';
135
+
136
+ // Extract function calls from response parts (including thoughtSignature for Gemini 3)
137
+ const responseParts = (response.candidates?.[0]?.content?.parts ?? []) as Array<{
138
+ text?: string;
139
+ functionCall?: { name: string; args: Record<string, unknown> };
140
+ thoughtSignature?: string;
141
+ }>;
142
+ const functionCallParts = responseParts.filter((p) => p.functionCall);
143
+ let toolCalls: AIToolCall[] | undefined;
144
+ if (functionCallParts.length > 0) {
145
+ toolCalls = functionCallParts.map((p) => ({
146
+ id: generateToolCallId(),
147
+ toolName: p.functionCall!.name,
148
+ args: p.functionCall!.args,
149
+ thoughtSignature: p.thoughtSignature,
150
+ }));
151
+ }
152
+
153
+ // Determine stop reason
154
+ const finishReason = response.candidates?.[0]?.finishReason as string | undefined;
155
+ const stopReason =
156
+ finishReason === 'STOP'
157
+ ? (toolCalls?.length ? 'tool_use' : 'end')
158
+ : finishReason === 'MAX_TOKENS'
159
+ ? 'length'
160
+ : finishReason === 'SAFETY'
161
+ ? 'content_filter'
162
+ : 'end';
163
+
164
+ // Extract usage from response including cached and thinking tokens
165
+ const meta = response.usageMetadata as Record<string, unknown> | undefined;
166
+ const usage = meta
167
+ ? {
168
+ inputTokens: Number(meta.promptTokenCount ?? 0),
169
+ outputTokens: Number(meta.candidatesTokenCount ?? 0),
170
+ cachedInputTokens: meta.cachedContentTokenCount ? Number(meta.cachedContentTokenCount) : undefined,
171
+ thinkingTokens: meta.thoughtsTokenCount ? Number(meta.thoughtsTokenCount) : undefined,
172
+ }
173
+ : undefined;
174
+
175
+ // For text responses, parsedValue is just the content
176
+ // For typed responses, the value comes from return tool calls (handled by tool-loop)
177
+ return { content, parsedValue: content, usage, toolCalls, stopReason };
178
+ } catch (error) {
179
+ // Handle Google API errors
180
+ if (error instanceof Error) {
181
+ const message = error.message.toLowerCase();
182
+ const isRetryable =
183
+ message.includes('429') ||
184
+ message.includes('rate limit') ||
185
+ message.includes('500') ||
186
+ message.includes('503') ||
187
+ message.includes('service unavailable');
188
+
189
+ // Extract status code if present
190
+ const statusMatch = error.message.match(/(\d{3})/);
191
+ const statusCode = statusMatch ? parseInt(statusMatch[1]) : undefined;
192
+
193
+ throw new AIError(
194
+ `Google API error: ${error.message}`,
195
+ statusCode,
196
+ isRetryable
197
+ );
198
+ }
199
+ throw error;
200
+ }
201
+ }
@@ -0,0 +1,156 @@
1
+ // OpenAI Provider Implementation using official SDK
2
+
3
+ import OpenAI from 'openai';
4
+ import type { AIRequest, AIResponse, AIToolCall, ThinkingLevel } from '../types';
5
+ import { AIError } from '../types';
6
+ import { buildMessages } from '../formatters';
7
+ import { toOpenAITools } from '../tool-schema';
8
+
9
+ /** OpenAI provider configuration */
10
+ export const OPENAI_CONFIG = {
11
+ defaultUrl: 'https://api.openai.com/v1',
12
+ };
13
+
14
+ /** Map thinking level to OpenAI reasoning_effort */
15
+ const REASONING_EFFORT_MAP: Record<ThinkingLevel, string> = {
16
+ none: 'none',
17
+ low: 'low',
18
+ medium: 'medium',
19
+ high: 'high',
20
+ max: 'xhigh', // OpenAI uses 'xhigh' for max reasoning
21
+ };
22
+
23
+ /**
24
+ * Execute an AI request using the OpenAI SDK.
25
+ */
26
+ export async function executeOpenAI(request: AIRequest): Promise<AIResponse> {
27
+ const { prompt, contextText, targetType, model, tools, previousToolCalls, toolResults } = request;
28
+
29
+ // Create OpenAI client
30
+ const client = new OpenAI({
31
+ apiKey: model.apiKey,
32
+ baseURL: model.url ?? OPENAI_CONFIG.defaultUrl,
33
+ });
34
+
35
+ // Build base messages
36
+ const baseMessages = buildMessages(prompt, contextText, tools);
37
+
38
+ // Build conversation messages - either simple or multi-turn with tool results
39
+ type ChatMessage = OpenAI.ChatCompletionMessageParam;
40
+ let messages: ChatMessage[] = baseMessages.map((m) => ({
41
+ role: m.role as 'system' | 'user' | 'assistant',
42
+ content: m.content,
43
+ }));
44
+
45
+ // Add tool call history if present (multi-turn conversation)
46
+ if (previousToolCalls?.length && toolResults?.length) {
47
+ // Add assistant message with tool calls
48
+ const assistantMessage: ChatMessage = {
49
+ role: 'assistant',
50
+ content: null,
51
+ tool_calls: previousToolCalls.map(call => ({
52
+ id: call.id,
53
+ type: 'function' as const,
54
+ function: {
55
+ name: call.toolName,
56
+ arguments: JSON.stringify(call.args),
57
+ },
58
+ })),
59
+ };
60
+ messages.push(assistantMessage);
61
+
62
+ // Add tool result messages
63
+ for (let i = 0; i < toolResults.length; i++) {
64
+ const result = toolResults[i];
65
+ const call = previousToolCalls[i];
66
+ const toolMessage: ChatMessage = {
67
+ role: 'tool',
68
+ tool_call_id: call.id,
69
+ content: result.error
70
+ ? `Error: ${result.error}`
71
+ : typeof result.result === 'string'
72
+ ? result.result
73
+ : JSON.stringify(result.result),
74
+ };
75
+ messages.push(toolMessage);
76
+ }
77
+ }
78
+
79
+ try {
80
+ // Build request parameters
81
+ const params: OpenAI.ChatCompletionCreateParamsNonStreaming = {
82
+ model: model.name,
83
+ messages,
84
+ };
85
+
86
+ // Add tools if provided
87
+ if (tools?.length) {
88
+ params.tools = toOpenAITools(tools) as OpenAI.ChatCompletionTool[];
89
+ }
90
+
91
+ // Add reasoning effort if thinking level specified
92
+ const thinkingLevel = model.thinkingLevel as ThinkingLevel | undefined;
93
+ if (thinkingLevel) {
94
+ (params as unknown as Record<string, unknown>).reasoning_effort = REASONING_EFFORT_MAP[thinkingLevel];
95
+ }
96
+
97
+ // Make API request
98
+ const completion = await client.chat.completions.create(params);
99
+
100
+ // Extract message
101
+ const message = completion.choices[0]?.message;
102
+ const content = message?.content ?? '';
103
+ const finishReason = completion.choices[0]?.finish_reason;
104
+
105
+ // Extract usage including cached and reasoning tokens
106
+ const rawUsage = completion.usage as unknown as Record<string, unknown> | undefined;
107
+ const promptDetails = rawUsage?.prompt_tokens_details as Record<string, unknown> | undefined;
108
+ const completionDetails = rawUsage?.completion_tokens_details as Record<string, unknown> | undefined;
109
+ const usage = rawUsage
110
+ ? {
111
+ inputTokens: Number(rawUsage.prompt_tokens ?? 0),
112
+ outputTokens: Number(rawUsage.completion_tokens ?? 0),
113
+ cachedInputTokens: promptDetails?.cached_tokens ? Number(promptDetails.cached_tokens) : undefined,
114
+ thinkingTokens: completionDetails?.reasoning_tokens ? Number(completionDetails.reasoning_tokens) : undefined,
115
+ }
116
+ : undefined;
117
+
118
+ // Parse tool calls if present
119
+ let toolCalls: AIToolCall[] | undefined;
120
+ if (message?.tool_calls?.length) {
121
+ toolCalls = message.tool_calls
122
+ .filter((tc): tc is OpenAI.ChatCompletionMessageToolCall & { function: { name: string; arguments: string } } =>
123
+ 'function' in tc && tc.function !== undefined
124
+ )
125
+ .map((tc) => ({
126
+ id: tc.id,
127
+ toolName: tc.function.name,
128
+ args: JSON.parse(tc.function.arguments),
129
+ }));
130
+ }
131
+
132
+ // Determine stop reason
133
+ const stopReason =
134
+ finishReason === 'tool_calls'
135
+ ? 'tool_use'
136
+ : finishReason === 'length'
137
+ ? 'length'
138
+ : finishReason === 'content_filter'
139
+ ? 'content_filter'
140
+ : 'end';
141
+
142
+ // For text responses, parsedValue is just the content
143
+ // For typed responses, the value comes from return tool calls (handled by tool-loop)
144
+ return { content, parsedValue: content, usage, toolCalls, stopReason };
145
+ } catch (error) {
146
+ if (error instanceof OpenAI.APIError) {
147
+ const isRetryable = error.status === 429 || (error.status ?? 0) >= 500;
148
+ throw new AIError(
149
+ `OpenAI API error (${error.status}): ${error.message}`,
150
+ error.status,
151
+ isRetryable
152
+ );
153
+ }
154
+ throw error;
155
+ }
156
+ }
@@ -0,0 +1,100 @@
1
+ // Retry utilities for AI operations
2
+
3
+ import { AIError, type RetryOptions } from './types';
4
+
5
+ /**
6
+ * Check if an error is retryable (5xx, 429, or network error).
7
+ */
8
+ export function isRetryableError(error: unknown): boolean {
9
+ if (error instanceof AIError) {
10
+ return error.isRetryable;
11
+ }
12
+
13
+ // Network errors
14
+ if (error instanceof TypeError && error.message.includes('fetch')) {
15
+ return true;
16
+ }
17
+
18
+ // Check for status codes
19
+ if (error instanceof Error) {
20
+ const message = error.message.toLowerCase();
21
+ // Network-related errors
22
+ if (
23
+ message.includes('network') ||
24
+ message.includes('timeout') ||
25
+ message.includes('econnreset') ||
26
+ message.includes('econnrefused') ||
27
+ message.includes('socket')
28
+ ) {
29
+ return true;
30
+ }
31
+ }
32
+
33
+ return false;
34
+ }
35
+
36
+ /**
37
+ * Calculate delay for exponential backoff with jitter.
38
+ * Formula: min(maxDelay, baseDelay * 2^attempt) * (0.5 + random(0.5))
39
+ */
40
+ export function calculateDelay(
41
+ attempt: number,
42
+ baseDelayMs = 1000,
43
+ maxDelayMs = 30000
44
+ ): number {
45
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt);
46
+ const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
47
+ // Add jitter: 50% to 100% of the delay
48
+ const jitter = 0.5 + Math.random() * 0.5;
49
+ return Math.floor(cappedDelay * jitter);
50
+ }
51
+
52
+ /**
53
+ * Sleep for a given number of milliseconds.
54
+ */
55
+ function sleep(ms: number): Promise<void> {
56
+ return new Promise((resolve) => setTimeout(resolve, ms));
57
+ }
58
+
59
+ /**
60
+ * Execute a function with retry logic.
61
+ * Retries on 5xx, 429, and network errors with exponential backoff.
62
+ */
63
+ export async function withRetry<T>(
64
+ fn: () => Promise<T>,
65
+ options: RetryOptions
66
+ ): Promise<T> {
67
+ const { maxRetries, baseDelayMs = 1000, maxDelayMs = 30000 } = options;
68
+ let lastError: Error | null = null;
69
+
70
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
71
+ try {
72
+ return await fn();
73
+ } catch (error) {
74
+ lastError = error instanceof Error ? error : new Error(String(error));
75
+
76
+ // Check if error is retryable and we have attempts left
77
+ if (!isRetryableError(error) || attempt >= maxRetries) {
78
+ throw lastError;
79
+ }
80
+
81
+ // Wait before retrying
82
+ const delay = calculateDelay(attempt, baseDelayMs, maxDelayMs);
83
+ await sleep(delay);
84
+ }
85
+ }
86
+
87
+ // This should never be reached, but TypeScript needs it
88
+ throw lastError ?? new Error('Retry failed');
89
+ }
90
+
91
+ /**
92
+ * Create an AIError from an HTTP response.
93
+ */
94
+ export function createAIErrorFromResponse(
95
+ status: number,
96
+ message: string
97
+ ): AIError {
98
+ const isRetryable = status === 429 || (status >= 500 && status < 600);
99
+ return new AIError(message, status, isRetryable);
100
+ }