neoagent 2.3.1-beta.101 → 2.3.1-beta.103
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/lib/manager.js +148 -2
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/services/ai/models.js +21 -0
- package/server/services/ai/providers/claudeCode.js +49 -0
- package/server/services/ai/providers/openaiCodex.js +62 -35
- package/server/services/ai/settings.js +10 -0
package/lib/manager.js
CHANGED
|
@@ -786,10 +786,153 @@ async function pollDeviceCode({ pollUrl, pollBody, pollHeaders = {}, intervalMs,
|
|
|
786
786
|
throw new Error('Authentication timed out after 15 minutes.');
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
+
async function cmdLoginClaudeCode() {
|
|
790
|
+
heading('Claude Code Login');
|
|
791
|
+
|
|
792
|
+
// Check for Claude CLI credential file first (set by `claude login`)
|
|
793
|
+
const cliCredsPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
794
|
+
if (fs.existsSync(cliCredsPath)) {
|
|
795
|
+
try {
|
|
796
|
+
const raw = fs.readFileSync(cliCredsPath, 'utf8');
|
|
797
|
+
const data = JSON.parse(raw);
|
|
798
|
+
const token = data?.claudeAiOauthTokens?.accessToken;
|
|
799
|
+
if (token) {
|
|
800
|
+
upsertEnvValue('CLAUDE_CODE_ACCESS_TOKEN', token);
|
|
801
|
+
logOk('Imported access token from Claude CLI credentials store');
|
|
802
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
803
|
+
cmdRestart();
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
} catch { }
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Fall back to browser-based PKCE OAuth flow
|
|
810
|
+
const http = require('http');
|
|
811
|
+
const { URL: NodeURL } = require('url');
|
|
812
|
+
|
|
813
|
+
const clientId = '9d1c250a-e61b-48f7-87a4-b8a14c8a8f5e';
|
|
814
|
+
const redirectPort = 54321;
|
|
815
|
+
const redirectUri = `http://localhost:${redirectPort}/callback`;
|
|
816
|
+
|
|
817
|
+
// Generate PKCE verifier and challenge
|
|
818
|
+
const codeVerifier = crypto.randomBytes(48).toString('base64url');
|
|
819
|
+
const codeChallenge = crypto
|
|
820
|
+
.createHash('sha256')
|
|
821
|
+
.update(codeVerifier)
|
|
822
|
+
.digest('base64url');
|
|
823
|
+
|
|
824
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
825
|
+
|
|
826
|
+
const authUrl = new URL('https://claude.ai/oauth/authorize');
|
|
827
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
828
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
829
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
830
|
+
authUrl.searchParams.set('scope', 'openid profile email offline_access');
|
|
831
|
+
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
832
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
833
|
+
authUrl.searchParams.set('state', state);
|
|
834
|
+
|
|
835
|
+
console.log(`\n ${COLORS.cyan}Opening browser for Claude Code authorization...${COLORS.reset}`);
|
|
836
|
+
console.log(` ${COLORS.dim}If the browser doesn't open, visit:${COLORS.reset}`);
|
|
837
|
+
console.log(` ${authUrl.toString()}\n`);
|
|
838
|
+
|
|
839
|
+
// Open browser
|
|
840
|
+
const openCmd = process.platform === 'darwin' ? 'open'
|
|
841
|
+
: process.platform === 'win32' ? 'start'
|
|
842
|
+
: 'xdg-open';
|
|
843
|
+
spawnSync(openCmd, [authUrl.toString()], { stdio: 'ignore' });
|
|
844
|
+
|
|
845
|
+
// Start local redirect server to capture authorization code
|
|
846
|
+
const authCode = await new Promise((resolve, reject) => {
|
|
847
|
+
const timeout = setTimeout(() => {
|
|
848
|
+
server.close();
|
|
849
|
+
reject(new Error('Claude Code authorization timed out after 5 minutes.'));
|
|
850
|
+
}, 5 * 60 * 1000);
|
|
851
|
+
|
|
852
|
+
const server = http.createServer((req, res) => {
|
|
853
|
+
try {
|
|
854
|
+
const reqUrl = new NodeURL(req.url, `http://localhost:${redirectPort}`);
|
|
855
|
+
const code = reqUrl.searchParams.get('code');
|
|
856
|
+
const returnedState = reqUrl.searchParams.get('state');
|
|
857
|
+
const error = reqUrl.searchParams.get('error');
|
|
858
|
+
|
|
859
|
+
if (error) {
|
|
860
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
861
|
+
res.end('<html><body><h2>Authorization failed.</h2><p>You can close this tab.</p></body></html>');
|
|
862
|
+
clearTimeout(timeout);
|
|
863
|
+
server.close();
|
|
864
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (returnedState && returnedState !== state) {
|
|
869
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
870
|
+
res.end('<html><body><h2>Authorization failed.</h2><p>State mismatch. You can close this tab.</p></body></html>');
|
|
871
|
+
clearTimeout(timeout);
|
|
872
|
+
server.close();
|
|
873
|
+
reject(new Error('OAuth state mismatch — possible CSRF attempt.'));
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (code) {
|
|
878
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
879
|
+
res.end('<html><body><h2>Authorization successful!</h2><p>You can close this tab and return to the terminal.</p></body></html>');
|
|
880
|
+
clearTimeout(timeout);
|
|
881
|
+
server.close();
|
|
882
|
+
resolve(code);
|
|
883
|
+
}
|
|
884
|
+
} catch (err) {
|
|
885
|
+
res.writeHead(500);
|
|
886
|
+
res.end('Internal error');
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
server.listen(redirectPort, '127.0.0.1', () => {
|
|
891
|
+
logInfo(`Waiting for authorization callback on port ${redirectPort}...`);
|
|
892
|
+
});
|
|
893
|
+
server.on('error', (err) => {
|
|
894
|
+
clearTimeout(timeout);
|
|
895
|
+
reject(new Error(`Could not start local redirect server: ${err.message}`));
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
logInfo('Exchanging authorization code for access token...');
|
|
900
|
+
const tokenRes = await fetch('https://claude.ai/api/oauth/token', {
|
|
901
|
+
method: 'POST',
|
|
902
|
+
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
903
|
+
body: JSON.stringify({
|
|
904
|
+
grant_type: 'authorization_code',
|
|
905
|
+
code: authCode,
|
|
906
|
+
redirect_uri: redirectUri,
|
|
907
|
+
client_id: clientId,
|
|
908
|
+
code_verifier: codeVerifier,
|
|
909
|
+
}),
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
if (!tokenRes.ok) {
|
|
913
|
+
const text = await tokenRes.text().catch(() => 'Unknown error');
|
|
914
|
+
throw new Error(`Token exchange failed: HTTP ${tokenRes.status} — ${text}`);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const tokenData = await tokenRes.json();
|
|
918
|
+
const accessToken = tokenData.access_token;
|
|
919
|
+
if (!accessToken) {
|
|
920
|
+
throw new Error('Token exchange succeeded but no access_token was returned.');
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
upsertEnvValue('CLAUDE_CODE_ACCESS_TOKEN', accessToken);
|
|
924
|
+
if (tokenData.refresh_token) {
|
|
925
|
+
upsertEnvValue('CLAUDE_CODE_REFRESH_TOKEN', tokenData.refresh_token);
|
|
926
|
+
}
|
|
927
|
+
logOk('Saved Claude Code access token to .env');
|
|
928
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
929
|
+
cmdRestart();
|
|
930
|
+
}
|
|
931
|
+
|
|
789
932
|
async function cmdLogin(args = []) {
|
|
790
933
|
const provider = args[0];
|
|
791
|
-
if (provider !== 'github-copilot' && provider !== 'openai-codex') {
|
|
792
|
-
throw new Error(`Unsupported login provider: ${provider || 'none'}. Available: github-copilot, openai-codex`);
|
|
934
|
+
if (provider !== 'github-copilot' && provider !== 'openai-codex' && provider !== 'claude-code') {
|
|
935
|
+
throw new Error(`Unsupported login provider: ${provider || 'none'}. Available: github-copilot, openai-codex, claude-code`);
|
|
793
936
|
}
|
|
794
937
|
|
|
795
938
|
if (provider === 'github-copilot') {
|
|
@@ -893,6 +1036,8 @@ async function cmdLogin(args = []) {
|
|
|
893
1036
|
logOk('Saved OpenAI Codex tokens to .env');
|
|
894
1037
|
logInfo('Restarting NeoAgent to apply credentials...');
|
|
895
1038
|
cmdRestart();
|
|
1039
|
+
} else if (provider === 'claude-code') {
|
|
1040
|
+
await cmdLoginClaudeCode();
|
|
896
1041
|
}
|
|
897
1042
|
}
|
|
898
1043
|
|
|
@@ -1471,6 +1616,7 @@ function printHelp() {
|
|
|
1471
1616
|
row('update stable|beta', 'Update and switch channel');
|
|
1472
1617
|
row('login github-copilot','Authenticate GitHub Copilot');
|
|
1473
1618
|
row('login openai-codex', 'Authenticate OpenAI Codex');
|
|
1619
|
+
row('login claude-code', 'Authenticate Claude Code');
|
|
1474
1620
|
console.log('');
|
|
1475
1621
|
|
|
1476
1622
|
console.log(`${c.bold}Maintenance${c.reset}`);
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
11c4133ebb724b223b80ad380661a4c1
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "1865794536" /* 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("
|
|
129341
|
+
r=B.b.t("mpacm3gu-abb7f02").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("
|
|
134149
|
+
if(B.b.t("mpacm3gu-abb7f02").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,"
|
|
134167
|
+
if(J.bm(j)===0||J.d(j,"mpacm3gu-abb7f02")){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("
|
|
134184
|
+
s=p}for(;;)switch(s){case 0:if(B.b.t("mpacm3gu-abb7f02").length===0||n.c){s=1
|
|
134185
134185
|
break}n.c=!0
|
|
134186
134186
|
n.D()
|
|
134187
134187
|
p=4
|
|
@@ -5,6 +5,7 @@ const { OllamaProvider } = require('./providers/ollama');
|
|
|
5
5
|
const { OpenAIProvider } = require('./providers/openai');
|
|
6
6
|
const { GithubCopilotProvider } = require('./providers/githubCopilot');
|
|
7
7
|
const { OpenAICodexProvider } = require('./providers/openaiCodex');
|
|
8
|
+
const { ClaudeCodeProvider } = require('./providers/claudeCode');
|
|
8
9
|
const {
|
|
9
10
|
AI_PROVIDER_DEFINITIONS,
|
|
10
11
|
getProviderConfigs,
|
|
@@ -72,6 +73,24 @@ const STATIC_MODELS = [
|
|
|
72
73
|
provider: 'anthropic',
|
|
73
74
|
purpose: 'fast'
|
|
74
75
|
},
|
|
76
|
+
{
|
|
77
|
+
id: 'claude-opus-4-7',
|
|
78
|
+
label: 'Claude Opus 4.7 (Claude Code / Default)',
|
|
79
|
+
provider: 'claude-code',
|
|
80
|
+
purpose: 'general'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'claude-sonnet-4-6',
|
|
84
|
+
label: 'Claude Sonnet 4.6 (Claude Code / Fast)',
|
|
85
|
+
provider: 'claude-code',
|
|
86
|
+
purpose: 'coding'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'claude-haiku-4-5-20251001',
|
|
90
|
+
label: 'Claude Haiku 4.5 (Claude Code / Subagents)',
|
|
91
|
+
provider: 'claude-code',
|
|
92
|
+
purpose: 'fast'
|
|
93
|
+
},
|
|
75
94
|
{
|
|
76
95
|
id: 'gemini-3.1-flash-lite-preview',
|
|
77
96
|
label: 'Gemini 3.1 Flash Lite (Preview)',
|
|
@@ -338,6 +357,8 @@ function createProviderInstance(providerStr, userId = null, configOverrides = {}
|
|
|
338
357
|
return new GithubCopilotProvider({ apiKey: runtime.apiKey, ...providerOverrides });
|
|
339
358
|
} else if (providerStr === 'openai-codex') {
|
|
340
359
|
return new OpenAICodexProvider({ apiKey: runtime.apiKey, ...providerOverrides });
|
|
360
|
+
} else if (providerStr === 'claude-code') {
|
|
361
|
+
return new ClaudeCodeProvider({ apiKey: runtime.apiKey, ...providerOverrides });
|
|
341
362
|
}
|
|
342
363
|
throw new Error(`Unknown provider: ${providerStr}`);
|
|
343
364
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const Anthropic = require('@anthropic-ai/sdk');
|
|
5
|
+
const { AnthropicProvider } = require('./anthropic');
|
|
6
|
+
|
|
7
|
+
const CLAUDE_CLI_CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
8
|
+
const CLAUDE_CODE_BASE_URL = 'https://api.claude.ai/api';
|
|
9
|
+
|
|
10
|
+
function readClaudeCliToken() {
|
|
11
|
+
try {
|
|
12
|
+
const raw = fs.readFileSync(CLAUDE_CLI_CREDS_PATH, 'utf8');
|
|
13
|
+
const data = JSON.parse(raw);
|
|
14
|
+
const token = data?.claudeAiOauthTokens?.accessToken;
|
|
15
|
+
return typeof token === 'string' && token ? token : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class ClaudeCodeProvider extends AnthropicProvider {
|
|
22
|
+
constructor(config = {}) {
|
|
23
|
+
super(config);
|
|
24
|
+
this.name = 'claude-code';
|
|
25
|
+
this.models = [
|
|
26
|
+
'claude-opus-4-7',
|
|
27
|
+
'claude-sonnet-4-6',
|
|
28
|
+
'claude-haiku-4-5-20251001',
|
|
29
|
+
];
|
|
30
|
+
this.contextWindows = {
|
|
31
|
+
'claude-opus-4-7': 200000,
|
|
32
|
+
'claude-sonnet-4-6': 200000,
|
|
33
|
+
'claude-haiku-4-5-20251001': 200000,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const authToken = config.apiKey || process.env.CLAUDE_CODE_ACCESS_TOKEN || readClaudeCliToken();
|
|
37
|
+
if (!authToken) {
|
|
38
|
+
console.warn('[ClaudeCode] No access token. Run `neoagent login claude-code` to authenticate.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// OAuth tokens use Authorization: Bearer — authToken option sends that header
|
|
42
|
+
this.client = new Anthropic({
|
|
43
|
+
authToken: authToken || undefined,
|
|
44
|
+
baseURL: config.baseUrl || CLAUDE_CODE_BASE_URL,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { ClaudeCodeProvider, readClaudeCliToken };
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
1
2
|
const OpenAI = require('openai');
|
|
2
3
|
const { BaseProvider } = require('./base');
|
|
3
4
|
|
|
5
|
+
const DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api/codex';
|
|
6
|
+
|
|
7
|
+
// Stable per-process installation ID — Codex backend uses it for request tracking.
|
|
8
|
+
const INSTALLATION_ID = crypto.randomUUID();
|
|
9
|
+
|
|
4
10
|
function decodeJwtPayload(token) {
|
|
5
11
|
try {
|
|
6
12
|
const parts = String(token || '').split('.');
|
|
@@ -12,8 +18,6 @@ function decodeJwtPayload(token) {
|
|
|
12
18
|
}
|
|
13
19
|
}
|
|
14
20
|
|
|
15
|
-
const DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api/codex';
|
|
16
|
-
|
|
17
21
|
function isCodexBackendBaseUrl(baseURL) {
|
|
18
22
|
const trimmed = String(baseURL || '').trim();
|
|
19
23
|
if (!trimmed) return false;
|
|
@@ -194,20 +198,27 @@ class OpenAICodexProvider extends BaseProvider {
|
|
|
194
198
|
'gpt-5.4',
|
|
195
199
|
'gpt-5.4-mini',
|
|
196
200
|
]);
|
|
201
|
+
|
|
197
202
|
let accountId = process.env.OPENAI_CODEX_ACCOUNT_ID || '';
|
|
198
203
|
if (!accountId && this.usesCodexBackend) {
|
|
199
204
|
const accessToken = config.apiKey || process.env.OPENAI_CODEX_ACCESS_TOKEN || '';
|
|
200
205
|
const payload = decodeJwtPayload(accessToken);
|
|
201
|
-
|
|
206
|
+
// account_id lives in access_token directly, or nested under the OIDC namespace in id_token
|
|
207
|
+
accountId = payload?.chatgpt_account_id
|
|
208
|
+
|| payload?.['https://api.openai.com/auth']?.chatgpt_account_id
|
|
209
|
+
|| '';
|
|
202
210
|
}
|
|
203
211
|
|
|
204
212
|
const defaultHeaders = this.usesCodexBackend
|
|
205
213
|
? {
|
|
206
|
-
|
|
207
|
-
'
|
|
208
|
-
'User-Agent':
|
|
209
|
-
|
|
210
|
-
|
|
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',
|
|
219
|
+
'x-codex-installation-id': INSTALLATION_ID,
|
|
220
|
+
'x-openai-internal-codex-residency': process.env.OPENAI_CODEX_RESIDENCY || 'us',
|
|
221
|
+
...(accountId ? { 'ChatGPT-Account-Id': accountId } : {}),
|
|
211
222
|
}
|
|
212
223
|
: undefined;
|
|
213
224
|
|
|
@@ -286,43 +297,60 @@ class OpenAICodexProvider extends BaseProvider {
|
|
|
286
297
|
};
|
|
287
298
|
|
|
288
299
|
if (this.usesCodexBackend) {
|
|
300
|
+
// instructions must always be present (even empty) — backend returns 400 if omitted
|
|
289
301
|
request.instructions = instructions.join('\n\n');
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
302
|
+
request.store = false;
|
|
303
|
+
// tools fields must always be explicit
|
|
304
|
+
if (tools && tools.length > 0) {
|
|
305
|
+
request.tools = this.formatTools(tools);
|
|
306
|
+
request.tool_choice = options.toolChoice || 'auto';
|
|
307
|
+
} else {
|
|
308
|
+
request.tools = [];
|
|
309
|
+
request.tool_choice = 'auto';
|
|
310
|
+
}
|
|
311
|
+
request.parallel_tool_calls = false;
|
|
312
|
+
// reasoning: both effort and summary required for Codex reasoning models
|
|
313
|
+
if (this._isReasoningModel(model)) {
|
|
314
|
+
const effort = options.reasoningEffort || options.reasoning_effort || 'medium';
|
|
315
|
+
request.reasoning = { effort, summary: 'auto' };
|
|
316
|
+
request.include = ['reasoning.encrypted_content'];
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
if (instructions.length > 0) {
|
|
320
|
+
request.instructions = instructions.join('\n\n');
|
|
321
|
+
}
|
|
322
|
+
if (tools && tools.length > 0) {
|
|
323
|
+
request.tools = this.formatTools(tools);
|
|
324
|
+
request.tool_choice = options.toolChoice || 'auto';
|
|
325
|
+
}
|
|
300
326
|
request.max_output_tokens = options.maxTokens || 16384;
|
|
301
|
-
|
|
302
327
|
if (options.temperature !== undefined && options.temperature !== null) {
|
|
303
328
|
request.temperature = options.temperature;
|
|
304
329
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
request.reasoning = {
|
|
310
|
-
effort: reasoningEffort || 'medium',
|
|
311
|
-
};
|
|
330
|
+
const reasoningEffort = options.reasoningEffort || options.reasoning_effort;
|
|
331
|
+
if (reasoningEffort || this._isReasoningModel(model)) {
|
|
332
|
+
request.reasoning = { effort: reasoningEffort || 'medium' };
|
|
333
|
+
}
|
|
312
334
|
}
|
|
313
335
|
|
|
314
336
|
return request;
|
|
315
337
|
}
|
|
316
338
|
|
|
339
|
+
_requestHeaders() {
|
|
340
|
+
return this.usesCodexBackend
|
|
341
|
+
? { 'x-client-request-id': crypto.randomUUID() }
|
|
342
|
+
: undefined;
|
|
343
|
+
}
|
|
344
|
+
|
|
317
345
|
async chat(messages, tools = [], options = {}) {
|
|
318
346
|
const model = options.model || this.config.model || this.getDefaultModel();
|
|
319
347
|
const request = this._buildRequest(messages, tools, options, model);
|
|
320
348
|
let response;
|
|
321
349
|
try {
|
|
322
|
-
response = await this.client.responses.create(
|
|
323
|
-
model,
|
|
324
|
-
|
|
325
|
-
|
|
350
|
+
response = await this.client.responses.create(
|
|
351
|
+
{ model, ...request },
|
|
352
|
+
{ headers: this._requestHeaders() },
|
|
353
|
+
);
|
|
326
354
|
} catch (err) {
|
|
327
355
|
throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`);
|
|
328
356
|
}
|
|
@@ -347,11 +375,10 @@ class OpenAICodexProvider extends BaseProvider {
|
|
|
347
375
|
const request = this._buildRequest(messages, tools, options, model);
|
|
348
376
|
let stream;
|
|
349
377
|
try {
|
|
350
|
-
stream = await this.client.responses.create(
|
|
351
|
-
model,
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
});
|
|
378
|
+
stream = await this.client.responses.create(
|
|
379
|
+
{ model, ...request, stream: true },
|
|
380
|
+
{ headers: this._requestHeaders() },
|
|
381
|
+
);
|
|
355
382
|
} catch (err) {
|
|
356
383
|
throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`);
|
|
357
384
|
}
|
|
@@ -79,6 +79,16 @@ const AI_PROVIDER_DEFINITIONS = Object.freeze({
|
|
|
79
79
|
defaultEnabled: false,
|
|
80
80
|
defaultBaseUrl: 'https://chatgpt.com/backend-api/codex'
|
|
81
81
|
},
|
|
82
|
+
'claude-code': {
|
|
83
|
+
id: 'claude-code',
|
|
84
|
+
label: 'Claude Code',
|
|
85
|
+
description: 'Claude models via Claude Code subscription. Login with `neoagent login claude-code`.',
|
|
86
|
+
envKey: 'CLAUDE_CODE_ACCESS_TOKEN',
|
|
87
|
+
supportsApiKey: true,
|
|
88
|
+
supportsBaseUrl: false,
|
|
89
|
+
defaultEnabled: false,
|
|
90
|
+
defaultBaseUrl: ''
|
|
91
|
+
},
|
|
82
92
|
ollama: {
|
|
83
93
|
id: 'ollama',
|
|
84
94
|
label: 'Ollama',
|