neoagent 2.3.1-beta.111 → 2.3.1-beta.114

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.111",
3
+ "version": "2.3.1-beta.114",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -1 +1 @@
1
- 9ad3e5040ffa79037fb5bea961ce891c
1
+ d24ae5d149080cae1dd694a9d7d1cca7
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1758267936" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "2783772789" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -129338,7 +129338,7 @@ r===$&&A.b()
129338
129338
  o.push(A.ii(p,A.iY(!1,new A.a3(B.tM,A.dT(new A.cI(B.he,new A.a5V(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
129339
129339
  if(!s.ay)if(!s.ch){r=s.e
129340
129340
  r===$&&A.b()
129341
- r=B.b.t("mpb2pck6-950cc89").length!==0&&r.b}if(r){r=s.d
129341
+ r=B.b.t("mpb91fkt-6e44b2b").length!==0&&r.b}if(r){r=s.d
129342
129342
  r===$&&A.b()
129343
129343
  r=r.ag&&!r.V?84:0
129344
129344
  q=s.e
@@ -134146,7 +134146,7 @@ $S:236}
134146
134146
  A.Ys.prototype={}
134147
134147
  A.Rr.prototype={
134148
134148
  mT(a){var s=this
134149
- if(B.b.t("mpb2pck6-950cc89").length===0||s.a!=null)return
134149
+ if(B.b.t("mpb91fkt-6e44b2b").length===0||s.a!=null)return
134150
134150
  s.A5()
134151
134151
  s.a=A.q1(B.PP,new A.b58(s))},
134152
134152
  A5(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -134164,7 +134164,7 @@ if(!t.f.b(k)){s=1
134164
134164
  break}i=J.Z(k,"buildId")
134165
134165
  h=i==null?null:B.b.t(J.r(i))
134166
134166
  j=h==null?"":h
134167
- if(J.bm(j)===0||J.d(j,"mpb2pck6-950cc89")){s=1
134167
+ if(J.bm(j)===0||J.d(j,"mpb91fkt-6e44b2b")){s=1
134168
134168
  break}n.b=!0
134169
134169
  n.D()
134170
134170
  p=2
@@ -134181,7 +134181,7 @@ case 2:return A.i(o.at(-1),r)}})
134181
134181
  return A.k($async$A5,r)},
134182
134182
  vb(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
134183
134183
  var $async$vb=A.h(function(a2,a3){if(a2===1){o.push(a3)
134184
- s=p}for(;;)switch(s){case 0:if(B.b.t("mpb2pck6-950cc89").length===0||n.c){s=1
134184
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mpb91fkt-6e44b2b").length===0||n.c){s=1
134185
134185
  break}n.c=!0
134186
134186
  n.D()
134187
134187
  p=4
@@ -5,17 +5,143 @@ const Anthropic = require('@anthropic-ai/sdk');
5
5
  const { AnthropicProvider } = require('./anthropic');
6
6
 
7
7
  const CLAUDE_CLI_CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
8
- const CLAUDE_CODE_BASE_URL = 'https://api.claude.ai/api';
8
+ const CLAUDE_CODE_BASE_URL = 'https://api.anthropic.com';
9
+ const CLAUDE_CODE_VERSION = process.env.CLAUDE_CODE_VERSION || '2.1.75';
10
+ const CLAUDE_CODE_OAUTH_BETA = 'claude-code-20250219,oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14';
11
+ const CLAUDE_CODE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
12
+ const CLAUDE_CODE_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
13
+ const CLAUDE_CODE_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
9
14
 
10
- function readClaudeCliToken() {
15
+ function readTokenRecord(data) {
16
+ const tokens = data?.claudeAiOauthTokens || data?.claudeAiOauth || {};
17
+ const access = tokens.accessToken || tokens.access;
18
+ const refresh = tokens.refreshToken || tokens.refresh;
19
+ const expires = tokens.expiresAt || tokens.expires;
20
+ return {
21
+ access: typeof access === 'string' && access ? access : null,
22
+ refresh: typeof refresh === 'string' && refresh ? refresh : null,
23
+ expires: typeof expires === 'number' && Number.isFinite(expires) ? expires : null,
24
+ };
25
+ }
26
+
27
+ function readTokenValue(data) {
28
+ return readTokenRecord(data).access;
29
+ }
30
+
31
+ function readClaudeCliTokenRecord() {
11
32
  try {
12
33
  const raw = fs.readFileSync(CLAUDE_CLI_CREDS_PATH, 'utf8');
13
34
  const data = JSON.parse(raw);
14
- const token = data?.claudeAiOauthTokens?.accessToken;
15
- return typeof token === 'string' && token ? token : null;
35
+ return readTokenRecord(data);
36
+ } catch {
37
+ return { access: null, refresh: null, expires: null };
38
+ }
39
+ }
40
+
41
+ function readClaudeCliToken() {
42
+ return readClaudeCliTokenRecord().access;
43
+ }
44
+
45
+ function mergeAnthropicBeta(existing) {
46
+ if (!existing) return CLAUDE_CODE_OAUTH_BETA;
47
+ const seen = new Set();
48
+ return String(existing)
49
+ .split(',')
50
+ .concat(CLAUDE_CODE_OAUTH_BETA.split(','))
51
+ .map((item) => item.trim())
52
+ .filter((item) => {
53
+ if (!item || seen.has(item)) return false;
54
+ seen.add(item);
55
+ return true;
56
+ })
57
+ .join(',');
58
+ }
59
+
60
+ function normalizeExpiresAt(data) {
61
+ if (typeof data.expires_at === 'number' && Number.isFinite(data.expires_at)) {
62
+ return data.expires_at > 10_000_000_000 ? data.expires_at : data.expires_at * 1000;
63
+ }
64
+ if (typeof data.expiresAt === 'number' && Number.isFinite(data.expiresAt)) {
65
+ return data.expiresAt > 10_000_000_000 ? data.expiresAt : data.expiresAt * 1000;
66
+ }
67
+ if (typeof data.expires_in === 'number' && Number.isFinite(data.expires_in)) {
68
+ return Date.now() + (data.expires_in * 1000);
69
+ }
70
+ return null;
71
+ }
72
+
73
+ function sanitizeEnvKey(key) {
74
+ return String(key).replace(/[\r\n]/g, '');
75
+ }
76
+
77
+ function sanitizeEnvValue(value) {
78
+ return String(value).replace(/[\r\n]/g, '');
79
+ }
80
+
81
+ function persistEnvValue(key, value) {
82
+ if (!value) return;
83
+ try {
84
+ const { ENV_FILE } = require('../../../../runtime/paths');
85
+ const safeKey = sanitizeEnvKey(key);
86
+ const safeValue = sanitizeEnvValue(value);
87
+ const raw = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, 'utf8') : '';
88
+ const lines = raw ? raw.split('\n') : [];
89
+ let replaced = false;
90
+ for (let i = 0; i < lines.length; i++) {
91
+ if (lines[i].startsWith(`${safeKey}=`)) {
92
+ lines[i] = `${safeKey}=${safeValue}`;
93
+ replaced = true;
94
+ break;
95
+ }
96
+ }
97
+ if (!replaced) lines.push(`${safeKey}=${safeValue}`);
98
+ const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n';
99
+ fs.mkdirSync(path.dirname(ENV_FILE), { recursive: true });
100
+ fs.writeFileSync(ENV_FILE, output, { mode: 0o600 });
101
+ } catch { }
102
+ }
103
+
104
+ async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch) {
105
+ if (!refreshToken) return null;
106
+ const response = await fetchImpl(CLAUDE_CODE_TOKEN_URL, {
107
+ method: 'POST',
108
+ headers: {
109
+ 'Content-Type': 'application/json',
110
+ 'Accept': 'application/json',
111
+ 'anthropic-version': '2023-06-01',
112
+ },
113
+ body: JSON.stringify({
114
+ grant_type: 'refresh_token',
115
+ refresh_token: refreshToken,
116
+ client_id: CLAUDE_CODE_CLIENT_ID,
117
+ }),
118
+ });
119
+
120
+ const text = await response.text();
121
+ let data = {};
122
+ try {
123
+ data = text ? JSON.parse(text) : {};
16
124
  } catch {
17
- return null;
125
+ data = {};
18
126
  }
127
+
128
+ if (!response.ok) {
129
+ const detail = data?.error?.message || data?.error_description || data?.error || text || 'Unknown error';
130
+ throw new Error(`Claude Code OAuth refresh failed: HTTP ${response.status} ${detail}`);
131
+ }
132
+ if (!data.access_token) {
133
+ throw new Error('Claude Code OAuth refresh succeeded but no access_token was returned.');
134
+ }
135
+
136
+ return {
137
+ access: data.access_token,
138
+ refresh: data.refresh_token || refreshToken,
139
+ expires: normalizeExpiresAt(data),
140
+ };
141
+ }
142
+
143
+ function isAuthenticationError(err) {
144
+ return err?.status === 401 || err?.error?.type === 'authentication_error';
19
145
  }
20
146
 
21
147
  class ClaudeCodeProvider extends AnthropicProvider {
@@ -33,17 +159,86 @@ class ClaudeCodeProvider extends AnthropicProvider {
33
159
  'claude-haiku-4-5-20251001': 200000,
34
160
  };
35
161
 
36
- const authToken = config.apiKey || process.env.CLAUDE_CODE_OAUTH_TOKEN || readClaudeCliToken();
162
+ const cliTokenRecord = readClaudeCliTokenRecord();
163
+ const authToken = config.apiKey || process.env.CLAUDE_CODE_OAUTH_TOKEN || cliTokenRecord.access;
37
164
  if (!authToken) {
38
165
  console.warn('[ClaudeCode] No access token. Run `neoagent login claude-code` to authenticate.');
39
166
  }
40
167
 
41
- // OAuth tokens use Authorization: Bearer — authToken option sends that header
42
- this.client = new Anthropic({
168
+ this.authToken = authToken || null;
169
+ this.refreshToken = config.refreshToken || process.env.CLAUDE_CODE_REFRESH_TOKEN || cliTokenRecord.refresh || null;
170
+ this.fetchImpl = config.fetch || fetch;
171
+ this.baseURL = config.baseUrl || CLAUDE_CODE_BASE_URL;
172
+ this.defaultHeaders = {
173
+ ...(config.defaultHeaders || {}),
174
+ accept: 'application/json',
175
+ 'anthropic-dangerous-direct-browser-access': 'true',
176
+ 'anthropic-beta': mergeAnthropicBeta(config.defaultHeaders?.['anthropic-beta']),
177
+ 'user-agent': `claude-cli/${CLAUDE_CODE_VERSION}`,
178
+ 'x-app': 'cli',
179
+ };
180
+
181
+ this.client = this.createClient(this.authToken, config);
182
+ }
183
+
184
+ createClient(authToken, config = this.config) {
185
+ // OAuth tokens use Authorization: Bearer. Claude Code subscription inference
186
+ // also requires the Claude Code beta surface headers used by the official CLI.
187
+ return new Anthropic({
43
188
  authToken: authToken || undefined,
44
- baseURL: config.baseUrl || CLAUDE_CODE_BASE_URL,
189
+ baseURL: this.baseURL,
190
+ defaultHeaders: authToken ? this.defaultHeaders : config.defaultHeaders,
191
+ ...(this.fetchImpl ? { fetch: this.fetchImpl } : {}),
45
192
  });
46
193
  }
194
+
195
+ async refreshClient() {
196
+ const refreshed = await refreshClaudeCodeAccessToken(this.refreshToken, this.fetchImpl);
197
+ if (!refreshed?.access) return false;
198
+ this.authToken = refreshed.access;
199
+ this.refreshToken = refreshed.refresh || this.refreshToken;
200
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = this.authToken;
201
+ persistEnvValue('CLAUDE_CODE_OAUTH_TOKEN', this.authToken);
202
+ if (this.refreshToken) {
203
+ process.env.CLAUDE_CODE_REFRESH_TOKEN = this.refreshToken;
204
+ persistEnvValue('CLAUDE_CODE_REFRESH_TOKEN', this.refreshToken);
205
+ }
206
+ this.client = this.createClient(this.authToken);
207
+ return true;
208
+ }
209
+
210
+ convertMessages(messages) {
211
+ const converted = super.convertMessages(messages);
212
+ if (!converted.system) {
213
+ converted.system = [{ type: 'text', text: CLAUDE_CODE_SYSTEM_PROMPT }];
214
+ return converted;
215
+ }
216
+ converted.system = [
217
+ { type: 'text', text: CLAUDE_CODE_SYSTEM_PROMPT },
218
+ { type: 'text', text: converted.system },
219
+ ];
220
+ return converted;
221
+ }
222
+
223
+ async chat(messages, tools = [], options = {}) {
224
+ try {
225
+ return await super.chat(messages, tools, options);
226
+ } catch (err) {
227
+ if (!isAuthenticationError(err) || !this.refreshToken) throw err;
228
+ await this.refreshClient();
229
+ return await super.chat(messages, tools, options);
230
+ }
231
+ }
232
+
233
+ async *stream(messages, tools = [], options = {}) {
234
+ try {
235
+ yield* super.stream(messages, tools, options);
236
+ } catch (err) {
237
+ if (!isAuthenticationError(err) || !this.refreshToken) throw err;
238
+ await this.refreshClient();
239
+ yield* super.stream(messages, tools, options);
240
+ }
241
+ }
47
242
  }
48
243
 
49
- module.exports = { ClaudeCodeProvider, readClaudeCliToken };
244
+ module.exports = { ClaudeCodeProvider, readClaudeCliToken, refreshClaudeCodeAccessToken };
@@ -3,6 +3,14 @@ const OpenAI = require('openai');
3
3
  const { BaseProvider } = require('./base');
4
4
 
5
5
  const DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api/codex';
6
+ const OPENAI_CODEX_EMPTY_INPUT_TEXT = ' ';
7
+ const NEOAGENT_VERSION = (() => {
8
+ try {
9
+ return require('../../../../package.json').version || 'unknown';
10
+ } catch {
11
+ return 'unknown';
12
+ }
13
+ })();
6
14
 
7
15
  // Stable per-process installation ID — Codex backend uses it for request tracking.
8
16
  const INSTALLATION_ID = crypto.randomUUID();
@@ -26,7 +34,7 @@ function isCodexBackendBaseUrl(baseURL) {
26
34
  const url = new URL(trimmed);
27
35
  const path = url.pathname.replace(/\/+$/, '');
28
36
  return url.hostname === 'chatgpt.com'
29
- && (path === '/backend-api' || path === '/backend-api/codex' || path === '/backend-api/codex/v1');
37
+ && (path === '/backend-api' || path === '/backend-api/v1' || path === '/backend-api/codex' || path === '/backend-api/codex/v1');
30
38
  } catch {
31
39
  return false;
32
40
  }
@@ -52,6 +60,10 @@ function normalizeCodexBaseUrl(baseURL) {
52
60
  return baseURL;
53
61
  }
54
62
 
63
+ function isNativeCodexResponsesBaseUrl(baseURL) {
64
+ return isCodexBackendBaseUrl(baseURL);
65
+ }
66
+
55
67
  function normalizeContent(content) {
56
68
  if (content == null) return '';
57
69
  if (typeof content === 'string') return content;
@@ -67,33 +79,45 @@ function normalizeContent(content) {
67
79
  return String(content);
68
80
  }
69
81
 
70
- function normalizeInputContent(content) {
82
+ function normalizeMessageContent(content, role = 'user') {
71
83
  if (content == null) return [];
84
+ const isAssistant = role === 'assistant';
85
+ const textType = isAssistant ? 'output_text' : 'input_text';
72
86
 
73
87
  if (typeof content === 'string') {
74
- return [{ type: 'input_text', text: content }];
88
+ return [{ type: textType, text: content, ...(isAssistant ? { annotations: [] } : {}) }];
75
89
  }
76
90
 
77
91
  if (!Array.isArray(content)) {
78
92
  const text = String(content);
79
- return text ? [{ type: 'input_text', text }] : [];
93
+ return text ? [{ type: textType, text, ...(isAssistant ? { annotations: [] } : {}) }] : [];
80
94
  }
81
95
 
82
96
  const parts = [];
83
97
  for (const part of content) {
84
98
  if (!part) continue;
85
99
  if (typeof part === 'string') {
86
- if (part.trim()) parts.push({ type: 'input_text', text: part });
100
+ if (part.trim()) parts.push({ type: textType, text: part, ...(isAssistant ? { annotations: [] } : {}) });
87
101
  continue;
88
102
  }
89
103
  if (part.type === 'text' && typeof part.text === 'string') {
90
- parts.push({ type: 'input_text', text: part.text });
104
+ parts.push({ type: textType, text: part.text, ...(isAssistant ? { annotations: [] } : {}) });
91
105
  continue;
92
106
  }
93
107
  if (part.type === 'input_text' && typeof part.text === 'string') {
94
- parts.push({ type: 'input_text', text: part.text });
108
+ parts.push({ type: textType, text: part.text, ...(isAssistant ? { annotations: [] } : {}) });
109
+ continue;
110
+ }
111
+ if (part.type === 'output_text' && typeof part.text === 'string') {
112
+ parts.push({ type: textType, text: part.text, ...(isAssistant ? { annotations: part.annotations || [] } : {}) });
95
113
  continue;
96
114
  }
115
+ if (isAssistant && part.type === 'refusal') {
116
+ const refusal = typeof part.refusal === 'string' ? part.refusal : part.text;
117
+ if (typeof refusal === 'string') parts.push({ type: 'refusal', refusal });
118
+ continue;
119
+ }
120
+ if (isAssistant) continue;
97
121
  if (part.type === 'image_url') {
98
122
  const imageUrl = typeof part.image_url === 'string' ? part.image_url : part.image_url?.url;
99
123
  if (imageUrl) {
@@ -211,11 +235,9 @@ class OpenAICodexProvider extends BaseProvider {
211
235
 
212
236
  const defaultHeaders = this.usesCodexBackend
213
237
  ? {
214
- // Required by Cloudflare bot detection on chatgpt.com
215
- 'originator': 'Codex Desktop',
216
- 'User-Agent': 'Codex Desktop/1.0.0 (darwin; arm64)',
217
- // Required by the Codex responses endpoint
218
- 'OpenAI-Beta': 'responses_websockets=2026-02-06',
238
+ 'originator': 'openclaw',
239
+ 'version': NEOAGENT_VERSION,
240
+ 'User-Agent': `openclaw/${NEOAGENT_VERSION}`,
219
241
  'x-codex-installation-id': INSTALLATION_ID,
220
242
  'x-openai-internal-codex-residency': process.env.OPENAI_CODEX_RESIDENCY || 'us',
221
243
  ...(accountId ? { 'ChatGPT-Account-Id': accountId } : {}),
@@ -226,6 +248,7 @@ class OpenAICodexProvider extends BaseProvider {
226
248
  apiKey: config.apiKey || process.env.OPENAI_CODEX_ACCESS_TOKEN,
227
249
  baseURL,
228
250
  defaultHeaders,
251
+ ...(config.fetch ? { fetch: config.fetch } : {}),
229
252
  });
230
253
  }
231
254
 
@@ -267,11 +290,12 @@ class OpenAICodexProvider extends BaseProvider {
267
290
  continue;
268
291
  }
269
292
 
270
- const content = normalizeInputContent(msg.content);
293
+ const role = msg.role === 'assistant' ? 'assistant' : 'user';
294
+ const content = normalizeMessageContent(msg.content, role);
271
295
  if (content.length > 0) {
272
296
  input.push({
273
297
  type: 'message',
274
- role: msg.role === 'assistant' ? 'assistant' : 'user',
298
+ role,
275
299
  content,
276
300
  });
277
301
  }
@@ -297,6 +321,13 @@ class OpenAICodexProvider extends BaseProvider {
297
321
  };
298
322
 
299
323
  if (this.usesCodexBackend) {
324
+ if (input.length === 0 && instructions.length > 0) {
325
+ input.push({
326
+ type: 'message',
327
+ role: 'user',
328
+ content: [{ type: 'input_text', text: OPENAI_CODEX_EMPTY_INPUT_TEXT }],
329
+ });
330
+ }
300
331
  // instructions must always be present (even empty) — backend returns 400 if omitted
301
332
  request.instructions = instructions.join('\n\n');
302
333
  request.store = false;
@@ -315,6 +346,7 @@ class OpenAICodexProvider extends BaseProvider {
315
346
  request.reasoning = { effort, summary: 'auto' };
316
347
  request.include = ['reasoning.encrypted_content'];
317
348
  }
349
+ this._sanitizeNativeCodexRequest(request);
318
350
  } else {
319
351
  if (instructions.length > 0) {
320
352
  request.instructions = instructions.join('\n\n');
@@ -337,12 +369,63 @@ class OpenAICodexProvider extends BaseProvider {
337
369
  }
338
370
 
339
371
  _requestHeaders() {
372
+ const requestId = crypto.randomUUID();
340
373
  return this.usesCodexBackend
341
- ? { 'x-client-request-id': crypto.randomUUID() }
374
+ ? {
375
+ 'x-client-request-id': requestId,
376
+ 'x-openclaw-session-id': requestId,
377
+ 'x-openclaw-turn-id': requestId,
378
+ 'x-openclaw-turn-attempt': '1',
379
+ }
342
380
  : undefined;
343
381
  }
344
382
 
383
+ _sanitizeNativeCodexRequest(request) {
384
+ if (!isNativeCodexResponsesBaseUrl(this.baseURL)) return request;
385
+ for (const key of [
386
+ 'max_output_tokens',
387
+ 'metadata',
388
+ 'prompt_cache_retention',
389
+ 'service_tier',
390
+ 'temperature',
391
+ 'top_p',
392
+ ]) {
393
+ delete request[key];
394
+ }
395
+ if (request.text && typeof request.text === 'object' && !Array.isArray(request.text)) {
396
+ const text = { ...request.text };
397
+ delete text.format;
398
+ if (Object.keys(text).length > 0) {
399
+ request.text = text;
400
+ } else {
401
+ delete request.text;
402
+ }
403
+ }
404
+ return request;
405
+ }
406
+
345
407
  async chat(messages, tools = [], options = {}) {
408
+ if (this.usesCodexBackend) {
409
+ let final = null;
410
+ let content = '';
411
+ for await (const event of this.stream(messages, tools, options)) {
412
+ if (event.type === 'content') {
413
+ content += event.content || '';
414
+ continue;
415
+ }
416
+ if (event.type === 'tool_calls' || event.type === 'done') {
417
+ final = event;
418
+ }
419
+ }
420
+ return {
421
+ content: final?.content || content,
422
+ toolCalls: final?.toolCalls || [],
423
+ finishReason: final?.finishReason || (final?.toolCalls?.length > 0 ? 'tool_calls' : 'stop'),
424
+ usage: final?.usage || null,
425
+ model: final?.model || options.model || this.config.model || this.getDefaultModel(),
426
+ };
427
+ }
428
+
346
429
  const model = options.model || this.config.model || this.getDefaultModel();
347
430
  const request = this._buildRequest(messages, tools, options, model);
348
431
  let response;