@office-ai/aioncli-core 0.1.18-beta.1

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 (567) hide show
  1. package/dist/.last_build +0 -0
  2. package/dist/index.d.ts +7 -0
  3. package/dist/index.js +8 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/__mocks__/fs/promises.d.ts +11 -0
  6. package/dist/src/__mocks__/fs/promises.js +17 -0
  7. package/dist/src/__mocks__/fs/promises.js.map +1 -0
  8. package/dist/src/code_assist/codeAssist.d.ts +9 -0
  9. package/dist/src/code_assist/codeAssist.js +19 -0
  10. package/dist/src/code_assist/codeAssist.js.map +1 -0
  11. package/dist/src/code_assist/converter.d.ts +70 -0
  12. package/dist/src/code_assist/converter.js +126 -0
  13. package/dist/src/code_assist/converter.js.map +1 -0
  14. package/dist/src/code_assist/converter.test.d.ts +6 -0
  15. package/dist/src/code_assist/converter.test.js +279 -0
  16. package/dist/src/code_assist/converter.test.js.map +1 -0
  17. package/dist/src/code_assist/oauth2.d.ts +20 -0
  18. package/dist/src/code_assist/oauth2.js +337 -0
  19. package/dist/src/code_assist/oauth2.js.map +1 -0
  20. package/dist/src/code_assist/oauth2.test.d.ts +6 -0
  21. package/dist/src/code_assist/oauth2.test.js +334 -0
  22. package/dist/src/code_assist/oauth2.test.js.map +1 -0
  23. package/dist/src/code_assist/server.d.ts +37 -0
  24. package/dist/src/code_assist/server.js +125 -0
  25. package/dist/src/code_assist/server.js.map +1 -0
  26. package/dist/src/code_assist/server.test.d.ts +6 -0
  27. package/dist/src/code_assist/server.test.js +134 -0
  28. package/dist/src/code_assist/server.test.js.map +1 -0
  29. package/dist/src/code_assist/setup.d.ts +20 -0
  30. package/dist/src/code_assist/setup.js +70 -0
  31. package/dist/src/code_assist/setup.js.map +1 -0
  32. package/dist/src/code_assist/setup.test.d.ts +6 -0
  33. package/dist/src/code_assist/setup.test.js +65 -0
  34. package/dist/src/code_assist/setup.test.js.map +1 -0
  35. package/dist/src/code_assist/types.d.ts +148 -0
  36. package/dist/src/code_assist/types.js +46 -0
  37. package/dist/src/code_assist/types.js.map +1 -0
  38. package/dist/src/config/config.d.ts +272 -0
  39. package/dist/src/config/config.js +554 -0
  40. package/dist/src/config/config.js.map +1 -0
  41. package/dist/src/config/config.test.d.ts +6 -0
  42. package/dist/src/config/config.test.js +365 -0
  43. package/dist/src/config/config.test.js.map +1 -0
  44. package/dist/src/config/flashFallback.test.d.ts +6 -0
  45. package/dist/src/config/flashFallback.test.js +87 -0
  46. package/dist/src/config/flashFallback.test.js.map +1 -0
  47. package/dist/src/config/models.d.ts +9 -0
  48. package/dist/src/config/models.js +10 -0
  49. package/dist/src/config/models.js.map +1 -0
  50. package/dist/src/core/client.d.ts +61 -0
  51. package/dist/src/core/client.js +485 -0
  52. package/dist/src/core/client.js.map +1 -0
  53. package/dist/src/core/client.test.d.ts +6 -0
  54. package/dist/src/core/client.test.js +1008 -0
  55. package/dist/src/core/client.test.js.map +1 -0
  56. package/dist/src/core/contentGenerator.d.ts +45 -0
  57. package/dist/src/core/contentGenerator.js +81 -0
  58. package/dist/src/core/contentGenerator.js.map +1 -0
  59. package/dist/src/core/contentGenerator.test.d.ts +6 -0
  60. package/dist/src/core/contentGenerator.test.js +100 -0
  61. package/dist/src/core/contentGenerator.test.js.map +1 -0
  62. package/dist/src/core/coreToolScheduler.d.ts +117 -0
  63. package/dist/src/core/coreToolScheduler.js +530 -0
  64. package/dist/src/core/coreToolScheduler.js.map +1 -0
  65. package/dist/src/core/coreToolScheduler.test.d.ts +6 -0
  66. package/dist/src/core/coreToolScheduler.test.js +625 -0
  67. package/dist/src/core/coreToolScheduler.test.js.map +1 -0
  68. package/dist/src/core/geminiChat.d.ts +118 -0
  69. package/dist/src/core/geminiChat.js +509 -0
  70. package/dist/src/core/geminiChat.js.map +1 -0
  71. package/dist/src/core/geminiChat.test.d.ts +6 -0
  72. package/dist/src/core/geminiChat.test.js +424 -0
  73. package/dist/src/core/geminiChat.test.js.map +1 -0
  74. package/dist/src/core/geminiRequest.d.ts +13 -0
  75. package/dist/src/core/geminiRequest.js +10 -0
  76. package/dist/src/core/geminiRequest.js.map +1 -0
  77. package/dist/src/core/logger.d.ts +37 -0
  78. package/dist/src/core/logger.js +273 -0
  79. package/dist/src/core/logger.js.map +1 -0
  80. package/dist/src/core/logger.test.d.ts +6 -0
  81. package/dist/src/core/logger.test.js +467 -0
  82. package/dist/src/core/logger.test.js.map +1 -0
  83. package/dist/src/core/loggingContentGenerator.d.ts +24 -0
  84. package/dist/src/core/loggingContentGenerator.js +89 -0
  85. package/dist/src/core/loggingContentGenerator.js.map +1 -0
  86. package/dist/src/core/modelCheck.d.ts +14 -0
  87. package/dist/src/core/modelCheck.js +62 -0
  88. package/dist/src/core/modelCheck.js.map +1 -0
  89. package/dist/src/core/nonInteractiveToolExecutor.d.ts +12 -0
  90. package/dist/src/core/nonInteractiveToolExecutor.js +124 -0
  91. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -0
  92. package/dist/src/core/nonInteractiveToolExecutor.test.d.ts +6 -0
  93. package/dist/src/core/nonInteractiveToolExecutor.test.js +165 -0
  94. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -0
  95. package/dist/src/core/openaiContentGenerator.d.ts +77 -0
  96. package/dist/src/core/openaiContentGenerator.js +1395 -0
  97. package/dist/src/core/openaiContentGenerator.js.map +1 -0
  98. package/dist/src/core/openaiContentGenerator.test.d.ts +6 -0
  99. package/dist/src/core/openaiContentGenerator.test.js +1904 -0
  100. package/dist/src/core/openaiContentGenerator.test.js.map +1 -0
  101. package/dist/src/core/prompts.d.ts +12 -0
  102. package/dist/src/core/prompts.js +359 -0
  103. package/dist/src/core/prompts.js.map +1 -0
  104. package/dist/src/core/prompts.test.d.ts +6 -0
  105. package/dist/src/core/prompts.test.js +214 -0
  106. package/dist/src/core/prompts.test.js.map +1 -0
  107. package/dist/src/core/subagent.d.ts +230 -0
  108. package/dist/src/core/subagent.js +447 -0
  109. package/dist/src/core/subagent.js.map +1 -0
  110. package/dist/src/core/subagent.test.d.ts +6 -0
  111. package/dist/src/core/subagent.test.js +515 -0
  112. package/dist/src/core/subagent.test.js.map +1 -0
  113. package/dist/src/core/tokenLimits.d.ts +10 -0
  114. package/dist/src/core/tokenLimits.js +28 -0
  115. package/dist/src/core/tokenLimits.js.map +1 -0
  116. package/dist/src/core/turn.d.ts +114 -0
  117. package/dist/src/core/turn.js +143 -0
  118. package/dist/src/core/turn.js.map +1 -0
  119. package/dist/src/core/turn.test.d.ts +6 -0
  120. package/dist/src/core/turn.test.js +369 -0
  121. package/dist/src/core/turn.test.js.map +1 -0
  122. package/dist/src/ide/detect-ide.d.ts +10 -0
  123. package/dist/src/ide/detect-ide.js +27 -0
  124. package/dist/src/ide/detect-ide.js.map +1 -0
  125. package/dist/src/ide/ide-client.d.ts +56 -0
  126. package/dist/src/ide/ide-client.js +268 -0
  127. package/dist/src/ide/ide-client.js.map +1 -0
  128. package/dist/src/ide/ide-installer.d.ts +14 -0
  129. package/dist/src/ide/ide-installer.js +109 -0
  130. package/dist/src/ide/ide-installer.js.map +1 -0
  131. package/dist/src/ide/ide-installer.test.d.ts +6 -0
  132. package/dist/src/ide/ide-installer.test.js +55 -0
  133. package/dist/src/ide/ide-installer.test.js.map +1 -0
  134. package/dist/src/ide/ideContext.d.ts +374 -0
  135. package/dist/src/ide/ideContext.js +147 -0
  136. package/dist/src/ide/ideContext.js.map +1 -0
  137. package/dist/src/ide/ideContext.test.d.ts +6 -0
  138. package/dist/src/ide/ideContext.test.js +265 -0
  139. package/dist/src/ide/ideContext.test.js.map +1 -0
  140. package/dist/src/index.d.ts +68 -0
  141. package/dist/src/index.js +78 -0
  142. package/dist/src/index.js.map +1 -0
  143. package/dist/src/index.test.d.ts +6 -0
  144. package/dist/src/index.test.js +12 -0
  145. package/dist/src/index.test.js.map +1 -0
  146. package/dist/src/mcp/google-auth-provider.d.ts +23 -0
  147. package/dist/src/mcp/google-auth-provider.js +63 -0
  148. package/dist/src/mcp/google-auth-provider.js.map +1 -0
  149. package/dist/src/mcp/google-auth-provider.test.d.ts +6 -0
  150. package/dist/src/mcp/google-auth-provider.test.js +54 -0
  151. package/dist/src/mcp/google-auth-provider.test.js.map +1 -0
  152. package/dist/src/mcp/oauth-provider.d.ts +147 -0
  153. package/dist/src/mcp/oauth-provider.js +484 -0
  154. package/dist/src/mcp/oauth-provider.js.map +1 -0
  155. package/dist/src/mcp/oauth-provider.test.d.ts +6 -0
  156. package/dist/src/mcp/oauth-provider.test.js +602 -0
  157. package/dist/src/mcp/oauth-provider.test.js.map +1 -0
  158. package/dist/src/mcp/oauth-token-storage.d.ts +83 -0
  159. package/dist/src/mcp/oauth-token-storage.js +151 -0
  160. package/dist/src/mcp/oauth-token-storage.js.map +1 -0
  161. package/dist/src/mcp/oauth-token-storage.test.d.ts +6 -0
  162. package/dist/src/mcp/oauth-token-storage.test.js +205 -0
  163. package/dist/src/mcp/oauth-token-storage.test.js.map +1 -0
  164. package/dist/src/mcp/oauth-utils.d.ts +109 -0
  165. package/dist/src/mcp/oauth-utils.js +183 -0
  166. package/dist/src/mcp/oauth-utils.js.map +1 -0
  167. package/dist/src/mcp/oauth-utils.test.d.ts +6 -0
  168. package/dist/src/mcp/oauth-utils.test.js +144 -0
  169. package/dist/src/mcp/oauth-utils.test.js.map +1 -0
  170. package/dist/src/prompts/mcp-prompts.d.ts +8 -0
  171. package/dist/src/prompts/mcp-prompts.js +13 -0
  172. package/dist/src/prompts/mcp-prompts.js.map +1 -0
  173. package/dist/src/prompts/prompt-registry.d.ts +34 -0
  174. package/dist/src/prompts/prompt-registry.js +63 -0
  175. package/dist/src/prompts/prompt-registry.js.map +1 -0
  176. package/dist/src/services/fileDiscoveryService.d.ts +35 -0
  177. package/dist/src/services/fileDiscoveryService.js +91 -0
  178. package/dist/src/services/fileDiscoveryService.js.map +1 -0
  179. package/dist/src/services/fileDiscoveryService.test.d.ts +6 -0
  180. package/dist/src/services/fileDiscoveryService.test.js +143 -0
  181. package/dist/src/services/fileDiscoveryService.test.js.map +1 -0
  182. package/dist/src/services/gitService.d.ts +21 -0
  183. package/dist/src/services/gitService.js +101 -0
  184. package/dist/src/services/gitService.js.map +1 -0
  185. package/dist/src/services/gitService.test.d.ts +6 -0
  186. package/dist/src/services/gitService.test.js +209 -0
  187. package/dist/src/services/gitService.test.js.map +1 -0
  188. package/dist/src/services/loopDetectionService.d.ts +97 -0
  189. package/dist/src/services/loopDetectionService.js +340 -0
  190. package/dist/src/services/loopDetectionService.js.map +1 -0
  191. package/dist/src/services/loopDetectionService.test.d.ts +6 -0
  192. package/dist/src/services/loopDetectionService.test.js +484 -0
  193. package/dist/src/services/loopDetectionService.test.js.map +1 -0
  194. package/dist/src/services/shellExecutionService.d.ts +70 -0
  195. package/dist/src/services/shellExecutionService.js +175 -0
  196. package/dist/src/services/shellExecutionService.js.map +1 -0
  197. package/dist/src/services/shellExecutionService.test.d.ts +6 -0
  198. package/dist/src/services/shellExecutionService.test.js +272 -0
  199. package/dist/src/services/shellExecutionService.test.js.map +1 -0
  200. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +65 -0
  201. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +652 -0
  202. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -0
  203. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +6 -0
  204. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +186 -0
  205. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -0
  206. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +59 -0
  207. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +159 -0
  208. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -0
  209. package/dist/src/telemetry/constants.d.ts +23 -0
  210. package/dist/src/telemetry/constants.js +24 -0
  211. package/dist/src/telemetry/constants.js.map +1 -0
  212. package/dist/src/telemetry/file-exporters.d.ts +28 -0
  213. package/dist/src/telemetry/file-exporters.js +62 -0
  214. package/dist/src/telemetry/file-exporters.js.map +1 -0
  215. package/dist/src/telemetry/index.d.ts +18 -0
  216. package/dist/src/telemetry/index.js +20 -0
  217. package/dist/src/telemetry/index.js.map +1 -0
  218. package/dist/src/telemetry/integration.test.circular.d.ts +6 -0
  219. package/dist/src/telemetry/integration.test.circular.js +53 -0
  220. package/dist/src/telemetry/integration.test.circular.js.map +1 -0
  221. package/dist/src/telemetry/loggers.d.ts +18 -0
  222. package/dist/src/telemetry/loggers.js +268 -0
  223. package/dist/src/telemetry/loggers.js.map +1 -0
  224. package/dist/src/telemetry/loggers.test.circular.d.ts +6 -0
  225. package/dist/src/telemetry/loggers.test.circular.js +107 -0
  226. package/dist/src/telemetry/loggers.test.circular.js.map +1 -0
  227. package/dist/src/telemetry/loggers.test.d.ts +6 -0
  228. package/dist/src/telemetry/loggers.test.js +568 -0
  229. package/dist/src/telemetry/loggers.test.js.map +1 -0
  230. package/dist/src/telemetry/metrics.d.ts +20 -0
  231. package/dist/src/telemetry/metrics.js +150 -0
  232. package/dist/src/telemetry/metrics.js.map +1 -0
  233. package/dist/src/telemetry/metrics.test.d.ts +6 -0
  234. package/dist/src/telemetry/metrics.test.js +212 -0
  235. package/dist/src/telemetry/metrics.test.js.map +1 -0
  236. package/dist/src/telemetry/sdk.d.ts +9 -0
  237. package/dist/src/telemetry/sdk.js +127 -0
  238. package/dist/src/telemetry/sdk.js.map +1 -0
  239. package/dist/src/telemetry/telemetry.test.d.ts +6 -0
  240. package/dist/src/telemetry/telemetry.test.js +50 -0
  241. package/dist/src/telemetry/telemetry.test.js.map +1 -0
  242. package/dist/src/telemetry/tool-call-decision.d.ts +13 -0
  243. package/dist/src/telemetry/tool-call-decision.js +29 -0
  244. package/dist/src/telemetry/tool-call-decision.js.map +1 -0
  245. package/dist/src/telemetry/types.d.ts +145 -0
  246. package/dist/src/telemetry/types.js +267 -0
  247. package/dist/src/telemetry/types.js.map +1 -0
  248. package/dist/src/telemetry/uiTelemetry.d.ts +71 -0
  249. package/dist/src/telemetry/uiTelemetry.js +140 -0
  250. package/dist/src/telemetry/uiTelemetry.js.map +1 -0
  251. package/dist/src/telemetry/uiTelemetry.test.d.ts +6 -0
  252. package/dist/src/telemetry/uiTelemetry.test.js +518 -0
  253. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -0
  254. package/dist/src/test-utils/mockWorkspaceContext.d.ts +13 -0
  255. package/dist/src/test-utils/mockWorkspaceContext.js +24 -0
  256. package/dist/src/test-utils/mockWorkspaceContext.js.map +1 -0
  257. package/dist/src/test-utils/tools.d.ts +23 -0
  258. package/dist/src/test-utils/tools.js +41 -0
  259. package/dist/src/test-utils/tools.js.map +1 -0
  260. package/dist/src/tools/diffOptions.d.ts +9 -0
  261. package/dist/src/tools/diffOptions.js +38 -0
  262. package/dist/src/tools/diffOptions.js.map +1 -0
  263. package/dist/src/tools/diffOptions.test.d.ts +6 -0
  264. package/dist/src/tools/diffOptions.test.js +119 -0
  265. package/dist/src/tools/diffOptions.test.js.map +1 -0
  266. package/dist/src/tools/edit.d.ts +55 -0
  267. package/dist/src/tools/edit.js +398 -0
  268. package/dist/src/tools/edit.js.map +1 -0
  269. package/dist/src/tools/edit.test.d.ts +6 -0
  270. package/dist/src/tools/edit.test.js +681 -0
  271. package/dist/src/tools/edit.test.js.map +1 -0
  272. package/dist/src/tools/glob.d.ts +51 -0
  273. package/dist/src/tools/glob.js +226 -0
  274. package/dist/src/tools/glob.js.map +1 -0
  275. package/dist/src/tools/glob.test.d.ts +6 -0
  276. package/dist/src/tools/glob.test.js +330 -0
  277. package/dist/src/tools/glob.test.js.map +1 -0
  278. package/dist/src/tools/grep.d.ts +46 -0
  279. package/dist/src/tools/grep.js +503 -0
  280. package/dist/src/tools/grep.js.map +1 -0
  281. package/dist/src/tools/grep.test.d.ts +6 -0
  282. package/dist/src/tools/grep.test.js +272 -0
  283. package/dist/src/tools/grep.test.js.map +1 -0
  284. package/dist/src/tools/ls.d.ts +86 -0
  285. package/dist/src/tools/ls.js +224 -0
  286. package/dist/src/tools/ls.js.map +1 -0
  287. package/dist/src/tools/ls.test.d.ts +6 -0
  288. package/dist/src/tools/ls.test.js +356 -0
  289. package/dist/src/tools/ls.test.js.map +1 -0
  290. package/dist/src/tools/mcp-client.d.ts +149 -0
  291. package/dist/src/tools/mcp-client.js +844 -0
  292. package/dist/src/tools/mcp-client.js.map +1 -0
  293. package/dist/src/tools/mcp-client.test.d.ts +6 -0
  294. package/dist/src/tools/mcp-client.test.js +643 -0
  295. package/dist/src/tools/mcp-client.test.js.map +1 -0
  296. package/dist/src/tools/mcp-tool.d.ts +29 -0
  297. package/dist/src/tools/mcp-tool.js +202 -0
  298. package/dist/src/tools/mcp-tool.js.map +1 -0
  299. package/dist/src/tools/mcp-tool.test.d.ts +6 -0
  300. package/dist/src/tools/mcp-tool.test.js +501 -0
  301. package/dist/src/tools/mcp-tool.test.js.map +1 -0
  302. package/dist/src/tools/memoryTool.d.ts +43 -0
  303. package/dist/src/tools/memoryTool.js +290 -0
  304. package/dist/src/tools/memoryTool.js.map +1 -0
  305. package/dist/src/tools/memoryTool.test.d.ts +6 -0
  306. package/dist/src/tools/memoryTool.test.js +266 -0
  307. package/dist/src/tools/memoryTool.test.js.map +1 -0
  308. package/dist/src/tools/modifiable-tool.d.ts +32 -0
  309. package/dist/src/tools/modifiable-tool.js +88 -0
  310. package/dist/src/tools/modifiable-tool.js.map +1 -0
  311. package/dist/src/tools/modifiable-tool.test.d.ts +6 -0
  312. package/dist/src/tools/modifiable-tool.test.js +193 -0
  313. package/dist/src/tools/modifiable-tool.test.js.map +1 -0
  314. package/dist/src/tools/read-file.d.ts +34 -0
  315. package/dist/src/tools/read-file.js +152 -0
  316. package/dist/src/tools/read-file.js.map +1 -0
  317. package/dist/src/tools/read-file.test.d.ts +6 -0
  318. package/dist/src/tools/read-file.test.js +300 -0
  319. package/dist/src/tools/read-file.test.js.map +1 -0
  320. package/dist/src/tools/read-many-files.d.ts +61 -0
  321. package/dist/src/tools/read-many-files.js +421 -0
  322. package/dist/src/tools/read-many-files.js.map +1 -0
  323. package/dist/src/tools/read-many-files.test.d.ts +6 -0
  324. package/dist/src/tools/read-many-files.test.js +455 -0
  325. package/dist/src/tools/read-many-files.test.js.map +1 -0
  326. package/dist/src/tools/shell.d.ts +23 -0
  327. package/dist/src/tools/shell.js +313 -0
  328. package/dist/src/tools/shell.js.map +1 -0
  329. package/dist/src/tools/shell.test.d.ts +6 -0
  330. package/dist/src/tools/shell.test.js +321 -0
  331. package/dist/src/tools/shell.test.js.map +1 -0
  332. package/dist/src/tools/tool-error.d.ts +26 -0
  333. package/dist/src/tools/tool-error.js +31 -0
  334. package/dist/src/tools/tool-error.js.map +1 -0
  335. package/dist/src/tools/tool-registry.d.ts +85 -0
  336. package/dist/src/tools/tool-registry.js +390 -0
  337. package/dist/src/tools/tool-registry.js.map +1 -0
  338. package/dist/src/tools/tool-registry.test.d.ts +6 -0
  339. package/dist/src/tools/tool-registry.test.js +417 -0
  340. package/dist/src/tools/tool-registry.test.js.map +1 -0
  341. package/dist/src/tools/tools.d.ts +328 -0
  342. package/dist/src/tools/tools.js +281 -0
  343. package/dist/src/tools/tools.js.map +1 -0
  344. package/dist/src/tools/tools.test.d.ts +6 -0
  345. package/dist/src/tools/tools.test.js +117 -0
  346. package/dist/src/tools/tools.test.js.map +1 -0
  347. package/dist/src/tools/web-fetch.d.ts +29 -0
  348. package/dist/src/tools/web-fetch.js +246 -0
  349. package/dist/src/tools/web-fetch.js.map +1 -0
  350. package/dist/src/tools/web-fetch.test.d.ts +6 -0
  351. package/dist/src/tools/web-fetch.test.js +71 -0
  352. package/dist/src/tools/web-fetch.test.js.map +1 -0
  353. package/dist/src/tools/web-search.d.ts +49 -0
  354. package/dist/src/tools/web-search.js +120 -0
  355. package/dist/src/tools/web-search.js.map +1 -0
  356. package/dist/src/tools/write-file.d.ts +46 -0
  357. package/dist/src/tools/write-file.js +321 -0
  358. package/dist/src/tools/write-file.js.map +1 -0
  359. package/dist/src/tools/write-file.test.d.ts +6 -0
  360. package/dist/src/tools/write-file.test.js +572 -0
  361. package/dist/src/tools/write-file.test.js.map +1 -0
  362. package/dist/src/utils/LruCache.d.ts +13 -0
  363. package/dist/src/utils/LruCache.js +38 -0
  364. package/dist/src/utils/LruCache.js.map +1 -0
  365. package/dist/src/utils/bfsFileSearch.d.ts +24 -0
  366. package/dist/src/utils/bfsFileSearch.js +89 -0
  367. package/dist/src/utils/bfsFileSearch.js.map +1 -0
  368. package/dist/src/utils/bfsFileSearch.test.d.ts +6 -0
  369. package/dist/src/utils/bfsFileSearch.test.js +163 -0
  370. package/dist/src/utils/bfsFileSearch.test.js.map +1 -0
  371. package/dist/src/utils/browser.d.ts +13 -0
  372. package/dist/src/utils/browser.js +49 -0
  373. package/dist/src/utils/browser.js.map +1 -0
  374. package/dist/src/utils/editCorrector.d.ts +53 -0
  375. package/dist/src/utils/editCorrector.js +546 -0
  376. package/dist/src/utils/editCorrector.js.map +1 -0
  377. package/dist/src/utils/editCorrector.test.d.ts +6 -0
  378. package/dist/src/utils/editCorrector.test.js +564 -0
  379. package/dist/src/utils/editCorrector.test.js.map +1 -0
  380. package/dist/src/utils/editor.d.ts +28 -0
  381. package/dist/src/utils/editor.js +186 -0
  382. package/dist/src/utils/editor.js.map +1 -0
  383. package/dist/src/utils/editor.test.d.ts +6 -0
  384. package/dist/src/utils/editor.test.js +445 -0
  385. package/dist/src/utils/editor.test.js.map +1 -0
  386. package/dist/src/utils/environmentContext.d.ts +21 -0
  387. package/dist/src/utils/environmentContext.js +90 -0
  388. package/dist/src/utils/environmentContext.js.map +1 -0
  389. package/dist/src/utils/environmentContext.test.d.ts +6 -0
  390. package/dist/src/utils/environmentContext.test.js +139 -0
  391. package/dist/src/utils/environmentContext.test.js.map +1 -0
  392. package/dist/src/utils/errorReporting.d.ts +14 -0
  393. package/dist/src/utils/errorReporting.js +88 -0
  394. package/dist/src/utils/errorReporting.js.map +1 -0
  395. package/dist/src/utils/errorReporting.test.d.ts +6 -0
  396. package/dist/src/utils/errorReporting.test.js +130 -0
  397. package/dist/src/utils/errorReporting.test.js.map +1 -0
  398. package/dist/src/utils/errors.d.ts +14 -0
  399. package/dist/src/utils/errors.js +54 -0
  400. package/dist/src/utils/errors.js.map +1 -0
  401. package/dist/src/utils/fetch.d.ts +11 -0
  402. package/dist/src/utils/fetch.js +51 -0
  403. package/dist/src/utils/fetch.js.map +1 -0
  404. package/dist/src/utils/fileUtils.d.ts +56 -0
  405. package/dist/src/utils/fileUtils.js +314 -0
  406. package/dist/src/utils/fileUtils.js.map +1 -0
  407. package/dist/src/utils/fileUtils.test.d.ts +6 -0
  408. package/dist/src/utils/fileUtils.test.js +363 -0
  409. package/dist/src/utils/fileUtils.test.js.map +1 -0
  410. package/dist/src/utils/filesearch/crawlCache.d.ts +25 -0
  411. package/dist/src/utils/filesearch/crawlCache.js +57 -0
  412. package/dist/src/utils/filesearch/crawlCache.js.map +1 -0
  413. package/dist/src/utils/filesearch/crawlCache.test.d.ts +6 -0
  414. package/dist/src/utils/filesearch/crawlCache.test.js +103 -0
  415. package/dist/src/utils/filesearch/crawlCache.test.js.map +1 -0
  416. package/dist/src/utils/filesearch/fileSearch.d.ts +81 -0
  417. package/dist/src/utils/filesearch/fileSearch.js +241 -0
  418. package/dist/src/utils/filesearch/fileSearch.js.map +1 -0
  419. package/dist/src/utils/filesearch/fileSearch.test.d.ts +6 -0
  420. package/dist/src/utils/filesearch/fileSearch.test.js +654 -0
  421. package/dist/src/utils/filesearch/fileSearch.test.js.map +1 -0
  422. package/dist/src/utils/filesearch/ignore.d.ts +35 -0
  423. package/dist/src/utils/filesearch/ignore.js +81 -0
  424. package/dist/src/utils/filesearch/ignore.js.map +1 -0
  425. package/dist/src/utils/filesearch/ignore.test.d.ts +6 -0
  426. package/dist/src/utils/filesearch/ignore.test.js +57 -0
  427. package/dist/src/utils/filesearch/ignore.test.js.map +1 -0
  428. package/dist/src/utils/filesearch/result-cache.d.ts +34 -0
  429. package/dist/src/utils/filesearch/result-cache.js +61 -0
  430. package/dist/src/utils/filesearch/result-cache.js.map +1 -0
  431. package/dist/src/utils/filesearch/result-cache.test.d.ts +6 -0
  432. package/dist/src/utils/filesearch/result-cache.test.js +47 -0
  433. package/dist/src/utils/filesearch/result-cache.test.js.map +1 -0
  434. package/dist/src/utils/flashFallback.integration.test.d.ts +6 -0
  435. package/dist/src/utils/flashFallback.integration.test.js +118 -0
  436. package/dist/src/utils/flashFallback.integration.test.js.map +1 -0
  437. package/dist/src/utils/formatters.d.ts +6 -0
  438. package/dist/src/utils/formatters.js +16 -0
  439. package/dist/src/utils/formatters.js.map +1 -0
  440. package/dist/src/utils/generateContentResponseUtilities.d.ts +14 -0
  441. package/dist/src/utils/generateContentResponseUtilities.js +92 -0
  442. package/dist/src/utils/generateContentResponseUtilities.js.map +1 -0
  443. package/dist/src/utils/generateContentResponseUtilities.test.d.ts +6 -0
  444. package/dist/src/utils/generateContentResponseUtilities.test.js +273 -0
  445. package/dist/src/utils/generateContentResponseUtilities.test.js.map +1 -0
  446. package/dist/src/utils/getFolderStructure.d.ts +31 -0
  447. package/dist/src/utils/getFolderStructure.js +246 -0
  448. package/dist/src/utils/getFolderStructure.js.map +1 -0
  449. package/dist/src/utils/getFolderStructure.test.d.ts +6 -0
  450. package/dist/src/utils/getFolderStructure.test.js +282 -0
  451. package/dist/src/utils/getFolderStructure.test.js.map +1 -0
  452. package/dist/src/utils/gitIgnoreParser.d.ts +20 -0
  453. package/dist/src/utils/gitIgnoreParser.js +61 -0
  454. package/dist/src/utils/gitIgnoreParser.js.map +1 -0
  455. package/dist/src/utils/gitIgnoreParser.test.d.ts +6 -0
  456. package/dist/src/utils/gitIgnoreParser.test.js +154 -0
  457. package/dist/src/utils/gitIgnoreParser.test.js.map +1 -0
  458. package/dist/src/utils/gitUtils.d.ts +17 -0
  459. package/dist/src/utils/gitUtils.js +61 -0
  460. package/dist/src/utils/gitUtils.js.map +1 -0
  461. package/dist/src/utils/memoryDiscovery.d.ts +15 -0
  462. package/dist/src/utils/memoryDiscovery.js +219 -0
  463. package/dist/src/utils/memoryDiscovery.js.map +1 -0
  464. package/dist/src/utils/memoryDiscovery.test.d.ts +6 -0
  465. package/dist/src/utils/memoryDiscovery.test.js +181 -0
  466. package/dist/src/utils/memoryDiscovery.test.js.map +1 -0
  467. package/dist/src/utils/memoryImportProcessor.d.ts +42 -0
  468. package/dist/src/utils/memoryImportProcessor.js +300 -0
  469. package/dist/src/utils/memoryImportProcessor.js.map +1 -0
  470. package/dist/src/utils/memoryImportProcessor.test.d.ts +6 -0
  471. package/dist/src/utils/memoryImportProcessor.test.js +715 -0
  472. package/dist/src/utils/memoryImportProcessor.test.js.map +1 -0
  473. package/dist/src/utils/messageInspectors.d.ts +8 -0
  474. package/dist/src/utils/messageInspectors.js +16 -0
  475. package/dist/src/utils/messageInspectors.js.map +1 -0
  476. package/dist/src/utils/nextSpeakerChecker.d.ts +12 -0
  477. package/dist/src/utils/nextSpeakerChecker.js +92 -0
  478. package/dist/src/utils/nextSpeakerChecker.js.map +1 -0
  479. package/dist/src/utils/nextSpeakerChecker.test.d.ts +6 -0
  480. package/dist/src/utils/nextSpeakerChecker.test.js +168 -0
  481. package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -0
  482. package/dist/src/utils/openaiLogger.d.ts +42 -0
  483. package/dist/src/utils/openaiLogger.js +123 -0
  484. package/dist/src/utils/openaiLogger.js.map +1 -0
  485. package/dist/src/utils/partUtils.d.ts +14 -0
  486. package/dist/src/utils/partUtils.js +65 -0
  487. package/dist/src/utils/partUtils.js.map +1 -0
  488. package/dist/src/utils/partUtils.test.d.ts +6 -0
  489. package/dist/src/utils/partUtils.test.js +130 -0
  490. package/dist/src/utils/partUtils.test.js.map +1 -0
  491. package/dist/src/utils/paths.d.ts +68 -0
  492. package/dist/src/utils/paths.js +170 -0
  493. package/dist/src/utils/paths.js.map +1 -0
  494. package/dist/src/utils/paths.test.d.ts +6 -0
  495. package/dist/src/utils/paths.test.js +153 -0
  496. package/dist/src/utils/paths.test.js.map +1 -0
  497. package/dist/src/utils/quotaErrorDetection.d.ts +22 -0
  498. package/dist/src/utils/quotaErrorDetection.js +65 -0
  499. package/dist/src/utils/quotaErrorDetection.js.map +1 -0
  500. package/dist/src/utils/retry.d.ts +30 -0
  501. package/dist/src/utils/retry.js +276 -0
  502. package/dist/src/utils/retry.js.map +1 -0
  503. package/dist/src/utils/retry.test.d.ts +6 -0
  504. package/dist/src/utils/retry.test.js +322 -0
  505. package/dist/src/utils/retry.test.js.map +1 -0
  506. package/dist/src/utils/safeJsonStringify.d.ts +13 -0
  507. package/dist/src/utils/safeJsonStringify.js +25 -0
  508. package/dist/src/utils/safeJsonStringify.js.map +1 -0
  509. package/dist/src/utils/safeJsonStringify.test.d.ts +6 -0
  510. package/dist/src/utils/safeJsonStringify.test.js +61 -0
  511. package/dist/src/utils/safeJsonStringify.test.js.map +1 -0
  512. package/dist/src/utils/schemaValidator.d.ts +22 -0
  513. package/dist/src/utils/schemaValidator.js +65 -0
  514. package/dist/src/utils/schemaValidator.js.map +1 -0
  515. package/dist/src/utils/secure-browser-launcher.d.ts +23 -0
  516. package/dist/src/utils/secure-browser-launcher.js +164 -0
  517. package/dist/src/utils/secure-browser-launcher.js.map +1 -0
  518. package/dist/src/utils/secure-browser-launcher.test.d.ts +6 -0
  519. package/dist/src/utils/secure-browser-launcher.test.js +149 -0
  520. package/dist/src/utils/secure-browser-launcher.test.js.map +1 -0
  521. package/dist/src/utils/session.d.ts +6 -0
  522. package/dist/src/utils/session.js +8 -0
  523. package/dist/src/utils/session.js.map +1 -0
  524. package/dist/src/utils/shell-utils.d.ts +78 -0
  525. package/dist/src/utils/shell-utils.js +306 -0
  526. package/dist/src/utils/shell-utils.js.map +1 -0
  527. package/dist/src/utils/shell-utils.test.d.ts +6 -0
  528. package/dist/src/utils/shell-utils.test.js +200 -0
  529. package/dist/src/utils/shell-utils.test.js.map +1 -0
  530. package/dist/src/utils/summarizer.d.ts +25 -0
  531. package/dist/src/utils/summarizer.js +51 -0
  532. package/dist/src/utils/summarizer.js.map +1 -0
  533. package/dist/src/utils/summarizer.test.d.ts +6 -0
  534. package/dist/src/utils/summarizer.test.js +131 -0
  535. package/dist/src/utils/summarizer.test.js.map +1 -0
  536. package/dist/src/utils/systemEncoding.d.ts +40 -0
  537. package/dist/src/utils/systemEncoding.js +149 -0
  538. package/dist/src/utils/systemEncoding.js.map +1 -0
  539. package/dist/src/utils/systemEncoding.test.d.ts +6 -0
  540. package/dist/src/utils/systemEncoding.test.js +368 -0
  541. package/dist/src/utils/systemEncoding.test.js.map +1 -0
  542. package/dist/src/utils/testUtils.d.ts +29 -0
  543. package/dist/src/utils/testUtils.js +70 -0
  544. package/dist/src/utils/testUtils.js.map +1 -0
  545. package/dist/src/utils/textUtils.d.ts +13 -0
  546. package/dist/src/utils/textUtils.js +28 -0
  547. package/dist/src/utils/textUtils.js.map +1 -0
  548. package/dist/src/utils/user_account.d.ts +9 -0
  549. package/dist/src/utils/user_account.js +99 -0
  550. package/dist/src/utils/user_account.js.map +1 -0
  551. package/dist/src/utils/user_account.test.d.ts +6 -0
  552. package/dist/src/utils/user_account.test.js +153 -0
  553. package/dist/src/utils/user_account.test.js.map +1 -0
  554. package/dist/src/utils/user_id.d.ts +11 -0
  555. package/dist/src/utils/user_id.js +49 -0
  556. package/dist/src/utils/user_id.js.map +1 -0
  557. package/dist/src/utils/user_id.test.d.ts +6 -0
  558. package/dist/src/utils/user_id.test.js +21 -0
  559. package/dist/src/utils/user_id.test.js.map +1 -0
  560. package/dist/src/utils/workspaceContext.d.ts +51 -0
  561. package/dist/src/utils/workspaceContext.js +139 -0
  562. package/dist/src/utils/workspaceContext.js.map +1 -0
  563. package/dist/src/utils/workspaceContext.test.d.ts +6 -0
  564. package/dist/src/utils/workspaceContext.test.js +209 -0
  565. package/dist/src/utils/workspaceContext.test.js.map +1 -0
  566. package/dist/tsconfig.tsbuildinfo +1 -0
  567. package/package.json +74 -0
