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
|
@@ -1,5 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { GoogleGenAI } = require('@google/genai');
|
|
2
4
|
const { BaseProvider } = require('./base');
|
|
5
|
+
const { fetchResponseText } = require('../../network/http');
|
|
6
|
+
|
|
7
|
+
function parseToolArguments(value) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(value || '{}');
|
|
10
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
11
|
+
? parsed
|
|
12
|
+
: {};
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeUsage(usage) {
|
|
19
|
+
if (!usage) return null;
|
|
20
|
+
return {
|
|
21
|
+
inputTokens: usage.promptTokenCount || 0,
|
|
22
|
+
outputTokens: usage.candidatesTokenCount || 0,
|
|
23
|
+
reasoningTokens: usage.thoughtsTokenCount || 0,
|
|
24
|
+
cachedReadTokens: usage.cachedContentTokenCount || 0,
|
|
25
|
+
cacheWriteTokens: 0,
|
|
26
|
+
promptTokens: usage.promptTokenCount || 0,
|
|
27
|
+
completionTokens: usage.candidatesTokenCount || 0,
|
|
28
|
+
totalTokens: usage.totalTokenCount || 0
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function collectResponseParts(response, toolCalls, seenToolCalls = null) {
|
|
33
|
+
let content = '';
|
|
34
|
+
for (const candidate of response?.candidates || []) {
|
|
35
|
+
for (const part of candidate.content?.parts || []) {
|
|
36
|
+
if (part.text && part.thought !== true) content += part.text;
|
|
37
|
+
if (!part.functionCall?.name) continue;
|
|
38
|
+
|
|
39
|
+
const args = part.functionCall.args || {};
|
|
40
|
+
const providerCallId = String(part.functionCall.id || '').trim();
|
|
41
|
+
const signature = providerCallId
|
|
42
|
+
|| `${part.functionCall.name}:${JSON.stringify(args)}`;
|
|
43
|
+
if (seenToolCalls?.has(signature)) continue;
|
|
44
|
+
seenToolCalls?.add(signature);
|
|
45
|
+
|
|
46
|
+
toolCalls.push({
|
|
47
|
+
id: providerCallId
|
|
48
|
+
|| `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
49
|
+
type: 'function',
|
|
50
|
+
function: {
|
|
51
|
+
name: part.functionCall.name,
|
|
52
|
+
arguments: JSON.stringify(args),
|
|
53
|
+
thought_signature: part.thoughtSignature
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return content;
|
|
59
|
+
}
|
|
3
60
|
|
|
4
61
|
class GoogleProvider extends BaseProvider {
|
|
5
62
|
constructor(config = {}) {
|
|
@@ -28,16 +85,33 @@ class GoogleProvider extends BaseProvider {
|
|
|
28
85
|
'gemini-1.5-flash': 1048576,
|
|
29
86
|
};
|
|
30
87
|
this.apiKey = config.apiKey || process.env.GOOGLE_AI_KEY;
|
|
31
|
-
this.genAI = new
|
|
88
|
+
this.genAI = new GoogleGenAI({ apiKey: this.apiKey });
|
|
32
89
|
}
|
|
33
90
|
|
|
34
|
-
async listModels() {
|
|
91
|
+
async listModels(signal = null) {
|
|
35
92
|
const DROP = /tts|lyria|robotics|deep-research|antigravity|computer-use|-image(?!.*it)/i;
|
|
36
|
-
const
|
|
37
|
-
|
|
93
|
+
const { response, text } = await fetchResponseText(
|
|
94
|
+
'https://generativelanguage.googleapis.com/v1beta/models?pageSize=200',
|
|
95
|
+
{
|
|
96
|
+
headers: { 'x-goog-api-key': this.apiKey },
|
|
97
|
+
maxResponseBytes: 5 * 1024 * 1024,
|
|
98
|
+
serviceName: 'Google model catalog',
|
|
99
|
+
signal,
|
|
100
|
+
},
|
|
38
101
|
);
|
|
39
|
-
if (!
|
|
40
|
-
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
const error = new Error(`Google models API returned ${response.status}`);
|
|
104
|
+
error.status = response.status;
|
|
105
|
+
error.headers = response.headers;
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
let payload;
|
|
109
|
+
try {
|
|
110
|
+
payload = JSON.parse(text || '{}');
|
|
111
|
+
} catch {
|
|
112
|
+
throw new Error('Google models API returned invalid JSON.');
|
|
113
|
+
}
|
|
114
|
+
const { models = [] } = payload;
|
|
41
115
|
return models
|
|
42
116
|
.filter((m) => {
|
|
43
117
|
const id = m.name.replace('models/', '');
|
|
@@ -60,11 +134,23 @@ class GoogleProvider extends BaseProvider {
|
|
|
60
134
|
functionDeclarations: tools.map(tool => ({
|
|
61
135
|
name: tool.name,
|
|
62
136
|
description: tool.description,
|
|
63
|
-
|
|
137
|
+
parametersJsonSchema: tool.parameters || { type: 'object', properties: {} }
|
|
64
138
|
}))
|
|
65
139
|
}];
|
|
66
140
|
}
|
|
67
141
|
|
|
142
|
+
buildGenerateConfig(systemInstruction, tools, options) {
|
|
143
|
+
const config = {};
|
|
144
|
+
if (systemInstruction) config.systemInstruction = systemInstruction;
|
|
145
|
+
if (tools.length > 0) config.tools = this.formatTools(tools);
|
|
146
|
+
if (options.signal) config.abortSignal = options.signal;
|
|
147
|
+
const maxOutputTokens = Number(options.maxTokens);
|
|
148
|
+
if (Number.isFinite(maxOutputTokens) && maxOutputTokens > 0) {
|
|
149
|
+
config.maxOutputTokens = Math.floor(maxOutputTokens);
|
|
150
|
+
}
|
|
151
|
+
return config;
|
|
152
|
+
}
|
|
153
|
+
|
|
68
154
|
convertMessages(messages) {
|
|
69
155
|
let systemInstruction = '';
|
|
70
156
|
const history = [];
|
|
@@ -75,14 +161,16 @@ class GoogleProvider extends BaseProvider {
|
|
|
75
161
|
continue;
|
|
76
162
|
}
|
|
77
163
|
if (msg.role === 'tool') {
|
|
164
|
+
const functionResponse = {
|
|
165
|
+
name: msg.name || 'tool',
|
|
166
|
+
response: { result: msg.content }
|
|
167
|
+
};
|
|
168
|
+
if (msg.tool_call_id) functionResponse.id = msg.tool_call_id;
|
|
78
169
|
history.push({
|
|
79
|
-
|
|
80
|
-
parts
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
response: { result: msg.content }
|
|
84
|
-
}
|
|
85
|
-
}]
|
|
170
|
+
// The current Gemini API represents tool results as functionResponse
|
|
171
|
+
// parts on a user turn.
|
|
172
|
+
role: 'user',
|
|
173
|
+
parts: [{ functionResponse }]
|
|
86
174
|
});
|
|
87
175
|
continue;
|
|
88
176
|
}
|
|
@@ -93,9 +181,10 @@ class GoogleProvider extends BaseProvider {
|
|
|
93
181
|
const functionCallPart = {
|
|
94
182
|
functionCall: {
|
|
95
183
|
name: tc.function.name,
|
|
96
|
-
args:
|
|
184
|
+
args: parseToolArguments(tc.function.arguments)
|
|
97
185
|
}
|
|
98
186
|
};
|
|
187
|
+
if (tc.id) functionCallPart.functionCall.id = tc.id;
|
|
99
188
|
if (tc.function.thought_signature) {
|
|
100
189
|
functionCallPart.thoughtSignature = tc.function.thought_signature;
|
|
101
190
|
}
|
|
@@ -129,6 +218,18 @@ class GoogleProvider extends BaseProvider {
|
|
|
129
218
|
normalizedHistory.push({ role: currentRole, parts: currentParts });
|
|
130
219
|
}
|
|
131
220
|
|
|
221
|
+
// Structured helpers can consist entirely of system instructions. Gemini
|
|
222
|
+
// still requires a user turn to trigger generation.
|
|
223
|
+
if (
|
|
224
|
+
normalizedHistory.length === 0
|
|
225
|
+
|| normalizedHistory[normalizedHistory.length - 1].role === 'model'
|
|
226
|
+
) {
|
|
227
|
+
normalizedHistory.push({
|
|
228
|
+
role: 'user',
|
|
229
|
+
parts: [{ text: 'Provide the requested response using the system instructions.' }]
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
132
233
|
if (normalizedHistory.length > 0 && normalizedHistory[0].role !== 'user') {
|
|
133
234
|
normalizedHistory.unshift({
|
|
134
235
|
role: 'user',
|
|
@@ -142,53 +243,18 @@ class GoogleProvider extends BaseProvider {
|
|
|
142
243
|
async chat(messages, tools = [], options = {}) {
|
|
143
244
|
const model = options.model || this.config.model || this.getDefaultModel();
|
|
144
245
|
const { systemInstruction, history } = this.convertMessages(messages);
|
|
145
|
-
|
|
146
|
-
const genModel = this.genAI.getGenerativeModel({
|
|
246
|
+
const response = await this.genAI.models.generateContent({
|
|
147
247
|
model,
|
|
148
|
-
|
|
149
|
-
|
|
248
|
+
contents: history,
|
|
249
|
+
config: this.buildGenerateConfig(systemInstruction, tools, options)
|
|
150
250
|
});
|
|
151
|
-
|
|
152
|
-
const lastMessage = history.pop();
|
|
153
|
-
const chat = genModel.startChat({ history });
|
|
154
|
-
const result = await chat.sendMessage(lastMessage.parts);
|
|
155
|
-
const response = result.response;
|
|
156
|
-
|
|
157
|
-
let content = '';
|
|
158
251
|
const toolCalls = [];
|
|
159
|
-
|
|
160
|
-
for (const candidate of response.candidates || []) {
|
|
161
|
-
for (const part of candidate.content?.parts || []) {
|
|
162
|
-
if (part.text) content += part.text;
|
|
163
|
-
if (part.functionCall) {
|
|
164
|
-
toolCalls.push({
|
|
165
|
-
id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
166
|
-
type: 'function',
|
|
167
|
-
function: {
|
|
168
|
-
name: part.functionCall.name,
|
|
169
|
-
arguments: JSON.stringify(part.functionCall.args || {}),
|
|
170
|
-
thought_signature: part.thoughtSignature
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const usage = response.usageMetadata;
|
|
252
|
+
const content = collectResponseParts(response, toolCalls);
|
|
178
253
|
return {
|
|
179
254
|
content,
|
|
180
255
|
toolCalls,
|
|
181
256
|
finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
|
|
182
|
-
usage:
|
|
183
|
-
inputTokens: usage.promptTokenCount || 0,
|
|
184
|
-
outputTokens: usage.candidatesTokenCount || 0,
|
|
185
|
-
reasoningTokens: usage.thoughtsTokenCount || 0,
|
|
186
|
-
cachedReadTokens: usage.cachedContentTokenCount || 0,
|
|
187
|
-
cacheWriteTokens: 0,
|
|
188
|
-
promptTokens: usage.promptTokenCount || 0,
|
|
189
|
-
completionTokens: usage.candidatesTokenCount || 0,
|
|
190
|
-
totalTokens: usage.totalTokenCount || 0
|
|
191
|
-
} : null,
|
|
257
|
+
usage: normalizeUsage(response.usageMetadata),
|
|
192
258
|
model
|
|
193
259
|
};
|
|
194
260
|
}
|
|
@@ -196,60 +262,32 @@ class GoogleProvider extends BaseProvider {
|
|
|
196
262
|
async *stream(messages, tools = [], options = {}) {
|
|
197
263
|
const model = options.model || this.config.model || this.getDefaultModel();
|
|
198
264
|
const { systemInstruction, history } = this.convertMessages(messages);
|
|
199
|
-
|
|
200
|
-
const genModel = this.genAI.getGenerativeModel({
|
|
265
|
+
const responseStream = await this.genAI.models.generateContentStream({
|
|
201
266
|
model,
|
|
202
|
-
|
|
203
|
-
|
|
267
|
+
contents: history,
|
|
268
|
+
config: this.buildGenerateConfig(systemInstruction, tools, options)
|
|
204
269
|
});
|
|
205
|
-
|
|
206
|
-
const lastMessage = history.pop();
|
|
207
|
-
const chat = genModel.startChat({ history });
|
|
208
|
-
const result = await chat.sendMessageStream(lastMessage.parts);
|
|
209
|
-
|
|
210
270
|
let content = '';
|
|
211
271
|
const toolCalls = [];
|
|
272
|
+
const seenToolCalls = new Set();
|
|
273
|
+
let usage = null;
|
|
212
274
|
|
|
213
|
-
for await (const chunk of
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
yield { type: 'content', content: part.text };
|
|
219
|
-
}
|
|
220
|
-
if (part.functionCall) {
|
|
221
|
-
toolCalls.push({
|
|
222
|
-
id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
223
|
-
type: 'function',
|
|
224
|
-
function: {
|
|
225
|
-
name: part.functionCall.name,
|
|
226
|
-
arguments: JSON.stringify(part.functionCall.args || {}),
|
|
227
|
-
thought_signature: part.thoughtSignature
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
275
|
+
for await (const chunk of responseStream) {
|
|
276
|
+
const chunkContent = collectResponseParts(chunk, toolCalls, seenToolCalls);
|
|
277
|
+
if (chunkContent) {
|
|
278
|
+
content += chunkContent;
|
|
279
|
+
yield { type: 'content', content: chunkContent };
|
|
232
280
|
}
|
|
281
|
+
if (chunk.usageMetadata) usage = chunk.usageMetadata;
|
|
233
282
|
}
|
|
234
283
|
|
|
235
|
-
const finalResponse = await result.response;
|
|
236
|
-
const usage = finalResponse.usageMetadata;
|
|
237
|
-
|
|
238
284
|
yield {
|
|
239
285
|
type: 'done',
|
|
240
286
|
content,
|
|
241
287
|
toolCalls,
|
|
242
288
|
finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
|
|
243
|
-
usage: usage
|
|
244
|
-
|
|
245
|
-
outputTokens: usage.candidatesTokenCount || 0,
|
|
246
|
-
reasoningTokens: usage.thoughtsTokenCount || 0,
|
|
247
|
-
cachedReadTokens: usage.cachedContentTokenCount || 0,
|
|
248
|
-
cacheWriteTokens: 0,
|
|
249
|
-
promptTokens: usage.promptTokenCount || 0,
|
|
250
|
-
completionTokens: usage.candidatesTokenCount || 0,
|
|
251
|
-
totalTokens: usage.totalTokenCount || 0
|
|
252
|
-
} : null
|
|
289
|
+
usage: normalizeUsage(usage),
|
|
290
|
+
model
|
|
253
291
|
};
|
|
254
292
|
}
|
|
255
293
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const OpenAI = require('openai');
|
|
2
2
|
const { OpenAICompatibleProvider } = require('./openaiCompatible');
|
|
3
|
+
const { wrapProviderError } = require('./provider_error');
|
|
3
4
|
|
|
4
5
|
class GrokProvider extends OpenAICompatibleProvider {
|
|
5
6
|
constructor(config = {}) {
|
|
@@ -11,15 +12,15 @@ class GrokProvider extends OpenAICompatibleProvider {
|
|
|
11
12
|
});
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
async listModels() {
|
|
15
|
+
async listModels(signal = null) {
|
|
15
16
|
try {
|
|
16
|
-
const res = await this.client.models.list();
|
|
17
|
+
const res = await this.client.models.list({ signal });
|
|
17
18
|
const DROP = /imagine|diffus|embed|-tts/i;
|
|
18
19
|
return res.data
|
|
19
20
|
.filter((m) => !DROP.test(m.id))
|
|
20
21
|
.map((m) => ({ id: m.id, name: m.id }));
|
|
21
22
|
} catch (err) {
|
|
22
|
-
throw
|
|
23
|
+
throw wrapProviderError(err, 'Failed to list Grok models', { signal });
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -61,7 +62,7 @@ class GrokProvider extends OpenAICompatibleProvider {
|
|
|
61
62
|
const model = options.model || 'grok-4-1-fast-reasoning';
|
|
62
63
|
const params = this._buildParams(model, messages, tools, options);
|
|
63
64
|
|
|
64
|
-
const response = await this.client.chat.completions.create(params);
|
|
65
|
+
const response = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
65
66
|
return this.normalizeResponse(response);
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -73,7 +74,7 @@ class GrokProvider extends OpenAICompatibleProvider {
|
|
|
73
74
|
stream_options: { include_usage: true }
|
|
74
75
|
};
|
|
75
76
|
|
|
76
|
-
const stream = await this.client.chat.completions.create(params);
|
|
77
|
+
const stream = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
77
78
|
|
|
78
79
|
let toolCalls = [];
|
|
79
80
|
let content = '';
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
1
|
const OpenAI = require('openai');
|
|
2
|
+
const { ENV_FILE, upsertEnvValue } = require('../../../../runtime/paths');
|
|
3
|
+
const { fetchResponseText } = require('../../network/http');
|
|
4
4
|
const { GrokProvider } = require('./grok');
|
|
5
5
|
|
|
6
6
|
const GROK_OAUTH_BASE_URL = 'https://api.x.ai/v1';
|
|
7
7
|
const GROK_OAUTH_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828';
|
|
8
8
|
const GROK_OAUTH_TOKEN_URL = 'https://auth.x.ai/oauth2/token';
|
|
9
9
|
const GROK_OAUTH_SCOPES = 'openid profile email offline_access grok-cli:access api:access';
|
|
10
|
+
const OAUTH_REFRESH_TIMEOUT_MS = 30000;
|
|
11
|
+
const OAUTH_MAX_RESPONSE_BYTES = 256 * 1024;
|
|
10
12
|
|
|
11
13
|
function normalizeExpiresAt(data) {
|
|
12
14
|
if (typeof data.expires_at === 'number' && Number.isFinite(data.expires_at)) {
|
|
@@ -21,29 +23,14 @@ function normalizeExpiresAt(data) {
|
|
|
21
23
|
function persistEnvValue(key, value) {
|
|
22
24
|
if (!value) return;
|
|
23
25
|
try {
|
|
24
|
-
|
|
25
|
-
const safeKey = String(key).replace(/[\r\n]/g, '');
|
|
26
|
-
const safeValue = String(value).replace(/[\r\n]/g, '');
|
|
27
|
-
const raw = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, 'utf8') : '';
|
|
28
|
-
const lines = raw ? raw.split('\n') : [];
|
|
29
|
-
let replaced = false;
|
|
30
|
-
for (let i = 0; i < lines.length; i++) {
|
|
31
|
-
if (lines[i].startsWith(`${safeKey}=`)) {
|
|
32
|
-
lines[i] = `${safeKey}=${safeValue}`;
|
|
33
|
-
replaced = true;
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
if (!replaced) lines.push(`${safeKey}=${safeValue}`);
|
|
38
|
-
const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n';
|
|
39
|
-
fs.mkdirSync(path.dirname(ENV_FILE), { recursive: true });
|
|
40
|
-
fs.writeFileSync(ENV_FILE, output, { mode: 0o600 });
|
|
26
|
+
upsertEnvValue(ENV_FILE, key, value);
|
|
41
27
|
} catch { }
|
|
42
28
|
}
|
|
43
29
|
|
|
44
|
-
async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch) {
|
|
30
|
+
async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch, signal = null) {
|
|
45
31
|
if (!refreshToken) return null;
|
|
46
|
-
const response = await
|
|
32
|
+
const { response, text } = await fetchResponseText(GROK_OAUTH_TOKEN_URL, {
|
|
33
|
+
fetchImpl,
|
|
47
34
|
method: 'POST',
|
|
48
35
|
headers: {
|
|
49
36
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
@@ -54,9 +41,14 @@ async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch) {
|
|
|
54
41
|
refresh_token: refreshToken,
|
|
55
42
|
client_id: GROK_OAUTH_CLIENT_ID,
|
|
56
43
|
}),
|
|
44
|
+
signal,
|
|
45
|
+
timeoutMs: OAUTH_REFRESH_TIMEOUT_MS,
|
|
46
|
+
maxResponseBytes: OAUTH_MAX_RESPONSE_BYTES,
|
|
47
|
+
serviceName: 'Grok OAuth refresh',
|
|
48
|
+
timeoutCode: 'PROVIDER_OAUTH_TIMEOUT',
|
|
49
|
+
tooLargeCode: 'PROVIDER_OAUTH_RESPONSE_TOO_LARGE',
|
|
57
50
|
});
|
|
58
51
|
|
|
59
|
-
const text = await response.text();
|
|
60
52
|
let data = {};
|
|
61
53
|
try {
|
|
62
54
|
data = text ? JSON.parse(text) : {};
|
|
@@ -68,7 +60,7 @@ async function refreshGrokOAuthAccessToken(refreshToken, fetchImpl = fetch) {
|
|
|
68
60
|
if (data?.error === 'invalid_grant') {
|
|
69
61
|
throw new Error('Grok OAuth refresh token is invalid or expired. Re-run `neoagent login grok-oauth` to re-authenticate.');
|
|
70
62
|
}
|
|
71
|
-
const detail = data?.error_description || data?.error || text || 'Unknown error';
|
|
63
|
+
const detail = String(data?.error_description || data?.error || text || 'Unknown error').slice(0, 2000);
|
|
72
64
|
throw new Error(`Grok OAuth refresh failed: HTTP ${response.status} ${detail}`);
|
|
73
65
|
}
|
|
74
66
|
if (!data.access_token) {
|
|
@@ -102,8 +94,8 @@ class GrokOAuthProvider extends GrokProvider {
|
|
|
102
94
|
this.fetchImpl = config.fetch || fetch;
|
|
103
95
|
}
|
|
104
96
|
|
|
105
|
-
async refreshClient() {
|
|
106
|
-
const refreshed = await refreshGrokOAuthAccessToken(this.refreshToken, this.fetchImpl);
|
|
97
|
+
async refreshClient(signal = null) {
|
|
98
|
+
const refreshed = await refreshGrokOAuthAccessToken(this.refreshToken, this.fetchImpl, signal);
|
|
107
99
|
if (!refreshed?.access) return false;
|
|
108
100
|
this.authToken = refreshed.access;
|
|
109
101
|
this.refreshToken = refreshed.refresh || this.refreshToken;
|
|
@@ -122,7 +114,7 @@ class GrokOAuthProvider extends GrokProvider {
|
|
|
122
114
|
return await super.chat(messages, tools, options);
|
|
123
115
|
} catch (err) {
|
|
124
116
|
if (err?.status !== 401 || !this.refreshToken) throw err;
|
|
125
|
-
await this.refreshClient();
|
|
117
|
+
await this.refreshClient(options.signal);
|
|
126
118
|
return await super.chat(messages, tools, options);
|
|
127
119
|
}
|
|
128
120
|
}
|
|
@@ -132,7 +124,7 @@ class GrokOAuthProvider extends GrokProvider {
|
|
|
132
124
|
yield* super.stream(messages, tools, options);
|
|
133
125
|
} catch (err) {
|
|
134
126
|
if (err?.status !== 401 || !this.refreshToken) throw err;
|
|
135
|
-
await this.refreshClient();
|
|
127
|
+
await this.refreshClient(options.signal);
|
|
136
128
|
yield* super.stream(messages, tools, options);
|
|
137
129
|
}
|
|
138
130
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const OpenAI = require('openai');
|
|
2
2
|
const { OpenAICompatibleProvider } = require('./openaiCompatible');
|
|
3
|
+
const { wrapProviderError } = require('./provider_error');
|
|
3
4
|
|
|
4
5
|
const NVIDIA_BASE_URL = 'https://integrate.api.nvidia.com/v1';
|
|
5
6
|
|
|
@@ -32,15 +33,15 @@ class NvidiaProvider extends OpenAICompatibleProvider {
|
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
async listModels() {
|
|
36
|
+
async listModels(signal = null) {
|
|
36
37
|
try {
|
|
37
|
-
const res = await this.client.models.list();
|
|
38
|
+
const res = await this.client.models.list({ signal });
|
|
38
39
|
const DROP = /embed|bge|e5-|rerank|guard|safety|moderat|diffus|flux|stable|imagen|vision-enc|whisper|tts|speech|paraphrase|classif/i;
|
|
39
40
|
return res.data
|
|
40
41
|
.filter((m) => !DROP.test(m.id))
|
|
41
42
|
.map((m) => ({ id: m.id, name: m.id }));
|
|
42
43
|
} catch (err) {
|
|
43
|
-
throw
|
|
44
|
+
throw wrapProviderError(err, 'NVIDIA NIM request failed', { signal });
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -76,9 +77,11 @@ class NvidiaProvider extends OpenAICompatibleProvider {
|
|
|
76
77
|
const params = this._buildParams(model, messages, tools, options);
|
|
77
78
|
let response;
|
|
78
79
|
try {
|
|
79
|
-
response = await this.client.chat.completions.create(params);
|
|
80
|
+
response = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
80
81
|
} catch (err) {
|
|
81
|
-
throw
|
|
82
|
+
throw wrapProviderError(err, 'NVIDIA NIM request failed', {
|
|
83
|
+
signal: options.signal,
|
|
84
|
+
});
|
|
82
85
|
}
|
|
83
86
|
return this.normalizeResponse(response);
|
|
84
87
|
}
|
|
@@ -93,9 +96,11 @@ class NvidiaProvider extends OpenAICompatibleProvider {
|
|
|
93
96
|
|
|
94
97
|
let stream;
|
|
95
98
|
try {
|
|
96
|
-
stream = await this.client.chat.completions.create(params);
|
|
99
|
+
stream = await this.client.chat.completions.create(params, { signal: options.signal });
|
|
97
100
|
} catch (err) {
|
|
98
|
-
throw
|
|
101
|
+
throw wrapProviderError(err, 'NVIDIA NIM request failed', {
|
|
102
|
+
signal: options.signal,
|
|
103
|
+
});
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
let toolCalls = [];
|