neoagent 3.2.1-beta.0 → 3.2.1-beta.10
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/docs/agent-run-lifecycle.md +10 -0
- package/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +654 -90
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +111 -20
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +3 -0
- package/flutter_app/lib/main_operations.dart +334 -321
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +4 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +78 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
- package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
- package/flutter_app/windows/runner/flutter_window.cpp +143 -32
- package/landing/index.html +3 -1
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +145 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +4 -4
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +85 -40
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +11 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +73083 -72209
- package/server/routes/admin.js +1 -1
- package/server/routes/agents.js +35 -2
- package/server/routes/android.js +30 -34
- package/server/routes/browser.js +23 -15
- package/server/routes/desktop.js +18 -1
- package/server/routes/integrations.js +107 -1
- package/server/routes/memory.js +1 -0
- package/server/routes/settings.js +16 -5
- package/server/routes/social_reach.js +12 -3
- package/server/routes/social_video.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/capabilityHealth.js +62 -96
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +45 -6
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +496 -166
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +121 -5
- package/server/services/ai/loop/conversation_loop.js +620 -340
- package/server/services/ai/loop/lifecycle.js +108 -0
- package/server/services/ai/loop/messaging_delivery.js +129 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +48 -56
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +28 -8
- package/server/services/ai/loopPolicy.js +48 -21
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_failure_cache.js +108 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +68 -163
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +166 -0
- package/server/services/ai/providers/anthropic.js +4 -4
- package/server/services/ai/providers/claudeCode.js +21 -33
- package/server/services/ai/providers/githubCopilot.js +41 -20
- package/server/services/ai/providers/google.js +135 -97
- package/server/services/ai/providers/grok.js +6 -5
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +12 -7
- package/server/services/ai/providers/ollama.js +114 -86
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +41 -7
- package/server/services/ai/providers/openaiCodex.js +13 -4
- package/server/services/ai/providers/openrouter.js +31 -9
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/systemPrompt.js +19 -12
- package/server/services/ai/taskAnalysis.js +58 -10
- package/server/services/ai/terminal_reply.js +18 -0
- package/server/services/ai/toolEvidence.js +350 -29
- package/server/services/ai/tools.js +190 -111
- package/server/services/android/controller.js +770 -237
- package/server/services/android/process.js +140 -0
- package/server/services/android/sdk_download.js +143 -0
- package/server/services/android/uia.js +6 -5
- package/server/services/artifacts/store.js +24 -0
- package/server/services/browser/controller.js +843 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +15 -1
- package/server/services/browser/extension/provider.js +71 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- package/server/services/desktop/gateway.js +41 -4
- package/server/services/desktop/protocol.js +3 -0
- package/server/services/desktop/provider.js +39 -42
- package/server/services/desktop/registry.js +137 -52
- package/server/services/integrations/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- package/server/services/integrations/figma/provider.js +78 -12
- package/server/services/integrations/github/common.js +11 -6
- package/server/services/integrations/github/provider.js +52 -53
- package/server/services/integrations/google/provider.js +55 -19
- package/server/services/integrations/home_assistant/network.js +17 -20
- package/server/services/integrations/home_assistant/provider.js +7 -5
- package/server/services/integrations/home_assistant/tools.js +17 -5
- package/server/services/integrations/http.js +51 -0
- package/server/services/integrations/manager.js +159 -53
- package/server/services/integrations/microsoft/provider.js +80 -13
- package/server/services/integrations/neoarchive/provider.js +55 -29
- package/server/services/integrations/neorecall/client.js +17 -10
- package/server/services/integrations/neorecall/provider.js +20 -11
- package/server/services/integrations/notion/provider.js +16 -13
- package/server/services/integrations/oauth_provider.js +115 -51
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/slack/provider.js +98 -9
- package/server/services/integrations/spotify/provider.js +67 -71
- package/server/services/integrations/trello/provider.js +21 -7
- package/server/services/integrations/weather/provider.js +18 -12
- package/server/services/integrations/whatsapp/provider.js +76 -16
- package/server/services/manager.js +110 -1
- package/server/services/memory/embedding_index.js +20 -8
- package/server/services/memory/embeddings.js +151 -90
- package/server/services/memory/ingestion.js +50 -9
- package/server/services/memory/ingestion_documents.js +13 -3
- package/server/services/memory/manager.js +52 -19
- package/server/services/messaging/automation.js +85 -10
- package/server/services/messaging/formatting_guides.js +7 -4
- package/server/services/messaging/http_platforms.js +33 -13
- package/server/services/messaging/inbound_queue.js +78 -24
- package/server/services/messaging/inbound_store.js +224 -0
- package/server/services/messaging/manager.js +326 -51
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +22 -14
- package/server/services/network/http.js +210 -0
- package/server/services/network/safe_request.js +307 -0
- package/server/services/runtime/backends/local-vm.js +227 -67
- package/server/services/runtime/docker-vm-manager.js +9 -0
- package/server/services/runtime/guest_bootstrap.js +30 -4
- package/server/services/runtime/guest_image.js +43 -12
- package/server/services/runtime/manager.js +77 -23
- package/server/services/runtime/validation.js +7 -6
- package/server/services/security/tool_categories.js +6 -0
- package/server/services/social_reach/channels/github.js +10 -4
- package/server/services/social_reach/channels/reddit.js +4 -4
- package/server/services/social_reach/channels/rss.js +2 -2
- package/server/services/social_reach/channels/social_video.js +12 -7
- package/server/services/social_reach/channels/v2ex.js +21 -8
- package/server/services/social_reach/channels/x.js +2 -2
- package/server/services/social_reach/channels/xueqiu.js +5 -5
- package/server/services/social_reach/service.js +9 -6
- package/server/services/social_reach/utils.js +65 -14
- package/server/services/social_video/service.js +160 -50
- package/server/services/tasks/integration_runtime.js +18 -8
- package/server/services/tasks/runtime.js +39 -4
- package/server/services/voice/agentBridge.js +17 -4
- package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
- package/server/services/voice/liveSession.js +31 -0
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtime.js +2 -2
- package/server/services/voice/runtimeManager.js +118 -20
- package/server/services/voice/turnRunner.js +6 -0
- package/server/services/wearable/firmware_manifest.js +51 -13
- package/server/services/wearable/service.js +1 -0
- package/server/utils/abort.js +96 -0
- package/server/utils/cloud-security.js +110 -3
- package/server/utils/files.js +31 -0
- package/server/utils/image_payload.js +95 -0
- package/server/utils/retry.js +107 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getAiSettings } = require('./settings');
|
|
4
|
+
const { createProviderInstance, getSupportedModels } = require('./models');
|
|
5
|
+
const {
|
|
6
|
+
getRawModelId,
|
|
7
|
+
normalizeModelSelections,
|
|
8
|
+
resolveModelSelection,
|
|
9
|
+
} = require('./model_identity');
|
|
10
|
+
const { isModelCoolingDown } = require('./model_failure_cache');
|
|
11
|
+
|
|
12
|
+
function buildSelection(model, userId, providerConfig) {
|
|
13
|
+
return {
|
|
14
|
+
provider: createProviderInstance(model.provider, userId, providerConfig),
|
|
15
|
+
model: getRawModelId(model),
|
|
16
|
+
modelSelectionId: model.id,
|
|
17
|
+
providerName: model.provider,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function getProviderForUser(
|
|
22
|
+
userId,
|
|
23
|
+
_task = '',
|
|
24
|
+
isSubagent = false,
|
|
25
|
+
modelOverride = null,
|
|
26
|
+
providerConfig = {},
|
|
27
|
+
) {
|
|
28
|
+
const agentId = providerConfig.agentId || null;
|
|
29
|
+
const aiSettings = getAiSettings(userId, agentId);
|
|
30
|
+
const models = await getSupportedModels(userId, agentId, {
|
|
31
|
+
signal: providerConfig.signal,
|
|
32
|
+
});
|
|
33
|
+
const selectableModels = models.filter((model) => model.available !== false);
|
|
34
|
+
const readyModels = selectableModels.filter(
|
|
35
|
+
(model) => !isModelCoolingDown(userId, agentId, model.id),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (modelOverride && typeof modelOverride === 'string') {
|
|
39
|
+
const requested = resolveModelSelection(selectableModels, modelOverride);
|
|
40
|
+
if (!requested) {
|
|
41
|
+
throw new Error(`Requested model '${modelOverride.trim()}' is not available.`);
|
|
42
|
+
}
|
|
43
|
+
if (isModelCoolingDown(userId, agentId, requested.id) && readyModels.length > 0) {
|
|
44
|
+
const configuredFallback = resolveModelSelection(
|
|
45
|
+
readyModels,
|
|
46
|
+
aiSettings.fallback_model_id,
|
|
47
|
+
);
|
|
48
|
+
const fallback = configuredFallback
|
|
49
|
+
|| readyModels.find((model) => model.provider !== requested.provider)
|
|
50
|
+
|| readyModels[0];
|
|
51
|
+
providerConfig.onStatus?.({
|
|
52
|
+
phase: 'model_fallback',
|
|
53
|
+
message: `Skipping recently unavailable model ${requested.id}; using ${fallback.id}.`,
|
|
54
|
+
});
|
|
55
|
+
return buildSelection(fallback, userId, providerConfig);
|
|
56
|
+
}
|
|
57
|
+
return buildSelection(requested, userId, providerConfig);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const configuredEnabledIds = Array.isArray(aiSettings.enabled_models)
|
|
61
|
+
? aiSettings.enabled_models.map((id) => String(id).trim()).filter(Boolean)
|
|
62
|
+
: [];
|
|
63
|
+
const enabledIds = normalizeModelSelections(selectableModels, configuredEnabledIds);
|
|
64
|
+
let availableModels = configuredEnabledIds.length > 0
|
|
65
|
+
? readyModels.filter((model) => enabledIds.includes(model.id))
|
|
66
|
+
: readyModels;
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
availableModels.length === 0
|
|
70
|
+
&& configuredEnabledIds.length > 0
|
|
71
|
+
&& enabledIds.length > 0
|
|
72
|
+
) {
|
|
73
|
+
const configuredFallback = resolveModelSelection(
|
|
74
|
+
readyModels,
|
|
75
|
+
aiSettings.fallback_model_id,
|
|
76
|
+
);
|
|
77
|
+
if (configuredFallback) {
|
|
78
|
+
providerConfig.onStatus?.({
|
|
79
|
+
phase: 'model_fallback',
|
|
80
|
+
message: `Enabled models are temporarily unavailable; using ${configuredFallback.id}.`,
|
|
81
|
+
});
|
|
82
|
+
availableModels = [configuredFallback];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If every configured model is cooling down and there is no alternative,
|
|
87
|
+
// allow one retry instead of making the installation permanently unavailable.
|
|
88
|
+
if (availableModels.length === 0 && readyModels.length === 0) {
|
|
89
|
+
availableModels = configuredEnabledIds.length > 0
|
|
90
|
+
? selectableModels.filter((model) => enabledIds.includes(model.id))
|
|
91
|
+
: selectableModels;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (availableModels.length === 0) {
|
|
95
|
+
const message = configuredEnabledIds.length > 0
|
|
96
|
+
? 'None of the enabled AI models are currently available. Check model and provider settings.'
|
|
97
|
+
: 'No AI providers are currently available. Open Settings and configure at least one provider.';
|
|
98
|
+
throw new Error(message);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const fallbackModel = availableModels[0];
|
|
102
|
+
const userSelectedDefault = isSubagent
|
|
103
|
+
? (aiSettings.default_subagent_model || 'auto')
|
|
104
|
+
: (aiSettings.default_chat_model || 'auto');
|
|
105
|
+
const smarterSelection = aiSettings.smarter_model_selector !== false
|
|
106
|
+
&& aiSettings.smarter_model_selector !== 'false';
|
|
107
|
+
|
|
108
|
+
let selectedModel = fallbackModel;
|
|
109
|
+
if (userSelectedDefault !== 'auto') {
|
|
110
|
+
selectedModel = resolveModelSelection(availableModels, userSelectedDefault) || fallbackModel;
|
|
111
|
+
const configuredDefault = resolveModelSelection(selectableModels, userSelectedDefault);
|
|
112
|
+
if (
|
|
113
|
+
configuredDefault
|
|
114
|
+
&& configuredDefault.id !== selectedModel.id
|
|
115
|
+
&& isModelCoolingDown(userId, agentId, configuredDefault.id)
|
|
116
|
+
) {
|
|
117
|
+
providerConfig.onStatus?.({
|
|
118
|
+
phase: 'model_fallback',
|
|
119
|
+
message: `Skipping recently unavailable model ${configuredDefault.id}; using ${selectedModel.id}.`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
const selectionHint = providerConfig.selectionHint && typeof providerConfig.selectionHint === 'object'
|
|
124
|
+
? providerConfig.selectionHint
|
|
125
|
+
: {};
|
|
126
|
+
const preferredPurpose = String(selectionHint.purpose || '').trim().toLowerCase();
|
|
127
|
+
const highAutonomy = selectionHint.autonomyLevel === 'high'
|
|
128
|
+
|| selectionHint.complexity === 'complex';
|
|
129
|
+
const requiredConfidence = String(selectionHint.requiredConfidence || '').trim().toLowerCase();
|
|
130
|
+
const costMode = String(selectionHint.costMode || aiSettings.cost_mode || 'balanced_auto')
|
|
131
|
+
.trim()
|
|
132
|
+
.toLowerCase();
|
|
133
|
+
const requestedPurpose = ['planning', 'coding', 'general', 'fast'].includes(preferredPurpose)
|
|
134
|
+
? preferredPurpose
|
|
135
|
+
: '';
|
|
136
|
+
const priceRank = { free: 0, cheap: 1, medium: 2, expensive: 3 };
|
|
137
|
+
const chooseForPurpose = (purpose) => {
|
|
138
|
+
const candidates = availableModels.filter((model) => model.purpose === purpose);
|
|
139
|
+
if (candidates.length === 0) return null;
|
|
140
|
+
if (['economy', 'cost_saver', 'lowest_cost'].includes(costMode)) {
|
|
141
|
+
return [...candidates].sort((left, right) => (
|
|
142
|
+
(priceRank[left.priceTier] ?? 99) - (priceRank[right.priceTier] ?? 99)
|
|
143
|
+
))[0];
|
|
144
|
+
}
|
|
145
|
+
if (['quality', 'highest_quality'].includes(costMode) || requiredConfidence === 'high') {
|
|
146
|
+
return candidates.find((model) => model.priceTier !== 'free' && model.priceTier !== 'cheap')
|
|
147
|
+
|| candidates[0];
|
|
148
|
+
}
|
|
149
|
+
return candidates[0];
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (smarterSelection && requestedPurpose) {
|
|
153
|
+
selectedModel = chooseForPurpose(requestedPurpose) || fallbackModel;
|
|
154
|
+
} else if (smarterSelection && highAutonomy) {
|
|
155
|
+
selectedModel = chooseForPurpose('planning') || chooseForPurpose('general') || fallbackModel;
|
|
156
|
+
} else if (isSubagent) {
|
|
157
|
+
selectedModel = chooseForPurpose('fast') || fallbackModel;
|
|
158
|
+
} else {
|
|
159
|
+
selectedModel = chooseForPurpose('general') || fallbackModel;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return buildSelection(selectedModel, userId, providerConfig);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = { getProviderForUser };
|
|
@@ -31,8 +31,8 @@ class AnthropicProvider extends BaseProvider {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
async listModels() {
|
|
35
|
-
const res = await this.client.models.list({ limit: 100 });
|
|
34
|
+
async listModels(signal = null) {
|
|
35
|
+
const res = await this.client.models.list({ limit: 100 }, { signal });
|
|
36
36
|
return (res.data || []).map((m) => ({ id: m.id, name: m.display_name || m.id }));
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -169,7 +169,7 @@ class AnthropicProvider extends BaseProvider {
|
|
|
169
169
|
|
|
170
170
|
if (system.length > 0) params.system = system;
|
|
171
171
|
if (tools.length > 0) params.tools = this.formatTools(tools);
|
|
172
|
-
const response = await this.client.messages.create(params);
|
|
172
|
+
const response = await this.client.messages.create(params, { signal: options.signal });
|
|
173
173
|
const responseBlocks = Array.isArray(response?.content)
|
|
174
174
|
? response.content
|
|
175
175
|
: (response?.content && typeof response.content === 'object' ? [response.content] : []);
|
|
@@ -225,7 +225,7 @@ class AnthropicProvider extends BaseProvider {
|
|
|
225
225
|
|
|
226
226
|
if (system.length > 0) params.system = system;
|
|
227
227
|
if (tools.length > 0) params.tools = this.formatTools(tools);
|
|
228
|
-
const stream = await this.client.messages.stream(params);
|
|
228
|
+
const stream = await this.client.messages.stream(params, { signal: options.signal });
|
|
229
229
|
|
|
230
230
|
let content = '';
|
|
231
231
|
let currentToolCalls = [];
|
|
@@ -2,6 +2,8 @@ const os = require('os');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const Anthropic = require('@anthropic-ai/sdk');
|
|
5
|
+
const { ENV_FILE, upsertEnvValue } = require('../../../../runtime/paths');
|
|
6
|
+
const { fetchResponseText } = require('../../network/http');
|
|
5
7
|
const { AnthropicProvider } = require('./anthropic');
|
|
6
8
|
|
|
7
9
|
const CLAUDE_CLI_CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
@@ -12,6 +14,8 @@ const CLAUDE_CODE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI
|
|
|
12
14
|
const CLAUDE_CODE_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
|
13
15
|
const CLAUDE_CODE_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
|
|
14
16
|
const CLAUDE_CODE_SCOPES = 'user:inference user:profile org:create_api_key user:sessions:claude_code user:mcp_servers';
|
|
17
|
+
const OAUTH_REFRESH_TIMEOUT_MS = 30000;
|
|
18
|
+
const OAUTH_MAX_RESPONSE_BYTES = 256 * 1024;
|
|
15
19
|
|
|
16
20
|
function readTokenRecord(data) {
|
|
17
21
|
const tokens = data?.claudeAiOauthTokens || data?.claudeAiOauth || {};
|
|
@@ -71,40 +75,17 @@ function normalizeExpiresAt(data) {
|
|
|
71
75
|
return null;
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
function sanitizeEnvKey(key) {
|
|
75
|
-
return String(key).replace(/[\r\n]/g, '');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function sanitizeEnvValue(value) {
|
|
79
|
-
return String(value).replace(/[\r\n]/g, '');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
78
|
function persistEnvValue(key, value) {
|
|
83
79
|
if (!value) return;
|
|
84
80
|
try {
|
|
85
|
-
|
|
86
|
-
const safeKey = sanitizeEnvKey(key);
|
|
87
|
-
const safeValue = sanitizeEnvValue(value);
|
|
88
|
-
const raw = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, 'utf8') : '';
|
|
89
|
-
const lines = raw ? raw.split('\n') : [];
|
|
90
|
-
let replaced = false;
|
|
91
|
-
for (let i = 0; i < lines.length; i++) {
|
|
92
|
-
if (lines[i].startsWith(`${safeKey}=`)) {
|
|
93
|
-
lines[i] = `${safeKey}=${safeValue}`;
|
|
94
|
-
replaced = true;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (!replaced) lines.push(`${safeKey}=${safeValue}`);
|
|
99
|
-
const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n';
|
|
100
|
-
fs.mkdirSync(path.dirname(ENV_FILE), { recursive: true });
|
|
101
|
-
fs.writeFileSync(ENV_FILE, output, { mode: 0o600 });
|
|
81
|
+
upsertEnvValue(ENV_FILE, key, value);
|
|
102
82
|
} catch { }
|
|
103
83
|
}
|
|
104
84
|
|
|
105
|
-
async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch) {
|
|
85
|
+
async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch, signal = null) {
|
|
106
86
|
if (!refreshToken) return null;
|
|
107
|
-
const response = await
|
|
87
|
+
const { response, text } = await fetchResponseText(CLAUDE_CODE_TOKEN_URL, {
|
|
88
|
+
fetchImpl,
|
|
108
89
|
method: 'POST',
|
|
109
90
|
headers: {
|
|
110
91
|
'Content-Type': 'application/json',
|
|
@@ -116,9 +97,14 @@ async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch) {
|
|
|
116
97
|
refresh_token: refreshToken,
|
|
117
98
|
client_id: CLAUDE_CODE_CLIENT_ID,
|
|
118
99
|
}),
|
|
100
|
+
signal,
|
|
101
|
+
timeoutMs: OAUTH_REFRESH_TIMEOUT_MS,
|
|
102
|
+
maxResponseBytes: OAUTH_MAX_RESPONSE_BYTES,
|
|
103
|
+
serviceName: 'Claude Code OAuth refresh',
|
|
104
|
+
timeoutCode: 'PROVIDER_OAUTH_TIMEOUT',
|
|
105
|
+
tooLargeCode: 'PROVIDER_OAUTH_RESPONSE_TOO_LARGE',
|
|
119
106
|
});
|
|
120
107
|
|
|
121
|
-
const text = await response.text();
|
|
122
108
|
let data = {};
|
|
123
109
|
try {
|
|
124
110
|
data = text ? JSON.parse(text) : {};
|
|
@@ -127,7 +113,9 @@ async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch) {
|
|
|
127
113
|
}
|
|
128
114
|
|
|
129
115
|
if (!response.ok) {
|
|
130
|
-
const detail =
|
|
116
|
+
const detail = String(
|
|
117
|
+
data?.error?.message || data?.error_description || data?.error || text || 'Unknown error',
|
|
118
|
+
).slice(0, 2000);
|
|
131
119
|
throw new Error(`Claude Code OAuth refresh failed: HTTP ${response.status} ${detail}`);
|
|
132
120
|
}
|
|
133
121
|
if (!data.access_token) {
|
|
@@ -209,8 +197,8 @@ class ClaudeCodeProvider extends AnthropicProvider {
|
|
|
209
197
|
});
|
|
210
198
|
}
|
|
211
199
|
|
|
212
|
-
async refreshClient() {
|
|
213
|
-
const refreshed = await refreshClaudeCodeAccessToken(this.refreshToken, this.fetchImpl);
|
|
200
|
+
async refreshClient(signal = null) {
|
|
201
|
+
const refreshed = await refreshClaudeCodeAccessToken(this.refreshToken, this.fetchImpl, signal);
|
|
214
202
|
if (!refreshed?.access) return false;
|
|
215
203
|
this.authToken = refreshed.access;
|
|
216
204
|
this.refreshToken = refreshed.refresh || this.refreshToken;
|
|
@@ -244,7 +232,7 @@ class ClaudeCodeProvider extends AnthropicProvider {
|
|
|
244
232
|
if ((!isAuthenticationError(err) && !isInferenceScopeError(err)) || !this.refreshToken) {
|
|
245
233
|
throw formatClaudeCodeCredentialError(err);
|
|
246
234
|
}
|
|
247
|
-
await this.refreshClient();
|
|
235
|
+
await this.refreshClient(options.signal);
|
|
248
236
|
try {
|
|
249
237
|
return await super.chat(messages, tools, options);
|
|
250
238
|
} catch (retryErr) {
|
|
@@ -260,7 +248,7 @@ class ClaudeCodeProvider extends AnthropicProvider {
|
|
|
260
248
|
if ((!isAuthenticationError(err) && !isInferenceScopeError(err)) || !this.refreshToken) {
|
|
261
249
|
throw formatClaudeCodeCredentialError(err);
|
|
262
250
|
}
|
|
263
|
-
await this.refreshClient();
|
|
251
|
+
await this.refreshClient(options.signal);
|
|
264
252
|
try {
|
|
265
253
|
yield* super.stream(messages, tools, options);
|
|
266
254
|
} catch (retryErr) {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
const { OpenAIProvider } = require('./openai');
|
|
2
|
+
const {
|
|
3
|
+
fetchResponseText,
|
|
4
|
+
waitForAbortableResult,
|
|
5
|
+
} = require('../../network/http');
|
|
6
|
+
const { sanitizeProviderErrorDetail } = require('./provider_error');
|
|
2
7
|
|
|
3
8
|
class GithubCopilotProvider extends OpenAIProvider {
|
|
4
9
|
constructor(config = {}) {
|
|
@@ -26,35 +31,47 @@ class GithubCopilotProvider extends OpenAIProvider {
|
|
|
26
31
|
this._refreshPromise = null;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
async _refreshCopilotToken() {
|
|
30
|
-
if (this._refreshPromise) return this._refreshPromise;
|
|
31
|
-
|
|
34
|
+
async _refreshCopilotToken(signal = null) {
|
|
32
35
|
const now = Math.floor(Date.now() / 1000);
|
|
33
36
|
// Refresh token if missing or expiring in less than 5 minutes
|
|
34
37
|
if (this.copilotToken && this.tokenExpiresAt >= now + 300) {
|
|
35
38
|
return;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
this._refreshPromise
|
|
39
|
-
|
|
41
|
+
if (!this._refreshPromise) {
|
|
42
|
+
this._refreshPromise = (async () => {
|
|
40
43
|
if (!this.githubToken) {
|
|
41
44
|
throw new Error('GitHub Copilot access token is missing. Please run `neoagent login github-copilot`.');
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
const
|
|
47
|
+
const { response, text } = await fetchResponseText(
|
|
48
|
+
'https://api.github.com/copilot_internal/v2/token',
|
|
49
|
+
{
|
|
45
50
|
headers: {
|
|
46
51
|
'Authorization': `token ${this.githubToken}`,
|
|
47
52
|
'Accept': 'application/json',
|
|
48
53
|
'User-Agent': 'NeoAgent/1.0.0'
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
},
|
|
55
|
+
maxResponseBytes: 1024 * 1024,
|
|
56
|
+
serviceName: 'GitHub Copilot token refresh',
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const error = new Error(
|
|
62
|
+
`Failed to refresh GitHub Copilot token: HTTP ${response.status} - ${sanitizeProviderErrorDetail(text.slice(0, 500))}`,
|
|
63
|
+
);
|
|
64
|
+
error.status = response.status;
|
|
65
|
+
error.headers = response.headers;
|
|
66
|
+
throw error;
|
|
55
67
|
}
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
let data;
|
|
70
|
+
try {
|
|
71
|
+
data = JSON.parse(text || '{}');
|
|
72
|
+
} catch {
|
|
73
|
+
throw new Error('Invalid token response from GitHub Copilot.');
|
|
74
|
+
}
|
|
58
75
|
if (!data || typeof data.token !== 'string' || !data.token) {
|
|
59
76
|
throw new Error('Invalid token response from GitHub Copilot.');
|
|
60
77
|
}
|
|
@@ -70,26 +87,30 @@ class GithubCopilotProvider extends OpenAIProvider {
|
|
|
70
87
|
|
|
71
88
|
// Update the client's API key
|
|
72
89
|
this.client.apiKey = this.copilotToken;
|
|
73
|
-
}
|
|
90
|
+
})().finally(() => {
|
|
74
91
|
this._refreshPromise = null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
77
94
|
|
|
78
|
-
return
|
|
95
|
+
return waitForAbortableResult(
|
|
96
|
+
this._refreshPromise,
|
|
97
|
+
signal,
|
|
98
|
+
'GitHub Copilot token refresh aborted.',
|
|
99
|
+
);
|
|
79
100
|
}
|
|
80
101
|
|
|
81
102
|
async chat(messages, tools = [], options = {}) {
|
|
82
|
-
await this._refreshCopilotToken();
|
|
103
|
+
await this._refreshCopilotToken(options.signal);
|
|
83
104
|
return super.chat(messages, tools, options);
|
|
84
105
|
}
|
|
85
106
|
|
|
86
107
|
async *stream(messages, tools = [], options = {}) {
|
|
87
|
-
await this._refreshCopilotToken();
|
|
108
|
+
await this._refreshCopilotToken(options.signal);
|
|
88
109
|
yield* super.stream(messages, tools, options);
|
|
89
110
|
}
|
|
90
111
|
|
|
91
112
|
async analyzeImage(options = {}) {
|
|
92
|
-
await this._refreshCopilotToken();
|
|
113
|
+
await this._refreshCopilotToken(options.signal);
|
|
93
114
|
return super.analyzeImage(options);
|
|
94
115
|
}
|
|
95
116
|
}
|