cowork-os 0.3.21 → 0.3.25

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 (252) hide show
  1. package/README.md +372 -10
  2. package/connectors/README.md +20 -0
  3. package/connectors/asana-mcp/README.md +24 -0
  4. package/connectors/asana-mcp/dist/index.js +427 -0
  5. package/connectors/asana-mcp/package.json +15 -0
  6. package/connectors/asana-mcp/src/index.ts +553 -0
  7. package/connectors/asana-mcp/tsconfig.json +13 -0
  8. package/connectors/hubspot-mcp/README.md +35 -0
  9. package/connectors/hubspot-mcp/dist/index.js +454 -0
  10. package/connectors/hubspot-mcp/package.json +15 -0
  11. package/connectors/hubspot-mcp/src/index.ts +562 -0
  12. package/connectors/hubspot-mcp/tsconfig.json +13 -0
  13. package/connectors/jira-mcp/README.md +49 -0
  14. package/connectors/jira-mcp/dist/index.js +588 -0
  15. package/connectors/jira-mcp/package.json +15 -0
  16. package/connectors/jira-mcp/src/index.ts +711 -0
  17. package/connectors/jira-mcp/tsconfig.json +13 -0
  18. package/connectors/linear-mcp/README.md +22 -0
  19. package/connectors/linear-mcp/dist/index.js +402 -0
  20. package/connectors/linear-mcp/package.json +15 -0
  21. package/connectors/linear-mcp/src/index.ts +522 -0
  22. package/connectors/linear-mcp/tsconfig.json +13 -0
  23. package/connectors/okta-mcp/README.md +24 -0
  24. package/connectors/okta-mcp/dist/index.js +411 -0
  25. package/connectors/okta-mcp/package.json +15 -0
  26. package/connectors/okta-mcp/src/index.ts +520 -0
  27. package/connectors/okta-mcp/tsconfig.json +13 -0
  28. package/connectors/salesforce-mcp/README.md +47 -0
  29. package/connectors/salesforce-mcp/dist/index.js +584 -0
  30. package/connectors/salesforce-mcp/package.json +15 -0
  31. package/connectors/salesforce-mcp/src/index.ts +722 -0
  32. package/connectors/salesforce-mcp/tsconfig.json +13 -0
  33. package/connectors/servicenow-mcp/README.md +26 -0
  34. package/connectors/servicenow-mcp/dist/index.js +400 -0
  35. package/connectors/servicenow-mcp/package.json +15 -0
  36. package/connectors/servicenow-mcp/src/index.ts +500 -0
  37. package/connectors/servicenow-mcp/tsconfig.json +13 -0
  38. package/connectors/templates/mcp-connector/README.md +31 -0
  39. package/connectors/templates/mcp-connector/package.json +15 -0
  40. package/connectors/templates/mcp-connector/src/index.ts +330 -0
  41. package/connectors/templates/mcp-connector/tsconfig.json +13 -0
  42. package/connectors/zendesk-mcp/README.md +40 -0
  43. package/connectors/zendesk-mcp/dist/index.js +431 -0
  44. package/connectors/zendesk-mcp/package.json +15 -0
  45. package/connectors/zendesk-mcp/src/index.ts +543 -0
  46. package/connectors/zendesk-mcp/tsconfig.json +13 -0
  47. package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
  48. package/dist/electron/electron/agent/daemon.js +189 -13
  49. package/dist/electron/electron/agent/executor.js +895 -78
  50. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  51. package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
  52. package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
  53. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  54. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  55. package/dist/electron/electron/agent/llm/index.js +13 -1
  56. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  57. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  58. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  59. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  60. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  61. package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
  62. package/dist/electron/electron/agent/llm/types.js +66 -1
  63. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  64. package/dist/electron/electron/agent/search/provider-factory.js +38 -2
  65. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  66. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  67. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  68. package/dist/electron/electron/agent/tools/file-tools.js +66 -3
  69. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  70. package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
  71. package/dist/electron/electron/agent/tools/image-tools.js +11 -1
  72. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  73. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  74. package/dist/electron/electron/agent/tools/registry.js +548 -10
  75. package/dist/electron/electron/agent/tools/search-tools.js +28 -10
  76. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  77. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  78. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  79. package/dist/electron/electron/agents/agent-dispatch.js +63 -0
  80. package/dist/electron/electron/database/repositories.js +19 -5
  81. package/dist/electron/electron/database/schema.js +8 -0
  82. package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
  83. package/dist/electron/electron/gateway/index.js +75 -1
  84. package/dist/electron/electron/gateway/router.js +209 -154
  85. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  86. package/dist/electron/electron/ipc/handlers.js +763 -267
  87. package/dist/electron/electron/main.js +63 -0
  88. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  89. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  90. package/dist/electron/electron/memory/MemoryService.js +2 -1
  91. package/dist/electron/electron/preload.js +78 -1
  92. package/dist/electron/electron/settings/appearance-manager.js +18 -1
  93. package/dist/electron/electron/settings/box-manager.js +54 -0
  94. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  95. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  96. package/dist/electron/electron/settings/notion-manager.js +56 -0
  97. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  98. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  99. package/dist/electron/electron/utils/box-api.js +153 -0
  100. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  101. package/dist/electron/electron/utils/env-migration.js +19 -0
  102. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  103. package/dist/electron/electron/utils/notion-api.js +103 -0
  104. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  105. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  106. package/dist/electron/electron/utils/validation.js +98 -3
  107. package/dist/electron/electron/utils/x-cli.js +1 -1
  108. package/dist/electron/shared/channelMessages.js +284 -3
  109. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  110. package/dist/electron/shared/types.js +90 -1
  111. package/package.json +14 -3
  112. package/resources/skills/nano-banana-pro.json +4 -4
  113. package/resources/skills/openai-image-gen.json +3 -3
  114. package/resources/skills/scripts/gen.py +163 -0
  115. package/resources/skills/scripts/generate_image.py +91 -0
  116. package/src/electron/agent/custom-skill-loader.ts +34 -1
  117. package/src/electron/agent/daemon.ts +210 -14
  118. package/src/electron/agent/executor.ts +1124 -85
  119. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  120. package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
  121. package/src/electron/agent/llm/bedrock-provider.ts +62 -9
  122. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  123. package/src/electron/agent/llm/groq-provider.ts +39 -0
  124. package/src/electron/agent/llm/index.ts +6 -0
  125. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  126. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  127. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  128. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  129. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  130. package/src/electron/agent/llm/provider-factory.ts +459 -6
  131. package/src/electron/agent/llm/types.ts +95 -1
  132. package/src/electron/agent/llm/xai-provider.ts +39 -0
  133. package/src/electron/agent/search/provider-factory.ts +43 -2
  134. package/src/electron/agent/tools/box-tools.ts +239 -0
  135. package/src/electron/agent/tools/builtin-settings.ts +36 -0
  136. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  137. package/src/electron/agent/tools/file-tools.ts +66 -3
  138. package/src/electron/agent/tools/gmail-tools.ts +240 -0
  139. package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
  140. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  141. package/src/electron/agent/tools/grep-tools.ts +97 -12
  142. package/src/electron/agent/tools/image-tools.ts +11 -1
  143. package/src/electron/agent/tools/notion-tools.ts +330 -0
  144. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  145. package/src/electron/agent/tools/registry.ts +794 -10
  146. package/src/electron/agent/tools/search-tools.ts +29 -11
  147. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  148. package/src/electron/agent/tools/shell-tools.ts +11 -3
  149. package/src/electron/agent/tools/x-tools.ts +1 -1
  150. package/src/electron/agents/agent-dispatch.ts +79 -0
  151. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  152. package/src/electron/database/repositories.ts +58 -6
  153. package/src/electron/database/schema.ts +8 -0
  154. package/src/electron/gateway/channels/discord.ts +4 -0
  155. package/src/electron/gateway/channels/google-chat.ts +3 -0
  156. package/src/electron/gateway/channels/line.ts +3 -0
  157. package/src/electron/gateway/channels/matrix-client.ts +15 -0
  158. package/src/electron/gateway/channels/matrix.ts +31 -0
  159. package/src/electron/gateway/channels/mattermost.ts +3 -0
  160. package/src/electron/gateway/channels/signal.ts +3 -0
  161. package/src/electron/gateway/channels/slack.ts +9 -4
  162. package/src/electron/gateway/channels/teams.ts +4 -0
  163. package/src/electron/gateway/channels/telegram.ts +2 -0
  164. package/src/electron/gateway/channels/twitch.ts +2 -0
  165. package/src/electron/gateway/channels/types.ts +8 -0
  166. package/src/electron/gateway/channels/whatsapp.ts +66 -0
  167. package/src/electron/gateway/index.ts +95 -2
  168. package/src/electron/gateway/router.ts +231 -161
  169. package/src/electron/gateway/security.ts +21 -9
  170. package/src/electron/ipc/canvas-handlers.ts +10 -0
  171. package/src/electron/ipc/handlers.ts +848 -292
  172. package/src/electron/main.ts +35 -0
  173. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  174. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  175. package/src/electron/memory/MemoryService.ts +7 -1
  176. package/src/electron/preload.ts +200 -5
  177. package/src/electron/settings/appearance-manager.ts +20 -2
  178. package/src/electron/settings/box-manager.ts +58 -0
  179. package/src/electron/settings/dropbox-manager.ts +58 -0
  180. package/src/electron/settings/google-workspace-manager.ts +59 -0
  181. package/src/electron/settings/notion-manager.ts +60 -0
  182. package/src/electron/settings/onedrive-manager.ts +58 -0
  183. package/src/electron/settings/sharepoint-manager.ts +58 -0
  184. package/src/electron/utils/box-api.ts +184 -0
  185. package/src/electron/utils/dropbox-api.ts +171 -0
  186. package/src/electron/utils/env-migration.ts +22 -0
  187. package/src/electron/utils/gmail-api.ts +121 -0
  188. package/src/electron/utils/google-calendar-api.ts +115 -0
  189. package/src/electron/utils/google-workspace-api.ts +228 -0
  190. package/src/electron/utils/google-workspace-auth.ts +109 -0
  191. package/src/electron/utils/google-workspace-oauth.ts +232 -0
  192. package/src/electron/utils/notion-api.ts +126 -0
  193. package/src/electron/utils/onedrive-api.ts +137 -0
  194. package/src/electron/utils/sharepoint-api.ts +132 -0
  195. package/src/electron/utils/validation.ts +128 -1
  196. package/src/electron/utils/x-cli.ts +1 -1
  197. package/src/renderer/App.tsx +119 -8
  198. package/src/renderer/components/ActivityFeedItem.tsx +34 -17
  199. package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
  200. package/src/renderer/components/AppearanceSettings.tsx +37 -2
  201. package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
  202. package/src/renderer/components/BoxSettings.tsx +203 -0
  203. package/src/renderer/components/BrowserView.tsx +101 -0
  204. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  205. package/src/renderer/components/CanvasPreview.tsx +68 -1
  206. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  207. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  208. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  209. package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
  210. package/src/renderer/components/DiscordSettings.tsx +18 -7
  211. package/src/renderer/components/DropboxSettings.tsx +202 -0
  212. package/src/renderer/components/EmailSettings.tsx +18 -7
  213. package/src/renderer/components/FileViewer.tsx +21 -13
  214. package/src/renderer/components/GoogleChatSettings.tsx +17 -7
  215. package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
  216. package/src/renderer/components/ImessageSettings.tsx +22 -11
  217. package/src/renderer/components/LineIcons.tsx +376 -0
  218. package/src/renderer/components/LineSettings.tsx +18 -7
  219. package/src/renderer/components/MCPSettings.tsx +56 -0
  220. package/src/renderer/components/MainContent.tsx +740 -76
  221. package/src/renderer/components/MatrixSettings.tsx +18 -7
  222. package/src/renderer/components/MattermostSettings.tsx +18 -7
  223. package/src/renderer/components/NodesSettings.tsx +58 -99
  224. package/src/renderer/components/NotificationPanel.tsx +25 -11
  225. package/src/renderer/components/NotionSettings.tsx +231 -0
  226. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  227. package/src/renderer/components/OnboardingModal.tsx +70 -1
  228. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  229. package/src/renderer/components/RightPanel.tsx +141 -28
  230. package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
  231. package/src/renderer/components/SearchSettings.tsx +118 -114
  232. package/src/renderer/components/Settings.tsx +1425 -651
  233. package/src/renderer/components/SharePointSettings.tsx +224 -0
  234. package/src/renderer/components/Sidebar.tsx +94 -19
  235. package/src/renderer/components/SignalSettings.tsx +18 -7
  236. package/src/renderer/components/SkillHubBrowser.tsx +144 -185
  237. package/src/renderer/components/SlackSettings.tsx +18 -7
  238. package/src/renderer/components/TaskQuickActions.tsx +11 -6
  239. package/src/renderer/components/TaskTimeline.tsx +58 -26
  240. package/src/renderer/components/TeamsSettings.tsx +18 -7
  241. package/src/renderer/components/TelegramSettings.tsx +18 -7
  242. package/src/renderer/components/ThemeIcon.tsx +16 -0
  243. package/src/renderer/components/TwitchSettings.tsx +18 -7
  244. package/src/renderer/components/VoiceSettings.tsx +30 -74
  245. package/src/renderer/components/WhatsAppSettings.tsx +48 -37
  246. package/src/renderer/components/WorkingStateHistory.tsx +7 -5
  247. package/src/renderer/components/WorkspaceSelector.tsx +42 -13
  248. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  249. package/src/renderer/styles/index.css +2333 -209
  250. package/src/shared/channelMessages.ts +367 -4
  251. package/src/shared/llm-provider-catalog.ts +217 -0
  252. package/src/shared/types.ts +251 -2
