@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.
- package/dist/.last_build +0 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/src/__mocks__/fs/promises.d.ts +11 -0
- package/dist/src/__mocks__/fs/promises.js +17 -0
- package/dist/src/__mocks__/fs/promises.js.map +1 -0
- package/dist/src/code_assist/codeAssist.d.ts +9 -0
- package/dist/src/code_assist/codeAssist.js +19 -0
- package/dist/src/code_assist/codeAssist.js.map +1 -0
- package/dist/src/code_assist/converter.d.ts +70 -0
- package/dist/src/code_assist/converter.js +126 -0
- package/dist/src/code_assist/converter.js.map +1 -0
- package/dist/src/code_assist/converter.test.d.ts +6 -0
- package/dist/src/code_assist/converter.test.js +279 -0
- package/dist/src/code_assist/converter.test.js.map +1 -0
- package/dist/src/code_assist/oauth2.d.ts +20 -0
- package/dist/src/code_assist/oauth2.js +337 -0
- package/dist/src/code_assist/oauth2.js.map +1 -0
- package/dist/src/code_assist/oauth2.test.d.ts +6 -0
- package/dist/src/code_assist/oauth2.test.js +334 -0
- package/dist/src/code_assist/oauth2.test.js.map +1 -0
- package/dist/src/code_assist/server.d.ts +37 -0
- package/dist/src/code_assist/server.js +125 -0
- package/dist/src/code_assist/server.js.map +1 -0
- package/dist/src/code_assist/server.test.d.ts +6 -0
- package/dist/src/code_assist/server.test.js +134 -0
- package/dist/src/code_assist/server.test.js.map +1 -0
- package/dist/src/code_assist/setup.d.ts +20 -0
- package/dist/src/code_assist/setup.js +70 -0
- package/dist/src/code_assist/setup.js.map +1 -0
- package/dist/src/code_assist/setup.test.d.ts +6 -0
- package/dist/src/code_assist/setup.test.js +65 -0
- package/dist/src/code_assist/setup.test.js.map +1 -0
- package/dist/src/code_assist/types.d.ts +148 -0
- package/dist/src/code_assist/types.js +46 -0
- package/dist/src/code_assist/types.js.map +1 -0
- package/dist/src/config/config.d.ts +272 -0
- package/dist/src/config/config.js +554 -0
- package/dist/src/config/config.js.map +1 -0
- package/dist/src/config/config.test.d.ts +6 -0
- package/dist/src/config/config.test.js +365 -0
- package/dist/src/config/config.test.js.map +1 -0
- package/dist/src/config/flashFallback.test.d.ts +6 -0
- package/dist/src/config/flashFallback.test.js +87 -0
- package/dist/src/config/flashFallback.test.js.map +1 -0
- package/dist/src/config/models.d.ts +9 -0
- package/dist/src/config/models.js +10 -0
- package/dist/src/config/models.js.map +1 -0
- package/dist/src/core/client.d.ts +61 -0
- package/dist/src/core/client.js +485 -0
- package/dist/src/core/client.js.map +1 -0
- package/dist/src/core/client.test.d.ts +6 -0
- package/dist/src/core/client.test.js +1008 -0
- package/dist/src/core/client.test.js.map +1 -0
- package/dist/src/core/contentGenerator.d.ts +45 -0
- package/dist/src/core/contentGenerator.js +81 -0
- package/dist/src/core/contentGenerator.js.map +1 -0
- package/dist/src/core/contentGenerator.test.d.ts +6 -0
- package/dist/src/core/contentGenerator.test.js +100 -0
- package/dist/src/core/contentGenerator.test.js.map +1 -0
- package/dist/src/core/coreToolScheduler.d.ts +117 -0
- package/dist/src/core/coreToolScheduler.js +530 -0
- package/dist/src/core/coreToolScheduler.js.map +1 -0
- package/dist/src/core/coreToolScheduler.test.d.ts +6 -0
- package/dist/src/core/coreToolScheduler.test.js +625 -0
- package/dist/src/core/coreToolScheduler.test.js.map +1 -0
- package/dist/src/core/geminiChat.d.ts +118 -0
- package/dist/src/core/geminiChat.js +509 -0
- package/dist/src/core/geminiChat.js.map +1 -0
- package/dist/src/core/geminiChat.test.d.ts +6 -0
- package/dist/src/core/geminiChat.test.js +424 -0
- package/dist/src/core/geminiChat.test.js.map +1 -0
- package/dist/src/core/geminiRequest.d.ts +13 -0
- package/dist/src/core/geminiRequest.js +10 -0
- package/dist/src/core/geminiRequest.js.map +1 -0
- package/dist/src/core/logger.d.ts +37 -0
- package/dist/src/core/logger.js +273 -0
- package/dist/src/core/logger.js.map +1 -0
- package/dist/src/core/logger.test.d.ts +6 -0
- package/dist/src/core/logger.test.js +467 -0
- package/dist/src/core/logger.test.js.map +1 -0
- package/dist/src/core/loggingContentGenerator.d.ts +24 -0
- package/dist/src/core/loggingContentGenerator.js +89 -0
- package/dist/src/core/loggingContentGenerator.js.map +1 -0
- package/dist/src/core/modelCheck.d.ts +14 -0
- package/dist/src/core/modelCheck.js +62 -0
- package/dist/src/core/modelCheck.js.map +1 -0
- package/dist/src/core/nonInteractiveToolExecutor.d.ts +12 -0
- package/dist/src/core/nonInteractiveToolExecutor.js +124 -0
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -0
- package/dist/src/core/nonInteractiveToolExecutor.test.d.ts +6 -0
- package/dist/src/core/nonInteractiveToolExecutor.test.js +165 -0
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -0
- package/dist/src/core/openaiContentGenerator.d.ts +77 -0
- package/dist/src/core/openaiContentGenerator.js +1395 -0
- package/dist/src/core/openaiContentGenerator.js.map +1 -0
- package/dist/src/core/openaiContentGenerator.test.d.ts +6 -0
- package/dist/src/core/openaiContentGenerator.test.js +1904 -0
- package/dist/src/core/openaiContentGenerator.test.js.map +1 -0
- package/dist/src/core/prompts.d.ts +12 -0
- package/dist/src/core/prompts.js +359 -0
- package/dist/src/core/prompts.js.map +1 -0
- package/dist/src/core/prompts.test.d.ts +6 -0
- package/dist/src/core/prompts.test.js +214 -0
- package/dist/src/core/prompts.test.js.map +1 -0
- package/dist/src/core/subagent.d.ts +230 -0
- package/dist/src/core/subagent.js +447 -0
- package/dist/src/core/subagent.js.map +1 -0
- package/dist/src/core/subagent.test.d.ts +6 -0
- package/dist/src/core/subagent.test.js +515 -0
- package/dist/src/core/subagent.test.js.map +1 -0
- package/dist/src/core/tokenLimits.d.ts +10 -0
- package/dist/src/core/tokenLimits.js +28 -0
- package/dist/src/core/tokenLimits.js.map +1 -0
- package/dist/src/core/turn.d.ts +114 -0
- package/dist/src/core/turn.js +143 -0
- package/dist/src/core/turn.js.map +1 -0
- package/dist/src/core/turn.test.d.ts +6 -0
- package/dist/src/core/turn.test.js +369 -0
- package/dist/src/core/turn.test.js.map +1 -0
- package/dist/src/ide/detect-ide.d.ts +10 -0
- package/dist/src/ide/detect-ide.js +27 -0
- package/dist/src/ide/detect-ide.js.map +1 -0
- package/dist/src/ide/ide-client.d.ts +56 -0
- package/dist/src/ide/ide-client.js +268 -0
- package/dist/src/ide/ide-client.js.map +1 -0
- package/dist/src/ide/ide-installer.d.ts +14 -0
- package/dist/src/ide/ide-installer.js +109 -0
- package/dist/src/ide/ide-installer.js.map +1 -0
- package/dist/src/ide/ide-installer.test.d.ts +6 -0
- package/dist/src/ide/ide-installer.test.js +55 -0
- package/dist/src/ide/ide-installer.test.js.map +1 -0
- package/dist/src/ide/ideContext.d.ts +374 -0
- package/dist/src/ide/ideContext.js +147 -0
- package/dist/src/ide/ideContext.js.map +1 -0
- package/dist/src/ide/ideContext.test.d.ts +6 -0
- package/dist/src/ide/ideContext.test.js +265 -0
- package/dist/src/ide/ideContext.test.js.map +1 -0
- package/dist/src/index.d.ts +68 -0
- package/dist/src/index.js +78 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/index.test.d.ts +6 -0
- package/dist/src/index.test.js +12 -0
- package/dist/src/index.test.js.map +1 -0
- package/dist/src/mcp/google-auth-provider.d.ts +23 -0
- package/dist/src/mcp/google-auth-provider.js +63 -0
- package/dist/src/mcp/google-auth-provider.js.map +1 -0
- package/dist/src/mcp/google-auth-provider.test.d.ts +6 -0
- package/dist/src/mcp/google-auth-provider.test.js +54 -0
- package/dist/src/mcp/google-auth-provider.test.js.map +1 -0
- package/dist/src/mcp/oauth-provider.d.ts +147 -0
- package/dist/src/mcp/oauth-provider.js +484 -0
- package/dist/src/mcp/oauth-provider.js.map +1 -0
- package/dist/src/mcp/oauth-provider.test.d.ts +6 -0
- package/dist/src/mcp/oauth-provider.test.js +602 -0
- package/dist/src/mcp/oauth-provider.test.js.map +1 -0
- package/dist/src/mcp/oauth-token-storage.d.ts +83 -0
- package/dist/src/mcp/oauth-token-storage.js +151 -0
- package/dist/src/mcp/oauth-token-storage.js.map +1 -0
- package/dist/src/mcp/oauth-token-storage.test.d.ts +6 -0
- package/dist/src/mcp/oauth-token-storage.test.js +205 -0
- package/dist/src/mcp/oauth-token-storage.test.js.map +1 -0
- package/dist/src/mcp/oauth-utils.d.ts +109 -0
- package/dist/src/mcp/oauth-utils.js +183 -0
- package/dist/src/mcp/oauth-utils.js.map +1 -0
- package/dist/src/mcp/oauth-utils.test.d.ts +6 -0
- package/dist/src/mcp/oauth-utils.test.js +144 -0
- package/dist/src/mcp/oauth-utils.test.js.map +1 -0
- package/dist/src/prompts/mcp-prompts.d.ts +8 -0
- package/dist/src/prompts/mcp-prompts.js +13 -0
- package/dist/src/prompts/mcp-prompts.js.map +1 -0
- package/dist/src/prompts/prompt-registry.d.ts +34 -0
- package/dist/src/prompts/prompt-registry.js +63 -0
- package/dist/src/prompts/prompt-registry.js.map +1 -0
- package/dist/src/services/fileDiscoveryService.d.ts +35 -0
- package/dist/src/services/fileDiscoveryService.js +91 -0
- package/dist/src/services/fileDiscoveryService.js.map +1 -0
- package/dist/src/services/fileDiscoveryService.test.d.ts +6 -0
- package/dist/src/services/fileDiscoveryService.test.js +143 -0
- package/dist/src/services/fileDiscoveryService.test.js.map +1 -0
- package/dist/src/services/gitService.d.ts +21 -0
- package/dist/src/services/gitService.js +101 -0
- package/dist/src/services/gitService.js.map +1 -0
- package/dist/src/services/gitService.test.d.ts +6 -0
- package/dist/src/services/gitService.test.js +209 -0
- package/dist/src/services/gitService.test.js.map +1 -0
- package/dist/src/services/loopDetectionService.d.ts +97 -0
- package/dist/src/services/loopDetectionService.js +340 -0
- package/dist/src/services/loopDetectionService.js.map +1 -0
- package/dist/src/services/loopDetectionService.test.d.ts +6 -0
- package/dist/src/services/loopDetectionService.test.js +484 -0
- package/dist/src/services/loopDetectionService.test.js.map +1 -0
- package/dist/src/services/shellExecutionService.d.ts +70 -0
- package/dist/src/services/shellExecutionService.js +175 -0
- package/dist/src/services/shellExecutionService.js.map +1 -0
- package/dist/src/services/shellExecutionService.test.d.ts +6 -0
- package/dist/src/services/shellExecutionService.test.js +272 -0
- package/dist/src/services/shellExecutionService.test.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +65 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +652 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +6 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +186 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +59 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +159 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -0
- package/dist/src/telemetry/constants.d.ts +23 -0
- package/dist/src/telemetry/constants.js +24 -0
- package/dist/src/telemetry/constants.js.map +1 -0
- package/dist/src/telemetry/file-exporters.d.ts +28 -0
- package/dist/src/telemetry/file-exporters.js +62 -0
- package/dist/src/telemetry/file-exporters.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +18 -0
- package/dist/src/telemetry/index.js +20 -0
- package/dist/src/telemetry/index.js.map +1 -0
- package/dist/src/telemetry/integration.test.circular.d.ts +6 -0
- package/dist/src/telemetry/integration.test.circular.js +53 -0
- package/dist/src/telemetry/integration.test.circular.js.map +1 -0
- package/dist/src/telemetry/loggers.d.ts +18 -0
- package/dist/src/telemetry/loggers.js +268 -0
- package/dist/src/telemetry/loggers.js.map +1 -0
- package/dist/src/telemetry/loggers.test.circular.d.ts +6 -0
- package/dist/src/telemetry/loggers.test.circular.js +107 -0
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -0
- package/dist/src/telemetry/loggers.test.d.ts +6 -0
- package/dist/src/telemetry/loggers.test.js +568 -0
- package/dist/src/telemetry/loggers.test.js.map +1 -0
- package/dist/src/telemetry/metrics.d.ts +20 -0
- package/dist/src/telemetry/metrics.js +150 -0
- package/dist/src/telemetry/metrics.js.map +1 -0
- package/dist/src/telemetry/metrics.test.d.ts +6 -0
- package/dist/src/telemetry/metrics.test.js +212 -0
- package/dist/src/telemetry/metrics.test.js.map +1 -0
- package/dist/src/telemetry/sdk.d.ts +9 -0
- package/dist/src/telemetry/sdk.js +127 -0
- package/dist/src/telemetry/sdk.js.map +1 -0
- package/dist/src/telemetry/telemetry.test.d.ts +6 -0
- package/dist/src/telemetry/telemetry.test.js +50 -0
- package/dist/src/telemetry/telemetry.test.js.map +1 -0
- package/dist/src/telemetry/tool-call-decision.d.ts +13 -0
- package/dist/src/telemetry/tool-call-decision.js +29 -0
- package/dist/src/telemetry/tool-call-decision.js.map +1 -0
- package/dist/src/telemetry/types.d.ts +145 -0
- package/dist/src/telemetry/types.js +267 -0
- package/dist/src/telemetry/types.js.map +1 -0
- package/dist/src/telemetry/uiTelemetry.d.ts +71 -0
- package/dist/src/telemetry/uiTelemetry.js +140 -0
- package/dist/src/telemetry/uiTelemetry.js.map +1 -0
- package/dist/src/telemetry/uiTelemetry.test.d.ts +6 -0
- package/dist/src/telemetry/uiTelemetry.test.js +518 -0
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -0
- package/dist/src/test-utils/mockWorkspaceContext.d.ts +13 -0
- package/dist/src/test-utils/mockWorkspaceContext.js +24 -0
- package/dist/src/test-utils/mockWorkspaceContext.js.map +1 -0
- package/dist/src/test-utils/tools.d.ts +23 -0
- package/dist/src/test-utils/tools.js +41 -0
- package/dist/src/test-utils/tools.js.map +1 -0
- package/dist/src/tools/diffOptions.d.ts +9 -0
- package/dist/src/tools/diffOptions.js +38 -0
- package/dist/src/tools/diffOptions.js.map +1 -0
- package/dist/src/tools/diffOptions.test.d.ts +6 -0
- package/dist/src/tools/diffOptions.test.js +119 -0
- package/dist/src/tools/diffOptions.test.js.map +1 -0
- package/dist/src/tools/edit.d.ts +55 -0
- package/dist/src/tools/edit.js +398 -0
- package/dist/src/tools/edit.js.map +1 -0
- package/dist/src/tools/edit.test.d.ts +6 -0
- package/dist/src/tools/edit.test.js +681 -0
- package/dist/src/tools/edit.test.js.map +1 -0
- package/dist/src/tools/glob.d.ts +51 -0
- package/dist/src/tools/glob.js +226 -0
- package/dist/src/tools/glob.js.map +1 -0
- package/dist/src/tools/glob.test.d.ts +6 -0
- package/dist/src/tools/glob.test.js +330 -0
- package/dist/src/tools/glob.test.js.map +1 -0
- package/dist/src/tools/grep.d.ts +46 -0
- package/dist/src/tools/grep.js +503 -0
- package/dist/src/tools/grep.js.map +1 -0
- package/dist/src/tools/grep.test.d.ts +6 -0
- package/dist/src/tools/grep.test.js +272 -0
- package/dist/src/tools/grep.test.js.map +1 -0
- package/dist/src/tools/ls.d.ts +86 -0
- package/dist/src/tools/ls.js +224 -0
- package/dist/src/tools/ls.js.map +1 -0
- package/dist/src/tools/ls.test.d.ts +6 -0
- package/dist/src/tools/ls.test.js +356 -0
- package/dist/src/tools/ls.test.js.map +1 -0
- package/dist/src/tools/mcp-client.d.ts +149 -0
- package/dist/src/tools/mcp-client.js +844 -0
- package/dist/src/tools/mcp-client.js.map +1 -0
- package/dist/src/tools/mcp-client.test.d.ts +6 -0
- package/dist/src/tools/mcp-client.test.js +643 -0
- package/dist/src/tools/mcp-client.test.js.map +1 -0
- package/dist/src/tools/mcp-tool.d.ts +29 -0
- package/dist/src/tools/mcp-tool.js +202 -0
- package/dist/src/tools/mcp-tool.js.map +1 -0
- package/dist/src/tools/mcp-tool.test.d.ts +6 -0
- package/dist/src/tools/mcp-tool.test.js +501 -0
- package/dist/src/tools/mcp-tool.test.js.map +1 -0
- package/dist/src/tools/memoryTool.d.ts +43 -0
- package/dist/src/tools/memoryTool.js +290 -0
- package/dist/src/tools/memoryTool.js.map +1 -0
- package/dist/src/tools/memoryTool.test.d.ts +6 -0
- package/dist/src/tools/memoryTool.test.js +266 -0
- package/dist/src/tools/memoryTool.test.js.map +1 -0
- package/dist/src/tools/modifiable-tool.d.ts +32 -0
- package/dist/src/tools/modifiable-tool.js +88 -0
- package/dist/src/tools/modifiable-tool.js.map +1 -0
- package/dist/src/tools/modifiable-tool.test.d.ts +6 -0
- package/dist/src/tools/modifiable-tool.test.js +193 -0
- package/dist/src/tools/modifiable-tool.test.js.map +1 -0
- package/dist/src/tools/read-file.d.ts +34 -0
- package/dist/src/tools/read-file.js +152 -0
- package/dist/src/tools/read-file.js.map +1 -0
- package/dist/src/tools/read-file.test.d.ts +6 -0
- package/dist/src/tools/read-file.test.js +300 -0
- package/dist/src/tools/read-file.test.js.map +1 -0
- package/dist/src/tools/read-many-files.d.ts +61 -0
- package/dist/src/tools/read-many-files.js +421 -0
- package/dist/src/tools/read-many-files.js.map +1 -0
- package/dist/src/tools/read-many-files.test.d.ts +6 -0
- package/dist/src/tools/read-many-files.test.js +455 -0
- package/dist/src/tools/read-many-files.test.js.map +1 -0
- package/dist/src/tools/shell.d.ts +23 -0
- package/dist/src/tools/shell.js +313 -0
- package/dist/src/tools/shell.js.map +1 -0
- package/dist/src/tools/shell.test.d.ts +6 -0
- package/dist/src/tools/shell.test.js +321 -0
- package/dist/src/tools/shell.test.js.map +1 -0
- package/dist/src/tools/tool-error.d.ts +26 -0
- package/dist/src/tools/tool-error.js +31 -0
- package/dist/src/tools/tool-error.js.map +1 -0
- package/dist/src/tools/tool-registry.d.ts +85 -0
- package/dist/src/tools/tool-registry.js +390 -0
- package/dist/src/tools/tool-registry.js.map +1 -0
- package/dist/src/tools/tool-registry.test.d.ts +6 -0
- package/dist/src/tools/tool-registry.test.js +417 -0
- package/dist/src/tools/tool-registry.test.js.map +1 -0
- package/dist/src/tools/tools.d.ts +328 -0
- package/dist/src/tools/tools.js +281 -0
- package/dist/src/tools/tools.js.map +1 -0
- package/dist/src/tools/tools.test.d.ts +6 -0
- package/dist/src/tools/tools.test.js +117 -0
- package/dist/src/tools/tools.test.js.map +1 -0
- package/dist/src/tools/web-fetch.d.ts +29 -0
- package/dist/src/tools/web-fetch.js +246 -0
- package/dist/src/tools/web-fetch.js.map +1 -0
- package/dist/src/tools/web-fetch.test.d.ts +6 -0
- package/dist/src/tools/web-fetch.test.js +71 -0
- package/dist/src/tools/web-fetch.test.js.map +1 -0
- package/dist/src/tools/web-search.d.ts +49 -0
- package/dist/src/tools/web-search.js +120 -0
- package/dist/src/tools/web-search.js.map +1 -0
- package/dist/src/tools/write-file.d.ts +46 -0
- package/dist/src/tools/write-file.js +321 -0
- package/dist/src/tools/write-file.js.map +1 -0
- package/dist/src/tools/write-file.test.d.ts +6 -0
- package/dist/src/tools/write-file.test.js +572 -0
- package/dist/src/tools/write-file.test.js.map +1 -0
- package/dist/src/utils/LruCache.d.ts +13 -0
- package/dist/src/utils/LruCache.js +38 -0
- package/dist/src/utils/LruCache.js.map +1 -0
- package/dist/src/utils/bfsFileSearch.d.ts +24 -0
- package/dist/src/utils/bfsFileSearch.js +89 -0
- package/dist/src/utils/bfsFileSearch.js.map +1 -0
- package/dist/src/utils/bfsFileSearch.test.d.ts +6 -0
- package/dist/src/utils/bfsFileSearch.test.js +163 -0
- package/dist/src/utils/bfsFileSearch.test.js.map +1 -0
- package/dist/src/utils/browser.d.ts +13 -0
- package/dist/src/utils/browser.js +49 -0
- package/dist/src/utils/browser.js.map +1 -0
- package/dist/src/utils/editCorrector.d.ts +53 -0
- package/dist/src/utils/editCorrector.js +546 -0
- package/dist/src/utils/editCorrector.js.map +1 -0
- package/dist/src/utils/editCorrector.test.d.ts +6 -0
- package/dist/src/utils/editCorrector.test.js +564 -0
- package/dist/src/utils/editCorrector.test.js.map +1 -0
- package/dist/src/utils/editor.d.ts +28 -0
- package/dist/src/utils/editor.js +186 -0
- package/dist/src/utils/editor.js.map +1 -0
- package/dist/src/utils/editor.test.d.ts +6 -0
- package/dist/src/utils/editor.test.js +445 -0
- package/dist/src/utils/editor.test.js.map +1 -0
- package/dist/src/utils/environmentContext.d.ts +21 -0
- package/dist/src/utils/environmentContext.js +90 -0
- package/dist/src/utils/environmentContext.js.map +1 -0
- package/dist/src/utils/environmentContext.test.d.ts +6 -0
- package/dist/src/utils/environmentContext.test.js +139 -0
- package/dist/src/utils/environmentContext.test.js.map +1 -0
- package/dist/src/utils/errorReporting.d.ts +14 -0
- package/dist/src/utils/errorReporting.js +88 -0
- package/dist/src/utils/errorReporting.js.map +1 -0
- package/dist/src/utils/errorReporting.test.d.ts +6 -0
- package/dist/src/utils/errorReporting.test.js +130 -0
- package/dist/src/utils/errorReporting.test.js.map +1 -0
- package/dist/src/utils/errors.d.ts +14 -0
- package/dist/src/utils/errors.js +54 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/fetch.d.ts +11 -0
- package/dist/src/utils/fetch.js +51 -0
- package/dist/src/utils/fetch.js.map +1 -0
- package/dist/src/utils/fileUtils.d.ts +56 -0
- package/dist/src/utils/fileUtils.js +314 -0
- package/dist/src/utils/fileUtils.js.map +1 -0
- package/dist/src/utils/fileUtils.test.d.ts +6 -0
- package/dist/src/utils/fileUtils.test.js +363 -0
- package/dist/src/utils/fileUtils.test.js.map +1 -0
- package/dist/src/utils/filesearch/crawlCache.d.ts +25 -0
- package/dist/src/utils/filesearch/crawlCache.js +57 -0
- package/dist/src/utils/filesearch/crawlCache.js.map +1 -0
- package/dist/src/utils/filesearch/crawlCache.test.d.ts +6 -0
- package/dist/src/utils/filesearch/crawlCache.test.js +103 -0
- package/dist/src/utils/filesearch/crawlCache.test.js.map +1 -0
- package/dist/src/utils/filesearch/fileSearch.d.ts +81 -0
- package/dist/src/utils/filesearch/fileSearch.js +241 -0
- package/dist/src/utils/filesearch/fileSearch.js.map +1 -0
- package/dist/src/utils/filesearch/fileSearch.test.d.ts +6 -0
- package/dist/src/utils/filesearch/fileSearch.test.js +654 -0
- package/dist/src/utils/filesearch/fileSearch.test.js.map +1 -0
- package/dist/src/utils/filesearch/ignore.d.ts +35 -0
- package/dist/src/utils/filesearch/ignore.js +81 -0
- package/dist/src/utils/filesearch/ignore.js.map +1 -0
- package/dist/src/utils/filesearch/ignore.test.d.ts +6 -0
- package/dist/src/utils/filesearch/ignore.test.js +57 -0
- package/dist/src/utils/filesearch/ignore.test.js.map +1 -0
- package/dist/src/utils/filesearch/result-cache.d.ts +34 -0
- package/dist/src/utils/filesearch/result-cache.js +61 -0
- package/dist/src/utils/filesearch/result-cache.js.map +1 -0
- package/dist/src/utils/filesearch/result-cache.test.d.ts +6 -0
- package/dist/src/utils/filesearch/result-cache.test.js +47 -0
- package/dist/src/utils/filesearch/result-cache.test.js.map +1 -0
- package/dist/src/utils/flashFallback.integration.test.d.ts +6 -0
- package/dist/src/utils/flashFallback.integration.test.js +118 -0
- package/dist/src/utils/flashFallback.integration.test.js.map +1 -0
- package/dist/src/utils/formatters.d.ts +6 -0
- package/dist/src/utils/formatters.js +16 -0
- package/dist/src/utils/formatters.js.map +1 -0
- package/dist/src/utils/generateContentResponseUtilities.d.ts +14 -0
- package/dist/src/utils/generateContentResponseUtilities.js +92 -0
- package/dist/src/utils/generateContentResponseUtilities.js.map +1 -0
- package/dist/src/utils/generateContentResponseUtilities.test.d.ts +6 -0
- package/dist/src/utils/generateContentResponseUtilities.test.js +273 -0
- package/dist/src/utils/generateContentResponseUtilities.test.js.map +1 -0
- package/dist/src/utils/getFolderStructure.d.ts +31 -0
- package/dist/src/utils/getFolderStructure.js +246 -0
- package/dist/src/utils/getFolderStructure.js.map +1 -0
- package/dist/src/utils/getFolderStructure.test.d.ts +6 -0
- package/dist/src/utils/getFolderStructure.test.js +282 -0
- package/dist/src/utils/getFolderStructure.test.js.map +1 -0
- package/dist/src/utils/gitIgnoreParser.d.ts +20 -0
- package/dist/src/utils/gitIgnoreParser.js +61 -0
- package/dist/src/utils/gitIgnoreParser.js.map +1 -0
- package/dist/src/utils/gitIgnoreParser.test.d.ts +6 -0
- package/dist/src/utils/gitIgnoreParser.test.js +154 -0
- package/dist/src/utils/gitIgnoreParser.test.js.map +1 -0
- package/dist/src/utils/gitUtils.d.ts +17 -0
- package/dist/src/utils/gitUtils.js +61 -0
- package/dist/src/utils/gitUtils.js.map +1 -0
- package/dist/src/utils/memoryDiscovery.d.ts +15 -0
- package/dist/src/utils/memoryDiscovery.js +219 -0
- package/dist/src/utils/memoryDiscovery.js.map +1 -0
- package/dist/src/utils/memoryDiscovery.test.d.ts +6 -0
- package/dist/src/utils/memoryDiscovery.test.js +181 -0
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -0
- package/dist/src/utils/memoryImportProcessor.d.ts +42 -0
- package/dist/src/utils/memoryImportProcessor.js +300 -0
- package/dist/src/utils/memoryImportProcessor.js.map +1 -0
- package/dist/src/utils/memoryImportProcessor.test.d.ts +6 -0
- package/dist/src/utils/memoryImportProcessor.test.js +715 -0
- package/dist/src/utils/memoryImportProcessor.test.js.map +1 -0
- package/dist/src/utils/messageInspectors.d.ts +8 -0
- package/dist/src/utils/messageInspectors.js +16 -0
- package/dist/src/utils/messageInspectors.js.map +1 -0
- package/dist/src/utils/nextSpeakerChecker.d.ts +12 -0
- package/dist/src/utils/nextSpeakerChecker.js +92 -0
- package/dist/src/utils/nextSpeakerChecker.js.map +1 -0
- package/dist/src/utils/nextSpeakerChecker.test.d.ts +6 -0
- package/dist/src/utils/nextSpeakerChecker.test.js +168 -0
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -0
- package/dist/src/utils/openaiLogger.d.ts +42 -0
- package/dist/src/utils/openaiLogger.js +123 -0
- package/dist/src/utils/openaiLogger.js.map +1 -0
- package/dist/src/utils/partUtils.d.ts +14 -0
- package/dist/src/utils/partUtils.js +65 -0
- package/dist/src/utils/partUtils.js.map +1 -0
- package/dist/src/utils/partUtils.test.d.ts +6 -0
- package/dist/src/utils/partUtils.test.js +130 -0
- package/dist/src/utils/partUtils.test.js.map +1 -0
- package/dist/src/utils/paths.d.ts +68 -0
- package/dist/src/utils/paths.js +170 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/utils/paths.test.d.ts +6 -0
- package/dist/src/utils/paths.test.js +153 -0
- package/dist/src/utils/paths.test.js.map +1 -0
- package/dist/src/utils/quotaErrorDetection.d.ts +22 -0
- package/dist/src/utils/quotaErrorDetection.js +65 -0
- package/dist/src/utils/quotaErrorDetection.js.map +1 -0
- package/dist/src/utils/retry.d.ts +30 -0
- package/dist/src/utils/retry.js +276 -0
- package/dist/src/utils/retry.js.map +1 -0
- package/dist/src/utils/retry.test.d.ts +6 -0
- package/dist/src/utils/retry.test.js +322 -0
- package/dist/src/utils/retry.test.js.map +1 -0
- package/dist/src/utils/safeJsonStringify.d.ts +13 -0
- package/dist/src/utils/safeJsonStringify.js +25 -0
- package/dist/src/utils/safeJsonStringify.js.map +1 -0
- package/dist/src/utils/safeJsonStringify.test.d.ts +6 -0
- package/dist/src/utils/safeJsonStringify.test.js +61 -0
- package/dist/src/utils/safeJsonStringify.test.js.map +1 -0
- package/dist/src/utils/schemaValidator.d.ts +22 -0
- package/dist/src/utils/schemaValidator.js +65 -0
- package/dist/src/utils/schemaValidator.js.map +1 -0
- package/dist/src/utils/secure-browser-launcher.d.ts +23 -0
- package/dist/src/utils/secure-browser-launcher.js +164 -0
- package/dist/src/utils/secure-browser-launcher.js.map +1 -0
- package/dist/src/utils/secure-browser-launcher.test.d.ts +6 -0
- package/dist/src/utils/secure-browser-launcher.test.js +149 -0
- package/dist/src/utils/secure-browser-launcher.test.js.map +1 -0
- package/dist/src/utils/session.d.ts +6 -0
- package/dist/src/utils/session.js +8 -0
- package/dist/src/utils/session.js.map +1 -0
- package/dist/src/utils/shell-utils.d.ts +78 -0
- package/dist/src/utils/shell-utils.js +306 -0
- package/dist/src/utils/shell-utils.js.map +1 -0
- package/dist/src/utils/shell-utils.test.d.ts +6 -0
- package/dist/src/utils/shell-utils.test.js +200 -0
- package/dist/src/utils/shell-utils.test.js.map +1 -0
- package/dist/src/utils/summarizer.d.ts +25 -0
- package/dist/src/utils/summarizer.js +51 -0
- package/dist/src/utils/summarizer.js.map +1 -0
- package/dist/src/utils/summarizer.test.d.ts +6 -0
- package/dist/src/utils/summarizer.test.js +131 -0
- package/dist/src/utils/summarizer.test.js.map +1 -0
- package/dist/src/utils/systemEncoding.d.ts +40 -0
- package/dist/src/utils/systemEncoding.js +149 -0
- package/dist/src/utils/systemEncoding.js.map +1 -0
- package/dist/src/utils/systemEncoding.test.d.ts +6 -0
- package/dist/src/utils/systemEncoding.test.js +368 -0
- package/dist/src/utils/systemEncoding.test.js.map +1 -0
- package/dist/src/utils/testUtils.d.ts +29 -0
- package/dist/src/utils/testUtils.js +70 -0
- package/dist/src/utils/testUtils.js.map +1 -0
- package/dist/src/utils/textUtils.d.ts +13 -0
- package/dist/src/utils/textUtils.js +28 -0
- package/dist/src/utils/textUtils.js.map +1 -0
- package/dist/src/utils/user_account.d.ts +9 -0
- package/dist/src/utils/user_account.js +99 -0
- package/dist/src/utils/user_account.js.map +1 -0
- package/dist/src/utils/user_account.test.d.ts +6 -0
- package/dist/src/utils/user_account.test.js +153 -0
- package/dist/src/utils/user_account.test.js.map +1 -0
- package/dist/src/utils/user_id.d.ts +11 -0
- package/dist/src/utils/user_id.js +49 -0
- package/dist/src/utils/user_id.js.map +1 -0
- package/dist/src/utils/user_id.test.d.ts +6 -0
- package/dist/src/utils/user_id.test.js +21 -0
- package/dist/src/utils/user_id.test.js.map +1 -0
- package/dist/src/utils/workspaceContext.d.ts +51 -0
- package/dist/src/utils/workspaceContext.js +139 -0
- package/dist/src/utils/workspaceContext.js.map +1 -0
- package/dist/src/utils/workspaceContext.test.d.ts +6 -0
- package/dist/src/utils/workspaceContext.test.js +209 -0
- package/dist/src/utils/workspaceContext.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- 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
|