@aituber-onair/chat 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.ja.md +318 -0
  2. package/README.md +318 -0
  3. package/dist/cjs/constants/chat.d.ts +26 -0
  4. package/dist/cjs/constants/chat.d.ts.map +1 -0
  5. package/dist/cjs/constants/chat.js +34 -0
  6. package/dist/cjs/constants/chat.js.map +1 -0
  7. package/dist/cjs/constants/claude.d.ts +9 -0
  8. package/dist/cjs/constants/claude.d.ts.map +1 -0
  9. package/dist/cjs/constants/claude.js +20 -0
  10. package/dist/cjs/constants/claude.js.map +1 -0
  11. package/dist/cjs/constants/gemini.d.ts +11 -0
  12. package/dist/cjs/constants/gemini.d.ts.map +1 -0
  13. package/dist/cjs/constants/gemini.js +26 -0
  14. package/dist/cjs/constants/gemini.js.map +1 -0
  15. package/dist/cjs/constants/index.d.ts +9 -0
  16. package/dist/cjs/constants/index.d.ts.map +1 -0
  17. package/dist/cjs/constants/index.js +25 -0
  18. package/dist/cjs/constants/index.js.map +1 -0
  19. package/dist/cjs/constants/openai.d.ts +13 -0
  20. package/dist/cjs/constants/openai.d.ts.map +1 -0
  21. package/dist/cjs/constants/openai.js +28 -0
  22. package/dist/cjs/constants/openai.js.map +1 -0
  23. package/dist/cjs/constants/prompts.d.ts +3 -0
  24. package/dist/cjs/constants/prompts.d.ts.map +1 -0
  25. package/dist/cjs/constants/prompts.js +16 -0
  26. package/dist/cjs/constants/prompts.js.map +1 -0
  27. package/dist/cjs/index.d.ts +17 -0
  28. package/dist/cjs/index.d.ts.map +1 -0
  29. package/dist/cjs/index.js +45 -0
  30. package/dist/cjs/index.js.map +1 -0
  31. package/dist/cjs/services/ChatService.d.ts +51 -0
  32. package/dist/cjs/services/ChatService.d.ts.map +1 -0
  33. package/dist/cjs/services/ChatService.js +3 -0
  34. package/dist/cjs/services/ChatService.js.map +1 -0
  35. package/dist/cjs/services/ChatServiceFactory.d.ts +39 -0
  36. package/dist/cjs/services/ChatServiceFactory.d.ts.map +1 -0
  37. package/dist/cjs/services/ChatServiceFactory.js +65 -0
  38. package/dist/cjs/services/ChatServiceFactory.js.map +1 -0
  39. package/dist/cjs/services/providers/ChatServiceProvider.d.ts +52 -0
  40. package/dist/cjs/services/providers/ChatServiceProvider.d.ts.map +1 -0
  41. package/dist/cjs/services/providers/ChatServiceProvider.js +3 -0
  42. package/dist/cjs/services/providers/ChatServiceProvider.js.map +1 -0
  43. package/dist/cjs/services/providers/claude/ClaudeChatService.d.ts +142 -0
  44. package/dist/cjs/services/providers/claude/ClaudeChatService.d.ts.map +1 -0
  45. package/dist/cjs/services/providers/claude/ClaudeChatService.js +501 -0
  46. package/dist/cjs/services/providers/claude/ClaudeChatService.js.map +1 -0
  47. package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.d.ts +40 -0
  48. package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.d.ts.map +1 -0
  49. package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.js +68 -0
  50. package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.js.map +1 -0
  51. package/dist/cjs/services/providers/gemini/GeminiChatService.d.ts +104 -0
  52. package/dist/cjs/services/providers/gemini/GeminiChatService.d.ts.map +1 -0
  53. package/dist/cjs/services/providers/gemini/GeminiChatService.js +653 -0
  54. package/dist/cjs/services/providers/gemini/GeminiChatService.js.map +1 -0
  55. package/dist/cjs/services/providers/gemini/GeminiChatServiceProvider.d.ts +40 -0
  56. package/dist/cjs/services/providers/gemini/GeminiChatServiceProvider.d.ts.map +1 -0
  57. package/dist/cjs/services/providers/gemini/GeminiChatServiceProvider.js +70 -0
  58. package/dist/cjs/services/providers/gemini/GeminiChatServiceProvider.js.map +1 -0
  59. package/dist/cjs/services/providers/openai/OpenAIChatService.d.ts +110 -0
  60. package/dist/cjs/services/providers/openai/OpenAIChatService.d.ts.map +1 -0
  61. package/dist/cjs/services/providers/openai/OpenAIChatService.js +544 -0
  62. package/dist/cjs/services/providers/openai/OpenAIChatService.js.map +1 -0
  63. package/dist/cjs/services/providers/openai/OpenAIChatServiceProvider.d.ts +40 -0
  64. package/dist/cjs/services/providers/openai/OpenAIChatServiceProvider.d.ts.map +1 -0
  65. package/dist/cjs/services/providers/openai/OpenAIChatServiceProvider.js +80 -0
  66. package/dist/cjs/services/providers/openai/OpenAIChatServiceProvider.js.map +1 -0
  67. package/dist/cjs/types/chat.d.ts +46 -0
  68. package/dist/cjs/types/chat.d.ts.map +1 -0
  69. package/dist/cjs/types/chat.js +6 -0
  70. package/dist/cjs/types/chat.js.map +1 -0
  71. package/dist/cjs/types/index.d.ts +8 -0
  72. package/dist/cjs/types/index.d.ts.map +1 -0
  73. package/dist/cjs/types/index.js +25 -0
  74. package/dist/cjs/types/index.js.map +1 -0
  75. package/dist/cjs/types/mcp.d.ts +37 -0
  76. package/dist/cjs/types/mcp.d.ts.map +1 -0
  77. package/dist/cjs/types/mcp.js +6 -0
  78. package/dist/cjs/types/mcp.js.map +1 -0
  79. package/dist/cjs/types/toolChat.d.ts +42 -0
  80. package/dist/cjs/types/toolChat.d.ts.map +1 -0
  81. package/dist/cjs/types/toolChat.js +3 -0
  82. package/dist/cjs/types/toolChat.js.map +1 -0
  83. package/dist/cjs/utils/chatServiceHttpClient.d.ts +47 -0
  84. package/dist/cjs/utils/chatServiceHttpClient.d.ts.map +1 -0
  85. package/dist/cjs/utils/chatServiceHttpClient.js +131 -0
  86. package/dist/cjs/utils/chatServiceHttpClient.js.map +1 -0
  87. package/dist/cjs/utils/emotionParser.d.ts +46 -0
  88. package/dist/cjs/utils/emotionParser.d.ts.map +1 -0
  89. package/dist/cjs/utils/emotionParser.js +59 -0
  90. package/dist/cjs/utils/emotionParser.js.map +1 -0
  91. package/dist/cjs/utils/index.d.ts +8 -0
  92. package/dist/cjs/utils/index.d.ts.map +1 -0
  93. package/dist/cjs/utils/index.js +24 -0
  94. package/dist/cjs/utils/index.js.map +1 -0
  95. package/dist/cjs/utils/mcpSchemaFetcher.d.ts +19 -0
  96. package/dist/cjs/utils/mcpSchemaFetcher.d.ts.map +1 -0
  97. package/dist/cjs/utils/mcpSchemaFetcher.js +98 -0
  98. package/dist/cjs/utils/mcpSchemaFetcher.js.map +1 -0
  99. package/dist/cjs/utils/screenplay.d.ts +20 -0
  100. package/dist/cjs/utils/screenplay.d.ts.map +1 -0
  101. package/dist/cjs/utils/screenplay.js +41 -0
  102. package/dist/cjs/utils/screenplay.js.map +1 -0
  103. package/dist/cjs/utils/streamTextAccumulator.d.ts +25 -0
  104. package/dist/cjs/utils/streamTextAccumulator.d.ts.map +1 -0
  105. package/dist/cjs/utils/streamTextAccumulator.js +47 -0
  106. package/dist/cjs/utils/streamTextAccumulator.js.map +1 -0
  107. package/dist/esm/constants/chat.d.ts +26 -0
  108. package/dist/esm/constants/chat.d.ts.map +1 -0
  109. package/dist/esm/constants/chat.js +30 -0
  110. package/dist/esm/constants/chat.js.map +1 -0
  111. package/dist/esm/constants/claude.d.ts +9 -0
  112. package/dist/esm/constants/claude.d.ts.map +1 -0
  113. package/dist/esm/constants/claude.js +17 -0
  114. package/dist/esm/constants/claude.js.map +1 -0
  115. package/dist/esm/constants/gemini.d.ts +11 -0
  116. package/dist/esm/constants/gemini.d.ts.map +1 -0
  117. package/dist/esm/constants/gemini.js +23 -0
  118. package/dist/esm/constants/gemini.js.map +1 -0
  119. package/dist/esm/constants/index.d.ts +9 -0
  120. package/dist/esm/constants/index.d.ts.map +1 -0
  121. package/dist/esm/constants/index.js +9 -0
  122. package/dist/esm/constants/index.js.map +1 -0
  123. package/dist/esm/constants/openai.d.ts +13 -0
  124. package/dist/esm/constants/openai.d.ts.map +1 -0
  125. package/dist/esm/constants/openai.js +25 -0
  126. package/dist/esm/constants/openai.js.map +1 -0
  127. package/dist/esm/constants/prompts.d.ts +3 -0
  128. package/dist/esm/constants/prompts.d.ts.map +1 -0
  129. package/dist/esm/constants/prompts.js +13 -0
  130. package/dist/esm/constants/prompts.js.map +1 -0
  131. package/dist/esm/index.d.ts +17 -0
  132. package/dist/esm/index.d.ts.map +1 -0
  133. package/dist/esm/index.js +21 -0
  134. package/dist/esm/index.js.map +1 -0
  135. package/dist/esm/services/ChatService.d.ts +51 -0
  136. package/dist/esm/services/ChatService.d.ts.map +1 -0
  137. package/dist/esm/services/ChatService.js +2 -0
  138. package/dist/esm/services/ChatService.js.map +1 -0
  139. package/dist/esm/services/ChatServiceFactory.d.ts +39 -0
  140. package/dist/esm/services/ChatServiceFactory.d.ts.map +1 -0
  141. package/dist/esm/services/ChatServiceFactory.js +61 -0
  142. package/dist/esm/services/ChatServiceFactory.js.map +1 -0
  143. package/dist/esm/services/providers/ChatServiceProvider.d.ts +52 -0
  144. package/dist/esm/services/providers/ChatServiceProvider.d.ts.map +1 -0
  145. package/dist/esm/services/providers/ChatServiceProvider.js +2 -0
  146. package/dist/esm/services/providers/ChatServiceProvider.js.map +1 -0
  147. package/dist/esm/services/providers/claude/ClaudeChatService.d.ts +142 -0
  148. package/dist/esm/services/providers/claude/ClaudeChatService.d.ts.map +1 -0
  149. package/dist/esm/services/providers/claude/ClaudeChatService.js +497 -0
  150. package/dist/esm/services/providers/claude/ClaudeChatService.js.map +1 -0
  151. package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.d.ts +40 -0
  152. package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.d.ts.map +1 -0
  153. package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.js +64 -0
  154. package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.js.map +1 -0
  155. package/dist/esm/services/providers/gemini/GeminiChatService.d.ts +104 -0
  156. package/dist/esm/services/providers/gemini/GeminiChatService.d.ts.map +1 -0
  157. package/dist/esm/services/providers/gemini/GeminiChatService.js +649 -0
  158. package/dist/esm/services/providers/gemini/GeminiChatService.js.map +1 -0
  159. package/dist/esm/services/providers/gemini/GeminiChatServiceProvider.d.ts +40 -0
  160. package/dist/esm/services/providers/gemini/GeminiChatServiceProvider.d.ts.map +1 -0
  161. package/dist/esm/services/providers/gemini/GeminiChatServiceProvider.js +66 -0
  162. package/dist/esm/services/providers/gemini/GeminiChatServiceProvider.js.map +1 -0
  163. package/dist/esm/services/providers/openai/OpenAIChatService.d.ts +110 -0
  164. package/dist/esm/services/providers/openai/OpenAIChatService.d.ts.map +1 -0
  165. package/dist/esm/services/providers/openai/OpenAIChatService.js +540 -0
  166. package/dist/esm/services/providers/openai/OpenAIChatService.js.map +1 -0
  167. package/dist/esm/services/providers/openai/OpenAIChatServiceProvider.d.ts +40 -0
  168. package/dist/esm/services/providers/openai/OpenAIChatServiceProvider.d.ts.map +1 -0
  169. package/dist/esm/services/providers/openai/OpenAIChatServiceProvider.js +76 -0
  170. package/dist/esm/services/providers/openai/OpenAIChatServiceProvider.js.map +1 -0
  171. package/dist/esm/types/chat.d.ts +46 -0
  172. package/dist/esm/types/chat.d.ts.map +1 -0
  173. package/dist/esm/types/chat.js +5 -0
  174. package/dist/esm/types/chat.js.map +1 -0
  175. package/dist/esm/types/index.d.ts +8 -0
  176. package/dist/esm/types/index.d.ts.map +1 -0
  177. package/dist/esm/types/index.js +9 -0
  178. package/dist/esm/types/index.js.map +1 -0
  179. package/dist/esm/types/mcp.d.ts +37 -0
  180. package/dist/esm/types/mcp.d.ts.map +1 -0
  181. package/dist/esm/types/mcp.js +5 -0
  182. package/dist/esm/types/mcp.js.map +1 -0
  183. package/dist/esm/types/toolChat.d.ts +42 -0
  184. package/dist/esm/types/toolChat.d.ts.map +1 -0
  185. package/dist/esm/types/toolChat.js +2 -0
  186. package/dist/esm/types/toolChat.js.map +1 -0
  187. package/dist/esm/utils/chatServiceHttpClient.d.ts +47 -0
  188. package/dist/esm/utils/chatServiceHttpClient.d.ts.map +1 -0
  189. package/dist/esm/utils/chatServiceHttpClient.js +126 -0
  190. package/dist/esm/utils/chatServiceHttpClient.js.map +1 -0
  191. package/dist/esm/utils/emotionParser.d.ts +46 -0
  192. package/dist/esm/utils/emotionParser.d.ts.map +1 -0
  193. package/dist/esm/utils/emotionParser.js +55 -0
  194. package/dist/esm/utils/emotionParser.js.map +1 -0
  195. package/dist/esm/utils/index.d.ts +8 -0
  196. package/dist/esm/utils/index.d.ts.map +1 -0
  197. package/dist/esm/utils/index.js +8 -0
  198. package/dist/esm/utils/index.js.map +1 -0
  199. package/dist/esm/utils/mcpSchemaFetcher.d.ts +19 -0
  200. package/dist/esm/utils/mcpSchemaFetcher.d.ts.map +1 -0
  201. package/dist/esm/utils/mcpSchemaFetcher.js +94 -0
  202. package/dist/esm/utils/mcpSchemaFetcher.js.map +1 -0
  203. package/dist/esm/utils/screenplay.d.ts +20 -0
  204. package/dist/esm/utils/screenplay.d.ts.map +1 -0
  205. package/dist/esm/utils/screenplay.js +36 -0
  206. package/dist/esm/utils/screenplay.js.map +1 -0
  207. package/dist/esm/utils/streamTextAccumulator.d.ts +25 -0
  208. package/dist/esm/utils/streamTextAccumulator.d.ts.map +1 -0
  209. package/dist/esm/utils/streamTextAccumulator.js +43 -0
  210. package/dist/esm/utils/streamTextAccumulator.js.map +1 -0
  211. package/package.json +54 -0