@@ -29,6 +29,8 @@ export class BedrockProvider implements LLMProvider {
29
29
  private client: BedrockRuntimeClient;
30
30
  private model: string;
31
31
 
32
+ private static readonly toolNameRegex = /^[a-zA-Z0-9_-]+$/;
33
+
32
34
  constructor(config: LLMProviderConfig) {
33
35
  const clientConfig: BedrockRuntimeClientConfig = {
34
36
  region: config.awsRegion || 'us-east-1',
@@ -56,9 +58,10 @@ export class BedrockProvider implements LLMProvider {
56
58
  }
57
59
 
58
60
  async createMessage(request: LLMRequest): Promise<LLMResponse> {
59
- const messages = this.convertMessages(request.messages);
61
+ const toolNameMap = request.tools ? this.buildToolNameMap(request.tools) : undefined;
62
+ const messages = this.convertMessages(request.messages, toolNameMap);
60
63
  const system = this.convertSystem(request.system);
61
- const toolConfig = request.tools ? this.convertTools(request.tools) : undefined;
64
+ const toolConfig = request.tools ? this.convertTools(request.tools, toolNameMap) : undefined;
62
65
 
63
66
  const command = new ConverseCommand({
64
67
  modelId: request.model,
@@ -77,7 +80,7 @@ export class BedrockProvider implements LLMProvider {
77
80
  // Pass abort signal to allow cancellation
78
81
  request.signal ? { abortSignal: request.signal } : undefined
79
82
  );
80
- return this.convertResponse(response);
83
+ return this.convertResponse(response, toolNameMap);
81
84
  } catch (error: any) {
82
85
  // Handle abort errors gracefully
83
86
  if (error.name === 'AbortError' || error.message?.includes('aborted')) {
@@ -135,7 +138,7 @@ export class BedrockProvider implements LLMProvider {
135
138
  return [{ text: system }];
136
139
  }
137
140
 
138
- private convertMessages(messages: LLMMessage[]): Message[] {
141
+ private convertMessages(messages: LLMMessage[], toolNameMap?: ToolNameMap): Message[] {
139
142
  return messages.map((msg) => {
140
143
  const content: ContentBlock[] = [];
141
144
 
@@ -146,10 +149,11 @@ export class BedrockProvider implements LLMProvider {
146
149
  if (item.type === 'text') {
147
150
  content.push({ text: item.text });
148
151
  } else if (item.type === 'tool_use') {
152
+ const mappedName = toolNameMap?.toProvider.get(item.name) || item.name;
149
153
  content.push({
150
154
  toolUse: {
151
155
  toolUseId: item.id,
152
- name: item.name,
156
+ name: mappedName,
153
157
  input: item.input,
154
158
  },
155
159
  });
@@ -172,11 +176,11 @@ export class BedrockProvider implements LLMProvider {
172
176
  });
173
177
  }
174
178
 
175
- private convertTools(tools: LLMTool[]): ToolConfiguration {
179
+ private convertTools(tools: LLMTool[], toolNameMap?: ToolNameMap): ToolConfiguration {
176
180
  return {
177
181
  tools: tools.map((tool) => ({
178
182
  toolSpec: {
179
- name: tool.name,
183
+ name: toolNameMap?.toProvider.get(tool.name) || tool.name,
180
184
  description: tool.description,
181
185
  inputSchema: {
182
186
  json: tool.input_schema,
@@ -186,7 +190,7 @@ export class BedrockProvider implements LLMProvider {
186
190
  };
187
191
  }
188
192
 
189
- private convertResponse(response: any): LLMResponse {
193
+ private convertResponse(response: any, toolNameMap?: ToolNameMap): LLMResponse {
190
194
  const content: LLMContent[] = [];
191
195
 
192
196
  if (response.output?.message?.content) {
@@ -197,10 +201,11 @@ export class BedrockProvider implements LLMProvider {
197
201
  text: block.text,
198
202
  });
199
203
  } else if (block.toolUse) {
204
+ const mappedName = toolNameMap?.fromProvider.get(block.toolUse.name) || block.toolUse.name;
200
205
  content.push({
201
206
  type: 'tool_use',
202
207
  id: block.toolUse.toolUseId,
203
- name: block.toolUse.name,
208
+ name: mappedName,
204
209
  input: block.toolUse.input,
205
210
  });
206
211
  }
@@ -219,6 +224,49 @@ export class BedrockProvider implements LLMProvider {
219
224
  };
220
225
  }
221
226
 
227
+ private buildToolNameMap(tools: LLMTool[]): ToolNameMap {
228
+ const toProvider = new Map<string, string>();
229
+ const fromProvider = new Map<string, string>();
230
+ const used = new Set<string>();
231
+
232
+ for (const tool of tools) {
233
+ let base = this.normalizeToolName(tool.name);
234
+ if (!base) {
235
+ base = `tool_${this.shortHash(tool.name)}`;
236
+ }
237
+
238
+ let candidate = base;
239
+ if (used.has(candidate)) {
240
+ const hashed = `${base}_${this.shortHash(tool.name)}`;
241
+ candidate = hashed;
242
+ let counter = 1;
243
+ while (used.has(candidate)) {
244
+ candidate = `${hashed}_${counter++}`;
245
+ }
246
+ }
247
+
248
+ used.add(candidate);
249
+ toProvider.set(tool.name, candidate);
250
+ fromProvider.set(candidate, tool.name);
251
+ }
252
+
253
+ return { toProvider, fromProvider };
254
+ }
255
+
256
+ private normalizeToolName(name: string): string {
257
+ const sanitized = name.replace(/[^a-zA-Z0-9_-]/g, '_');
258
+ return BedrockProvider.toolNameRegex.test(sanitized) ? sanitized : '';
259
+ }
260
+
261
+ private shortHash(input: string): string {
262
+ let hash = 2166136261;
263
+ for (let i = 0; i < input.length; i++) {
264
+ hash ^= input.charCodeAt(i);
265
+ hash = Math.imul(hash, 16777619);
266
+ }
267
+ return (hash >>> 0).toString(36);
268
+ }
269
+
222
270
  private mapStopReason(reason: StopReason | undefined): LLMResponse['stopReason'] {
223
271
  switch (reason) {
224
272
  case 'end_turn':
@@ -234,3 +282,8 @@ export class BedrockProvider implements LLMProvider {
234
282
  }
235
283
  }
236
284
  }
285
+
286
+ interface ToolNameMap {
287
+ toProvider: Map<string, string>;
288
+ fromProvider: Map<string, string>;
289
+ }
@@ -0,0 +1,117 @@
1
+ import { LLMProvider, LLMProviderConfig, LLMRequest, LLMResponse } from './types';
2
+ import { OpenAICompatibleProvider } from './openai-compatible-provider';
3
+
4
+ const COPILOT_TOKEN_URL = 'https://api.github.com/copilot_internal/v2/token';
5
+ const DEFAULT_COPILOT_BASE_URL = 'https://api.individual.githubcopilot.com';
6
+
7
+ type CopilotTokenCache = {
8
+ token: string;
9
+ expiresAt: number;
10
+ baseUrl: string;
11
+ };
12
+
13
+ function isTokenValid(cache: CopilotTokenCache, now = Date.now()): boolean {
14
+ return cache.expiresAt - now > 5 * 60 * 1000;
15
+ }
16
+
17
+ function parseCopilotTokenResponse(payload: any): { token: string; expiresAt: number } {
18
+ if (!payload || typeof payload !== 'object') {
19
+ throw new Error('Unexpected response from Copilot token endpoint');
20
+ }
21
+ const token = payload.token;
22
+ const expiresAt = payload.expires_at;
23
+ if (typeof token !== 'string' || !token.trim()) {
24
+ throw new Error('Copilot token response missing token');
25
+ }
26
+
27
+ if (typeof expiresAt === 'number' && Number.isFinite(expiresAt)) {
28
+ return { token, expiresAt: expiresAt > 10_000_000_000 ? expiresAt : expiresAt * 1000 };
29
+ }
30
+
31
+ if (typeof expiresAt === 'string' && expiresAt.trim()) {
32
+ const parsed = Number.parseInt(expiresAt, 10);
33
+ if (!Number.isFinite(parsed)) {
34
+ throw new Error('Copilot token response has invalid expires_at');
35
+ }
36
+ return { token, expiresAt: parsed > 10_000_000_000 ? parsed : parsed * 1000 };
37
+ }
38
+
39
+ throw new Error('Copilot token response missing expires_at');
40
+ }
41
+
42
+ function deriveCopilotBaseUrl(token: string): string {
43
+ const match = token.match(/(?:^|;)\s*proxy-ep=([^;\s]+)/i);
44
+ const proxyEp = match?.[1]?.trim();
45
+ if (!proxyEp) return DEFAULT_COPILOT_BASE_URL;
46
+ const host = proxyEp.replace(/^https?:\/\//, '').replace(/^proxy\./i, 'api.');
47
+ return host ? `https://${host}` : DEFAULT_COPILOT_BASE_URL;
48
+ }
49
+
50
+ export class GitHubCopilotProvider implements LLMProvider {
51
+ readonly type = 'github-copilot' as const;
52
+ private githubToken: string;
53
+ private model: string;
54
+ private static cache?: CopilotTokenCache;
55
+
56
+ constructor(config: LLMProviderConfig) {
57
+ const token = config.providerApiKey;
58
+ if (!token) {
59
+ throw new Error('GitHub token is required for Copilot. Configure it in Settings.');
60
+ }
61
+ this.githubToken = token;
62
+ this.model = config.model || 'gpt-4o';
63
+ }
64
+
65
+ async createMessage(request: LLMRequest): Promise<LLMResponse> {
66
+ const client = await this.getClient(request.model || this.model);
67
+ return client.createMessage(request);
68
+ }
69
+
70
+ async testConnection(): Promise<{ success: boolean; error?: string }> {
71
+ try {
72
+ const client = await this.getClient(this.model);
73
+ return await client.testConnection();
74
+ } catch (error: any) {
75
+ return { success: false, error: error.message || 'Failed to connect to Copilot' };
76
+ }
77
+ }
78
+
79
+ private async getClient(model: string): Promise<OpenAICompatibleProvider> {
80
+ const auth = await this.getCopilotAuth();
81
+ return new OpenAICompatibleProvider({
82
+ type: 'github-copilot',
83
+ providerName: 'GitHub Copilot',
84
+ apiKey: auth.token,
85
+ baseUrl: auth.baseUrl,
86
+ defaultModel: model,
87
+ });
88
+ }
89
+
90
+ private async getCopilotAuth(): Promise<CopilotTokenCache> {
91
+ if (GitHubCopilotProvider.cache && isTokenValid(GitHubCopilotProvider.cache)) {
92
+ return GitHubCopilotProvider.cache;
93
+ }
94
+
95
+ const response = await fetch(COPILOT_TOKEN_URL, {
96
+ method: 'GET',
97
+ headers: {
98
+ Accept: 'application/json',
99
+ Authorization: `Bearer ${this.githubToken}`,
100
+ },
101
+ });
102
+
103
+ if (!response.ok) {
104
+ throw new Error(`Copilot token exchange failed: HTTP ${response.status}`);
105
+ }
106
+
107
+ const json = await response.json();
108
+ const parsed = parseCopilotTokenResponse(json);
109
+ const cache: CopilotTokenCache = {
110
+ token: parsed.token,
111
+ expiresAt: parsed.expiresAt,
112
+ baseUrl: deriveCopilotBaseUrl(parsed.token),
113
+ };
114
+ GitHubCopilotProvider.cache = cache;
115
+ return cache;
116
+ }
117
+ }
@@ -0,0 +1,39 @@
1
+ import { LLMProvider, LLMProviderConfig, LLMRequest, LLMResponse } from './types';
2
+ import { OpenAICompatibleProvider } from './openai-compatible-provider';
3
+
4
+ const GROQ_BASE_URL = 'https://api.groq.com/openai/v1';
5
+ const DEFAULT_GROQ_MODEL = 'llama-3.1-8b-instant';
6
+
7
+ export class GroqProvider implements LLMProvider {
8
+ readonly type = 'groq' as const;
9
+ private client: OpenAICompatibleProvider;
10
+
11
+ constructor(config: LLMProviderConfig) {
12
+ const apiKey = config.groqApiKey;
13
+ if (!apiKey) {
14
+ throw new Error('Groq API key is required. Configure it in Settings.');
15
+ }
16
+
17
+ const baseUrl = config.groqBaseUrl || GROQ_BASE_URL;
18
+
19
+ this.client = new OpenAICompatibleProvider({
20
+ type: 'groq',
21
+ providerName: 'Groq',
22
+ apiKey,
23
+ baseUrl,
24
+ defaultModel: config.model || DEFAULT_GROQ_MODEL,
25
+ });
26
+ }
27
+
28
+ createMessage(request: LLMRequest): Promise<LLMResponse> {
29
+ return this.client.createMessage(request);
30
+ }
31
+
32
+ testConnection() {
33
+ return this.client.testConnection();
34
+ }
35
+
36
+ getAvailableModels() {
37
+ return this.client.getAvailableModels();
38
+ }
39
+ }
@@ -5,5 +5,11 @@ export { OllamaProvider } from './ollama-provider';
5
5
  export { GeminiProvider } from './gemini-provider';
6
6
  export { OpenRouterProvider } from './openrouter-provider';
7
7
  export { OpenAIProvider } from './openai-provider';
8
+ export { AzureOpenAIProvider } from './azure-openai-provider';
9
+ export { GroqProvider } from './groq-provider';
10
+ export { XAIProvider } from './xai-provider';
11
+ export { KimiProvider } from './kimi-provider';
12
+ export { AnthropicCompatibleProvider } from './anthropic-compatible-provider';
13
+ export { GitHubCopilotProvider } from './github-copilot-provider';
8
14
  export { OpenAIOAuth, OpenAIOAuthTokens } from './openai-oauth';
9
15
  export { LLMProviderFactory, LLMSettings, CachedModelInfo } from './provider-factory';
@@ -0,0 +1,39 @@
1
+ import { LLMProvider, LLMProviderConfig, LLMRequest, LLMResponse } from './types';
2
+ import { OpenAICompatibleProvider } from './openai-compatible-provider';
3
+
4
+ const KIMI_BASE_URL = 'https://api.moonshot.ai/v1';
5
+ const DEFAULT_KIMI_MODEL = 'kimi-k2.5';
6
+
7
+ export class KimiProvider implements LLMProvider {
8
+ readonly type = 'kimi' as const;
9
+ private client: OpenAICompatibleProvider;
10
+
11
+ constructor(config: LLMProviderConfig) {
12
+ const apiKey = config.kimiApiKey;
13
+ if (!apiKey) {
14
+ throw new Error('Kimi API key is required. Configure it in Settings.');
15
+ }
16
+
17
+ const baseUrl = config.kimiBaseUrl || KIMI_BASE_URL;
18
+
19
+ this.client = new OpenAICompatibleProvider({
20
+ type: 'kimi',
21
+ providerName: 'Kimi',
22
+ apiKey,
23
+ baseUrl,
24
+ defaultModel: config.model || DEFAULT_KIMI_MODEL,
25
+ });
26
+ }
27
+
28
+ createMessage(request: LLMRequest): Promise<LLMResponse> {
29
+ return this.client.createMessage(request);
30
+ }
31
+
32
+ testConnection() {
33
+ return this.client.testConnection();
34
+ }
35
+
36
+ getAvailableModels() {
37
+ return this.client.getAvailableModels();
38
+ }
39
+ }
@@ -0,0 +1,153 @@
1
+ import {
2
+ LLMProvider,
3
+ LLMProviderType,
4
+ LLMRequest,
5
+ LLMResponse,
6
+ } from './types';
7
+ import {
8
+ toOpenAICompatibleMessages,
9
+ toOpenAICompatibleTools,
10
+ fromOpenAICompatibleResponse,
11
+ } from './openai-compatible';
12
+
13
+ export interface OpenAICompatibleProviderOptions {
14
+ type: LLMProviderType;
15
+ providerName: string;
16
+ apiKey: string;
17
+ baseUrl: string;
18
+ defaultModel: string;
19
+ extraHeaders?: Record<string, string>;
20
+ }
21
+
22
+ export class OpenAICompatibleProvider implements LLMProvider {
23
+ readonly type: LLMProviderType;
24
+ private apiKey: string;
25
+ private baseUrl: string;
26
+ private defaultModel: string;
27
+ private providerName: string;
28
+ private extraHeaders?: Record<string, string>;
29
+
30
+ constructor(options: OpenAICompatibleProviderOptions) {
31
+ this.type = options.type;
32
+ this.apiKey = options.apiKey;
33
+ this.baseUrl = options.baseUrl;
34
+ this.defaultModel = options.defaultModel;
35
+ this.providerName = options.providerName;
36
+ this.extraHeaders = options.extraHeaders;
37
+ }
38
+
39
+ async createMessage(request: LLMRequest): Promise<LLMResponse> {
40
+ const messages = toOpenAICompatibleMessages(request.messages, request.system);
41
+ const tools = request.tools ? toOpenAICompatibleTools(request.tools) : undefined;
42
+
43
+ try {
44
+ const model = request.model || this.defaultModel;
45
+ console.log(`[${this.providerName}] Calling API with model: ${model}`);
46
+
47
+ const headers: Record<string, string> = {
48
+ 'Content-Type': 'application/json',
49
+ ...(this.extraHeaders || {}),
50
+ };
51
+ if (this.apiKey) {
52
+ headers.Authorization = `Bearer ${this.apiKey}`;
53
+ }
54
+
55
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
56
+ method: 'POST',
57
+ headers,
58
+ body: JSON.stringify({
59
+ model,
60
+ messages,
61
+ max_tokens: request.maxTokens,
62
+ ...(tools && { tools, tool_choice: 'auto' }),
63
+ }),
64
+ signal: request.signal,
65
+ });
66
+
67
+ if (!response.ok) {
68
+ const errorData = await response.json().catch(() => ({})) as { error?: { message?: string } };
69
+ throw new Error(
70
+ `${this.providerName} API error: ${response.status} ${response.statusText}` +
71
+ (errorData.error?.message ? ` - ${errorData.error.message}` : '')
72
+ );
73
+ }
74
+
75
+ const data = await response.json() as any;
76
+ return fromOpenAICompatibleResponse(data);
77
+ } catch (error: any) {
78
+ if (error.name === 'AbortError' || error.message?.includes('aborted')) {
79
+ console.log(`[${this.providerName}] Request aborted`);
80
+ throw new Error('Request cancelled');
81
+ }
82
+
83
+ console.error(`[${this.providerName}] API error:`, {
84
+ message: error.message,
85
+ status: error.status,
86
+ });
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ async testConnection(): Promise<{ success: boolean; error?: string }> {
92
+ try {
93
+ const headers: Record<string, string> = {
94
+ 'Content-Type': 'application/json',
95
+ ...(this.extraHeaders || {}),
96
+ };
97
+ if (this.apiKey) {
98
+ headers.Authorization = `Bearer ${this.apiKey}`;
99
+ }
100
+
101
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
102
+ method: 'POST',
103
+ headers,
104
+ body: JSON.stringify({
105
+ model: this.defaultModel,
106
+ messages: [{ role: 'user', content: 'Hi' }],
107
+ max_tokens: 10,
108
+ }),
109
+ });
110
+
111
+ if (!response.ok) {
112
+ const errorData = await response.json().catch(() => ({})) as { error?: { message?: string } };
113
+ return {
114
+ success: false,
115
+ error: errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,
116
+ };
117
+ }
118
+
119
+ return { success: true };
120
+ } catch (error: any) {
121
+ return {
122
+ success: false,
123
+ error: error.message || `Failed to connect to ${this.providerName} API`,
124
+ };
125
+ }
126
+ }
127
+
128
+ async getAvailableModels(): Promise<Array<{ id: string; name: string }>> {
129
+ try {
130
+ const headers: Record<string, string> = {};
131
+ if (this.apiKey) {
132
+ headers.Authorization = `Bearer ${this.apiKey}`;
133
+ }
134
+
135
+ const response = await fetch(`${this.baseUrl}/models`, {
136
+ headers,
137
+ });
138
+
139
+ if (!response.ok) {
140
+ return [];
141
+ }
142
+
143
+ const data = await response.json() as { data?: any[] };
144
+ return (data.data || []).map((model: any) => ({
145
+ id: model.id,
146
+ name: model.id,
147
+ }));
148
+ } catch (error) {
149
+ console.error(`[${this.providerName}] Failed to fetch models:`, error);
150
+ return [];
151
+ }
152
+ }
153
+ }
@@ -0,0 +1,133 @@
1
+ import {
2
+ LLMContent,
3
+ LLMMessage,
4
+ LLMResponse,
5
+ LLMTool,
6
+ } from './types';
7
+
8
+ export function toOpenAICompatibleMessages(
9
+ messages: LLMMessage[],
10
+ system?: string
11
+ ): Array<{ role: string; content: any; tool_call_id?: string; tool_calls?: any[] }> {
12
+ const result: Array<{ role: string; content: any; tool_call_id?: string; tool_calls?: any[] }> = [];
13
+
14
+ if (system) {
15
+ result.push({ role: 'system', content: system });
16
+ }
17
+
18
+ for (const msg of messages) {
19
+ if (typeof msg.content === 'string') {
20
+ result.push({ role: msg.role, content: msg.content });
21
+ continue;
22
+ }
23
+
24
+ for (const item of msg.content) {
25
+ if (item.type === 'tool_result') {
26
+ result.push({
27
+ role: 'tool',
28
+ content: item.content,
29
+ tool_call_id: item.tool_use_id,
30
+ });
31
+ } else if (item.type === 'tool_use') {
32
+ result.push({
33
+ role: 'assistant',
34
+ content: null,
35
+ tool_calls: [{
36
+ id: item.id,
37
+ type: 'function',
38
+ function: {
39
+ name: item.name,
40
+ arguments: JSON.stringify(item.input),
41
+ },
42
+ }],
43
+ });
44
+ } else if (item.type === 'text') {
45
+ result.push({ role: msg.role, content: item.text });
46
+ }
47
+ }
48
+ }
49
+
50
+ return result;
51
+ }
52
+
53
+ export function toOpenAICompatibleTools(tools: LLMTool[]): Array<{
54
+ type: 'function';
55
+ function: {
56
+ name: string;
57
+ description: string;
58
+ parameters: any;
59
+ };
60
+ }> {
61
+ return tools.map((tool) => ({
62
+ type: 'function' as const,
63
+ function: {
64
+ name: tool.name,
65
+ description: tool.description,
66
+ parameters: tool.input_schema,
67
+ },
68
+ }));
69
+ }
70
+
71
+ export function fromOpenAICompatibleResponse(response: any): LLMResponse {
72
+ const content: LLMContent[] = [];
73
+ const choice = response.choices?.[0];
74
+
75
+ if (!choice) {
76
+ return {
77
+ content: [{ type: 'text', text: '' }],
78
+ stopReason: 'end_turn',
79
+ };
80
+ }
81
+
82
+ const message = choice.message;
83
+
84
+ if (message?.content) {
85
+ content.push({
86
+ type: 'text',
87
+ text: message.content,
88
+ });
89
+ }
90
+
91
+ if (message?.tool_calls) {
92
+ for (const toolCall of message.tool_calls) {
93
+ if (toolCall.type === 'function') {
94
+ content.push({
95
+ type: 'tool_use',
96
+ id: toolCall.id,
97
+ name: toolCall.function.name,
98
+ input: JSON.parse(toolCall.function.arguments || '{}'),
99
+ });
100
+ }
101
+ }
102
+ }
103
+
104
+ if (content.length === 0) {
105
+ content.push({ type: 'text', text: '' });
106
+ }
107
+
108
+ return {
109
+ content,
110
+ stopReason: mapStopReason(choice.finish_reason),
111
+ usage: response.usage
112
+ ? {
113
+ inputTokens: response.usage.prompt_tokens || 0,
114
+ outputTokens: response.usage.completion_tokens || 0,
115
+ }
116
+ : undefined,
117
+ };
118
+ }
119
+
120
+ export function mapStopReason(finishReason?: string): LLMResponse['stopReason'] {
121
+ switch (finishReason) {
122
+ case 'stop':
123
+ return 'end_turn';
124
+ case 'length':
125
+ return 'max_tokens';
126
+ case 'tool_calls':
127
+ return 'tool_use';
128
+ case 'content_filter':
129
+ return 'stop_sequence';
130
+ default:
131
+ return 'end_turn';
132
+ }
133
+ }
@@ -20,11 +20,12 @@ export interface OpenAIOAuthTokens {
20
20
  * Convert pi-ai OAuthCredentials to our token format
21
21
  */
22
22
  function credentialsToTokens(credentials: OAuthCredentials): OpenAIOAuthTokens {
23
+ const email = typeof credentials.email === 'string' ? credentials.email : undefined;
23
24
  return {
24
25
  access_token: credentials.access,
25
26
  refresh_token: credentials.refresh,
26
27
  expires_at: credentials.expires,
27
- email: credentials.email,
28
+ email,
28
29
  };
29
30
  }
30
31
 
@@ -15,7 +15,7 @@ import {
15
15
  export class OpenRouterProvider implements LLMProvider {
16
16
  readonly type = 'openrouter' as const;
17
17
  private apiKey: string;
18
- private baseUrl = 'https://openrouter.ai/api/v1';
18
+ private baseUrl: string;
19
19
  private defaultModel: string;
20
20
 
21
21
  constructor(config: LLMProviderConfig) {
@@ -25,6 +25,7 @@ export class OpenRouterProvider implements LLMProvider {
25
25
  }
26
26
 
27
27
  this.apiKey = apiKey;
28
+ this.baseUrl = config.openrouterBaseUrl || 'https://openrouter.ai/api/v1';
28
29
  this.defaultModel = config.model || 'anthropic/claude-3.5-sonnet';
29
30
  }
30
31