neoagent 3.2.1-beta.1 → 3.2.1-beta.11
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/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 +157 -24
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +7 -2
- package/flutter_app/lib/main_operations.dart +334 -370
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +342 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +97 -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 +115 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +2 -2
- 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 +12 -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 +81930 -80509
- package/server/routes/admin.js +1 -1
- package/server/routes/android.js +30 -34
- package/server/routes/behavior.js +80 -0
- 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 +20 -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 +452 -176
- 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 +291 -8
- package/server/services/ai/loop/conversation_loop.js +515 -342
- package/server/services/ai/loop/messaging_delivery.js +176 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +20 -45
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +19 -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 +2 -2
- 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 +4 -3
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +111 -84
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +39 -5
- package/server/services/ai/providers/openaiCodex.js +11 -4
- package/server/services/ai/providers/openrouter.js +29 -7
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/systemPrompt.js +32 -123
- package/server/services/ai/taskAnalysis.js +104 -11
- package/server/services/ai/toolEvidence.js +256 -29
- package/server/services/ai/tools.js +248 -117
- 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/behavior/config.js +251 -0
- package/server/services/behavior/defaults.js +68 -0
- package/server/services/behavior/delivery.js +176 -0
- package/server/services/behavior/index.js +43 -0
- package/server/services/behavior/model_client.js +35 -0
- package/server/services/behavior/modules/agent_identity.js +37 -0
- package/server/services/behavior/modules/channel_style.js +28 -0
- package/server/services/behavior/modules/index.js +29 -0
- package/server/services/behavior/modules/norms.js +101 -0
- package/server/services/behavior/modules/persona.js +48 -0
- package/server/services/behavior/modules/persona_prompt.js +33 -0
- package/server/services/behavior/modules/social_memory.js +86 -0
- package/server/services/behavior/modules/social_observability.js +94 -0
- package/server/services/behavior/modules/social_signals.js +41 -0
- package/server/services/behavior/modules/theory_of_mind.js +110 -0
- package/server/services/behavior/modules/turn_taking.js +237 -0
- package/server/services/behavior/pipeline.js +285 -0
- package/server/services/behavior/registry.js +78 -0
- package/server/services/behavior/signals.js +107 -0
- package/server/services/behavior/state.js +99 -0
- package/server/services/behavior/system_prompt.js +75 -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 +66 -52
- package/server/services/messaging/access_policy.js +10 -6
- package/server/services/messaging/automation.js +240 -34
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +37 -16
- package/server/services/messaging/inbound_queue.js +143 -28
- package/server/services/messaging/inbound_store.js +257 -0
- package/server/services/messaging/manager.js +344 -51
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +33 -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 +13 -8
- 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/captions.js +2 -2
- package/server/services/social_video/service.js +343 -68
- 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/logger.js +19 -0
- package/server/utils/retry.js +107 -0
|
@@ -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
|
}
|
|
@@ -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, { signal: options.signal });
|
|
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, { signal: options.signal });
|
|
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
|
|
|
@@ -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
|
}
|