neoagent 2.3.1-beta.113 → 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.113",
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
- 0c62ef7d28ad8b7fe9751b4f055e1afb
1
+ d24ae5d149080cae1dd694a9d7d1cca7
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "699610273" /* 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("mpb8ba09-f0b0778").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("mpb8ba09-f0b0778").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,"mpb8ba09-f0b0778")){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("mpb8ba09-f0b0778").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
@@ -9,25 +9,39 @@ const CLAUDE_CODE_BASE_URL = 'https://api.anthropic.com';
9
9
  const CLAUDE_CODE_VERSION = process.env.CLAUDE_CODE_VERSION || '2.1.75';
10
10
  const CLAUDE_CODE_OAUTH_BETA = 'claude-code-20250219,oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14';
11
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';
14
+
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
+ }
12
26
 
13
27
  function readTokenValue(data) {
14
- return data?.claudeAiOauthTokens?.accessToken
15
- || data?.claudeAiOauth?.accessToken
16
- || data?.claudeAiOauth?.access
17
- || null;
28
+ return readTokenRecord(data).access;
18
29
  }
19
30
 
20
- function readClaudeCliToken() {
31
+ function readClaudeCliTokenRecord() {
21
32
  try {
22
33
  const raw = fs.readFileSync(CLAUDE_CLI_CREDS_PATH, 'utf8');
23
34
  const data = JSON.parse(raw);
24
- const token = readTokenValue(data);
25
- return typeof token === 'string' && token ? token : null;
35
+ return readTokenRecord(data);
26
36
  } catch {
27
- return null;
37
+ return { access: null, refresh: null, expires: null };
28
38
  }
29
39
  }
30
40
 
41
+ function readClaudeCliToken() {
42
+ return readClaudeCliTokenRecord().access;
43
+ }
44
+
31
45
  function mergeAnthropicBeta(existing) {
32
46
  if (!existing) return CLAUDE_CODE_OAUTH_BETA;
33
47
  const seen = new Set();
@@ -43,6 +57,93 @@ function mergeAnthropicBeta(existing) {
43
57
  .join(',');
44
58
  }
45
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) : {};
124
+ } catch {
125
+ data = {};
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';
145
+ }
146
+
46
147
  class ClaudeCodeProvider extends AnthropicProvider {
47
148
  constructor(config = {}) {
48
149
  super(config);
@@ -58,12 +159,17 @@ class ClaudeCodeProvider extends AnthropicProvider {
58
159
  'claude-haiku-4-5-20251001': 200000,
59
160
  };
60
161
 
61
- 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;
62
164
  if (!authToken) {
63
165
  console.warn('[ClaudeCode] No access token. Run `neoagent login claude-code` to authenticate.');
64
166
  }
65
167
 
66
- const defaultHeaders = {
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 = {
67
173
  ...(config.defaultHeaders || {}),
68
174
  accept: 'application/json',
69
175
  'anthropic-dangerous-direct-browser-access': 'true',
@@ -72,16 +178,35 @@ class ClaudeCodeProvider extends AnthropicProvider {
72
178
  'x-app': 'cli',
73
179
  };
74
180
 
181
+ this.client = this.createClient(this.authToken, config);
182
+ }
183
+
184
+ createClient(authToken, config = this.config) {
75
185
  // OAuth tokens use Authorization: Bearer. Claude Code subscription inference
76
186
  // also requires the Claude Code beta surface headers used by the official CLI.
77
- this.client = new Anthropic({
187
+ return new Anthropic({
78
188
  authToken: authToken || undefined,
79
- baseURL: config.baseUrl || CLAUDE_CODE_BASE_URL,
80
- defaultHeaders: authToken ? defaultHeaders : config.defaultHeaders,
81
- ...(config.fetch ? { fetch: config.fetch } : {}),
189
+ baseURL: this.baseURL,
190
+ defaultHeaders: authToken ? this.defaultHeaders : config.defaultHeaders,
191
+ ...(this.fetchImpl ? { fetch: this.fetchImpl } : {}),
82
192
  });
83
193
  }
84
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
+
85
210
  convertMessages(messages) {
86
211
  const converted = super.convertMessages(messages);
87
212
  if (!converted.system) {
@@ -94,6 +219,26 @@ class ClaudeCodeProvider extends AnthropicProvider {
94
219
  ];
95
220
  return converted;
96
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
+ }
97
242
  }
98
243
 
99
- module.exports = { ClaudeCodeProvider, readClaudeCliToken };
244
+ module.exports = { ClaudeCodeProvider, readClaudeCliToken, refreshClaudeCodeAccessToken };
@@ -79,33 +79,45 @@ function normalizeContent(content) {
79
79
  return String(content);
80
80
  }
81
81
 
82
- function normalizeInputContent(content) {
82
+ function normalizeMessageContent(content, role = 'user') {
83
83
  if (content == null) return [];
84
+ const isAssistant = role === 'assistant';
85
+ const textType = isAssistant ? 'output_text' : 'input_text';
84
86
 
85
87
  if (typeof content === 'string') {
86
- return [{ type: 'input_text', text: content }];
88
+ return [{ type: textType, text: content, ...(isAssistant ? { annotations: [] } : {}) }];
87
89
  }
88
90
 
89
91
  if (!Array.isArray(content)) {
90
92
  const text = String(content);
91
- return text ? [{ type: 'input_text', text }] : [];
93
+ return text ? [{ type: textType, text, ...(isAssistant ? { annotations: [] } : {}) }] : [];
92
94
  }
93
95
 
94
96
  const parts = [];
95
97
  for (const part of content) {
96
98
  if (!part) continue;
97
99
  if (typeof part === 'string') {
98
- if (part.trim()) parts.push({ type: 'input_text', text: part });
100
+ if (part.trim()) parts.push({ type: textType, text: part, ...(isAssistant ? { annotations: [] } : {}) });
99
101
  continue;
100
102
  }
101
103
  if (part.type === 'text' && typeof part.text === 'string') {
102
- parts.push({ type: 'input_text', text: part.text });
104
+ parts.push({ type: textType, text: part.text, ...(isAssistant ? { annotations: [] } : {}) });
103
105
  continue;
104
106
  }
105
107
  if (part.type === 'input_text' && typeof part.text === 'string') {
106
- parts.push({ type: 'input_text', text: part.text });
108
+ parts.push({ type: textType, text: part.text, ...(isAssistant ? { annotations: [] } : {}) });
107
109
  continue;
108
110
  }
111
+ if (part.type === 'output_text' && typeof part.text === 'string') {
112
+ parts.push({ type: textType, text: part.text, ...(isAssistant ? { annotations: part.annotations || [] } : {}) });
113
+ continue;
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;
109
121
  if (part.type === 'image_url') {
110
122
  const imageUrl = typeof part.image_url === 'string' ? part.image_url : part.image_url?.url;
111
123
  if (imageUrl) {
@@ -278,11 +290,12 @@ class OpenAICodexProvider extends BaseProvider {
278
290
  continue;
279
291
  }
280
292
 
281
- const content = normalizeInputContent(msg.content);
293
+ const role = msg.role === 'assistant' ? 'assistant' : 'user';
294
+ const content = normalizeMessageContent(msg.content, role);
282
295
  if (content.length > 0) {
283
296
  input.push({
284
297
  type: 'message',
285
- role: msg.role === 'assistant' ? 'assistant' : 'user',
298
+ role,
286
299
  content,
287
300
  });
288
301
  }