cowork-os 0.3.21 → 0.3.23
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/README.md +293 -6
- package/connectors/README.md +20 -0
- package/connectors/asana-mcp/README.md +24 -0
- package/connectors/asana-mcp/dist/index.js +427 -0
- package/connectors/asana-mcp/package.json +15 -0
- package/connectors/asana-mcp/src/index.ts +553 -0
- package/connectors/asana-mcp/tsconfig.json +13 -0
- package/connectors/hubspot-mcp/README.md +35 -0
- package/connectors/hubspot-mcp/dist/index.js +454 -0
- package/connectors/hubspot-mcp/package.json +15 -0
- package/connectors/hubspot-mcp/src/index.ts +562 -0
- package/connectors/hubspot-mcp/tsconfig.json +13 -0
- package/connectors/jira-mcp/README.md +49 -0
- package/connectors/jira-mcp/dist/index.js +588 -0
- package/connectors/jira-mcp/package.json +15 -0
- package/connectors/jira-mcp/src/index.ts +711 -0
- package/connectors/jira-mcp/tsconfig.json +13 -0
- package/connectors/linear-mcp/README.md +22 -0
- package/connectors/linear-mcp/dist/index.js +402 -0
- package/connectors/linear-mcp/package.json +15 -0
- package/connectors/linear-mcp/src/index.ts +522 -0
- package/connectors/linear-mcp/tsconfig.json +13 -0
- package/connectors/okta-mcp/README.md +24 -0
- package/connectors/okta-mcp/dist/index.js +411 -0
- package/connectors/okta-mcp/package.json +15 -0
- package/connectors/okta-mcp/src/index.ts +520 -0
- package/connectors/okta-mcp/tsconfig.json +13 -0
- package/connectors/salesforce-mcp/README.md +47 -0
- package/connectors/salesforce-mcp/dist/index.js +584 -0
- package/connectors/salesforce-mcp/package.json +15 -0
- package/connectors/salesforce-mcp/src/index.ts +722 -0
- package/connectors/salesforce-mcp/tsconfig.json +13 -0
- package/connectors/servicenow-mcp/README.md +26 -0
- package/connectors/servicenow-mcp/dist/index.js +400 -0
- package/connectors/servicenow-mcp/package.json +15 -0
- package/connectors/servicenow-mcp/src/index.ts +500 -0
- package/connectors/servicenow-mcp/tsconfig.json +13 -0
- package/connectors/templates/mcp-connector/README.md +31 -0
- package/connectors/templates/mcp-connector/package.json +15 -0
- package/connectors/templates/mcp-connector/src/index.ts +330 -0
- package/connectors/templates/mcp-connector/tsconfig.json +13 -0
- package/connectors/zendesk-mcp/README.md +40 -0
- package/connectors/zendesk-mcp/dist/index.js +431 -0
- package/connectors/zendesk-mcp/package.json +15 -0
- package/connectors/zendesk-mcp/src/index.ts +543 -0
- package/connectors/zendesk-mcp/tsconfig.json +13 -0
- package/dist/electron/electron/agent/daemon.js +25 -0
- package/dist/electron/electron/agent/executor.js +181 -26
- package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
- package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
- package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
- package/dist/electron/electron/agent/llm/index.js +11 -1
- package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
- package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
- package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
- package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
- package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
- package/dist/electron/electron/agent/llm/types.js +66 -1
- package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
- package/dist/electron/electron/agent/tools/box-tools.js +231 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
- package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
- package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
- package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
- package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
- package/dist/electron/electron/agent/tools/registry.js +541 -0
- package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
- package/dist/electron/electron/agent/tools/x-tools.js +1 -1
- package/dist/electron/electron/gateway/index.js +1 -0
- package/dist/electron/electron/gateway/router.js +123 -143
- package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
- package/dist/electron/electron/ipc/handlers.js +627 -158
- package/dist/electron/electron/main.js +63 -0
- package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
- package/dist/electron/electron/memory/MemoryService.js +1 -1
- package/dist/electron/electron/preload.js +74 -1
- package/dist/electron/electron/settings/box-manager.js +54 -0
- package/dist/electron/electron/settings/dropbox-manager.js +54 -0
- package/dist/electron/electron/settings/google-drive-manager.js +54 -0
- package/dist/electron/electron/settings/notion-manager.js +56 -0
- package/dist/electron/electron/settings/onedrive-manager.js +54 -0
- package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
- package/dist/electron/electron/utils/box-api.js +153 -0
- package/dist/electron/electron/utils/dropbox-api.js +144 -0
- package/dist/electron/electron/utils/env-migration.js +19 -0
- package/dist/electron/electron/utils/google-drive-api.js +152 -0
- package/dist/electron/electron/utils/notion-api.js +103 -0
- package/dist/electron/electron/utils/onedrive-api.js +113 -0
- package/dist/electron/electron/utils/sharepoint-api.js +109 -0
- package/dist/electron/electron/utils/validation.js +82 -3
- package/dist/electron/electron/utils/x-cli.js +1 -1
- package/dist/electron/shared/channelMessages.js +284 -3
- package/dist/electron/shared/llm-provider-catalog.js +198 -0
- package/dist/electron/shared/types.js +88 -1
- package/package.json +12 -2
- package/src/electron/agent/executor.ts +205 -28
- package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
- package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
- package/src/electron/agent/llm/groq-provider.ts +39 -0
- package/src/electron/agent/llm/index.ts +5 -0
- package/src/electron/agent/llm/kimi-provider.ts +39 -0
- package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
- package/src/electron/agent/llm/openai-compatible.ts +133 -0
- package/src/electron/agent/llm/openai-oauth.ts +2 -1
- package/src/electron/agent/llm/openrouter-provider.ts +2 -1
- package/src/electron/agent/llm/provider-factory.ts +414 -6
- package/src/electron/agent/llm/types.ts +90 -1
- package/src/electron/agent/llm/xai-provider.ts +39 -0
- package/src/electron/agent/tools/box-tools.ts +239 -0
- package/src/electron/agent/tools/builtin-settings.ts +34 -0
- package/src/electron/agent/tools/dropbox-tools.ts +237 -0
- package/src/electron/agent/tools/google-drive-tools.ts +228 -0
- package/src/electron/agent/tools/notion-tools.ts +330 -0
- package/src/electron/agent/tools/onedrive-tools.ts +217 -0
- package/src/electron/agent/tools/registry.ts +565 -0
- package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
- package/src/electron/agent/tools/shell-tools.ts +11 -3
- package/src/electron/agent/tools/x-tools.ts +1 -1
- package/src/electron/database/SecureSettingsRepository.ts +7 -1
- package/src/electron/gateway/index.ts +1 -0
- package/src/electron/gateway/router.ts +134 -149
- package/src/electron/ipc/canvas-handlers.ts +10 -0
- package/src/electron/ipc/handlers.ts +673 -153
- package/src/electron/main.ts +35 -0
- package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
- package/src/electron/memory/MemoryService.ts +5 -1
- package/src/electron/preload.ts +167 -4
- package/src/electron/settings/box-manager.ts +58 -0
- package/src/electron/settings/dropbox-manager.ts +58 -0
- package/src/electron/settings/google-drive-manager.ts +58 -0
- package/src/electron/settings/notion-manager.ts +60 -0
- package/src/electron/settings/onedrive-manager.ts +58 -0
- package/src/electron/settings/sharepoint-manager.ts +58 -0
- package/src/electron/utils/box-api.ts +184 -0
- package/src/electron/utils/dropbox-api.ts +171 -0
- package/src/electron/utils/env-migration.ts +22 -0
- package/src/electron/utils/google-drive-api.ts +183 -0
- package/src/electron/utils/notion-api.ts +126 -0
- package/src/electron/utils/onedrive-api.ts +137 -0
- package/src/electron/utils/sharepoint-api.ts +132 -0
- package/src/electron/utils/validation.ts +102 -1
- package/src/electron/utils/x-cli.ts +1 -1
- package/src/renderer/App.tsx +20 -2
- package/src/renderer/components/BoxSettings.tsx +203 -0
- package/src/renderer/components/BrowserView.tsx +101 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
- package/src/renderer/components/CanvasPreview.tsx +68 -1
- package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
- package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
- package/src/renderer/components/ConnectorsSettings.tsx +397 -0
- package/src/renderer/components/DropboxSettings.tsx +202 -0
- package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
- package/src/renderer/components/MCPSettings.tsx +56 -0
- package/src/renderer/components/MainContent.tsx +270 -34
- package/src/renderer/components/NotionSettings.tsx +231 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
- package/src/renderer/components/OnboardingModal.tsx +70 -1
- package/src/renderer/components/OneDriveSettings.tsx +212 -0
- package/src/renderer/components/Settings.tsx +611 -8
- package/src/renderer/components/SharePointSettings.tsx +224 -0
- package/src/renderer/components/Sidebar.tsx +25 -9
- package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
- package/src/renderer/styles/index.css +438 -25
- package/src/shared/channelMessages.ts +367 -4
- package/src/shared/llm-provider-catalog.ts +217 -0
- package/src/shared/types.ts +226 -1
|
@@ -46,6 +46,7 @@ const pricing_1 = require("./llm/pricing");
|
|
|
46
46
|
const custom_skill_loader_1 = require("./custom-skill-loader");
|
|
47
47
|
const MemoryService_1 = require("../memory/MemoryService");
|
|
48
48
|
const security_1 = require("./security");
|
|
49
|
+
const builtin_settings_1 = require("./tools/builtin-settings");
|
|
49
50
|
class AwaitingUserInputError extends Error {
|
|
50
51
|
constructor(message) {
|
|
51
52
|
super(message);
|
|
@@ -56,7 +57,7 @@ class AwaitingUserInputError extends Error {
|
|
|
56
57
|
const LLM_TIMEOUT_MS = 2 * 60 * 1000;
|
|
57
58
|
// Per-step timeout (5 minutes max per step)
|
|
58
59
|
const STEP_TIMEOUT_MS = 5 * 60 * 1000;
|
|
59
|
-
//
|
|
60
|
+
// Default per-tool execution timeout (overrideable per tool)
|
|
60
61
|
const TOOL_TIMEOUT_MS = 30 * 1000;
|
|
61
62
|
// Maximum consecutive failures for the same tool before giving up
|
|
62
63
|
const MAX_TOOL_FAILURES = 2;
|
|
@@ -915,6 +916,7 @@ class TaskExecutor {
|
|
|
915
916
|
// Global turn tracking (across all steps) - similar to Claude Agent SDK's maxTurns
|
|
916
917
|
this.globalTurnCount = 0;
|
|
917
918
|
this.maxGlobalTurns = 100; // Configurable global limit
|
|
919
|
+
this.lastUserMessage = task.prompt;
|
|
918
920
|
this.requiresTestRun = this.detectTestRequirement(`${task.title}\n${task.prompt}`);
|
|
919
921
|
// Get base settings
|
|
920
922
|
const settings = llm_1.LLMProviderFactory.loadSettings();
|
|
@@ -927,7 +929,7 @@ class TaskExecutor {
|
|
|
927
929
|
// Use task's model key if specified, otherwise use global settings
|
|
928
930
|
const effectiveModelKey = taskModelKey || settings.modelKey;
|
|
929
931
|
// Get the model ID
|
|
930
|
-
this.modelId = llm_1.LLMProviderFactory.getModelId(effectiveModelKey, settings.providerType, settings.ollama?.model, settings.gemini?.model, settings.openrouter?.model, settings.openai?.model);
|
|
932
|
+
this.modelId = llm_1.LLMProviderFactory.getModelId(effectiveModelKey, settings.providerType, settings.ollama?.model, settings.gemini?.model, settings.openrouter?.model, settings.openai?.model, settings.groq?.model, settings.xai?.model, settings.kimi?.model, settings.customProviders);
|
|
931
933
|
this.modelKey = effectiveModelKey;
|
|
932
934
|
// Initialize context manager for handling long conversations
|
|
933
935
|
this.contextManager = new context_manager_1.ContextManager(effectiveModelKey);
|
|
@@ -1046,6 +1048,20 @@ class TaskExecutor {
|
|
|
1046
1048
|
this.iterationCount++;
|
|
1047
1049
|
this.globalTurnCount++; // Track global turns across all steps
|
|
1048
1050
|
}
|
|
1051
|
+
getToolTimeoutMs(toolName, input) {
|
|
1052
|
+
const settingsTimeout = builtin_settings_1.BuiltinToolsSettingsManager.getToolTimeoutMs(toolName);
|
|
1053
|
+
const normalizedSettingsTimeout = settingsTimeout && settingsTimeout > 0 ? settingsTimeout : null;
|
|
1054
|
+
if (toolName === 'run_command') {
|
|
1055
|
+
const inputTimeout = typeof input?.timeout === 'number'
|
|
1056
|
+
? input.timeout
|
|
1057
|
+
: undefined;
|
|
1058
|
+
if (typeof inputTimeout === 'number' && Number.isFinite(inputTimeout) && inputTimeout > 0) {
|
|
1059
|
+
return Math.round(inputTimeout);
|
|
1060
|
+
}
|
|
1061
|
+
return normalizedSettingsTimeout ?? TOOL_TIMEOUT_MS;
|
|
1062
|
+
}
|
|
1063
|
+
return normalizedSettingsTimeout ?? TOOL_TIMEOUT_MS;
|
|
1064
|
+
}
|
|
1049
1065
|
/**
|
|
1050
1066
|
* Check if a file operation should be blocked (redundant read or duplicate creation)
|
|
1051
1067
|
* @returns Object with blocked flag, reason, and suggestion if blocked, plus optional cached result
|
|
@@ -1266,6 +1282,33 @@ class TaskExecutor {
|
|
|
1266
1282
|
}
|
|
1267
1283
|
return { input, modified: false };
|
|
1268
1284
|
}
|
|
1285
|
+
async handleCanvasPushFallback(content, assistantText) {
|
|
1286
|
+
if (content.name !== 'canvas_push') {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
const inputContent = content.input?.content;
|
|
1290
|
+
const hasContent = typeof inputContent === 'string' && inputContent.trim().length > 0;
|
|
1291
|
+
const filename = content.input?.filename;
|
|
1292
|
+
const isHtmlTarget = !filename || filename === 'index.html';
|
|
1293
|
+
if (hasContent || !isHtmlTarget) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
const extracted = this.extractHtmlFromText(assistantText);
|
|
1297
|
+
const generated = extracted || await this.generateCanvasHtml(this.lastUserMessage || this.task.prompt);
|
|
1298
|
+
if (!generated) {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
content.input = {
|
|
1302
|
+
...(content.input || {}),
|
|
1303
|
+
content: generated,
|
|
1304
|
+
};
|
|
1305
|
+
this.daemon.logEvent(this.task.id, 'parameter_inference', {
|
|
1306
|
+
tool: content.name,
|
|
1307
|
+
inference: extracted
|
|
1308
|
+
? 'Recovered HTML from assistant text'
|
|
1309
|
+
: 'Auto-generated HTML from latest user request',
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1269
1312
|
/**
|
|
1270
1313
|
* Get available tools, filtering out disabled ones
|
|
1271
1314
|
* This prevents the LLM from trying to use tools that have been disabled by the circuit breaker
|
|
@@ -2610,12 +2653,13 @@ SCHEDULING & REMINDERS:
|
|
|
2610
2653
|
this.checkBudgets();
|
|
2611
2654
|
// Compact messages if context is getting too large
|
|
2612
2655
|
messages = this.contextManager.compactMessages(messages, systemPromptTokens);
|
|
2656
|
+
const availableTools = this.getAvailableTools();
|
|
2613
2657
|
// Use retry wrapper for resilient API calls
|
|
2614
2658
|
const response = await this.callLLMWithRetry(() => withTimeout(this.provider.createMessage({
|
|
2615
2659
|
model: this.modelId,
|
|
2616
2660
|
maxTokens: 4096,
|
|
2617
2661
|
system: this.systemPrompt,
|
|
2618
|
-
tools:
|
|
2662
|
+
tools: availableTools,
|
|
2619
2663
|
messages,
|
|
2620
2664
|
signal: this.abortController.signal,
|
|
2621
2665
|
}), LLM_TIMEOUT_MS, 'LLM execution step'), `Step execution (iteration ${iterationCount})`);
|
|
@@ -2630,6 +2674,10 @@ SCHEDULING & REMINDERS:
|
|
|
2630
2674
|
}
|
|
2631
2675
|
// Log any text responses from the assistant and check if asking a question
|
|
2632
2676
|
let assistantAskedQuestion = false;
|
|
2677
|
+
const assistantText = (response.content || [])
|
|
2678
|
+
.filter((item) => item.type === 'text' && item.text)
|
|
2679
|
+
.map((item) => item.text)
|
|
2680
|
+
.join('\n');
|
|
2633
2681
|
if (response.content) {
|
|
2634
2682
|
for (const content of response.content) {
|
|
2635
2683
|
if (content.type === 'text' && content.text) {
|
|
@@ -2674,6 +2722,8 @@ SCHEDULING & REMINDERS:
|
|
|
2674
2722
|
const toolResults = [];
|
|
2675
2723
|
let hasDisabledToolAttempt = false;
|
|
2676
2724
|
let hasDuplicateToolAttempt = false;
|
|
2725
|
+
let hasUnavailableToolAttempt = false;
|
|
2726
|
+
const availableToolNames = new Set(availableTools.map(tool => tool.name));
|
|
2677
2727
|
for (const content of response.content || []) {
|
|
2678
2728
|
if (content.type === 'tool_use') {
|
|
2679
2729
|
// Check if this tool is disabled (circuit breaker tripped)
|
|
@@ -2697,6 +2747,37 @@ SCHEDULING & REMINDERS:
|
|
|
2697
2747
|
hasDisabledToolAttempt = true;
|
|
2698
2748
|
continue;
|
|
2699
2749
|
}
|
|
2750
|
+
// Validate tool availability before attempting any inference
|
|
2751
|
+
if (!availableToolNames.has(content.name)) {
|
|
2752
|
+
console.log(`[TaskExecutor] Tool not available in this context: ${content.name}`);
|
|
2753
|
+
this.daemon.logEvent(this.task.id, 'tool_error', {
|
|
2754
|
+
tool: content.name,
|
|
2755
|
+
error: 'Tool not available in current context or permissions',
|
|
2756
|
+
blocked: true,
|
|
2757
|
+
});
|
|
2758
|
+
toolResults.push({
|
|
2759
|
+
type: 'tool_result',
|
|
2760
|
+
tool_use_id: content.id,
|
|
2761
|
+
content: JSON.stringify({
|
|
2762
|
+
error: `Tool "${content.name}" is not available in this context. Please choose a different tool or check permissions/integrations.`,
|
|
2763
|
+
unavailable: true,
|
|
2764
|
+
}),
|
|
2765
|
+
is_error: true,
|
|
2766
|
+
});
|
|
2767
|
+
hasUnavailableToolAttempt = true;
|
|
2768
|
+
continue;
|
|
2769
|
+
}
|
|
2770
|
+
// Infer missing parameters for weaker models (normalize inputs before deduplication)
|
|
2771
|
+
const inference = this.inferMissingParameters(content.name, content.input);
|
|
2772
|
+
if (inference.modified) {
|
|
2773
|
+
content.input = inference.input;
|
|
2774
|
+
this.daemon.logEvent(this.task.id, 'parameter_inference', {
|
|
2775
|
+
tool: content.name,
|
|
2776
|
+
inference: inference.inference,
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
// If canvas_push is missing content, try extracting HTML from assistant text or auto-generate
|
|
2780
|
+
await this.handleCanvasPushFallback(content, assistantText);
|
|
2700
2781
|
// Check for duplicate tool calls (prevents stuck loops)
|
|
2701
2782
|
const duplicateCheck = this.toolCallDeduplicator.checkDuplicate(content.name, content.input);
|
|
2702
2783
|
if (duplicateCheck.isDuplicate) {
|
|
@@ -2767,22 +2848,14 @@ SCHEDULING & REMINDERS:
|
|
|
2767
2848
|
}
|
|
2768
2849
|
continue;
|
|
2769
2850
|
}
|
|
2770
|
-
// Infer missing parameters for weaker models
|
|
2771
|
-
const inference = this.inferMissingParameters(content.name, content.input);
|
|
2772
|
-
if (inference.modified) {
|
|
2773
|
-
content.input = inference.input;
|
|
2774
|
-
this.daemon.logEvent(this.task.id, 'parameter_inference', {
|
|
2775
|
-
tool: content.name,
|
|
2776
|
-
inference: inference.inference,
|
|
2777
|
-
});
|
|
2778
|
-
}
|
|
2779
2851
|
this.daemon.logEvent(this.task.id, 'tool_call', {
|
|
2780
2852
|
tool: content.name,
|
|
2781
2853
|
input: content.input,
|
|
2782
2854
|
});
|
|
2783
2855
|
try {
|
|
2784
2856
|
// Execute tool with timeout to prevent hanging
|
|
2785
|
-
const
|
|
2857
|
+
const toolTimeoutMs = this.getToolTimeoutMs(content.name, content.input);
|
|
2858
|
+
const result = await withTimeout(this.toolRegistry.executeTool(content.name, content.input), toolTimeoutMs, `Tool ${content.name}`);
|
|
2786
2859
|
// Tool succeeded - reset failure counter
|
|
2787
2860
|
this.toolFailureTracker.recordSuccess(content.name);
|
|
2788
2861
|
// Record this call for deduplication
|
|
@@ -2882,7 +2955,7 @@ SCHEDULING & REMINDERS:
|
|
|
2882
2955
|
// If all tool attempts were for disabled or duplicate tools, don't continue looping
|
|
2883
2956
|
// This prevents infinite retry loops
|
|
2884
2957
|
const allToolsFailed = toolResults.every(r => r.is_error);
|
|
2885
|
-
if ((hasDisabledToolAttempt || hasDuplicateToolAttempt) && allToolsFailed) {
|
|
2958
|
+
if ((hasDisabledToolAttempt || hasDuplicateToolAttempt || hasUnavailableToolAttempt) && allToolsFailed) {
|
|
2886
2959
|
console.log('[TaskExecutor] All tool calls failed, were disabled, or duplicates - stopping iteration');
|
|
2887
2960
|
if (hasDuplicateToolAttempt) {
|
|
2888
2961
|
// Duplicate detection triggered - step is likely complete
|
|
@@ -2993,6 +3066,57 @@ SCHEDULING & REMINDERS:
|
|
|
2993
3066
|
});
|
|
2994
3067
|
}
|
|
2995
3068
|
}
|
|
3069
|
+
extractHtmlFromText(text) {
|
|
3070
|
+
if (!text)
|
|
3071
|
+
return null;
|
|
3072
|
+
const fenceMatch = text.match(/```html([\s\S]*?)```/i);
|
|
3073
|
+
const raw = fenceMatch ? fenceMatch[1].trim() : text;
|
|
3074
|
+
const doctypeIndex = raw.indexOf('<!DOCTYPE html');
|
|
3075
|
+
if (doctypeIndex >= 0) {
|
|
3076
|
+
const endIndex = raw.lastIndexOf('</html>');
|
|
3077
|
+
if (endIndex > doctypeIndex) {
|
|
3078
|
+
return raw.slice(doctypeIndex, endIndex + '</html>'.length).trim();
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
const htmlIndex = raw.indexOf('<html');
|
|
3082
|
+
if (htmlIndex >= 0) {
|
|
3083
|
+
const endIndex = raw.lastIndexOf('</html>');
|
|
3084
|
+
if (endIndex > htmlIndex) {
|
|
3085
|
+
return raw.slice(htmlIndex, endIndex + '</html>'.length).trim();
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
return null;
|
|
3089
|
+
}
|
|
3090
|
+
async generateCanvasHtml(prompt) {
|
|
3091
|
+
const system = [
|
|
3092
|
+
'You generate a single self-contained HTML document for an in-app canvas.',
|
|
3093
|
+
'Output ONLY the HTML document (no markdown, no commentary).',
|
|
3094
|
+
'Use inline CSS and JS. Do not reference external assets or remote URLs.',
|
|
3095
|
+
'Keep it reasonably compact and interactive where appropriate.',
|
|
3096
|
+
].join(' ');
|
|
3097
|
+
try {
|
|
3098
|
+
const response = await this.provider.createMessage({
|
|
3099
|
+
model: this.modelId,
|
|
3100
|
+
maxTokens: 1800,
|
|
3101
|
+
system,
|
|
3102
|
+
messages: [
|
|
3103
|
+
{
|
|
3104
|
+
role: 'user',
|
|
3105
|
+
content: `Build an interactive HTML demo for this request:\n${prompt}`,
|
|
3106
|
+
},
|
|
3107
|
+
],
|
|
3108
|
+
});
|
|
3109
|
+
const text = (response.content || [])
|
|
3110
|
+
.filter((c) => c.type === 'text')
|
|
3111
|
+
.map((c) => c.text)
|
|
3112
|
+
.join('\n');
|
|
3113
|
+
return this.extractHtmlFromText(text);
|
|
3114
|
+
}
|
|
3115
|
+
catch (error) {
|
|
3116
|
+
console.error('[TaskExecutor] Failed to auto-generate canvas HTML:', error);
|
|
3117
|
+
return null;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
2996
3120
|
/**
|
|
2997
3121
|
* Send a follow-up message to continue the conversation
|
|
2998
3122
|
*/
|
|
@@ -3003,6 +3127,7 @@ SCHEDULING & REMINDERS:
|
|
|
3003
3127
|
let resumeAttempted = false;
|
|
3004
3128
|
this.waitingForUserInput = false;
|
|
3005
3129
|
this.paused = false;
|
|
3130
|
+
this.lastUserMessage = message;
|
|
3006
3131
|
this.toolRegistry.setCanvasSessionCutoff(shouldStartNewCanvasSession ? Date.now() : null);
|
|
3007
3132
|
this.daemon.updateTaskStatus(this.task.id, 'executing');
|
|
3008
3133
|
this.daemon.logEvent(this.task.id, 'executing', { message: 'Processing follow-up message' });
|
|
@@ -3198,12 +3323,14 @@ SCHEDULING & REMINDERS:
|
|
|
3198
3323
|
this.checkBudgets();
|
|
3199
3324
|
// Compact messages if context is getting too large
|
|
3200
3325
|
messages = this.contextManager.compactMessages(messages, systemPromptTokens);
|
|
3326
|
+
const availableTools = this.getAvailableTools();
|
|
3327
|
+
const availableToolNames = new Set(availableTools.map(tool => tool.name));
|
|
3201
3328
|
// Use retry wrapper for resilient API calls
|
|
3202
3329
|
const response = await this.callLLMWithRetry(() => withTimeout(this.provider.createMessage({
|
|
3203
3330
|
model: this.modelId,
|
|
3204
3331
|
maxTokens: 4096,
|
|
3205
3332
|
system: this.systemPrompt,
|
|
3206
|
-
tools:
|
|
3333
|
+
tools: availableTools,
|
|
3207
3334
|
messages,
|
|
3208
3335
|
signal: this.abortController.signal,
|
|
3209
3336
|
}), LLM_TIMEOUT_MS, 'LLM message processing'), `Message processing (iteration ${iterationCount})`);
|
|
@@ -3216,6 +3343,10 @@ SCHEDULING & REMINDERS:
|
|
|
3216
3343
|
// Log any text responses from the assistant and check if asking a question
|
|
3217
3344
|
let assistantAskedQuestion = false;
|
|
3218
3345
|
let hasTextInThisResponse = false;
|
|
3346
|
+
const assistantText = (response.content || [])
|
|
3347
|
+
.filter((item) => item.type === 'text' && item.text)
|
|
3348
|
+
.map((item) => item.text)
|
|
3349
|
+
.join('\n');
|
|
3219
3350
|
if (response.content) {
|
|
3220
3351
|
for (const content of response.content) {
|
|
3221
3352
|
if (content.type === 'text' && content.text && content.text.trim().length > 0) {
|
|
@@ -3262,6 +3393,7 @@ SCHEDULING & REMINDERS:
|
|
|
3262
3393
|
const toolResults = [];
|
|
3263
3394
|
let hasDisabledToolAttempt = false;
|
|
3264
3395
|
let hasDuplicateToolAttempt = false;
|
|
3396
|
+
let hasUnavailableToolAttempt = false;
|
|
3265
3397
|
for (const content of response.content || []) {
|
|
3266
3398
|
if (content.type === 'tool_use') {
|
|
3267
3399
|
// Check if this tool is disabled (circuit breaker tripped)
|
|
@@ -3285,6 +3417,37 @@ SCHEDULING & REMINDERS:
|
|
|
3285
3417
|
hasDisabledToolAttempt = true;
|
|
3286
3418
|
continue;
|
|
3287
3419
|
}
|
|
3420
|
+
// Validate tool availability before attempting any inference
|
|
3421
|
+
if (!availableToolNames.has(content.name)) {
|
|
3422
|
+
console.log(`[TaskExecutor] Tool not available in this context: ${content.name}`);
|
|
3423
|
+
this.daemon.logEvent(this.task.id, 'tool_error', {
|
|
3424
|
+
tool: content.name,
|
|
3425
|
+
error: 'Tool not available in current context or permissions',
|
|
3426
|
+
blocked: true,
|
|
3427
|
+
});
|
|
3428
|
+
toolResults.push({
|
|
3429
|
+
type: 'tool_result',
|
|
3430
|
+
tool_use_id: content.id,
|
|
3431
|
+
content: JSON.stringify({
|
|
3432
|
+
error: `Tool "${content.name}" is not available in this context. Please choose a different tool or check permissions/integrations.`,
|
|
3433
|
+
unavailable: true,
|
|
3434
|
+
}),
|
|
3435
|
+
is_error: true,
|
|
3436
|
+
});
|
|
3437
|
+
hasUnavailableToolAttempt = true;
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
// Infer missing parameters for weaker models (normalize inputs before deduplication)
|
|
3441
|
+
const inference = this.inferMissingParameters(content.name, content.input);
|
|
3442
|
+
if (inference.modified) {
|
|
3443
|
+
content.input = inference.input;
|
|
3444
|
+
this.daemon.logEvent(this.task.id, 'parameter_inference', {
|
|
3445
|
+
tool: content.name,
|
|
3446
|
+
inference: inference.inference,
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
// If canvas_push is missing content, try extracting HTML from assistant text or auto-generate
|
|
3450
|
+
await this.handleCanvasPushFallback(content, assistantText);
|
|
3288
3451
|
// Check for duplicate tool calls (prevents stuck loops)
|
|
3289
3452
|
const duplicateCheck = this.toolCallDeduplicator.checkDuplicate(content.name, content.input);
|
|
3290
3453
|
if (duplicateCheck.isDuplicate) {
|
|
@@ -3353,22 +3516,14 @@ SCHEDULING & REMINDERS:
|
|
|
3353
3516
|
}
|
|
3354
3517
|
continue;
|
|
3355
3518
|
}
|
|
3356
|
-
// Infer missing parameters for weaker models
|
|
3357
|
-
const inference = this.inferMissingParameters(content.name, content.input);
|
|
3358
|
-
if (inference.modified) {
|
|
3359
|
-
content.input = inference.input;
|
|
3360
|
-
this.daemon.logEvent(this.task.id, 'parameter_inference', {
|
|
3361
|
-
tool: content.name,
|
|
3362
|
-
inference: inference.inference,
|
|
3363
|
-
});
|
|
3364
|
-
}
|
|
3365
3519
|
this.daemon.logEvent(this.task.id, 'tool_call', {
|
|
3366
3520
|
tool: content.name,
|
|
3367
3521
|
input: content.input,
|
|
3368
3522
|
});
|
|
3369
3523
|
try {
|
|
3370
3524
|
// Execute tool with timeout to prevent hanging
|
|
3371
|
-
const
|
|
3525
|
+
const toolTimeoutMs = this.getToolTimeoutMs(content.name, content.input);
|
|
3526
|
+
const result = await withTimeout(this.toolRegistry.executeTool(content.name, content.input), toolTimeoutMs, `Tool ${content.name}`);
|
|
3372
3527
|
// Tool succeeded - reset failure counter
|
|
3373
3528
|
this.toolFailureTracker.recordSuccess(content.name);
|
|
3374
3529
|
// Record this call for deduplication
|
|
@@ -3430,7 +3585,7 @@ SCHEDULING & REMINDERS:
|
|
|
3430
3585
|
});
|
|
3431
3586
|
// If all tool attempts were for disabled or duplicate tools, don't continue looping
|
|
3432
3587
|
const allToolsFailed = toolResults.every(r => r.is_error);
|
|
3433
|
-
if ((hasDisabledToolAttempt || hasDuplicateToolAttempt) && allToolsFailed) {
|
|
3588
|
+
if ((hasDisabledToolAttempt || hasDuplicateToolAttempt || hasUnavailableToolAttempt) && allToolsFailed) {
|
|
3434
3589
|
console.log('[TaskExecutor] All tool calls failed, were disabled, or duplicates - stopping iteration');
|
|
3435
3590
|
continueLoop = false;
|
|
3436
3591
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AnthropicCompatibleProvider = void 0;
|
|
4
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
5
|
+
function joinUrl(baseUrl, path) {
|
|
6
|
+
const trimmedBase = baseUrl.replace(/\/+$/, '');
|
|
7
|
+
const trimmedPath = path.startsWith('/') ? path : `/${path}`;
|
|
8
|
+
return `${trimmedBase}${trimmedPath}`;
|
|
9
|
+
}
|
|
10
|
+
class AnthropicCompatibleProvider {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.type = options.type;
|
|
13
|
+
this.apiKey = options.apiKey;
|
|
14
|
+
this.baseUrl = options.baseUrl;
|
|
15
|
+
this.defaultModel = options.defaultModel;
|
|
16
|
+
this.providerName = options.providerName;
|
|
17
|
+
}
|
|
18
|
+
async createMessage(request) {
|
|
19
|
+
const messages = this.convertMessages(request.messages);
|
|
20
|
+
const tools = request.tools ? this.convertTools(request.tools) : undefined;
|
|
21
|
+
const model = request.model || this.defaultModel;
|
|
22
|
+
try {
|
|
23
|
+
console.log(`[${this.providerName}] Calling API with model: ${model}`);
|
|
24
|
+
const response = await fetch(joinUrl(this.baseUrl, '/messages'), {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'x-api-key': this.apiKey,
|
|
29
|
+
'anthropic-version': ANTHROPIC_VERSION,
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
model,
|
|
33
|
+
max_tokens: request.maxTokens,
|
|
34
|
+
system: request.system,
|
|
35
|
+
messages,
|
|
36
|
+
...(tools && { tools }),
|
|
37
|
+
}),
|
|
38
|
+
signal: request.signal,
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
const errorData = await response.json().catch(() => ({}));
|
|
42
|
+
throw new Error(`${this.providerName} API error: ${response.status} ${response.statusText}` +
|
|
43
|
+
(errorData.error?.message ? ` - ${errorData.error.message}` : ''));
|
|
44
|
+
}
|
|
45
|
+
const data = await response.json();
|
|
46
|
+
return this.convertResponse(data);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error.name === 'AbortError' || error.message?.includes('aborted')) {
|
|
50
|
+
console.log(`[${this.providerName}] Request aborted`);
|
|
51
|
+
throw new Error('Request cancelled');
|
|
52
|
+
}
|
|
53
|
+
console.error(`[${this.providerName}] API error:`, {
|
|
54
|
+
message: error.message,
|
|
55
|
+
status: error.status,
|
|
56
|
+
});
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async testConnection() {
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(joinUrl(this.baseUrl, '/messages'), {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
'x-api-key': this.apiKey,
|
|
67
|
+
'anthropic-version': ANTHROPIC_VERSION,
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
model: this.defaultModel,
|
|
71
|
+
max_tokens: 10,
|
|
72
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const errorData = await response.json().catch(() => ({}));
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return { success: true };
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
error: error.message || `Failed to connect to ${this.providerName} API`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
convertMessages(messages) {
|
|
92
|
+
return messages.map((msg) => {
|
|
93
|
+
if (typeof msg.content === 'string') {
|
|
94
|
+
return {
|
|
95
|
+
role: msg.role,
|
|
96
|
+
content: msg.content,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const content = msg.content.map((item) => {
|
|
100
|
+
if (item.type === 'tool_result') {
|
|
101
|
+
return {
|
|
102
|
+
type: 'tool_result',
|
|
103
|
+
tool_use_id: item.tool_use_id,
|
|
104
|
+
content: item.content,
|
|
105
|
+
...(item.is_error && { is_error: true }),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (item.type === 'tool_use') {
|
|
109
|
+
return {
|
|
110
|
+
type: 'tool_use',
|
|
111
|
+
id: item.id,
|
|
112
|
+
name: item.name,
|
|
113
|
+
input: item.input,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
type: 'text',
|
|
118
|
+
text: item.text,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
role: msg.role,
|
|
123
|
+
content,
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
convertTools(tools) {
|
|
128
|
+
return tools.map((tool) => ({
|
|
129
|
+
name: tool.name,
|
|
130
|
+
description: tool.description,
|
|
131
|
+
input_schema: tool.input_schema,
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
convertResponse(response) {
|
|
135
|
+
const content = (response.content || [])
|
|
136
|
+
.filter((block) => block.type === 'text' || block.type === 'tool_use')
|
|
137
|
+
.map((block) => {
|
|
138
|
+
if (block.type === 'tool_use') {
|
|
139
|
+
return {
|
|
140
|
+
type: 'tool_use',
|
|
141
|
+
id: block.id,
|
|
142
|
+
name: block.name,
|
|
143
|
+
input: block.input,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
type: 'text',
|
|
148
|
+
text: block.text || '',
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
content: content.length > 0 ? content : [{ type: 'text', text: '' }],
|
|
153
|
+
stopReason: this.mapStopReason(response.stop_reason),
|
|
154
|
+
usage: response.usage
|
|
155
|
+
? {
|
|
156
|
+
inputTokens: response.usage.input_tokens || 0,
|
|
157
|
+
outputTokens: response.usage.output_tokens || 0,
|
|
158
|
+
}
|
|
159
|
+
: undefined,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
mapStopReason(reason) {
|
|
163
|
+
switch (reason) {
|
|
164
|
+
case 'end_turn':
|
|
165
|
+
return 'end_turn';
|
|
166
|
+
case 'tool_use':
|
|
167
|
+
return 'tool_use';
|
|
168
|
+
case 'max_tokens':
|
|
169
|
+
return 'max_tokens';
|
|
170
|
+
case 'stop_sequence':
|
|
171
|
+
return 'stop_sequence';
|
|
172
|
+
default:
|
|
173
|
+
return 'end_turn';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.AnthropicCompatibleProvider = AnthropicCompatibleProvider;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GitHubCopilotProvider = void 0;
|
|
4
|
+
const openai_compatible_provider_1 = require("./openai-compatible-provider");
|
|
5
|
+
const COPILOT_TOKEN_URL = 'https://api.github.com/copilot_internal/v2/token';
|
|
6
|
+
const DEFAULT_COPILOT_BASE_URL = 'https://api.individual.githubcopilot.com';
|
|
7
|
+
function isTokenValid(cache, now = Date.now()) {
|
|
8
|
+
return cache.expiresAt - now > 5 * 60 * 1000;
|
|
9
|
+
}
|
|
10
|
+
function parseCopilotTokenResponse(payload) {
|
|
11
|
+
if (!payload || typeof payload !== 'object') {
|
|
12
|
+
throw new Error('Unexpected response from Copilot token endpoint');
|
|
13
|
+
}
|
|
14
|
+
const token = payload.token;
|
|
15
|
+
const expiresAt = payload.expires_at;
|
|
16
|
+
if (typeof token !== 'string' || !token.trim()) {
|
|
17
|
+
throw new Error('Copilot token response missing token');
|
|
18
|
+
}
|
|
19
|
+
if (typeof expiresAt === 'number' && Number.isFinite(expiresAt)) {
|
|
20
|
+
return { token, expiresAt: expiresAt > 10000000000 ? expiresAt : expiresAt * 1000 };
|
|
21
|
+
}
|
|
22
|
+
if (typeof expiresAt === 'string' && expiresAt.trim()) {
|
|
23
|
+
const parsed = Number.parseInt(expiresAt, 10);
|
|
24
|
+
if (!Number.isFinite(parsed)) {
|
|
25
|
+
throw new Error('Copilot token response has invalid expires_at');
|
|
26
|
+
}
|
|
27
|
+
return { token, expiresAt: parsed > 10000000000 ? parsed : parsed * 1000 };
|
|
28
|
+
}
|
|
29
|
+
throw new Error('Copilot token response missing expires_at');
|
|
30
|
+
}
|
|
31
|
+
function deriveCopilotBaseUrl(token) {
|
|
32
|
+
const match = token.match(/(?:^|;)\s*proxy-ep=([^;\s]+)/i);
|
|
33
|
+
const proxyEp = match?.[1]?.trim();
|
|
34
|
+
if (!proxyEp)
|
|
35
|
+
return DEFAULT_COPILOT_BASE_URL;
|
|
36
|
+
const host = proxyEp.replace(/^https?:\/\//, '').replace(/^proxy\./i, 'api.');
|
|
37
|
+
return host ? `https://${host}` : DEFAULT_COPILOT_BASE_URL;
|
|
38
|
+
}
|
|
39
|
+
class GitHubCopilotProvider {
|
|
40
|
+
constructor(config) {
|
|
41
|
+
this.type = 'github-copilot';
|
|
42
|
+
const token = config.providerApiKey;
|
|
43
|
+
if (!token) {
|
|
44
|
+
throw new Error('GitHub token is required for Copilot. Configure it in Settings.');
|
|
45
|
+
}
|
|
46
|
+
this.githubToken = token;
|
|
47
|
+
this.model = config.model || 'gpt-4o';
|
|
48
|
+
}
|
|
49
|
+
async createMessage(request) {
|
|
50
|
+
const client = await this.getClient(request.model || this.model);
|
|
51
|
+
return client.createMessage(request);
|
|
52
|
+
}
|
|
53
|
+
async testConnection() {
|
|
54
|
+
try {
|
|
55
|
+
const client = await this.getClient(this.model);
|
|
56
|
+
return await client.testConnection();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return { success: false, error: error.message || 'Failed to connect to Copilot' };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async getClient(model) {
|
|
63
|
+
const auth = await this.getCopilotAuth();
|
|
64
|
+
return new openai_compatible_provider_1.OpenAICompatibleProvider({
|
|
65
|
+
type: 'github-copilot',
|
|
66
|
+
providerName: 'GitHub Copilot',
|
|
67
|
+
apiKey: auth.token,
|
|
68
|
+
baseUrl: auth.baseUrl,
|
|
69
|
+
defaultModel: model,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async getCopilotAuth() {
|
|
73
|
+
if (GitHubCopilotProvider.cache && isTokenValid(GitHubCopilotProvider.cache)) {
|
|
74
|
+
return GitHubCopilotProvider.cache;
|
|
75
|
+
}
|
|
76
|
+
const response = await fetch(COPILOT_TOKEN_URL, {
|
|
77
|
+
method: 'GET',
|
|
78
|
+
headers: {
|
|
79
|
+
Accept: 'application/json',
|
|
80
|
+
Authorization: `Bearer ${this.githubToken}`,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new Error(`Copilot token exchange failed: HTTP ${response.status}`);
|
|
85
|
+
}
|
|
86
|
+
const json = await response.json();
|
|
87
|
+
const parsed = parseCopilotTokenResponse(json);
|
|
88
|
+
const cache = {
|
|
89
|
+
token: parsed.token,
|
|
90
|
+
expiresAt: parsed.expiresAt,
|
|
91
|
+
baseUrl: deriveCopilotBaseUrl(parsed.token),
|
|
92
|
+
};
|
|
93
|
+
GitHubCopilotProvider.cache = cache;
|
|
94
|
+
return cache;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.GitHubCopilotProvider = GitHubCopilotProvider;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GroqProvider = void 0;
|
|
4
|
+
const openai_compatible_provider_1 = require("./openai-compatible-provider");
|
|
5
|
+
const GROQ_BASE_URL = 'https://api.groq.com/openai/v1';
|
|
6
|
+
const DEFAULT_GROQ_MODEL = 'llama-3.1-8b-instant';
|
|
7
|
+
class GroqProvider {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.type = 'groq';
|
|
10
|
+
const apiKey = config.groqApiKey;
|
|
11
|
+
if (!apiKey) {
|
|
12
|
+
throw new Error('Groq API key is required. Configure it in Settings.');
|
|
13
|
+
}
|
|
14
|
+
const baseUrl = config.groqBaseUrl || GROQ_BASE_URL;
|
|
15
|
+
this.client = new openai_compatible_provider_1.OpenAICompatibleProvider({
|
|
16
|
+
type: 'groq',
|
|
17
|
+
providerName: 'Groq',
|
|
18
|
+
apiKey,
|
|
19
|
+
baseUrl,
|
|
20
|
+
defaultModel: config.model || DEFAULT_GROQ_MODEL,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
createMessage(request) {
|
|
24
|
+
return this.client.createMessage(request);
|
|
25
|
+
}
|
|
26
|
+
testConnection() {
|
|
27
|
+
return this.client.testConnection();
|
|
28
|
+
}
|
|
29
|
+
getAvailableModels() {
|
|
30
|
+
return this.client.getAvailableModels();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.GroqProvider = GroqProvider;
|