@@ -0,0 +1,653 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GeminiChatService = void 0;
4
+ const constants_1 = require("../../../constants");
5
+ const chat_1 = require("../../../constants/chat");
6
+ const streamTextAccumulator_1 = require("../../../utils/streamTextAccumulator");
7
+ const chatServiceHttpClient_1 = require("../../../utils/chatServiceHttpClient");
8
+ const mcpSchemaFetcher_1 = require("../../../utils/mcpSchemaFetcher");
9
+ /**
10
+ * Gemini implementation of ChatService
11
+ */
12
+ class GeminiChatService {
13
+ /* ────────────────────────────────── */
14
+ /* Utilities */
15
+ /* ────────────────────────────────── */
16
+ safeJsonParse(str) {
17
+ try {
18
+ return JSON.parse(str);
19
+ }
20
+ catch {
21
+ return str; // keep as string
22
+ }
23
+ }
24
+ normalizeToolResult(val) {
25
+ if (val === null)
26
+ return { content: null };
27
+ if (typeof val === 'object')
28
+ return val;
29
+ return { content: val }; // wrap primitive
30
+ }
31
+ /**
32
+ * camelCase → snake_case conversion (v1beta)
33
+ */
34
+ adaptKeysForApi(obj) {
35
+ const map = {
36
+ toolConfig: 'tool_config',
37
+ functionCallingConfig: 'function_calling_config',
38
+ functionDeclarations: 'function_declarations',
39
+ functionCall: 'function_call',
40
+ functionResponse: 'function_response',
41
+ };
42
+ if (Array.isArray(obj))
43
+ return obj.map((v) => this.adaptKeysForApi(v));
44
+ if (obj && typeof obj === 'object') {
45
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => [
46
+ map[k] ?? k,
47
+ this.adaptKeysForApi(v),
48
+ ]));
49
+ }
50
+ return obj;
51
+ }
52
+ /**
53
+ * Constructor
54
+ * @param apiKey Google API key
55
+ * @param model Name of the model to use
56
+ * @param visionModel Name of the vision model
57
+ * @param tools Array of tool definitions
58
+ * @param mcpServers Array of MCP server configurations
59
+ */
60
+ constructor(apiKey, model = constants_1.MODEL_GEMINI_2_0_FLASH_LITE, visionModel = constants_1.MODEL_GEMINI_2_0_FLASH_LITE, tools = [], mcpServers = [], responseLength) {
61
+ /** Provider name */
62
+ this.provider = 'gemini';
63
+ this.mcpToolSchemas = [];
64
+ this.mcpSchemasInitialized = false;
65
+ /** id(OpenAI) → name(Gemini) mapping */
66
+ this.callIdMap = new Map();
67
+ this.apiKey = apiKey;
68
+ this.model = model;
69
+ this.responseLength = responseLength;
70
+ // check if the vision model is supported
71
+ if (!constants_1.GEMINI_VISION_SUPPORTED_MODELS.includes(visionModel)) {
72
+ throw new Error(`Model ${visionModel} does not support vision capabilities.`);
73
+ }
74
+ this.visionModel = visionModel;
75
+ this.tools = tools;
76
+ this.mcpServers = mcpServers;
77
+ }
78
+ /**
79
+ * Get the current model name
80
+ * @returns Model name
81
+ */
82
+ getModel() {
83
+ return this.model;
84
+ }
85
+ /**
86
+ * Get the current vision model name
87
+ * @returns Vision model name
88
+ */
89
+ getVisionModel() {
90
+ return this.visionModel;
91
+ }
92
+ /**
93
+ * Get configured MCP servers
94
+ * @returns Array of MCP server configurations
95
+ */
96
+ getMCPServers() {
97
+ return this.mcpServers;
98
+ }
99
+ /**
100
+ * Add MCP server configuration
101
+ * @param serverConfig MCP server configuration
102
+ */
103
+ addMCPServer(serverConfig) {
104
+ this.mcpServers.push(serverConfig);
105
+ // Reset initialization flag to re-fetch schemas
106
+ this.mcpSchemasInitialized = false;
107
+ }
108
+ /**
109
+ * Remove MCP server by name
110
+ * @param serverName Name of the server to remove
111
+ */
112
+ removeMCPServer(serverName) {
113
+ this.mcpServers = this.mcpServers.filter((server) => server.name !== serverName);
114
+ // Reset initialization flag to re-fetch schemas
115
+ this.mcpSchemasInitialized = false;
116
+ }
117
+ /**
118
+ * Check if MCP servers are configured
119
+ * @returns True if MCP servers are configured
120
+ */
121
+ hasMCPServers() {
122
+ return this.mcpServers.length > 0;
123
+ }
124
+ /**
125
+ * Initialize MCP tool schemas by fetching from servers
126
+ * @private
127
+ */
128
+ async initializeMCPSchemas() {
129
+ if (this.mcpSchemasInitialized || this.mcpServers.length === 0) {
130
+ return;
131
+ }
132
+ try {
133
+ // Add timeout to prevent hanging
134
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('MCP schema fetch timeout')), 5000));
135
+ const schemasPromise = mcpSchemaFetcher_1.MCPSchemaFetcher.fetchAllToolSchemas(this.mcpServers);
136
+ this.mcpToolSchemas = await Promise.race([
137
+ schemasPromise,
138
+ timeoutPromise,
139
+ ]);
140
+ this.mcpSchemasInitialized = true;
141
+ }
142
+ catch (error) {
143
+ console.warn('Failed to initialize MCP schemas, using fallback:', error);
144
+ // Use fallback schemas - always provide basic functionality
145
+ this.mcpToolSchemas = this.mcpServers.map((server) => ({
146
+ name: `mcp_${server.name}_search`,
147
+ description: `Search using ${server.name} MCP server (fallback)`,
148
+ parameters: {
149
+ type: 'object',
150
+ properties: {
151
+ query: {
152
+ type: 'string',
153
+ description: 'Search query',
154
+ },
155
+ },
156
+ required: ['query'],
157
+ },
158
+ }));
159
+ this.mcpSchemasInitialized = true;
160
+ }
161
+ }
162
+ /**
163
+ * Process chat messages
164
+ * @param messages Array of messages to send
165
+ * @param onPartialResponse Callback to receive each part of streaming response
166
+ * @param onCompleteResponse Callback to execute when response is complete
167
+ */
168
+ async processChat(messages, onPartialResponse, onCompleteResponse) {
169
+ try {
170
+ // not use tools or MCP servers
171
+ if (this.tools.length === 0 && this.mcpServers.length === 0) {
172
+ const res = await this.callGemini(messages, this.model, true);
173
+ const { blocks } = await this.parseStream(res, onPartialResponse);
174
+ const full = blocks
175
+ .filter((b) => b.type === 'text')
176
+ .map((b) => b.text)
177
+ .join('');
178
+ await onCompleteResponse(full);
179
+ return;
180
+ }
181
+ /* with tools (1 turn) */
182
+ const { blocks, stop_reason } = await this.chatOnce(messages, true, onPartialResponse);
183
+ if (stop_reason === 'end') {
184
+ const full = blocks
185
+ .filter((b) => b.type === 'text')
186
+ .map((b) => b.text)
187
+ .join('');
188
+ await onCompleteResponse(full);
189
+ return;
190
+ }
191
+ throw new Error('Received functionCall. Use chatOnce() loop when tools are enabled.');
192
+ }
193
+ catch (err) {
194
+ console.error('Error in processChat:', err);
195
+ throw err;
196
+ }
197
+ }
198
+ async processVisionChat(messages, onPartialResponse, onCompleteResponse) {
199
+ try {
200
+ if (this.tools.length === 0 && this.mcpServers.length === 0) {
201
+ const res = await this.callGemini(messages, this.visionModel, true);
202
+ const { blocks } = await this.parseStream(res, onPartialResponse);
203
+ const full = blocks
204
+ .filter((b) => b.type === 'text')
205
+ .map((b) => b.text)
206
+ .join('');
207
+ await onCompleteResponse(full);
208
+ return;
209
+ }
210
+ const { blocks, stop_reason } = await this.visionChatOnce(messages);
211
+ blocks
212
+ .filter((b) => b.type === 'text')
213
+ .forEach((b) => onPartialResponse(b.text));
214
+ if (stop_reason === 'end') {
215
+ const full = blocks
216
+ .filter((b) => b.type === 'text')
217
+ .map((b) => b.text)
218
+ .join('');
219
+ await onCompleteResponse(full);
220
+ return;
221
+ }
222
+ throw new Error('Received functionCall. Use visionChatOnce() loop when tools are enabled.');
223
+ }
224
+ catch (err) {
225
+ console.error('Error in processVisionChat:', err);
226
+ throw err;
227
+ }
228
+ }
229
+ /* ────────────────────────────────── */
230
+ /* OpenAI → Gemini conversion */
231
+ /* ────────────────────────────────── */
232
+ convertMessagesToGeminiFormat(messages) {
233
+ const gemini = [];
234
+ let currentRole = null;
235
+ let currentParts = [];
236
+ const pushCurrent = () => {
237
+ if (currentRole && currentParts.length) {
238
+ gemini.push({ role: currentRole, parts: [...currentParts] });
239
+ currentParts = [];
240
+ }
241
+ };
242
+ for (const msg of messages) {
243
+ const role = this.mapRoleToGemini(msg.role);
244
+ /* assistant: tool_calls -> functionCall */
245
+ if (msg.tool_calls) {
246
+ pushCurrent();
247
+ for (const call of msg.tool_calls) {
248
+ this.callIdMap.set(call.id, call.function.name);
249
+ gemini.push({
250
+ role: 'model',
251
+ parts: [
252
+ {
253
+ functionCall: {
254
+ name: call.function.name,
255
+ args: JSON.parse(call.function.arguments || '{}'),
256
+ },
257
+ },
258
+ ],
259
+ });
260
+ }
261
+ continue;
262
+ }
263
+ /* tool → functionResponse */
264
+ if (msg.role === 'tool') {
265
+ pushCurrent();
266
+ const funcName = msg.name ??
267
+ this.callIdMap.get(msg.tool_call_id) ??
268
+ 'result';
269
+ gemini.push({
270
+ role: 'user',
271
+ parts: [
272
+ {
273
+ functionResponse: {
274
+ name: funcName,
275
+ response: this.normalizeToolResult(this.safeJsonParse(msg.content)),
276
+ },
277
+ },
278
+ ],
279
+ });
280
+ continue;
281
+ }
282
+ /* normal text */
283
+ if (role !== currentRole)
284
+ pushCurrent();
285
+ currentRole = role;
286
+ currentParts.push({ text: msg.content });
287
+ }
288
+ pushCurrent();
289
+ return gemini;
290
+ }
291
+ /* ────────────────────────────────── */
292
+ /* HTTP call */
293
+ /* ────────────────────────────────── */
294
+ async callGemini(messages, model, stream = false, maxTokens) {
295
+ const hasVision = messages.some((m) => Array.isArray(m.content) &&
296
+ m.content.some((b) => b?.type === 'image_url' || b?.inlineData));
297
+ const contents = hasVision
298
+ ? await this.convertVisionMessagesToGeminiFormat(messages)
299
+ : this.convertMessagesToGeminiFormat(messages);
300
+ const body = {
301
+ contents,
302
+ generationConfig: {
303
+ maxOutputTokens: maxTokens !== undefined
304
+ ? maxTokens
305
+ : (0, chat_1.getMaxTokensForResponseLength)(this.responseLength),
306
+ },
307
+ };
308
+ // Add tools configuration (regular tools + MCP tools as functionDeclarations)
309
+ const allToolDeclarations = [];
310
+ // Add regular function tools
311
+ if (this.tools.length > 0) {
312
+ allToolDeclarations.push(...this.tools.map((t) => ({
313
+ name: t.name,
314
+ description: t.description,
315
+ parameters: t.parameters,
316
+ })));
317
+ }
318
+ // Add MCP tools as functionDeclarations
319
+ if (this.mcpServers.length > 0) {
320
+ try {
321
+ // Initialize MCP schemas if not already done
322
+ await this.initializeMCPSchemas();
323
+ // Add MCP tool schemas as regular function declarations
324
+ // Gemini will call these as normal functions, and ToolExecutor will handle the MCP routing
325
+ allToolDeclarations.push(...this.mcpToolSchemas.map((t) => ({
326
+ name: t.name,
327
+ description: t.description,
328
+ parameters: t.parameters,
329
+ })));
330
+ }
331
+ catch (error) {
332
+ console.warn('MCP initialization failed, skipping MCP tools:', error);
333
+ // Continue without MCP tools if initialization fails
334
+ }
335
+ }
336
+ if (allToolDeclarations.length > 0) {
337
+ body.tools = [
338
+ {
339
+ functionDeclarations: allToolDeclarations,
340
+ },
341
+ ];
342
+ body.toolConfig = { functionCallingConfig: { mode: 'AUTO' } };
343
+ }
344
+ const fetchOnce = async (ver, payload) => {
345
+ const fn = stream ? 'streamGenerateContent' : 'generateContent';
346
+ const alt = stream ? '?alt=sse' : '';
347
+ const url = `${constants_1.ENDPOINT_GEMINI_API}/${ver}/models/${model}:${fn}${alt}${alt ? '&' : '?'}key=${this.apiKey}`;
348
+ return chatServiceHttpClient_1.ChatServiceHttpClient.post(url, payload);
349
+ };
350
+ const isLite = /flash[-_]lite/.test(model);
351
+ const isGemini25 = /gemini-2\.5/.test(model);
352
+ const firstVer = isLite || isGemini25 ? 'v1beta' : 'v1';
353
+ const tryApi = async () => {
354
+ try {
355
+ const payload = firstVer === 'v1' ? body : this.adaptKeysForApi(body); // snake_case conversion
356
+ return await fetchOnce(firstVer, payload);
357
+ }
358
+ catch (e) {
359
+ // Only retry v1beta if camel/snake case mismatch error occurs in models that don't require v1beta
360
+ if (!(isLite || isGemini25) &&
361
+ /Unknown name|Cannot find field|404/.test(e.message)) {
362
+ return await fetchOnce('v1beta', this.adaptKeysForApi(body));
363
+ }
364
+ throw e; // otherwise, throw to upper layer
365
+ }
366
+ };
367
+ try {
368
+ const res = await tryApi();
369
+ return res;
370
+ }
371
+ catch (error) {
372
+ // Enhanced error logging for debugging
373
+ if (error.body) {
374
+ console.error('Gemini API Error Details:', error.body);
375
+ console.error('Request Body:', JSON.stringify(body, null, 2));
376
+ }
377
+ throw error;
378
+ }
379
+ }
380
+ /**
381
+ * Convert AITuber OnAir vision messages to Gemini format
382
+ * @param messages Array of vision messages
383
+ * @returns Gemini formatted vision messages
384
+ */
385
+ async convertVisionMessagesToGeminiFormat(messages) {
386
+ const geminiMessages = [];
387
+ let currentRole = null;
388
+ let currentParts = [];
389
+ for (const msg of messages) {
390
+ // Map AITuber OnAir roles to Gemini roles
391
+ const role = this.mapRoleToGemini(msg.role);
392
+ /* ----------- OpenAI compatible tool metadata ----------- */
393
+ // assistant: { tool_calls:[{id,name,function:{arguments}}] }
394
+ if (msg.tool_calls) {
395
+ for (const call of msg.tool_calls) {
396
+ // Gemini does not need id. Insert functionCall into parts
397
+ geminiMessages.push({
398
+ role: 'model',
399
+ parts: [
400
+ {
401
+ functionCall: {
402
+ name: call.function.name,
403
+ args: JSON.parse(call.function.arguments || '{}'),
404
+ },
405
+ },
406
+ ],
407
+ });
408
+ }
409
+ continue;
410
+ }
411
+ // tool role → user role + functionResponse
412
+ if (msg.role === 'tool') {
413
+ const funcName = msg.name ??
414
+ this.callIdMap.get(msg.tool_call_id) ??
415
+ 'result';
416
+ geminiMessages.push({
417
+ role: 'user',
418
+ parts: [
419
+ {
420
+ functionResponse: {
421
+ name: funcName,
422
+ response: this.normalizeToolResult(this.safeJsonParse(msg.content)),
423
+ },
424
+ },
425
+ ],
426
+ });
427
+ continue;
428
+ }
429
+ // If role changes, start a new message
430
+ if (role !== currentRole && currentParts.length > 0) {
431
+ geminiMessages.push({
432
+ role: currentRole,
433
+ parts: [...currentParts],
434
+ });
435
+ currentParts = [];
436
+ }
437
+ currentRole = role;
438
+ // If the message has content blocks, process them
439
+ if (typeof msg.content === 'string') {
440
+ currentParts.push({ text: msg.content });
441
+ }
442
+ else if (Array.isArray(msg.content)) {
443
+ // Process each content block (text or image)
444
+ for (const block of msg.content) {
445
+ if (block.type === 'text') {
446
+ currentParts.push({ text: block.text });
447
+ }
448
+ else if (block.type === 'image_url') {
449
+ try {
450
+ // Fetch the image data from URL
451
+ const imageResponse = await chatServiceHttpClient_1.ChatServiceHttpClient.get(block.image_url.url);
452
+ // Convert image to blob and then to base64
453
+ const imageBlob = await imageResponse.blob();
454
+ const base64Data = await this.blobToBase64(imageBlob);
455
+ // Add image data in Gemini format
456
+ currentParts.push({
457
+ inlineData: {
458
+ mimeType: imageBlob.type || 'image/jpeg',
459
+ data: base64Data.split(',')[1], // Remove the "data:image/jpeg;base64," prefix
460
+ },
461
+ });
462
+ }
463
+ catch (error) {
464
+ console.error('Error processing image:', error);
465
+ throw new Error(`Failed to process image: ${error.message}`);
466
+ }
467
+ }
468
+ }
469
+ }
470
+ }
471
+ // Add the last message
472
+ if (currentRole && currentParts.length > 0) {
473
+ geminiMessages.push({
474
+ role: currentRole,
475
+ parts: [...currentParts],
476
+ });
477
+ }
478
+ return geminiMessages;
479
+ }
480
+ /**
481
+ * Convert Blob to Base64 string
482
+ * @param blob Image blob
483
+ * @returns Promise with base64 encoded string
484
+ */
485
+ blobToBase64(blob) {
486
+ return new Promise((resolve, reject) => {
487
+ const reader = new FileReader();
488
+ reader.onloadend = () => resolve(reader.result);
489
+ reader.onerror = reject;
490
+ reader.readAsDataURL(blob);
491
+ });
492
+ }
493
+ /**
494
+ * Map AITuber OnAir roles to Gemini roles
495
+ * @param role AITuber OnAir role
496
+ * @returns Gemini role
497
+ */
498
+ mapRoleToGemini(role) {
499
+ switch (role) {
500
+ case 'system':
501
+ return 'model'; // Gemini uses 'model' for system messages
502
+ case 'user':
503
+ return 'user';
504
+ case 'assistant':
505
+ return 'model';
506
+ default:
507
+ return 'user';
508
+ }
509
+ }
510
+ /* ────────────────────────────────────────────────────────── */
511
+ /* Convert NDJSON stream to common format */
512
+ /* ────────────────────────────────────────────────────────── */
513
+ async parseStream(res, onPartial) {
514
+ const reader = res.body.getReader();
515
+ const dec = new TextDecoder();
516
+ const textBlocks = [];
517
+ const toolBlocks = [];
518
+ let buf = '';
519
+ const flush = (payload) => {
520
+ if (!payload || payload === '[DONE]')
521
+ return;
522
+ let obj;
523
+ try {
524
+ obj = JSON.parse(payload);
525
+ }
526
+ catch {
527
+ return;
528
+ }
529
+ for (const cand of obj.candidates ?? []) {
530
+ for (const part of cand.content?.parts ?? []) {
531
+ if (part.text) {
532
+ onPartial(part.text);
533
+ streamTextAccumulator_1.StreamTextAccumulator.addTextBlock(textBlocks, part.text);
534
+ }
535
+ if (part.functionCall) {
536
+ toolBlocks.push({
537
+ type: 'tool_use',
538
+ id: this.genUUID(),
539
+ name: part.functionCall.name,
540
+ input: part.functionCall.args ?? {},
541
+ });
542
+ }
543
+ if (part.functionResponse) {
544
+ toolBlocks.push({
545
+ type: 'tool_result',
546
+ tool_use_id: part.functionResponse.name,
547
+ content: JSON.stringify(part.functionResponse.response),
548
+ });
549
+ }
550
+ }
551
+ }
552
+ };
553
+ while (true) {
554
+ const { done, value } = await reader.read();
555
+ if (done)
556
+ break;
557
+ buf += dec.decode(value, { stream: true });
558
+ let nl;
559
+ while ((nl = buf.indexOf('\n')) !== -1) {
560
+ let line = buf.slice(0, nl);
561
+ buf = buf.slice(nl + 1);
562
+ if (line.endsWith('\r'))
563
+ line = line.slice(0, -1); // CRLF support
564
+ if (!line.trim()) {
565
+ flush('');
566
+ continue;
567
+ } // keep-alive empty line
568
+ if (line.startsWith('data:'))
569
+ line = line.slice(5).trim();
570
+ if (!line)
571
+ continue;
572
+ flush(line);
573
+ }
574
+ }
575
+ if (buf)
576
+ flush(buf);
577
+ const blocks = [...textBlocks, ...toolBlocks];
578
+ return {
579
+ blocks,
580
+ stop_reason: toolBlocks.some((b) => b.type === 'tool_use')
581
+ ? 'tool_use'
582
+ : 'end',
583
+ };
584
+ }
585
+ /* ────────────────────────────────────────────────────────── */
586
+ /* Convert JSON of non-stream (= generateContent) */
587
+ /* ────────────────────────────────────────────────────────── */
588
+ parseOneShot(data) {
589
+ const textBlocks = [];
590
+ const toolBlocks = [];
591
+ for (const cand of data.candidates ?? []) {
592
+ for (const part of cand.content?.parts ?? []) {
593
+ if (part.text) {
594
+ textBlocks.push({ type: 'text', text: part.text });
595
+ }
596
+ if (part.functionCall) {
597
+ toolBlocks.push({
598
+ type: 'tool_use',
599
+ id: this.genUUID(),
600
+ name: part.functionCall.name,
601
+ input: part.functionCall.args ?? {},
602
+ });
603
+ }
604
+ if (part.functionResponse) {
605
+ toolBlocks.push({
606
+ type: 'tool_result',
607
+ tool_use_id: part.functionResponse.name,
608
+ content: JSON.stringify(part.functionResponse.response),
609
+ });
610
+ }
611
+ }
612
+ }
613
+ const blocks = [...textBlocks, ...toolBlocks];
614
+ return {
615
+ blocks,
616
+ stop_reason: toolBlocks.some((b) => b.type === 'tool_use')
617
+ ? 'tool_use'
618
+ : 'end',
619
+ };
620
+ }
621
+ /* ────────────────────────────────────────────────────────── */
622
+ /* chatOnce (text) */
623
+ /* ────────────────────────────────────────────────────────── */
624
+ async chatOnce(messages, stream = true, onPartialResponse = () => { }, maxTokens) {
625
+ const res = await this.callGemini(messages, this.model, stream, maxTokens);
626
+ return stream
627
+ ? this.parseStream(res, onPartialResponse)
628
+ : this.parseOneShot(await res.json());
629
+ }
630
+ /* ────────────────────────────────────────────────────────── */
631
+ /* visionChatOnce (images) */
632
+ /* ────────────────────────────────────────────────────────── */
633
+ async visionChatOnce(messages, stream = false, onPartialResponse = () => { }, maxTokens) {
634
+ const res = await this.callGemini(messages, this.visionModel, stream, maxTokens);
635
+ return stream
636
+ ? this.parseStream(res, onPartialResponse)
637
+ : this.parseOneShot(await res.json());
638
+ }
639
+ /* ────────────────────────────────────────────────────────── */
640
+ /* UUID helper */
641
+ /* ────────────────────────────────────────────────────────── */
642
+ genUUID() {
643
+ return typeof crypto !== 'undefined' && crypto.randomUUID
644
+ ? crypto.randomUUID()
645
+ : 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
646
+ const r = (Math.random() * 16) | 0;
647
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
648
+ return v.toString(16);
649
+ });
650
+ }
651
+ }
652
+ exports.GeminiChatService = GeminiChatService;
653
+ //# sourceMappingURL=GeminiChatService.js.map