@link-assistant/agent 0.1.4 → 0.2.1
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 +1 -1
- package/src/auth/plugins.ts +276 -0
- package/src/bun/index.ts +28 -2
- package/src/flag/flag.ts +8 -0
- package/src/global/index.ts +1 -0
- package/src/index.js +18 -0
- package/src/provider/provider.ts +44 -0
package/package.json
CHANGED
package/src/auth/plugins.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
|
+
import * as http from 'node:http';
|
|
3
|
+
import * as net from 'node:net';
|
|
2
4
|
import { Auth } from './index';
|
|
3
5
|
import { Log } from '../util/log';
|
|
4
6
|
|
|
@@ -832,6 +834,279 @@ const OpenAIPlugin: AuthPlugin = {
|
|
|
832
834
|
},
|
|
833
835
|
};
|
|
834
836
|
|
|
837
|
+
/**
|
|
838
|
+
* Google OAuth Configuration
|
|
839
|
+
* Used for Google AI Pro/Ultra subscription authentication
|
|
840
|
+
*
|
|
841
|
+
* These credentials are from the official Gemini CLI (google-gemini/gemini-cli)
|
|
842
|
+
* and are public for installed applications as per Google OAuth documentation:
|
|
843
|
+
* https://developers.google.com/identity/protocols/oauth2#installed
|
|
844
|
+
*/
|
|
845
|
+
const GOOGLE_OAUTH_CLIENT_ID =
|
|
846
|
+
'681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com';
|
|
847
|
+
const GOOGLE_OAUTH_CLIENT_SECRET = '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';
|
|
848
|
+
const GOOGLE_OAUTH_SCOPES = [
|
|
849
|
+
'https://www.googleapis.com/auth/cloud-platform',
|
|
850
|
+
'https://www.googleapis.com/auth/userinfo.email',
|
|
851
|
+
'https://www.googleapis.com/auth/userinfo.profile',
|
|
852
|
+
];
|
|
853
|
+
|
|
854
|
+
// Google OAuth endpoints
|
|
855
|
+
const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
856
|
+
const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
857
|
+
const GOOGLE_USERINFO_URL = 'https://www.googleapis.com/oauth2/v2/userinfo';
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Google OAuth Plugin
|
|
861
|
+
* Supports:
|
|
862
|
+
* - Google AI Pro/Ultra OAuth login
|
|
863
|
+
* - Manual API key entry
|
|
864
|
+
*
|
|
865
|
+
* Note: This plugin uses OAuth 2.0 with PKCE for Google AI subscription authentication.
|
|
866
|
+
* After authenticating, you can use Gemini models with subscription benefits.
|
|
867
|
+
*/
|
|
868
|
+
const GooglePlugin: AuthPlugin = {
|
|
869
|
+
provider: 'google',
|
|
870
|
+
methods: [
|
|
871
|
+
{
|
|
872
|
+
label: 'Google AI Pro/Ultra (OAuth)',
|
|
873
|
+
type: 'oauth',
|
|
874
|
+
async authorize() {
|
|
875
|
+
const pkce = await generatePKCE();
|
|
876
|
+
const state = generateRandomString(16);
|
|
877
|
+
|
|
878
|
+
// Start local server to handle OAuth redirect
|
|
879
|
+
const server = http.createServer();
|
|
880
|
+
let serverPort = 0;
|
|
881
|
+
let authCode: string | null = null;
|
|
882
|
+
let authState: string | null = null;
|
|
883
|
+
|
|
884
|
+
const authPromise = new Promise<{ code: string; state: string }>(
|
|
885
|
+
(resolve, reject) => {
|
|
886
|
+
server.on('request', (req, res) => {
|
|
887
|
+
const url = new URL(req.url!, `http://localhost:${serverPort}`);
|
|
888
|
+
const code = url.searchParams.get('code');
|
|
889
|
+
const receivedState = url.searchParams.get('state');
|
|
890
|
+
const error = url.searchParams.get('error');
|
|
891
|
+
|
|
892
|
+
if (error) {
|
|
893
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
894
|
+
res.end(`
|
|
895
|
+
<html>
|
|
896
|
+
<body>
|
|
897
|
+
<h1>Authentication Failed</h1>
|
|
898
|
+
<p>Error: ${error}</p>
|
|
899
|
+
<p>You can close this window.</p>
|
|
900
|
+
</body>
|
|
901
|
+
</html>
|
|
902
|
+
`);
|
|
903
|
+
server.close();
|
|
904
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (code && receivedState) {
|
|
909
|
+
if (receivedState !== state) {
|
|
910
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
911
|
+
res.end('Invalid state parameter');
|
|
912
|
+
server.close();
|
|
913
|
+
reject(new Error('State mismatch - possible CSRF attack'));
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
918
|
+
res.end(`
|
|
919
|
+
<html>
|
|
920
|
+
<body>
|
|
921
|
+
<h1>Authentication Successful!</h1>
|
|
922
|
+
<p>You can close this window and return to the terminal.</p>
|
|
923
|
+
<script>window.close();</script>
|
|
924
|
+
</body>
|
|
925
|
+
</html>
|
|
926
|
+
`);
|
|
927
|
+
server.close();
|
|
928
|
+
resolve({ code, state: receivedState });
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
933
|
+
res.end('Missing code or state parameter');
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
server.listen(0, () => {
|
|
937
|
+
const address = server.address() as net.AddressInfo;
|
|
938
|
+
serverPort = address.port;
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
server.on('error', reject);
|
|
942
|
+
|
|
943
|
+
// Timeout after 5 minutes
|
|
944
|
+
setTimeout(
|
|
945
|
+
() => {
|
|
946
|
+
server.close();
|
|
947
|
+
reject(new Error('OAuth timeout'));
|
|
948
|
+
},
|
|
949
|
+
5 * 60 * 1000
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
// Build authorization URL with local redirect URI
|
|
955
|
+
const redirectUri = `http://localhost:${serverPort}/oauth/callback`;
|
|
956
|
+
const url = new URL(GOOGLE_AUTH_URL);
|
|
957
|
+
url.searchParams.set('client_id', GOOGLE_OAUTH_CLIENT_ID);
|
|
958
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
959
|
+
url.searchParams.set('response_type', 'code');
|
|
960
|
+
url.searchParams.set('scope', GOOGLE_OAUTH_SCOPES.join(' '));
|
|
961
|
+
url.searchParams.set('access_type', 'offline');
|
|
962
|
+
url.searchParams.set('code_challenge', pkce.challenge);
|
|
963
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
964
|
+
url.searchParams.set('state', state);
|
|
965
|
+
url.searchParams.set('prompt', 'consent');
|
|
966
|
+
|
|
967
|
+
return {
|
|
968
|
+
url: url.toString(),
|
|
969
|
+
instructions:
|
|
970
|
+
'Your browser will open for authentication. Complete the login and return to the terminal.',
|
|
971
|
+
method: 'auto' as const,
|
|
972
|
+
async callback(): Promise<AuthResult> {
|
|
973
|
+
try {
|
|
974
|
+
const { code } = await authPromise;
|
|
975
|
+
|
|
976
|
+
// Exchange authorization code for tokens
|
|
977
|
+
const tokenResult = await fetch(GOOGLE_TOKEN_URL, {
|
|
978
|
+
method: 'POST',
|
|
979
|
+
headers: {
|
|
980
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
981
|
+
},
|
|
982
|
+
body: new URLSearchParams({
|
|
983
|
+
code: code,
|
|
984
|
+
client_id: GOOGLE_OAUTH_CLIENT_ID,
|
|
985
|
+
client_secret: GOOGLE_OAUTH_CLIENT_SECRET,
|
|
986
|
+
redirect_uri: redirectUri,
|
|
987
|
+
grant_type: 'authorization_code',
|
|
988
|
+
code_verifier: pkce.verifier,
|
|
989
|
+
}),
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
if (!tokenResult.ok) {
|
|
993
|
+
log.error('google oauth token exchange failed', {
|
|
994
|
+
status: tokenResult.status,
|
|
995
|
+
});
|
|
996
|
+
return { type: 'failed' };
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const json = await tokenResult.json();
|
|
1000
|
+
if (
|
|
1001
|
+
!json.access_token ||
|
|
1002
|
+
!json.refresh_token ||
|
|
1003
|
+
typeof json.expires_in !== 'number'
|
|
1004
|
+
) {
|
|
1005
|
+
log.error('google oauth token response missing fields');
|
|
1006
|
+
return { type: 'failed' };
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
return {
|
|
1010
|
+
type: 'success',
|
|
1011
|
+
refresh: json.refresh_token,
|
|
1012
|
+
access: json.access_token,
|
|
1013
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
1014
|
+
};
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
log.error('google oauth failed', { error });
|
|
1017
|
+
return { type: 'failed' };
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
};
|
|
1021
|
+
},
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
label: 'Manually enter API Key',
|
|
1025
|
+
type: 'api',
|
|
1026
|
+
},
|
|
1027
|
+
],
|
|
1028
|
+
async loader(getAuth, provider) {
|
|
1029
|
+
const auth = await getAuth();
|
|
1030
|
+
if (!auth || auth.type !== 'oauth') return {};
|
|
1031
|
+
|
|
1032
|
+
// Zero out cost for subscription users
|
|
1033
|
+
if (provider?.models) {
|
|
1034
|
+
for (const model of Object.values(provider.models)) {
|
|
1035
|
+
(model as any).cost = {
|
|
1036
|
+
input: 0,
|
|
1037
|
+
output: 0,
|
|
1038
|
+
cache: {
|
|
1039
|
+
read: 0,
|
|
1040
|
+
write: 0,
|
|
1041
|
+
},
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return {
|
|
1047
|
+
apiKey: 'oauth-token-used-via-custom-fetch',
|
|
1048
|
+
async fetch(input: RequestInfo | URL, init?: RequestInit) {
|
|
1049
|
+
let currentAuth = await getAuth();
|
|
1050
|
+
if (!currentAuth || currentAuth.type !== 'oauth')
|
|
1051
|
+
return fetch(input, init);
|
|
1052
|
+
|
|
1053
|
+
// Refresh token if expired (with 5 minute buffer)
|
|
1054
|
+
const FIVE_MIN_MS = 5 * 60 * 1000;
|
|
1055
|
+
if (
|
|
1056
|
+
!currentAuth.access ||
|
|
1057
|
+
currentAuth.expires < Date.now() + FIVE_MIN_MS
|
|
1058
|
+
) {
|
|
1059
|
+
log.info('refreshing google oauth token');
|
|
1060
|
+
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
1061
|
+
method: 'POST',
|
|
1062
|
+
headers: {
|
|
1063
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
1064
|
+
},
|
|
1065
|
+
body: new URLSearchParams({
|
|
1066
|
+
client_id: GOOGLE_OAUTH_CLIENT_ID,
|
|
1067
|
+
client_secret: GOOGLE_OAUTH_CLIENT_SECRET,
|
|
1068
|
+
refresh_token: currentAuth.refresh,
|
|
1069
|
+
grant_type: 'refresh_token',
|
|
1070
|
+
}),
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
if (!response.ok) {
|
|
1074
|
+
throw new Error(`Token refresh failed: ${response.status}`);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const json = await response.json();
|
|
1078
|
+
await Auth.set('google', {
|
|
1079
|
+
type: 'oauth',
|
|
1080
|
+
// Google doesn't return a new refresh token on refresh
|
|
1081
|
+
refresh: currentAuth.refresh,
|
|
1082
|
+
access: json.access_token,
|
|
1083
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
1084
|
+
});
|
|
1085
|
+
currentAuth = {
|
|
1086
|
+
type: 'oauth',
|
|
1087
|
+
refresh: currentAuth.refresh,
|
|
1088
|
+
access: json.access_token,
|
|
1089
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Google API uses Bearer token authentication
|
|
1094
|
+
const headers: Record<string, string> = {
|
|
1095
|
+
...(init?.headers as Record<string, string>),
|
|
1096
|
+
Authorization: `Bearer ${currentAuth.access}`,
|
|
1097
|
+
};
|
|
1098
|
+
// Remove any API key header if present since we're using OAuth
|
|
1099
|
+
delete headers['x-goog-api-key'];
|
|
1100
|
+
|
|
1101
|
+
return fetch(input, {
|
|
1102
|
+
...init,
|
|
1103
|
+
headers,
|
|
1104
|
+
});
|
|
1105
|
+
},
|
|
1106
|
+
};
|
|
1107
|
+
},
|
|
1108
|
+
};
|
|
1109
|
+
|
|
835
1110
|
/**
|
|
836
1111
|
* Registry of all auth plugins
|
|
837
1112
|
*/
|
|
@@ -839,6 +1114,7 @@ const plugins: Record<string, AuthPlugin> = {
|
|
|
839
1114
|
anthropic: AnthropicPlugin,
|
|
840
1115
|
'github-copilot': GitHubCopilotPlugin,
|
|
841
1116
|
openai: OpenAIPlugin,
|
|
1117
|
+
google: GooglePlugin,
|
|
842
1118
|
};
|
|
843
1119
|
|
|
844
1120
|
/**
|
package/src/bun/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Log } from '../util/log';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { NamedError } from '../util/error';
|
|
6
6
|
import { readableStreamToText } from 'bun';
|
|
7
|
+
import { Flag } from '../flag/flag';
|
|
7
8
|
|
|
8
9
|
export namespace BunProc {
|
|
9
10
|
const log = Log.create({ service: 'bun' });
|
|
@@ -43,7 +44,10 @@ export namespace BunProc {
|
|
|
43
44
|
stderr,
|
|
44
45
|
});
|
|
45
46
|
if (code !== 0) {
|
|
46
|
-
|
|
47
|
+
const parts = [`Command failed with exit code ${result.exitCode}`];
|
|
48
|
+
if (stderr) parts.push(`stderr: ${stderr}`);
|
|
49
|
+
if (stdout) parts.push(`stdout: ${stdout}`);
|
|
50
|
+
throw new Error(parts.join('\n'));
|
|
47
51
|
}
|
|
48
52
|
return result;
|
|
49
53
|
}
|
|
@@ -57,6 +61,7 @@ export namespace BunProc {
|
|
|
57
61
|
z.object({
|
|
58
62
|
pkg: z.string(),
|
|
59
63
|
version: z.string(),
|
|
64
|
+
details: z.string().optional(),
|
|
60
65
|
})
|
|
61
66
|
);
|
|
62
67
|
|
|
@@ -70,6 +75,20 @@ export namespace BunProc {
|
|
|
70
75
|
});
|
|
71
76
|
if (parsed.dependencies[pkg] === version) return mod;
|
|
72
77
|
|
|
78
|
+
// Check for dry-run mode
|
|
79
|
+
if (Flag.OPENCODE_DRY_RUN) {
|
|
80
|
+
log.info(
|
|
81
|
+
'[DRY RUN] Would install package (skipping actual installation)',
|
|
82
|
+
{
|
|
83
|
+
pkg,
|
|
84
|
+
version,
|
|
85
|
+
targetPath: mod,
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
// In dry-run mode, pretend the package is installed
|
|
89
|
+
return mod;
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
// Build command arguments
|
|
74
93
|
const args = [
|
|
75
94
|
'add',
|
|
@@ -92,13 +111,20 @@ export namespace BunProc {
|
|
|
92
111
|
await BunProc.run(args, {
|
|
93
112
|
cwd: Global.Path.cache,
|
|
94
113
|
}).catch((e) => {
|
|
114
|
+
log.error('package installation failed', {
|
|
115
|
+
pkg,
|
|
116
|
+
version,
|
|
117
|
+
error: e instanceof Error ? e.message : String(e),
|
|
118
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
119
|
+
});
|
|
95
120
|
throw new InstallFailedError(
|
|
96
|
-
{ pkg, version },
|
|
121
|
+
{ pkg, version, details: e instanceof Error ? e.message : String(e) },
|
|
97
122
|
{
|
|
98
123
|
cause: e,
|
|
99
124
|
}
|
|
100
125
|
);
|
|
101
126
|
});
|
|
127
|
+
log.info('package installed successfully', { pkg, version });
|
|
102
128
|
parsed.dependencies[pkg] = version;
|
|
103
129
|
await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2));
|
|
104
130
|
return mod;
|
package/src/flag/flag.ts
CHANGED
|
@@ -22,11 +22,19 @@ export namespace Flag {
|
|
|
22
22
|
// Verbose mode - enables detailed logging of API requests
|
|
23
23
|
export let OPENCODE_VERBOSE = truthy('OPENCODE_VERBOSE');
|
|
24
24
|
|
|
25
|
+
// Dry run mode - simulate operations without making actual API calls or changes
|
|
26
|
+
export let OPENCODE_DRY_RUN = truthy('OPENCODE_DRY_RUN');
|
|
27
|
+
|
|
25
28
|
// Allow setting verbose mode programmatically (e.g., from CLI --verbose flag)
|
|
26
29
|
export function setVerbose(value: boolean) {
|
|
27
30
|
OPENCODE_VERBOSE = value;
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
// Allow setting dry run mode programmatically (e.g., from CLI --dry-run flag)
|
|
34
|
+
export function setDryRun(value: boolean) {
|
|
35
|
+
OPENCODE_DRY_RUN = value;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
function truthy(key: string) {
|
|
31
39
|
const value = process.env[key]?.toLowerCase();
|
|
32
40
|
return value === 'true' || value === '1';
|
package/src/global/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export namespace Global {
|
|
|
24
24
|
|
|
25
25
|
await Promise.all([
|
|
26
26
|
fs.mkdir(Global.Path.data, { recursive: true }),
|
|
27
|
+
fs.mkdir(Global.Path.cache, { recursive: true }),
|
|
27
28
|
fs.mkdir(Global.Path.config, { recursive: true }),
|
|
28
29
|
fs.mkdir(Global.Path.state, { recursive: true }),
|
|
29
30
|
fs.mkdir(Global.Path.log, { recursive: true }),
|
package/src/index.js
CHANGED
|
@@ -110,6 +110,13 @@ async function runAgentMode(argv) {
|
|
|
110
110
|
console.error(`Script path: ${import.meta.path}`);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// Log dry-run mode if enabled
|
|
114
|
+
if (Flag.OPENCODE_DRY_RUN) {
|
|
115
|
+
console.error(
|
|
116
|
+
`[DRY RUN MODE] No actual API calls or package installations will be made`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
113
120
|
// Parse model argument (handle model IDs with slashes like groq/qwen/qwen3-32b)
|
|
114
121
|
const modelParts = argv.model.split('/');
|
|
115
122
|
let providerID = modelParts[0] || 'opencode';
|
|
@@ -574,6 +581,12 @@ async function main() {
|
|
|
574
581
|
'Enable verbose mode to debug API requests (shows system prompt, token counts, etc.)',
|
|
575
582
|
default: false,
|
|
576
583
|
})
|
|
584
|
+
.option('dry-run', {
|
|
585
|
+
type: 'boolean',
|
|
586
|
+
description:
|
|
587
|
+
'Simulate operations without making actual API calls or package installations (useful for testing)',
|
|
588
|
+
default: false,
|
|
589
|
+
})
|
|
577
590
|
.option('use-existing-claude-oauth', {
|
|
578
591
|
type: 'boolean',
|
|
579
592
|
description:
|
|
@@ -588,6 +601,11 @@ async function main() {
|
|
|
588
601
|
Flag.setVerbose(true);
|
|
589
602
|
}
|
|
590
603
|
|
|
604
|
+
// Set dry-run flag if requested
|
|
605
|
+
if (argv['dry-run']) {
|
|
606
|
+
Flag.setDryRun(true);
|
|
607
|
+
}
|
|
608
|
+
|
|
591
609
|
// Initialize logging system
|
|
592
610
|
// - If verbose: print logs to stderr for debugging
|
|
593
611
|
// - Otherwise: write logs to file to keep CLI output clean
|
package/src/provider/provider.ts
CHANGED
|
@@ -319,6 +319,33 @@ export namespace Provider {
|
|
|
319
319
|
options: {},
|
|
320
320
|
};
|
|
321
321
|
},
|
|
322
|
+
/**
|
|
323
|
+
* Google OAuth provider for Gemini subscription users
|
|
324
|
+
* Uses OAuth credentials from agent auth login (Google AI Pro/Ultra)
|
|
325
|
+
*
|
|
326
|
+
* To authenticate, run: agent auth google
|
|
327
|
+
*/
|
|
328
|
+
google: async (input) => {
|
|
329
|
+
const auth = await Auth.get('google');
|
|
330
|
+
if (auth?.type === 'oauth') {
|
|
331
|
+
log.info('using google oauth credentials');
|
|
332
|
+
const loaderFn = await AuthPlugins.getLoader('google');
|
|
333
|
+
if (loaderFn) {
|
|
334
|
+
const result = await loaderFn(() => Auth.get('google'), input);
|
|
335
|
+
if (result.fetch) {
|
|
336
|
+
return {
|
|
337
|
+
autoload: true,
|
|
338
|
+
options: {
|
|
339
|
+
apiKey: result.apiKey || '',
|
|
340
|
+
fetch: result.fetch,
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Default: API key auth (no OAuth credentials found)
|
|
347
|
+
return { autoload: false };
|
|
348
|
+
},
|
|
322
349
|
/**
|
|
323
350
|
* GitHub Copilot OAuth provider
|
|
324
351
|
* Uses OAuth credentials from agent auth login
|
|
@@ -694,7 +721,17 @@ export namespace Provider {
|
|
|
694
721
|
|
|
695
722
|
let installedPath: string;
|
|
696
723
|
if (!pkg.startsWith('file://')) {
|
|
724
|
+
log.info('installing provider package', {
|
|
725
|
+
providerID: provider.id,
|
|
726
|
+
pkg,
|
|
727
|
+
version: 'latest',
|
|
728
|
+
});
|
|
697
729
|
installedPath = await BunProc.install(pkg, 'latest');
|
|
730
|
+
log.info('provider package installed successfully', {
|
|
731
|
+
providerID: provider.id,
|
|
732
|
+
pkg,
|
|
733
|
+
installedPath,
|
|
734
|
+
});
|
|
698
735
|
} else {
|
|
699
736
|
log.info('loading local provider', { pkg });
|
|
700
737
|
installedPath = pkg;
|
|
@@ -742,6 +779,13 @@ export namespace Provider {
|
|
|
742
779
|
s.sdk.set(key, loaded);
|
|
743
780
|
return loaded as SDK;
|
|
744
781
|
})().catch((e) => {
|
|
782
|
+
log.error('provider initialization failed', {
|
|
783
|
+
providerID: provider.id,
|
|
784
|
+
pkg: model.provider?.npm ?? provider.npm ?? provider.id,
|
|
785
|
+
error: e instanceof Error ? e.message : String(e),
|
|
786
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
787
|
+
cause: e instanceof Error && e.cause ? String(e.cause) : undefined,
|
|
788
|
+
});
|
|
745
789
|
throw new InitError({ providerID: provider.id }, { cause: e });
|
|
746
790
|
});
|
|
747
791
|
}
|