@@ -0,0 +1,1904 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Qwen
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import { OpenAIContentGenerator } from './openaiContentGenerator.js';
8
+ import OpenAI from 'openai';
9
+ import { Type, FinishReason } from '@google/genai';
10
+ // Mock OpenAI
11
+ vi.mock('openai');
12
+ // Mock logger modules
13
+ vi.mock('../telemetry/loggers.js', () => ({
14
+ logApiResponse: vi.fn(),
15
+ }));
16
+ vi.mock('../utils/openaiLogger.js', () => ({
17
+ openaiLogger: {
18
+ logInteraction: vi.fn(),
19
+ },
20
+ }));
21
+ // Mock tiktoken
22
+ vi.mock('tiktoken', () => ({
23
+ get_encoding: vi.fn().mockReturnValue({
24
+ encode: vi.fn().mockReturnValue(new Array(50)), // Mock 50 tokens
25
+ free: vi.fn(),
26
+ }),
27
+ }));
28
+ describe('OpenAIContentGenerator', () => {
29
+ let generator;
30
+ let mockConfig;
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ let mockOpenAIClient;
33
+ beforeEach(() => {
34
+ // Reset mocks
35
+ vi.clearAllMocks();
36
+ // Mock environment variables
37
+ vi.stubEnv('OPENAI_BASE_URL', '');
38
+ // Mock config
39
+ mockConfig = {
40
+ getContentGeneratorConfig: vi.fn().mockReturnValue({
41
+ authType: 'openai',
42
+ enableOpenAILogging: false,
43
+ timeout: 120000,
44
+ maxRetries: 3,
45
+ samplingParams: {
46
+ temperature: 0.7,
47
+ max_tokens: 1000,
48
+ top_p: 0.9,
49
+ },
50
+ }),
51
+ };
52
+ // Mock OpenAI client
53
+ mockOpenAIClient = {
54
+ chat: {
55
+ completions: {
56
+ create: vi.fn(),
57
+ },
58
+ },
59
+ embeddings: {
60
+ create: vi.fn(),
61
+ },
62
+ };
63
+ vi.mocked(OpenAI).mockImplementation(() => mockOpenAIClient);
64
+ // Create generator instance
65
+ generator = new OpenAIContentGenerator('test-key', 'gpt-4', mockConfig);
66
+ });
67
+ afterEach(() => {
68
+ vi.restoreAllMocks();
69
+ });
70
+ describe('constructor', () => {
71
+ it('should initialize with basic configuration', () => {
72
+ expect(OpenAI).toHaveBeenCalledWith({
73
+ apiKey: 'test-key',
74
+ baseURL: '',
75
+ timeout: 120000,
76
+ maxRetries: 3,
77
+ defaultHeaders: {
78
+ 'User-Agent': expect.stringMatching(/^QwenCode/),
79
+ },
80
+ });
81
+ });
82
+ it('should handle custom base URL', () => {
83
+ vi.stubEnv('OPENAI_BASE_URL', 'https://api.custom.com');
84
+ new OpenAIContentGenerator('test-key', 'gpt-4', mockConfig);
85
+ expect(OpenAI).toHaveBeenCalledWith({
86
+ apiKey: 'test-key',
87
+ baseURL: 'https://api.custom.com',
88
+ timeout: 120000,
89
+ maxRetries: 3,
90
+ defaultHeaders: {
91
+ 'User-Agent': expect.stringMatching(/^QwenCode/),
92
+ },
93
+ });
94
+ });
95
+ it('should configure OpenRouter headers when using OpenRouter', () => {
96
+ vi.stubEnv('OPENAI_BASE_URL', 'https://openrouter.ai/api/v1');
97
+ new OpenAIContentGenerator('test-key', 'gpt-4', mockConfig);
98
+ expect(OpenAI).toHaveBeenCalledWith({
99
+ apiKey: 'test-key',
100
+ baseURL: 'https://openrouter.ai/api/v1',
101
+ timeout: 120000,
102
+ maxRetries: 3,
103
+ defaultHeaders: {
104
+ 'User-Agent': expect.stringMatching(/^QwenCode/),
105
+ 'HTTP-Referer': 'https://github.com/QwenLM/qwen-code.git',
106
+ 'X-Title': 'Qwen Code',
107
+ },
108
+ });
109
+ });
110
+ it('should override timeout settings from config', () => {
111
+ const customConfig = {
112
+ getContentGeneratorConfig: vi.fn().mockReturnValue({
113
+ timeout: 300000,
114
+ maxRetries: 5,
115
+ }),
116
+ };
117
+ new OpenAIContentGenerator('test-key', 'gpt-4', customConfig);
118
+ expect(OpenAI).toHaveBeenCalledWith({
119
+ apiKey: 'test-key',
120
+ baseURL: '',
121
+ timeout: 300000,
122
+ maxRetries: 5,
123
+ defaultHeaders: {
124
+ 'User-Agent': expect.stringMatching(/^QwenCode/),
125
+ },
126
+ });
127
+ });
128
+ });
129
+ describe('generateContent', () => {
130
+ it('should generate content successfully', async () => {
131
+ const mockResponse = {
132
+ id: 'chatcmpl-123',
133
+ object: 'chat.completion',
134
+ created: 1677652288,
135
+ model: 'gpt-4',
136
+ choices: [
137
+ {
138
+ index: 0,
139
+ message: {
140
+ role: 'assistant',
141
+ content: 'Hello! How can I help you?',
142
+ },
143
+ finish_reason: 'stop',
144
+ },
145
+ ],
146
+ usage: {
147
+ prompt_tokens: 10,
148
+ completion_tokens: 15,
149
+ total_tokens: 25,
150
+ },
151
+ };
152
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
153
+ const request = {
154
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
155
+ model: 'gpt-4',
156
+ };
157
+ const result = await generator.generateContent(request, 'test-prompt-id');
158
+ expect(result.candidates).toHaveLength(1);
159
+ if (result.candidates &&
160
+ result.candidates.length > 0 &&
161
+ result.candidates[0]) {
162
+ const firstCandidate = result.candidates[0];
163
+ if (firstCandidate.content) {
164
+ expect(firstCandidate.content.parts).toEqual([
165
+ { text: 'Hello! How can I help you?' },
166
+ ]);
167
+ }
168
+ }
169
+ expect(result.usageMetadata).toEqual({
170
+ promptTokenCount: 10,
171
+ candidatesTokenCount: 15,
172
+ totalTokenCount: 25,
173
+ cachedContentTokenCount: 0,
174
+ });
175
+ });
176
+ it('should handle system instructions', async () => {
177
+ const mockResponse = {
178
+ id: 'chatcmpl-123',
179
+ choices: [
180
+ {
181
+ index: 0,
182
+ message: { role: 'assistant', content: 'Response' },
183
+ finish_reason: 'stop',
184
+ },
185
+ ],
186
+ created: 1677652288,
187
+ model: 'gpt-4',
188
+ };
189
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
190
+ const request = {
191
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
192
+ model: 'gpt-4',
193
+ config: {
194
+ systemInstruction: 'You are a helpful assistant.',
195
+ },
196
+ };
197
+ await generator.generateContent(request, 'test-prompt-id');
198
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
199
+ messages: [
200
+ { role: 'system', content: 'You are a helpful assistant.' },
201
+ { role: 'user', content: 'Hello' },
202
+ ],
203
+ }));
204
+ });
205
+ it('should handle function calls', async () => {
206
+ const mockResponse = {
207
+ id: 'chatcmpl-123',
208
+ choices: [
209
+ {
210
+ index: 0,
211
+ message: {
212
+ role: 'assistant',
213
+ content: null,
214
+ tool_calls: [
215
+ {
216
+ id: 'call_123',
217
+ type: 'function',
218
+ function: {
219
+ name: 'get_weather',
220
+ arguments: '{"location": "New York"}',
221
+ },
222
+ },
223
+ ],
224
+ },
225
+ finish_reason: 'tool_calls',
226
+ },
227
+ ],
228
+ created: 1677652288,
229
+ model: 'gpt-4',
230
+ };
231
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
232
+ const request = {
233
+ contents: [{ role: 'user', parts: [{ text: 'What is the weather?' }] }],
234
+ model: 'gpt-4',
235
+ config: {
236
+ tools: [
237
+ {
238
+ callTool: vi.fn(),
239
+ tool: () => Promise.resolve({
240
+ functionDeclarations: [
241
+ {
242
+ name: 'get_weather',
243
+ description: 'Get weather information',
244
+ parameters: {
245
+ type: Type.OBJECT,
246
+ properties: { location: { type: Type.STRING } },
247
+ },
248
+ },
249
+ ],
250
+ }),
251
+ },
252
+ ],
253
+ },
254
+ };
255
+ const result = await generator.generateContent(request, 'test-prompt-id');
256
+ if (result.candidates &&
257
+ result.candidates.length > 0 &&
258
+ result.candidates[0]) {
259
+ const firstCandidate = result.candidates[0];
260
+ if (firstCandidate.content) {
261
+ expect(firstCandidate.content.parts).toEqual([
262
+ {
263
+ functionCall: {
264
+ id: 'call_123',
265
+ name: 'get_weather',
266
+ args: { location: 'New York' },
267
+ },
268
+ },
269
+ ]);
270
+ }
271
+ }
272
+ });
273
+ it('should apply sampling parameters from config', async () => {
274
+ const mockResponse = {
275
+ id: 'chatcmpl-123',
276
+ choices: [
277
+ {
278
+ index: 0,
279
+ message: { role: 'assistant', content: 'Response' },
280
+ finish_reason: 'stop',
281
+ },
282
+ ],
283
+ created: 1677652288,
284
+ model: 'gpt-4',
285
+ };
286
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
287
+ const request = {
288
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
289
+ model: 'gpt-4',
290
+ };
291
+ await generator.generateContent(request, 'test-prompt-id');
292
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
293
+ temperature: 0.7,
294
+ max_tokens: 1000,
295
+ top_p: 0.9,
296
+ }));
297
+ });
298
+ it('should prioritize request-level parameters over config', async () => {
299
+ const mockResponse = {
300
+ id: 'chatcmpl-123',
301
+ choices: [
302
+ {
303
+ index: 0,
304
+ message: { role: 'assistant', content: 'Response' },
305
+ finish_reason: 'stop',
306
+ },
307
+ ],
308
+ created: 1677652288,
309
+ model: 'gpt-4',
310
+ };
311
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
312
+ const request = {
313
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
314
+ model: 'gpt-4',
315
+ config: {
316
+ temperature: 0.5,
317
+ maxOutputTokens: 500,
318
+ },
319
+ };
320
+ await generator.generateContent(request, 'test-prompt-id');
321
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
322
+ temperature: 0.7, // From config sampling params (higher priority)
323
+ max_tokens: 1000, // From config sampling params (higher priority)
324
+ top_p: 0.9,
325
+ }));
326
+ });
327
+ });
328
+ describe('generateContentStream', () => {
329
+ it('should handle streaming responses', async () => {
330
+ const mockStream = [
331
+ {
332
+ id: 'chatcmpl-123',
333
+ choices: [
334
+ {
335
+ index: 0,
336
+ delta: { content: 'Hello' },
337
+ finish_reason: null,
338
+ },
339
+ ],
340
+ created: 1677652288,
341
+ },
342
+ {
343
+ id: 'chatcmpl-123',
344
+ choices: [
345
+ {
346
+ index: 0,
347
+ delta: { content: ' there!' },
348
+ finish_reason: 'stop',
349
+ },
350
+ ],
351
+ created: 1677652288,
352
+ usage: {
353
+ prompt_tokens: 10,
354
+ completion_tokens: 5,
355
+ total_tokens: 15,
356
+ },
357
+ },
358
+ ];
359
+ // Mock async iterable
360
+ mockOpenAIClient.chat.completions.create.mockResolvedValue({
361
+ async *[Symbol.asyncIterator]() {
362
+ for (const chunk of mockStream) {
363
+ yield chunk;
364
+ }
365
+ },
366
+ });
367
+ const request = {
368
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
369
+ model: 'gpt-4',
370
+ };
371
+ const stream = await generator.generateContentStream(request, 'test-prompt-id');
372
+ const responses = [];
373
+ for await (const response of stream) {
374
+ responses.push(response);
375
+ }
376
+ expect(responses).toHaveLength(2);
377
+ if (responses[0]?.candidates &&
378
+ responses[0].candidates.length > 0 &&
379
+ responses[0].candidates[0]) {
380
+ const firstCandidate = responses[0].candidates[0];
381
+ if (firstCandidate.content) {
382
+ expect(firstCandidate.content.parts).toEqual([{ text: 'Hello' }]);
383
+ }
384
+ }
385
+ if (responses[1]?.candidates &&
386
+ responses[1].candidates.length > 0 &&
387
+ responses[1].candidates[0]) {
388
+ const secondCandidate = responses[1].candidates[0];
389
+ if (secondCandidate.content) {
390
+ expect(secondCandidate.content.parts).toEqual([{ text: ' there!' }]);
391
+ }
392
+ }
393
+ expect(responses[1].usageMetadata).toEqual({
394
+ promptTokenCount: 10,
395
+ candidatesTokenCount: 5,
396
+ totalTokenCount: 15,
397
+ cachedContentTokenCount: 0,
398
+ });
399
+ });
400
+ it('should handle streaming tool calls', async () => {
401
+ const mockStream = [
402
+ {
403
+ id: 'chatcmpl-123',
404
+ choices: [
405
+ {
406
+ index: 0,
407
+ delta: {
408
+ tool_calls: [
409
+ {
410
+ index: 0,
411
+ id: 'call_123',
412
+ function: { name: 'get_weather' },
413
+ },
414
+ ],
415
+ },
416
+ finish_reason: null,
417
+ },
418
+ ],
419
+ created: 1677652288,
420
+ },
421
+ {
422
+ id: 'chatcmpl-123',
423
+ choices: [
424
+ {
425
+ index: 0,
426
+ delta: {
427
+ tool_calls: [
428
+ {
429
+ index: 0,
430
+ function: { arguments: '{"location": "NYC"}' },
431
+ },
432
+ ],
433
+ },
434
+ finish_reason: 'tool_calls',
435
+ },
436
+ ],
437
+ created: 1677652288,
438
+ },
439
+ ];
440
+ mockOpenAIClient.chat.completions.create.mockResolvedValue({
441
+ async *[Symbol.asyncIterator]() {
442
+ for (const chunk of mockStream) {
443
+ yield chunk;
444
+ }
445
+ },
446
+ });
447
+ const request = {
448
+ contents: [{ role: 'user', parts: [{ text: 'Weather?' }] }],
449
+ model: 'gpt-4',
450
+ };
451
+ const stream = await generator.generateContentStream(request, 'test-prompt-id');
452
+ const responses = [];
453
+ for await (const response of stream) {
454
+ responses.push(response);
455
+ }
456
+ // Tool calls should only appear in the final response
457
+ if (responses[0]?.candidates &&
458
+ responses[0].candidates.length > 0 &&
459
+ responses[0].candidates[0]) {
460
+ const firstCandidate = responses[0].candidates[0];
461
+ if (firstCandidate.content) {
462
+ expect(firstCandidate.content.parts).toEqual([]);
463
+ }
464
+ }
465
+ if (responses[1]?.candidates &&
466
+ responses[1].candidates.length > 0 &&
467
+ responses[1].candidates[0]) {
468
+ const secondCandidate = responses[1].candidates[0];
469
+ if (secondCandidate.content) {
470
+ expect(secondCandidate.content.parts).toEqual([
471
+ {
472
+ functionCall: {
473
+ id: 'call_123',
474
+ name: 'get_weather',
475
+ args: { location: 'NYC' },
476
+ },
477
+ },
478
+ ]);
479
+ }
480
+ }
481
+ });
482
+ });
483
+ describe('countTokens', () => {
484
+ it('should count tokens using tiktoken', async () => {
485
+ const request = {
486
+ contents: [{ role: 'user', parts: [{ text: 'Hello world' }] }],
487
+ model: 'gpt-4',
488
+ };
489
+ const result = await generator.countTokens(request);
490
+ expect(result.totalTokens).toBe(50); // Mocked value
491
+ });
492
+ it('should fall back to character approximation if tiktoken fails', async () => {
493
+ // Mock tiktoken to throw error
494
+ vi.doMock('tiktoken', () => ({
495
+ get_encoding: vi.fn().mockImplementation(() => {
496
+ throw new Error('Tiktoken failed');
497
+ }),
498
+ }));
499
+ const request = {
500
+ contents: [{ role: 'user', parts: [{ text: 'Hello world' }] }],
501
+ model: 'gpt-4',
502
+ };
503
+ const result = await generator.countTokens(request);
504
+ // Should use character approximation (content length / 4)
505
+ expect(result.totalTokens).toBeGreaterThan(0);
506
+ });
507
+ });
508
+ describe('embedContent', () => {
509
+ it('should generate embeddings for text content', async () => {
510
+ const mockEmbedding = {
511
+ data: [{ embedding: [0.1, 0.2, 0.3, 0.4] }],
512
+ model: 'text-embedding-ada-002',
513
+ usage: { prompt_tokens: 5, total_tokens: 5 },
514
+ };
515
+ mockOpenAIClient.embeddings.create.mockResolvedValue(mockEmbedding);
516
+ const request = {
517
+ contents: [{ role: 'user', parts: [{ text: 'Hello world' }] }],
518
+ model: 'text-embedding-ada-002',
519
+ };
520
+ const result = await generator.embedContent(request);
521
+ expect(result.embeddings).toHaveLength(1);
522
+ expect(result.embeddings?.[0]?.values).toEqual([0.1, 0.2, 0.3, 0.4]);
523
+ expect(mockOpenAIClient.embeddings.create).toHaveBeenCalledWith({
524
+ model: 'text-embedding-ada-002',
525
+ input: 'Hello world',
526
+ });
527
+ });
528
+ it('should handle string content', async () => {
529
+ const mockEmbedding = {
530
+ data: [{ embedding: [0.1, 0.2] }],
531
+ };
532
+ mockOpenAIClient.embeddings.create.mockResolvedValue(mockEmbedding);
533
+ const request = {
534
+ contents: 'Simple text',
535
+ model: 'text-embedding-ada-002',
536
+ };
537
+ const _result = await generator.embedContent(request);
538
+ expect(mockOpenAIClient.embeddings.create).toHaveBeenCalledWith({
539
+ model: 'text-embedding-ada-002',
540
+ input: 'Simple text',
541
+ });
542
+ });
543
+ it('should handle embedding errors', async () => {
544
+ const error = new Error('Embedding failed');
545
+ mockOpenAIClient.embeddings.create.mockRejectedValue(error);
546
+ const request = {
547
+ contents: 'Test text',
548
+ model: 'text-embedding-ada-002',
549
+ };
550
+ await expect(generator.embedContent(request)).rejects.toThrow('Embedding failed');
551
+ });
552
+ });
553
+ describe('error handling', () => {
554
+ it('should handle API errors with proper error message', async () => {
555
+ const apiError = new Error('Invalid API key');
556
+ mockOpenAIClient.chat.completions.create.mockRejectedValue(apiError);
557
+ const request = {
558
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
559
+ model: 'gpt-4',
560
+ };
561
+ await expect(generator.generateContent(request, 'test-prompt-id')).rejects.toThrow('Invalid API key');
562
+ });
563
+ it('should estimate tokens on error for telemetry', async () => {
564
+ const apiError = new Error('API error');
565
+ mockOpenAIClient.chat.completions.create.mockRejectedValue(apiError);
566
+ const request = {
567
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
568
+ model: 'gpt-4',
569
+ };
570
+ try {
571
+ await generator.generateContent(request, 'test-prompt-id');
572
+ }
573
+ catch (error) {
574
+ // Error should be thrown but token estimation should have been attempted
575
+ expect(error).toBeInstanceOf(Error);
576
+ }
577
+ });
578
+ it('should preserve error status codes like 429', async () => {
579
+ // Create an error object with status property like OpenAI SDK would
580
+ const apiError = Object.assign(new Error('Rate limit exceeded'), {
581
+ status: 429,
582
+ });
583
+ mockOpenAIClient.chat.completions.create.mockRejectedValue(apiError);
584
+ const request = {
585
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
586
+ model: 'gpt-4',
587
+ };
588
+ try {
589
+ await generator.generateContent(request, 'test-prompt-id');
590
+ expect.fail('Expected error to be thrown');
591
+ }
592
+ catch (error) {
593
+ // Should throw the original error object with status preserved
594
+ expect(error.message).toBe('Rate limit exceeded');
595
+ expect(error.status).toBe(429);
596
+ }
597
+ });
598
+ });
599
+ describe('message conversion', () => {
600
+ it('should convert function responses to tool messages', async () => {
601
+ const mockResponse = {
602
+ id: 'chatcmpl-123',
603
+ choices: [
604
+ {
605
+ index: 0,
606
+ message: { role: 'assistant', content: 'Response' },
607
+ finish_reason: 'stop',
608
+ },
609
+ ],
610
+ created: 1677652288,
611
+ model: 'gpt-4',
612
+ };
613
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
614
+ const request = {
615
+ contents: [
616
+ { role: 'user', parts: [{ text: 'What is the weather?' }] },
617
+ {
618
+ role: 'model',
619
+ parts: [
620
+ {
621
+ functionCall: {
622
+ id: 'call_123',
623
+ name: 'get_weather',
624
+ args: { location: 'NYC' },
625
+ },
626
+ },
627
+ ],
628
+ },
629
+ {
630
+ role: 'user',
631
+ parts: [
632
+ {
633
+ functionResponse: {
634
+ id: 'call_123',
635
+ name: 'get_weather',
636
+ response: { temperature: '72F', condition: 'sunny' },
637
+ },
638
+ },
639
+ ],
640
+ },
641
+ ],
642
+ model: 'gpt-4',
643
+ };
644
+ await generator.generateContent(request, 'test-prompt-id');
645
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
646
+ messages: expect.arrayContaining([
647
+ { role: 'user', content: 'What is the weather?' },
648
+ {
649
+ role: 'assistant',
650
+ content: null,
651
+ tool_calls: [
652
+ {
653
+ id: 'call_123',
654
+ type: 'function',
655
+ function: {
656
+ name: 'get_weather',
657
+ arguments: '{"location":"NYC"}',
658
+ },
659
+ },
660
+ ],
661
+ },
662
+ {
663
+ role: 'tool',
664
+ tool_call_id: 'call_123',
665
+ content: '{"temperature":"72F","condition":"sunny"}',
666
+ },
667
+ ]),
668
+ }));
669
+ });
670
+ it('should clean up orphaned tool calls', async () => {
671
+ const mockResponse = {
672
+ id: 'chatcmpl-123',
673
+ choices: [
674
+ {
675
+ index: 0,
676
+ message: { role: 'assistant', content: 'Response' },
677
+ finish_reason: 'stop',
678
+ },
679
+ ],
680
+ created: 1677652288,
681
+ model: 'gpt-4',
682
+ };
683
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
684
+ const request = {
685
+ contents: [
686
+ {
687
+ role: 'model',
688
+ parts: [
689
+ {
690
+ functionCall: {
691
+ id: 'call_orphaned',
692
+ name: 'orphaned_function',
693
+ args: {},
694
+ },
695
+ },
696
+ ],
697
+ },
698
+ // No corresponding function response
699
+ ],
700
+ model: 'gpt-4',
701
+ };
702
+ await generator.generateContent(request, 'test-prompt-id');
703
+ // Should not include the orphaned tool call
704
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
705
+ messages: [], // Empty because orphaned tool call was cleaned up
706
+ }));
707
+ });
708
+ });
709
+ describe('finish reason mapping', () => {
710
+ it('should map OpenAI finish reasons to Gemini format', async () => {
711
+ const testCases = [
712
+ { openai: 'stop', expected: FinishReason.STOP },
713
+ { openai: 'length', expected: FinishReason.MAX_TOKENS },
714
+ { openai: 'content_filter', expected: FinishReason.SAFETY },
715
+ { openai: 'function_call', expected: FinishReason.STOP },
716
+ { openai: 'tool_calls', expected: FinishReason.STOP },
717
+ ];
718
+ for (const testCase of testCases) {
719
+ const mockResponse = {
720
+ id: 'chatcmpl-123',
721
+ choices: [
722
+ {
723
+ index: 0,
724
+ message: { role: 'assistant', content: 'Response' },
725
+ finish_reason: testCase.openai,
726
+ },
727
+ ],
728
+ created: 1677652288,
729
+ model: 'gpt-4',
730
+ };
731
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
732
+ const request = {
733
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
734
+ model: 'gpt-4',
735
+ };
736
+ const result = await generator.generateContent(request, 'test-prompt-id');
737
+ if (result.candidates &&
738
+ result.candidates.length > 0 &&
739
+ result.candidates[0]) {
740
+ const firstCandidate = result.candidates[0];
741
+ expect(firstCandidate.finishReason).toBe(testCase.expected);
742
+ }
743
+ }
744
+ });
745
+ });
746
+ describe('logging integration', () => {
747
+ it('should log interactions when enabled', async () => {
748
+ const loggingConfig = {
749
+ getContentGeneratorConfig: vi.fn().mockReturnValue({
750
+ enableOpenAILogging: true,
751
+ }),
752
+ };
753
+ const loggingGenerator = new OpenAIContentGenerator('test-key', 'gpt-4', loggingConfig);
754
+ const mockResponse = {
755
+ id: 'chatcmpl-123',
756
+ choices: [
757
+ {
758
+ index: 0,
759
+ message: { role: 'assistant', content: 'Response' },
760
+ finish_reason: 'stop',
761
+ },
762
+ ],
763
+ created: 1677652288,
764
+ model: 'gpt-4',
765
+ };
766
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
767
+ const request = {
768
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
769
+ model: 'gpt-4',
770
+ };
771
+ await loggingGenerator.generateContent(request, 'test-prompt-id');
772
+ // Verify logging was called
773
+ });
774
+ });
775
+ describe('timeout error detection', () => {
776
+ it('should detect various timeout error patterns', async () => {
777
+ const timeoutErrors = [
778
+ new Error('timeout'),
779
+ new Error('Request timed out'),
780
+ new Error('Connection timeout occurred'),
781
+ new Error('ETIMEDOUT'),
782
+ new Error('ESOCKETTIMEDOUT'),
783
+ { code: 'ETIMEDOUT', message: 'Connection timed out' },
784
+ { type: 'timeout', message: 'Request timeout' },
785
+ new Error('deadline exceeded'),
786
+ ];
787
+ for (const error of timeoutErrors) {
788
+ mockOpenAIClient.chat.completions.create.mockRejectedValueOnce(error);
789
+ const request = {
790
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
791
+ model: 'gpt-4',
792
+ };
793
+ try {
794
+ await generator.generateContent(request, 'test-prompt-id');
795
+ // Should not reach here
796
+ expect(true).toBe(false);
797
+ }
798
+ catch (error) {
799
+ const errorMessage = error instanceof Error ? error.message : String(error);
800
+ expect(errorMessage).toMatch(/timeout|Troubleshooting tips/);
801
+ }
802
+ }
803
+ });
804
+ it('should provide timeout-specific error messages', async () => {
805
+ const timeoutError = new Error('Request timeout');
806
+ mockOpenAIClient.chat.completions.create.mockRejectedValue(timeoutError);
807
+ const request = {
808
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
809
+ model: 'gpt-4',
810
+ };
811
+ await expect(generator.generateContent(request, 'test-prompt-id')).rejects.toThrow(/Troubleshooting tips.*Reduce input length.*Increase timeout.*Check network/s);
812
+ });
813
+ });
814
+ describe('streaming error handling', () => {
815
+ it('should handle errors during streaming setup', async () => {
816
+ const setupError = new Error('Streaming setup failed');
817
+ mockOpenAIClient.chat.completions.create.mockRejectedValue(setupError);
818
+ const request = {
819
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
820
+ model: 'gpt-4',
821
+ };
822
+ await expect(generator.generateContent(request, 'test-prompt-id')).rejects.toThrow('Streaming setup failed');
823
+ });
824
+ it('should handle timeout errors during streaming setup', async () => {
825
+ const timeoutError = new Error('Streaming setup timeout');
826
+ mockOpenAIClient.chat.completions.create.mockRejectedValue(timeoutError);
827
+ const request = {
828
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
829
+ model: 'gpt-4',
830
+ };
831
+ await expect(generator.generateContentStream(request, 'test-prompt-id')).rejects.toThrow(/Streaming setup timeout troubleshooting.*Reduce input length/s);
832
+ });
833
+ it('should handle errors during streaming with logging', async () => {
834
+ const loggingConfig = {
835
+ getContentGeneratorConfig: vi.fn().mockReturnValue({
836
+ enableOpenAILogging: true,
837
+ }),
838
+ };
839
+ const loggingGenerator = new OpenAIContentGenerator('test-key', 'gpt-4', loggingConfig);
840
+ // Mock stream that throws an error
841
+ const mockStream = {
842
+ async *[Symbol.asyncIterator]() {
843
+ yield {
844
+ id: 'chatcmpl-123',
845
+ choices: [
846
+ {
847
+ index: 0,
848
+ delta: { content: 'Hello' },
849
+ finish_reason: null,
850
+ },
851
+ ],
852
+ created: 1677652288,
853
+ };
854
+ throw new Error('Stream error');
855
+ },
856
+ };
857
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockStream);
858
+ const request = {
859
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
860
+ model: 'gpt-4',
861
+ };
862
+ const stream = await loggingGenerator.generateContentStream(request, 'test-prompt-id');
863
+ // Consume the stream and expect error
864
+ await expect(async () => {
865
+ for await (const chunk of stream) {
866
+ // Stream will throw during iteration
867
+ console.log('Processing chunk:', chunk); // Use chunk to avoid warning
868
+ }
869
+ }).rejects.toThrow('Stream error');
870
+ });
871
+ });
872
+ describe('tool parameter conversion', () => {
873
+ it('should convert Gemini types to OpenAI JSON Schema types', async () => {
874
+ const mockResponse = {
875
+ id: 'chatcmpl-123',
876
+ choices: [
877
+ {
878
+ index: 0,
879
+ message: { role: 'assistant', content: 'Response' },
880
+ finish_reason: 'stop',
881
+ },
882
+ ],
883
+ created: 1677652288,
884
+ model: 'gpt-4',
885
+ };
886
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
887
+ const request = {
888
+ contents: [{ role: 'user', parts: [{ text: 'Test' }] }],
889
+ model: 'gpt-4',
890
+ config: {
891
+ tools: [
892
+ {
893
+ callTool: vi.fn(),
894
+ tool: () => Promise.resolve({
895
+ functionDeclarations: [
896
+ {
897
+ name: 'test_function',
898
+ description: 'Test function',
899
+ parameters: {
900
+ type: 'OBJECT',
901
+ properties: {
902
+ count: {
903
+ type: 'INTEGER',
904
+ minimum: '1',
905
+ maximum: '100',
906
+ },
907
+ name: {
908
+ type: 'STRING',
909
+ minLength: '1',
910
+ maxLength: '50',
911
+ },
912
+ score: { type: 'NUMBER', multipleOf: '0.1' },
913
+ items: {
914
+ type: 'ARRAY',
915
+ minItems: '1',
916
+ maxItems: '10',
917
+ },
918
+ },
919
+ },
920
+ },
921
+ ],
922
+ }),
923
+ },
924
+ ],
925
+ },
926
+ };
927
+ await generator.generateContent(request, 'test-prompt-id');
928
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
929
+ tools: [
930
+ {
931
+ type: 'function',
932
+ function: {
933
+ name: 'test_function',
934
+ description: 'Test function',
935
+ parameters: {
936
+ type: 'object',
937
+ properties: {
938
+ count: { type: 'integer', minimum: 1, maximum: 100 },
939
+ name: { type: 'string', minLength: 1, maxLength: 50 },
940
+ score: { type: 'number', multipleOf: 0.1 },
941
+ items: { type: 'array', minItems: 1, maxItems: 10 },
942
+ },
943
+ },
944
+ },
945
+ },
946
+ ],
947
+ }));
948
+ });
949
+ it('should handle nested parameter objects', async () => {
950
+ const mockResponse = {
951
+ id: 'chatcmpl-123',
952
+ choices: [
953
+ {
954
+ index: 0,
955
+ message: { role: 'assistant', content: 'Response' },
956
+ finish_reason: 'stop',
957
+ },
958
+ ],
959
+ created: 1677652288,
960
+ model: 'gpt-4',
961
+ };
962
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
963
+ const request = {
964
+ contents: [{ role: 'user', parts: [{ text: 'Test' }] }],
965
+ model: 'gpt-4',
966
+ config: {
967
+ tools: [
968
+ {
969
+ callTool: vi.fn(),
970
+ tool: () => Promise.resolve({
971
+ functionDeclarations: [
972
+ {
973
+ name: 'nested_function',
974
+ description: 'Function with nested parameters',
975
+ parameters: {
976
+ type: 'OBJECT',
977
+ properties: {
978
+ config: {
979
+ type: 'OBJECT',
980
+ properties: {
981
+ nested_count: { type: 'INTEGER' },
982
+ nested_array: {
983
+ type: 'ARRAY',
984
+ items: { type: 'STRING' },
985
+ },
986
+ },
987
+ },
988
+ },
989
+ },
990
+ },
991
+ ],
992
+ }),
993
+ },
994
+ ],
995
+ },
996
+ };
997
+ await generator.generateContent(request, 'test-prompt-id');
998
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
999
+ tools: [
1000
+ {
1001
+ type: 'function',
1002
+ function: {
1003
+ name: 'nested_function',
1004
+ description: 'Function with nested parameters',
1005
+ parameters: {
1006
+ type: 'object',
1007
+ properties: {
1008
+ config: {
1009
+ type: 'object',
1010
+ properties: {
1011
+ nested_count: { type: 'integer' },
1012
+ nested_array: {
1013
+ type: 'array',
1014
+ items: { type: 'string' },
1015
+ },
1016
+ },
1017
+ },
1018
+ },
1019
+ },
1020
+ },
1021
+ },
1022
+ ],
1023
+ }));
1024
+ });
1025
+ });
1026
+ describe('message cleanup and conversion', () => {
1027
+ it('should handle complex conversation with multiple tool calls', async () => {
1028
+ const mockResponse = {
1029
+ id: 'chatcmpl-123',
1030
+ choices: [
1031
+ {
1032
+ index: 0,
1033
+ message: { role: 'assistant', content: 'Response' },
1034
+ finish_reason: 'stop',
1035
+ },
1036
+ ],
1037
+ created: 1677652288,
1038
+ model: 'gpt-4',
1039
+ };
1040
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1041
+ const request = {
1042
+ contents: [
1043
+ { role: 'user', parts: [{ text: 'What tools are available?' }] },
1044
+ {
1045
+ role: 'model',
1046
+ parts: [
1047
+ {
1048
+ functionCall: {
1049
+ id: 'call_1',
1050
+ name: 'list_tools',
1051
+ args: { category: 'all' },
1052
+ },
1053
+ },
1054
+ ],
1055
+ },
1056
+ {
1057
+ role: 'user',
1058
+ parts: [
1059
+ {
1060
+ functionResponse: {
1061
+ id: 'call_1',
1062
+ name: 'list_tools',
1063
+ response: { tools: ['calculator', 'weather'] },
1064
+ },
1065
+ },
1066
+ ],
1067
+ },
1068
+ {
1069
+ role: 'model',
1070
+ parts: [
1071
+ {
1072
+ functionCall: {
1073
+ id: 'call_2',
1074
+ name: 'get_weather',
1075
+ args: { location: 'NYC' },
1076
+ },
1077
+ },
1078
+ ],
1079
+ },
1080
+ {
1081
+ role: 'user',
1082
+ parts: [
1083
+ {
1084
+ functionResponse: {
1085
+ id: 'call_2',
1086
+ name: 'get_weather',
1087
+ response: { temperature: '22°C', condition: 'sunny' },
1088
+ },
1089
+ },
1090
+ ],
1091
+ },
1092
+ ],
1093
+ model: 'gpt-4',
1094
+ };
1095
+ await generator.generateContent(request, 'test-prompt-id');
1096
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
1097
+ messages: [
1098
+ { role: 'user', content: 'What tools are available?' },
1099
+ {
1100
+ role: 'assistant',
1101
+ content: null,
1102
+ tool_calls: [
1103
+ {
1104
+ id: 'call_1',
1105
+ type: 'function',
1106
+ function: {
1107
+ name: 'list_tools',
1108
+ arguments: '{"category":"all"}',
1109
+ },
1110
+ },
1111
+ ],
1112
+ },
1113
+ {
1114
+ role: 'tool',
1115
+ tool_call_id: 'call_1',
1116
+ content: '{"tools":["calculator","weather"]}',
1117
+ },
1118
+ {
1119
+ role: 'assistant',
1120
+ content: null,
1121
+ tool_calls: [
1122
+ {
1123
+ id: 'call_2',
1124
+ type: 'function',
1125
+ function: {
1126
+ name: 'get_weather',
1127
+ arguments: '{"location":"NYC"}',
1128
+ },
1129
+ },
1130
+ ],
1131
+ },
1132
+ {
1133
+ role: 'tool',
1134
+ tool_call_id: 'call_2',
1135
+ content: '{"temperature":"22°C","condition":"sunny"}',
1136
+ },
1137
+ ],
1138
+ }));
1139
+ });
1140
+ it('should clean up orphaned tool calls without corresponding responses', async () => {
1141
+ const mockResponse = {
1142
+ id: 'chatcmpl-123',
1143
+ choices: [
1144
+ {
1145
+ index: 0,
1146
+ message: { role: 'assistant', content: 'Response' },
1147
+ finish_reason: 'stop',
1148
+ },
1149
+ ],
1150
+ created: 1677652288,
1151
+ model: 'gpt-4',
1152
+ };
1153
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1154
+ const request = {
1155
+ contents: [
1156
+ { role: 'user', parts: [{ text: 'Test' }] },
1157
+ {
1158
+ role: 'model',
1159
+ parts: [
1160
+ {
1161
+ functionCall: {
1162
+ id: 'call_orphaned',
1163
+ name: 'orphaned_function',
1164
+ args: {},
1165
+ },
1166
+ },
1167
+ ],
1168
+ },
1169
+ {
1170
+ role: 'model',
1171
+ parts: [
1172
+ {
1173
+ functionCall: {
1174
+ id: 'call_valid',
1175
+ name: 'valid_function',
1176
+ args: {},
1177
+ },
1178
+ },
1179
+ ],
1180
+ },
1181
+ {
1182
+ role: 'user',
1183
+ parts: [
1184
+ {
1185
+ functionResponse: {
1186
+ id: 'call_valid',
1187
+ name: 'valid_function',
1188
+ response: { result: 'success' },
1189
+ },
1190
+ },
1191
+ ],
1192
+ },
1193
+ ],
1194
+ model: 'gpt-4',
1195
+ };
1196
+ await generator.generateContent(request, 'test-prompt-id');
1197
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
1198
+ messages: [
1199
+ { role: 'user', content: 'Test' },
1200
+ {
1201
+ role: 'assistant',
1202
+ content: null,
1203
+ tool_calls: [
1204
+ {
1205
+ id: 'call_valid',
1206
+ type: 'function',
1207
+ function: {
1208
+ name: 'valid_function',
1209
+ arguments: '{}',
1210
+ },
1211
+ },
1212
+ ],
1213
+ },
1214
+ {
1215
+ role: 'tool',
1216
+ tool_call_id: 'call_valid',
1217
+ content: '{"result":"success"}',
1218
+ },
1219
+ ],
1220
+ }));
1221
+ });
1222
+ it('should merge consecutive assistant messages', async () => {
1223
+ const mockResponse = {
1224
+ id: 'chatcmpl-123',
1225
+ choices: [
1226
+ {
1227
+ index: 0,
1228
+ message: { role: 'assistant', content: 'Response' },
1229
+ finish_reason: 'stop',
1230
+ },
1231
+ ],
1232
+ created: 1677652288,
1233
+ model: 'gpt-4',
1234
+ };
1235
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1236
+ const request = {
1237
+ contents: [
1238
+ { role: 'user', parts: [{ text: 'Hello' }] },
1239
+ { role: 'model', parts: [{ text: 'Part 1' }] },
1240
+ { role: 'model', parts: [{ text: 'Part 2' }] },
1241
+ { role: 'user', parts: [{ text: 'Continue' }] },
1242
+ ],
1243
+ model: 'gpt-4',
1244
+ };
1245
+ await generator.generateContent(request, 'test-prompt-id');
1246
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
1247
+ messages: [
1248
+ { role: 'user', content: 'Hello' },
1249
+ { role: 'assistant', content: 'Part 1Part 2' },
1250
+ { role: 'user', content: 'Continue' },
1251
+ ],
1252
+ }));
1253
+ });
1254
+ });
1255
+ describe('error suppression functionality', () => {
1256
+ it('should allow subclasses to suppress error logging', async () => {
1257
+ class TestGenerator extends OpenAIContentGenerator {
1258
+ shouldSuppressErrorLogging() {
1259
+ return true; // Always suppress for this test
1260
+ }
1261
+ }
1262
+ const testGenerator = new TestGenerator('test-key', 'gpt-4', mockConfig);
1263
+ const consoleSpy = vi
1264
+ .spyOn(console, 'error')
1265
+ .mockImplementation(() => { });
1266
+ const apiError = new Error('Test error');
1267
+ mockOpenAIClient.chat.completions.create.mockRejectedValue(apiError);
1268
+ const request = {
1269
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1270
+ model: 'gpt-4',
1271
+ };
1272
+ await expect(testGenerator.generateContent(request, 'test-prompt-id')).rejects.toThrow();
1273
+ // Error logging should be suppressed
1274
+ expect(consoleSpy).not.toHaveBeenCalledWith('OpenAI API Error:', expect.any(String));
1275
+ consoleSpy.mockRestore();
1276
+ });
1277
+ it('should log errors when not suppressed', async () => {
1278
+ const consoleSpy = vi
1279
+ .spyOn(console, 'error')
1280
+ .mockImplementation(() => { });
1281
+ const apiError = new Error('Test error');
1282
+ mockOpenAIClient.chat.completions.create.mockRejectedValue(apiError);
1283
+ const request = {
1284
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1285
+ model: 'gpt-4',
1286
+ };
1287
+ await expect(generator.generateContent(request, 'test-prompt-id')).rejects.toThrow();
1288
+ // Error logging should occur by default
1289
+ expect(consoleSpy).toHaveBeenCalledWith('OpenAI API Error:', 'Test error');
1290
+ consoleSpy.mockRestore();
1291
+ });
1292
+ });
1293
+ describe('edge cases and error scenarios', () => {
1294
+ it('should handle malformed tool call arguments', async () => {
1295
+ const mockResponse = {
1296
+ id: 'chatcmpl-123',
1297
+ choices: [
1298
+ {
1299
+ index: 0,
1300
+ message: {
1301
+ role: 'assistant',
1302
+ content: null,
1303
+ tool_calls: [
1304
+ {
1305
+ id: 'call_123',
1306
+ type: 'function',
1307
+ function: {
1308
+ name: 'test_function',
1309
+ arguments: 'invalid json{',
1310
+ },
1311
+ },
1312
+ ],
1313
+ },
1314
+ finish_reason: 'tool_calls',
1315
+ },
1316
+ ],
1317
+ created: 1677652288,
1318
+ model: 'gpt-4',
1319
+ };
1320
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1321
+ const request = {
1322
+ contents: [{ role: 'user', parts: [{ text: 'Test' }] }],
1323
+ model: 'gpt-4',
1324
+ };
1325
+ const result = await generator.generateContent(request, 'test-prompt-id');
1326
+ // Should handle malformed JSON gracefully
1327
+ if (result.candidates &&
1328
+ result.candidates.length > 0 &&
1329
+ result.candidates[0]) {
1330
+ const firstCandidate = result.candidates[0];
1331
+ if (firstCandidate.content) {
1332
+ expect(firstCandidate.content.parts).toEqual([
1333
+ {
1334
+ functionCall: {
1335
+ id: 'call_123',
1336
+ name: 'test_function',
1337
+ args: {}, // Should default to empty object
1338
+ },
1339
+ },
1340
+ ]);
1341
+ }
1342
+ }
1343
+ });
1344
+ it('should handle streaming with malformed tool call arguments', async () => {
1345
+ const mockStream = [
1346
+ {
1347
+ id: 'chatcmpl-123',
1348
+ choices: [
1349
+ {
1350
+ index: 0,
1351
+ delta: {
1352
+ tool_calls: [
1353
+ {
1354
+ index: 0,
1355
+ id: 'call_123',
1356
+ function: { name: 'test_function' },
1357
+ },
1358
+ ],
1359
+ },
1360
+ finish_reason: null,
1361
+ },
1362
+ ],
1363
+ created: 1677652288,
1364
+ },
1365
+ {
1366
+ id: 'chatcmpl-123',
1367
+ choices: [
1368
+ {
1369
+ index: 0,
1370
+ delta: {
1371
+ tool_calls: [
1372
+ {
1373
+ index: 0,
1374
+ function: { arguments: 'invalid json{' },
1375
+ },
1376
+ ],
1377
+ },
1378
+ finish_reason: 'tool_calls',
1379
+ },
1380
+ ],
1381
+ created: 1677652288,
1382
+ },
1383
+ ];
1384
+ mockOpenAIClient.chat.completions.create.mockResolvedValue({
1385
+ async *[Symbol.asyncIterator]() {
1386
+ for (const chunk of mockStream) {
1387
+ yield chunk;
1388
+ }
1389
+ },
1390
+ });
1391
+ const request = {
1392
+ contents: [{ role: 'user', parts: [{ text: 'Test' }] }],
1393
+ model: 'gpt-4',
1394
+ };
1395
+ const stream = await generator.generateContentStream(request, 'test-prompt-id');
1396
+ const responses = [];
1397
+ for await (const response of stream) {
1398
+ responses.push(response);
1399
+ }
1400
+ // Should handle malformed JSON in streaming gracefully
1401
+ const finalResponse = responses[responses.length - 1];
1402
+ if (finalResponse.candidates &&
1403
+ finalResponse.candidates.length > 0 &&
1404
+ finalResponse.candidates[0]) {
1405
+ const firstCandidate = finalResponse.candidates[0];
1406
+ if (firstCandidate.content) {
1407
+ expect(firstCandidate.content.parts).toEqual([
1408
+ {
1409
+ functionCall: {
1410
+ id: 'call_123',
1411
+ name: 'test_function',
1412
+ args: {}, // Should default to empty object
1413
+ },
1414
+ },
1415
+ ]);
1416
+ }
1417
+ }
1418
+ });
1419
+ it('should handle empty or null content gracefully', async () => {
1420
+ const mockResponse = {
1421
+ id: 'chatcmpl-123',
1422
+ choices: [
1423
+ {
1424
+ index: 0,
1425
+ message: { role: 'assistant', content: null },
1426
+ finish_reason: 'stop',
1427
+ },
1428
+ ],
1429
+ created: 1677652288,
1430
+ model: 'gpt-4',
1431
+ };
1432
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1433
+ const request = {
1434
+ contents: [],
1435
+ model: 'gpt-4',
1436
+ };
1437
+ const result = await generator.generateContent(request, 'test-prompt-id');
1438
+ expect(result.candidates).toHaveLength(1);
1439
+ if (result.candidates &&
1440
+ result.candidates.length > 0 &&
1441
+ result.candidates[0]) {
1442
+ const firstCandidate = result.candidates[0];
1443
+ if (firstCandidate.content) {
1444
+ expect(firstCandidate.content.parts).toEqual([]);
1445
+ }
1446
+ }
1447
+ });
1448
+ it('should handle usage metadata estimation when breakdown is missing', async () => {
1449
+ const mockResponse = {
1450
+ id: 'chatcmpl-123',
1451
+ choices: [
1452
+ {
1453
+ index: 0,
1454
+ message: { role: 'assistant', content: 'Test response' },
1455
+ finish_reason: 'stop',
1456
+ },
1457
+ ],
1458
+ created: 1677652288,
1459
+ model: 'gpt-4',
1460
+ usage: {
1461
+ prompt_tokens: 0,
1462
+ completion_tokens: 0,
1463
+ total_tokens: 100,
1464
+ },
1465
+ };
1466
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1467
+ const request = {
1468
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1469
+ model: 'gpt-4',
1470
+ };
1471
+ const result = await generator.generateContent(request, 'test-prompt-id');
1472
+ expect(result.usageMetadata).toEqual({
1473
+ promptTokenCount: 70, // 70% of 100
1474
+ candidatesTokenCount: 30, // 30% of 100
1475
+ totalTokenCount: 100,
1476
+ cachedContentTokenCount: 0,
1477
+ });
1478
+ });
1479
+ it('should handle cached token metadata', async () => {
1480
+ const mockResponse = {
1481
+ id: 'chatcmpl-123',
1482
+ choices: [
1483
+ {
1484
+ index: 0,
1485
+ message: { role: 'assistant', content: 'Test response' },
1486
+ finish_reason: 'stop',
1487
+ },
1488
+ ],
1489
+ created: 1677652288,
1490
+ model: 'gpt-4',
1491
+ usage: {
1492
+ prompt_tokens: 50,
1493
+ completion_tokens: 25,
1494
+ total_tokens: 75,
1495
+ prompt_tokens_details: {
1496
+ cached_tokens: 10,
1497
+ },
1498
+ },
1499
+ };
1500
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1501
+ const request = {
1502
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1503
+ model: 'gpt-4',
1504
+ };
1505
+ const result = await generator.generateContent(request, 'test-prompt-id');
1506
+ expect(result.usageMetadata).toEqual({
1507
+ promptTokenCount: 50,
1508
+ candidatesTokenCount: 25,
1509
+ totalTokenCount: 75,
1510
+ cachedContentTokenCount: 10,
1511
+ });
1512
+ });
1513
+ });
1514
+ describe('request/response logging conversion', () => {
1515
+ it('should convert complex Gemini request to OpenAI format for logging', async () => {
1516
+ const loggingConfig = {
1517
+ getContentGeneratorConfig: vi.fn().mockReturnValue({
1518
+ enableOpenAILogging: true,
1519
+ samplingParams: {
1520
+ temperature: 0.8,
1521
+ max_tokens: 500,
1522
+ },
1523
+ }),
1524
+ };
1525
+ const loggingGenerator = new OpenAIContentGenerator('test-key', 'gpt-4', loggingConfig);
1526
+ const mockResponse = {
1527
+ id: 'chatcmpl-123',
1528
+ choices: [
1529
+ {
1530
+ index: 0,
1531
+ message: {
1532
+ role: 'assistant',
1533
+ content: null,
1534
+ tool_calls: [
1535
+ {
1536
+ id: 'call_123',
1537
+ type: 'function',
1538
+ function: {
1539
+ name: 'test_function',
1540
+ arguments: '{"param":"value"}',
1541
+ },
1542
+ },
1543
+ ],
1544
+ },
1545
+ finish_reason: 'tool_calls',
1546
+ },
1547
+ ],
1548
+ created: 1677652288,
1549
+ model: 'gpt-4',
1550
+ usage: {
1551
+ prompt_tokens: 100,
1552
+ completion_tokens: 50,
1553
+ total_tokens: 150,
1554
+ },
1555
+ };
1556
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1557
+ const request = {
1558
+ contents: [
1559
+ { role: 'user', parts: [{ text: 'Test complex request' }] },
1560
+ {
1561
+ role: 'model',
1562
+ parts: [
1563
+ {
1564
+ functionCall: {
1565
+ id: 'prev_call',
1566
+ name: 'previous_function',
1567
+ args: { data: 'test' },
1568
+ },
1569
+ },
1570
+ ],
1571
+ },
1572
+ {
1573
+ role: 'user',
1574
+ parts: [
1575
+ {
1576
+ functionResponse: {
1577
+ id: 'prev_call',
1578
+ name: 'previous_function',
1579
+ response: { result: 'success' },
1580
+ },
1581
+ },
1582
+ ],
1583
+ },
1584
+ ],
1585
+ model: 'gpt-4',
1586
+ config: {
1587
+ systemInstruction: 'You are a helpful assistant',
1588
+ temperature: 0.9,
1589
+ tools: [
1590
+ {
1591
+ callTool: vi.fn(),
1592
+ tool: () => Promise.resolve({
1593
+ functionDeclarations: [
1594
+ {
1595
+ name: 'test_function',
1596
+ description: 'Test function',
1597
+ parameters: { type: 'object' },
1598
+ },
1599
+ ],
1600
+ }),
1601
+ },
1602
+ ],
1603
+ },
1604
+ };
1605
+ await loggingGenerator.generateContent(request, 'test-prompt-id');
1606
+ // Verify that logging was called with properly converted request/response
1607
+ });
1608
+ });
1609
+ describe('advanced streaming scenarios', () => {
1610
+ it('should combine streaming responses correctly for logging', async () => {
1611
+ const loggingConfig = {
1612
+ getContentGeneratorConfig: vi.fn().mockReturnValue({
1613
+ enableOpenAILogging: true,
1614
+ }),
1615
+ };
1616
+ const loggingGenerator = new OpenAIContentGenerator('test-key', 'gpt-4', loggingConfig);
1617
+ const mockStream = [
1618
+ {
1619
+ id: 'chatcmpl-123',
1620
+ choices: [
1621
+ {
1622
+ index: 0,
1623
+ delta: { content: 'Hello' },
1624
+ finish_reason: null,
1625
+ },
1626
+ ],
1627
+ created: 1677652288,
1628
+ },
1629
+ {
1630
+ id: 'chatcmpl-123',
1631
+ choices: [
1632
+ {
1633
+ index: 0,
1634
+ delta: { content: ' world' },
1635
+ finish_reason: null,
1636
+ },
1637
+ ],
1638
+ created: 1677652288,
1639
+ },
1640
+ {
1641
+ id: 'chatcmpl-123',
1642
+ choices: [
1643
+ {
1644
+ index: 0,
1645
+ delta: {},
1646
+ finish_reason: 'stop',
1647
+ },
1648
+ ],
1649
+ created: 1677652288,
1650
+ usage: {
1651
+ prompt_tokens: 10,
1652
+ completion_tokens: 5,
1653
+ total_tokens: 15,
1654
+ },
1655
+ },
1656
+ ];
1657
+ mockOpenAIClient.chat.completions.create.mockResolvedValue({
1658
+ async *[Symbol.asyncIterator]() {
1659
+ for (const chunk of mockStream) {
1660
+ yield chunk;
1661
+ }
1662
+ },
1663
+ });
1664
+ const request = {
1665
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1666
+ model: 'gpt-4',
1667
+ };
1668
+ const stream = await loggingGenerator.generateContentStream(request, 'test-prompt-id');
1669
+ const responses = [];
1670
+ for await (const response of stream) {
1671
+ responses.push(response);
1672
+ }
1673
+ });
1674
+ it('should handle streaming without choices', async () => {
1675
+ const mockStream = [
1676
+ {
1677
+ id: 'chatcmpl-123',
1678
+ choices: [],
1679
+ created: 1677652288,
1680
+ },
1681
+ ];
1682
+ mockOpenAIClient.chat.completions.create.mockResolvedValue({
1683
+ async *[Symbol.asyncIterator]() {
1684
+ for (const chunk of mockStream) {
1685
+ yield chunk;
1686
+ }
1687
+ },
1688
+ });
1689
+ const request = {
1690
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1691
+ model: 'gpt-4',
1692
+ };
1693
+ const stream = await generator.generateContentStream(request, 'test-prompt-id');
1694
+ const responses = [];
1695
+ for await (const response of stream) {
1696
+ responses.push(response);
1697
+ }
1698
+ expect(responses).toHaveLength(1);
1699
+ expect(responses[0].candidates).toEqual([]);
1700
+ });
1701
+ });
1702
+ describe('embed content edge cases', () => {
1703
+ it('should handle mixed content types in embed request', async () => {
1704
+ const mockEmbedding = {
1705
+ data: [{ embedding: [0.1, 0.2, 0.3] }],
1706
+ model: 'text-embedding-ada-002',
1707
+ usage: { prompt_tokens: 5, total_tokens: 5 },
1708
+ };
1709
+ mockOpenAIClient.embeddings.create.mockResolvedValue(mockEmbedding);
1710
+ const request = {
1711
+ contents: 'Hello world Direct string Another part',
1712
+ model: 'text-embedding-ada-002',
1713
+ };
1714
+ const result = await generator.embedContent(request);
1715
+ expect(mockOpenAIClient.embeddings.create).toHaveBeenCalledWith({
1716
+ model: 'text-embedding-ada-002',
1717
+ input: 'Hello world Direct string Another part',
1718
+ });
1719
+ expect(result.embeddings).toHaveLength(1);
1720
+ expect(result.embeddings?.[0]?.values).toEqual([0.1, 0.2, 0.3]);
1721
+ });
1722
+ it('should handle empty content in embed request', async () => {
1723
+ const mockEmbedding = {
1724
+ data: [{ embedding: [] }],
1725
+ };
1726
+ mockOpenAIClient.embeddings.create.mockResolvedValue(mockEmbedding);
1727
+ const request = {
1728
+ contents: [],
1729
+ model: 'text-embedding-ada-002',
1730
+ };
1731
+ await generator.embedContent(request);
1732
+ expect(mockOpenAIClient.embeddings.create).toHaveBeenCalledWith({
1733
+ model: 'text-embedding-ada-002',
1734
+ input: '',
1735
+ });
1736
+ });
1737
+ });
1738
+ describe('system instruction edge cases', () => {
1739
+ it('should handle array system instructions', async () => {
1740
+ const mockResponse = {
1741
+ id: 'chatcmpl-123',
1742
+ choices: [
1743
+ {
1744
+ index: 0,
1745
+ message: { role: 'assistant', content: 'Response' },
1746
+ finish_reason: 'stop',
1747
+ },
1748
+ ],
1749
+ created: 1677652288,
1750
+ model: 'gpt-4',
1751
+ };
1752
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1753
+ const request = {
1754
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1755
+ model: 'gpt-4',
1756
+ config: {
1757
+ systemInstruction: 'You are helpful\nBe concise',
1758
+ },
1759
+ };
1760
+ await generator.generateContent(request, 'test-prompt-id');
1761
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
1762
+ messages: [
1763
+ { role: 'system', content: 'You are helpful\nBe concise' },
1764
+ { role: 'user', content: 'Hello' },
1765
+ ],
1766
+ }));
1767
+ });
1768
+ it('should handle object system instruction', async () => {
1769
+ const mockResponse = {
1770
+ id: 'chatcmpl-123',
1771
+ choices: [
1772
+ {
1773
+ index: 0,
1774
+ message: { role: 'assistant', content: 'Response' },
1775
+ finish_reason: 'stop',
1776
+ },
1777
+ ],
1778
+ created: 1677652288,
1779
+ model: 'gpt-4',
1780
+ };
1781
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1782
+ const request = {
1783
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1784
+ model: 'gpt-4',
1785
+ config: {
1786
+ systemInstruction: {
1787
+ parts: [{ text: 'System message' }, { text: 'Additional text' }],
1788
+ },
1789
+ },
1790
+ };
1791
+ await generator.generateContent(request, 'test-prompt-id');
1792
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
1793
+ messages: [
1794
+ { role: 'system', content: 'System message\nAdditional text' },
1795
+ { role: 'user', content: 'Hello' },
1796
+ ],
1797
+ }));
1798
+ });
1799
+ });
1800
+ describe('sampling parameters edge cases', () => {
1801
+ it('should handle undefined sampling parameters gracefully', async () => {
1802
+ const configWithUndefined = {
1803
+ getContentGeneratorConfig: vi.fn().mockReturnValue({
1804
+ samplingParams: {
1805
+ temperature: undefined,
1806
+ max_tokens: undefined,
1807
+ top_p: undefined,
1808
+ },
1809
+ }),
1810
+ };
1811
+ const testGenerator = new OpenAIContentGenerator('test-key', 'gpt-4', configWithUndefined);
1812
+ const mockResponse = {
1813
+ id: 'chatcmpl-123',
1814
+ choices: [
1815
+ {
1816
+ index: 0,
1817
+ message: { role: 'assistant', content: 'Response' },
1818
+ finish_reason: 'stop',
1819
+ },
1820
+ ],
1821
+ created: 1677652288,
1822
+ model: 'gpt-4',
1823
+ };
1824
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1825
+ const request = {
1826
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1827
+ model: 'gpt-4',
1828
+ config: {
1829
+ temperature: undefined,
1830
+ maxOutputTokens: undefined,
1831
+ topP: undefined,
1832
+ },
1833
+ };
1834
+ await testGenerator.generateContent(request, 'test-prompt-id');
1835
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
1836
+ temperature: 0.0, // Default value
1837
+ top_p: 1.0, // Default value
1838
+ // max_tokens should not be present when undefined
1839
+ }));
1840
+ });
1841
+ it('should handle all config-level sampling parameters', async () => {
1842
+ const fullSamplingConfig = {
1843
+ getContentGeneratorConfig: vi.fn().mockReturnValue({
1844
+ samplingParams: {
1845
+ temperature: 0.8,
1846
+ max_tokens: 1500,
1847
+ top_p: 0.95,
1848
+ top_k: 40,
1849
+ repetition_penalty: 1.1,
1850
+ presence_penalty: 0.5,
1851
+ frequency_penalty: 0.3,
1852
+ },
1853
+ }),
1854
+ };
1855
+ const testGenerator = new OpenAIContentGenerator('test-key', 'gpt-4', fullSamplingConfig);
1856
+ const mockResponse = {
1857
+ id: 'chatcmpl-123',
1858
+ choices: [
1859
+ {
1860
+ index: 0,
1861
+ message: { role: 'assistant', content: 'Response' },
1862
+ finish_reason: 'stop',
1863
+ },
1864
+ ],
1865
+ created: 1677652288,
1866
+ model: 'gpt-4',
1867
+ };
1868
+ mockOpenAIClient.chat.completions.create.mockResolvedValue(mockResponse);
1869
+ const request = {
1870
+ contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
1871
+ model: 'gpt-4',
1872
+ };
1873
+ await testGenerator.generateContent(request, 'test-prompt-id');
1874
+ expect(mockOpenAIClient.chat.completions.create).toHaveBeenCalledWith(expect.objectContaining({
1875
+ temperature: 0.8,
1876
+ max_tokens: 1500,
1877
+ top_p: 0.95,
1878
+ top_k: 40,
1879
+ repetition_penalty: 1.1,
1880
+ presence_penalty: 0.5,
1881
+ frequency_penalty: 0.3,
1882
+ }));
1883
+ });
1884
+ });
1885
+ describe('token counting edge cases', () => {
1886
+ it('should handle tiktoken import failure with console warning', async () => {
1887
+ // Mock tiktoken to fail on import
1888
+ vi.doMock('tiktoken', () => {
1889
+ throw new Error('Failed to import tiktoken');
1890
+ });
1891
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
1892
+ const request = {
1893
+ contents: [{ role: 'user', parts: [{ text: 'Test content' }] }],
1894
+ model: 'gpt-4',
1895
+ };
1896
+ const result = await generator.countTokens(request);
1897
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringMatching(/Failed to load tiktoken.*falling back/), expect.any(Error));
1898
+ // Should use character approximation
1899
+ expect(result.totalTokens).toBeGreaterThan(0);
1900
+ consoleSpy.mockRestore();
1901
+ });
1902
+ });
1903
+ });
1904
+ //# sourceMappingURL=openaiContentGenerator.test.js.map