@agi-cli/sdk 0.1.168 → 0.1.170
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/src/copilot-oauth.ts +190 -0
- package/src/auth/src/index.ts +9 -0
- package/src/core/src/providers/resolver.ts +11 -0
- package/src/core/src/tools/builtin/patch.txt +6 -0
- package/src/index.ts +15 -0
- package/src/providers/src/catalog-manual.ts +0 -1
- package/src/providers/src/catalog.ts +1417 -109
- package/src/providers/src/copilot-client.ts +40 -0
- package/src/providers/src/env.ts +1 -0
- package/src/providers/src/index.ts +2 -0
- package/src/providers/src/openai-oauth-client.ts +15 -122
- package/src/providers/src/pricing.ts +1 -0
- package/src/providers/src/setu-client.ts +1 -1
- package/src/providers/src/utils.ts +3 -0
- package/src/types/src/index.ts +1 -0
- package/src/types/src/provider.ts +1 -0
package/package.json
CHANGED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const CLIENT_ID = 'Ov23li8tweQw6odWQebz';
|
|
4
|
+
const POLLING_SAFETY_MARGIN_MS = 3000;
|
|
5
|
+
|
|
6
|
+
const DEVICE_CODE_URL = 'https://github.com/login/device/code';
|
|
7
|
+
const ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token';
|
|
8
|
+
|
|
9
|
+
export type CopilotDeviceCodeResponse = {
|
|
10
|
+
verification_uri: string;
|
|
11
|
+
user_code: string;
|
|
12
|
+
device_code: string;
|
|
13
|
+
interval: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type CopilotOAuthResult = {
|
|
17
|
+
access_token: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function openBrowser(url: string) {
|
|
21
|
+
const platform = process.platform;
|
|
22
|
+
let command: string;
|
|
23
|
+
switch (platform) {
|
|
24
|
+
case 'darwin':
|
|
25
|
+
command = `open "${url}"`;
|
|
26
|
+
break;
|
|
27
|
+
case 'win32':
|
|
28
|
+
command = `start "${url}"`;
|
|
29
|
+
break;
|
|
30
|
+
default:
|
|
31
|
+
command = `xdg-open "${url}"`;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
return new Promise<void>((resolve, reject) => {
|
|
35
|
+
const child = spawn(command, [], { shell: true });
|
|
36
|
+
child.on('error', reject);
|
|
37
|
+
child.on('exit', (code) => {
|
|
38
|
+
if (code === 0) resolve();
|
|
39
|
+
else reject(new Error(`Failed to open browser (exit code ${code})`));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function requestDeviceCode(): Promise<CopilotDeviceCodeResponse> {
|
|
45
|
+
const response = await fetch(DEVICE_CODE_URL, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: {
|
|
48
|
+
Accept: 'application/json',
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify({
|
|
52
|
+
client_id: CLIENT_ID,
|
|
53
|
+
scope: 'read:user',
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error('Failed to initiate GitHub device authorization');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (await response.json()) as CopilotDeviceCodeResponse;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function pollForToken(
|
|
65
|
+
deviceCode: string,
|
|
66
|
+
interval: number,
|
|
67
|
+
): Promise<string> {
|
|
68
|
+
while (true) {
|
|
69
|
+
const response = await fetch(ACCESS_TOKEN_URL, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
Accept: 'application/json',
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
client_id: CLIENT_ID,
|
|
77
|
+
device_code: deviceCode,
|
|
78
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error('Token exchange request failed');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const data = (await response.json()) as {
|
|
87
|
+
access_token?: string;
|
|
88
|
+
error?: string;
|
|
89
|
+
interval?: number;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
if (data.access_token) {
|
|
93
|
+
return data.access_token;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (data.error === 'authorization_pending') {
|
|
97
|
+
await Bun.sleep(interval * 1000 + POLLING_SAFETY_MARGIN_MS);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (data.error === 'slow_down') {
|
|
102
|
+
let newInterval = (interval + 5) * 1000;
|
|
103
|
+
const serverInterval = data.interval;
|
|
104
|
+
if (
|
|
105
|
+
serverInterval &&
|
|
106
|
+
typeof serverInterval === 'number' &&
|
|
107
|
+
serverInterval > 0
|
|
108
|
+
) {
|
|
109
|
+
newInterval = serverInterval * 1000;
|
|
110
|
+
}
|
|
111
|
+
await Bun.sleep(newInterval + POLLING_SAFETY_MARGIN_MS);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (data.error) {
|
|
116
|
+
throw new Error(`GitHub OAuth error: ${data.error}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await Bun.sleep(interval * 1000 + POLLING_SAFETY_MARGIN_MS);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function authorizeCopilot(): Promise<{
|
|
124
|
+
verificationUri: string;
|
|
125
|
+
userCode: string;
|
|
126
|
+
deviceCode: string;
|
|
127
|
+
interval: number;
|
|
128
|
+
}> {
|
|
129
|
+
const deviceData = await requestDeviceCode();
|
|
130
|
+
return {
|
|
131
|
+
verificationUri: deviceData.verification_uri,
|
|
132
|
+
userCode: deviceData.user_code,
|
|
133
|
+
deviceCode: deviceData.device_code,
|
|
134
|
+
interval: deviceData.interval,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type CopilotPollResult =
|
|
139
|
+
| { status: 'complete'; accessToken: string }
|
|
140
|
+
| { status: 'pending' }
|
|
141
|
+
| { status: 'error'; error: string };
|
|
142
|
+
|
|
143
|
+
export async function pollForTokenOnce(
|
|
144
|
+
deviceCode: string,
|
|
145
|
+
): Promise<CopilotPollResult> {
|
|
146
|
+
const response = await fetch(ACCESS_TOKEN_URL, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: {
|
|
149
|
+
Accept: 'application/json',
|
|
150
|
+
'Content-Type': 'application/json',
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
client_id: CLIENT_ID,
|
|
154
|
+
device_code: deviceCode,
|
|
155
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
return { status: 'error', error: 'Token exchange request failed' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const data = (await response.json()) as {
|
|
164
|
+
access_token?: string;
|
|
165
|
+
error?: string;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (data.access_token) {
|
|
169
|
+
return { status: 'complete', accessToken: data.access_token };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (data.error === 'authorization_pending' || data.error === 'slow_down') {
|
|
173
|
+
return { status: 'pending' };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (data.error) {
|
|
177
|
+
return { status: 'error', error: `GitHub OAuth error: ${data.error}` };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { status: 'pending' };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function openCopilotAuthUrl(url: string): Promise<boolean> {
|
|
184
|
+
try {
|
|
185
|
+
await openBrowser(url);
|
|
186
|
+
return true;
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
package/src/auth/src/index.ts
CHANGED
|
@@ -89,3 +89,12 @@ export {
|
|
|
89
89
|
ensureSetuWallet,
|
|
90
90
|
type WalletInfo,
|
|
91
91
|
} from './wallet.ts';
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
authorizeCopilot,
|
|
95
|
+
pollForToken as pollForCopilotToken,
|
|
96
|
+
pollForTokenOnce as pollForCopilotTokenOnce,
|
|
97
|
+
openCopilotAuthUrl,
|
|
98
|
+
type CopilotDeviceCodeResponse,
|
|
99
|
+
type CopilotPollResult,
|
|
100
|
+
} from './copilot-oauth.ts';
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createSetuModel,
|
|
9
9
|
createOpenAIOAuthModel,
|
|
10
10
|
} from '../../../providers/src/index.ts';
|
|
11
|
+
import { createCopilotModel } from '../../../providers/src/copilot-client.ts';
|
|
11
12
|
import type { OAuth } from '../../../types/src/index.ts';
|
|
12
13
|
|
|
13
14
|
function needsResponsesApi(model: string): boolean {
|
|
@@ -33,6 +34,7 @@ export type ProviderName =
|
|
|
33
34
|
| 'google'
|
|
34
35
|
| 'openrouter'
|
|
35
36
|
| 'opencode'
|
|
37
|
+
| 'copilot'
|
|
36
38
|
| 'setu'
|
|
37
39
|
| 'zai'
|
|
38
40
|
| 'zai-coding'
|
|
@@ -152,6 +154,15 @@ export async function resolveModel(
|
|
|
152
154
|
return ocOpenAI(resolvedModelId);
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
if (provider === 'copilot') {
|
|
158
|
+
if (config.oauth) {
|
|
159
|
+
return createCopilotModel(model, { oauth: config.oauth });
|
|
160
|
+
}
|
|
161
|
+
throw new Error(
|
|
162
|
+
'Copilot provider requires OAuth. Run `agi auth login copilot`.',
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
155
166
|
if (provider === 'setu') {
|
|
156
167
|
const privateKey = config.apiKey || process.env.SETU_PRIVATE_KEY || '';
|
|
157
168
|
if (!privateKey) {
|
|
@@ -184,6 +184,12 @@ The tool will convert it automatically, but the enveloped format is still recomm
|
|
|
184
184
|
|
|
185
185
|
## Common Errors
|
|
186
186
|
|
|
187
|
+
**"Missing *** End Patch marker"**: Every patch MUST end with `*** End Patch` on its own line.
|
|
188
|
+
This is the most common error. Double-check your patch ends with:
|
|
189
|
+
```
|
|
190
|
+
*** End Patch
|
|
191
|
+
```
|
|
192
|
+
|
|
187
193
|
**"Failed to find expected lines"**: The file content doesn't match your patch. Common causes:
|
|
188
194
|
- Missing context lines (lines with space prefix)
|
|
189
195
|
- Using `@@` line as context instead of real context lines
|
package/src/index.ts
CHANGED
|
@@ -106,6 +106,11 @@ export { createOpencodeModel } from './providers/src/index.ts';
|
|
|
106
106
|
export type { OpencodeProviderConfig } from './providers/src/index.ts';
|
|
107
107
|
export { createMoonshotModel } from './providers/src/index.ts';
|
|
108
108
|
export type { MoonshotProviderConfig } from './providers/src/index.ts';
|
|
109
|
+
export {
|
|
110
|
+
createCopilotFetch,
|
|
111
|
+
createCopilotModel,
|
|
112
|
+
} from './providers/src/index.ts';
|
|
113
|
+
export type { CopilotOAuthConfig } from './providers/src/index.ts';
|
|
109
114
|
|
|
110
115
|
// =======================
|
|
111
116
|
// Authentication (from internal auth module)
|
|
@@ -140,6 +145,16 @@ export {
|
|
|
140
145
|
ensureSetuWallet,
|
|
141
146
|
} from './auth/src/index.ts';
|
|
142
147
|
export type { WalletInfo } from './auth/src/index.ts';
|
|
148
|
+
export {
|
|
149
|
+
authorizeCopilot,
|
|
150
|
+
pollForCopilotToken,
|
|
151
|
+
pollForCopilotTokenOnce,
|
|
152
|
+
openCopilotAuthUrl,
|
|
153
|
+
} from './auth/src/index.ts';
|
|
154
|
+
export type {
|
|
155
|
+
CopilotDeviceCodeResponse,
|
|
156
|
+
CopilotPollResult,
|
|
157
|
+
} from './auth/src/index.ts';
|
|
143
158
|
|
|
144
159
|
// =======================
|
|
145
160
|
// Configuration (from internal config module)
|
|
@@ -82,7 +82,6 @@ function buildSetuEntry(base: CatalogMap): ProviderCatalogEntry | null {
|
|
|
82
82
|
|
|
83
83
|
if (!setuModels.length) return null;
|
|
84
84
|
|
|
85
|
-
// Prefer OpenAI-family models first so defaults are stable
|
|
86
85
|
setuModels.sort((a, b) => {
|
|
87
86
|
const providerA = a.provider?.npm ?? '';
|
|
88
87
|
const providerB = b.provider?.npm ?? '';
|