@witqq/agent-sdk 0.3.0 → 0.4.0

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.
@@ -0,0 +1,352 @@
1
+ import { createHash, randomBytes } from 'crypto';
2
+
3
+ // src/auth/types.ts
4
+ var AuthError = class extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = "AuthError";
8
+ }
9
+ };
10
+ var DeviceCodeExpiredError = class extends AuthError {
11
+ constructor() {
12
+ super("Device code expired. Please restart the auth flow.");
13
+ this.name = "DeviceCodeExpiredError";
14
+ }
15
+ };
16
+ var AccessDeniedError = class extends AuthError {
17
+ constructor() {
18
+ super("Access was denied by the user.");
19
+ this.name = "AccessDeniedError";
20
+ }
21
+ };
22
+ var TokenExchangeError = class extends AuthError {
23
+ constructor(message, options) {
24
+ super(message, options);
25
+ this.name = "TokenExchangeError";
26
+ }
27
+ };
28
+
29
+ // src/auth/copilot-auth.ts
30
+ var CLIENT_ID = "Ov23ctDVkRmgkPke0Mmm";
31
+ var DEVICE_CODE_URL = "https://github.com/login/device/code";
32
+ var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
33
+ var USER_API_URL = "https://api.github.com/user";
34
+ var DEFAULT_SCOPES = "read:user,read:org,repo,gist";
35
+ var GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
36
+ var CopilotAuth = class {
37
+ fetchFn;
38
+ /** @param options - Optional configuration with custom fetch for testing */
39
+ constructor(options) {
40
+ this.fetchFn = options?.fetch ?? globalThis.fetch;
41
+ }
42
+ /**
43
+ * Start the GitHub Device Flow.
44
+ * Returns a device code result with user code, verification URL,
45
+ * and a `waitForToken()` function that polls until the user authorizes.
46
+ *
47
+ * @param options - Optional scopes and abort signal
48
+ * @returns Device flow result with user code, verification URL, and waitForToken poller
49
+ * @throws {AuthError} If the device code request fails
50
+ * @throws {DeviceCodeExpiredError} If the device code expires before user authorizes
51
+ * @throws {AccessDeniedError} If the user denies access
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const auth = new CopilotAuth();
56
+ * const { userCode, verificationUrl, waitForToken } = await auth.startDeviceFlow();
57
+ * console.log(`Open ${verificationUrl} and enter code: ${userCode}`);
58
+ * const token = await waitForToken();
59
+ * ```
60
+ */
61
+ async startDeviceFlow(options) {
62
+ const scopes = options?.scopes ?? DEFAULT_SCOPES;
63
+ const response = await this.fetchFn(DEVICE_CODE_URL, {
64
+ method: "POST",
65
+ headers: {
66
+ "Content-Type": "application/x-www-form-urlencoded",
67
+ Accept: "application/json"
68
+ },
69
+ body: new URLSearchParams({
70
+ client_id: CLIENT_ID,
71
+ scope: scopes
72
+ }),
73
+ signal: options?.signal
74
+ });
75
+ if (!response.ok) {
76
+ throw new AuthError(
77
+ `Failed to request device code: ${response.status} ${response.statusText}`
78
+ );
79
+ }
80
+ const data = await response.json();
81
+ return {
82
+ userCode: data.user_code,
83
+ verificationUrl: data.verification_uri,
84
+ waitForToken: (signal) => this.pollForToken(data.device_code, data.interval, signal)
85
+ };
86
+ }
87
+ async pollForToken(deviceCode, interval, signal) {
88
+ let pollIntervalMs = interval * 1e3;
89
+ while (true) {
90
+ if (signal?.aborted) {
91
+ throw new AuthError("Authentication was aborted.");
92
+ }
93
+ await this.delay(pollIntervalMs, signal);
94
+ const response = await this.fetchFn(ACCESS_TOKEN_URL, {
95
+ method: "POST",
96
+ headers: {
97
+ "Content-Type": "application/x-www-form-urlencoded",
98
+ Accept: "application/json"
99
+ },
100
+ body: new URLSearchParams({
101
+ client_id: CLIENT_ID,
102
+ device_code: deviceCode,
103
+ grant_type: GRANT_TYPE
104
+ }),
105
+ signal
106
+ });
107
+ if (!response.ok) {
108
+ throw new AuthError(
109
+ `Token poll failed: ${response.status} ${response.statusText}`
110
+ );
111
+ }
112
+ const data = await response.json();
113
+ if (data.access_token) {
114
+ const token = {
115
+ accessToken: data.access_token,
116
+ tokenType: data.token_type ?? "bearer",
117
+ obtainedAt: Date.now()
118
+ };
119
+ try {
120
+ const login = await this.fetchUserLogin(data.access_token, signal);
121
+ if (login) token.login = login;
122
+ } catch {
123
+ }
124
+ return token;
125
+ }
126
+ if (data.error === "authorization_pending") {
127
+ continue;
128
+ }
129
+ if (data.error === "slow_down") {
130
+ pollIntervalMs = (data.interval ?? interval + 5) * 1e3;
131
+ continue;
132
+ }
133
+ if (data.error === "expired_token") {
134
+ throw new DeviceCodeExpiredError();
135
+ }
136
+ if (data.error === "access_denied") {
137
+ throw new AccessDeniedError();
138
+ }
139
+ throw new AuthError(
140
+ data.error_description ?? `Unexpected error: ${data.error}`
141
+ );
142
+ }
143
+ }
144
+ async fetchUserLogin(token, signal) {
145
+ const response = await this.fetchFn(USER_API_URL, {
146
+ headers: {
147
+ Authorization: `Bearer ${token}`,
148
+ Accept: "application/json"
149
+ },
150
+ signal
151
+ });
152
+ if (!response.ok) return void 0;
153
+ const user = await response.json();
154
+ return user.login;
155
+ }
156
+ delay(ms, signal) {
157
+ return new Promise((resolve, reject) => {
158
+ const timer = setTimeout(resolve, ms);
159
+ signal?.addEventListener(
160
+ "abort",
161
+ () => {
162
+ clearTimeout(timer);
163
+ reject(new AuthError("Authentication was aborted."));
164
+ },
165
+ { once: true }
166
+ );
167
+ });
168
+ }
169
+ };
170
+ var CLIENT_ID2 = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
171
+ var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
172
+ var TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
173
+ var DEFAULT_REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
174
+ var DEFAULT_SCOPES2 = "user:profile user:inference user:sessions:claude_code user:mcp_servers";
175
+ var ClaudeAuth = class _ClaudeAuth {
176
+ fetchFn;
177
+ randomBytes;
178
+ /**
179
+ * @param options - Optional configuration with custom fetch and random bytes for testing
180
+ */
181
+ constructor(options) {
182
+ this.fetchFn = options?.fetch ?? globalThis.fetch;
183
+ this.randomBytes = options?.randomBytes ?? defaultRandomBytes;
184
+ }
185
+ /**
186
+ * Start the Claude OAuth+PKCE flow.
187
+ * Generates PKCE code verifier/challenge and returns an authorize URL
188
+ * plus a `completeAuth(code)` function for token exchange.
189
+ *
190
+ * @param options - Redirect URI and optional scopes
191
+ * @returns OAuth flow result with authorize URL and completeAuth function
192
+ * @throws {AuthError} If PKCE generation fails
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * const auth = new ClaudeAuth();
197
+ * const { authorizeUrl, completeAuth } = auth.startOAuthFlow({
198
+ * redirectUri: "https://platform.claude.com/oauth/code/callback",
199
+ * });
200
+ * console.log(`Open: ${authorizeUrl}`);
201
+ * const token = await completeAuth(authorizationCode);
202
+ * ```
203
+ */
204
+ startOAuthFlow(options) {
205
+ const redirectUri = options?.redirectUri ?? DEFAULT_REDIRECT_URI;
206
+ const scopes = options?.scopes ?? DEFAULT_SCOPES2;
207
+ const codeVerifier = this.generateCodeVerifier();
208
+ const state = this.generateState();
209
+ const authorizeUrl = this.buildAuthorizeUrl(
210
+ redirectUri,
211
+ scopes,
212
+ codeVerifier,
213
+ state
214
+ );
215
+ return {
216
+ authorizeUrl,
217
+ completeAuth: (codeOrUrl) => {
218
+ const code = _ClaudeAuth.extractCode(codeOrUrl);
219
+ return this.exchangeCode(code, codeVerifier, state, redirectUri);
220
+ }
221
+ };
222
+ }
223
+ /**
224
+ * Extract an authorization code from user input.
225
+ * Accepts a raw code string or a full redirect URL containing a `code` query parameter.
226
+ *
227
+ * @param input - Raw authorization code or redirect URL
228
+ * @returns The extracted authorization code
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * ClaudeAuth.extractCode("abc123"); // "abc123"
233
+ * ClaudeAuth.extractCode("https://platform.claude.com/oauth/code/callback?code=abc123&state=xyz"); // "abc123"
234
+ * ```
235
+ */
236
+ static extractCode(input) {
237
+ const trimmed = input.trim();
238
+ try {
239
+ const url = new URL(trimmed);
240
+ const code = url.searchParams.get("code");
241
+ if (code) return code;
242
+ } catch {
243
+ }
244
+ const hashIdx = trimmed.indexOf("#");
245
+ if (hashIdx > 0) {
246
+ return trimmed.substring(0, hashIdx);
247
+ }
248
+ return trimmed;
249
+ }
250
+ /**
251
+ * Refresh an expired Claude token.
252
+ *
253
+ * @param refreshToken - The refresh token from a previous authentication
254
+ * @returns New auth token with refreshed access token
255
+ * @throws {TokenExchangeError} If the refresh request fails
256
+ *
257
+ * @example
258
+ * ```ts
259
+ * const auth = new ClaudeAuth();
260
+ * const newToken = await auth.refreshToken(oldToken.refreshToken);
261
+ * ```
262
+ */
263
+ async refreshToken(refreshToken) {
264
+ const response = await this.fetchFn(TOKEN_URL, {
265
+ method: "POST",
266
+ headers: { "Content-Type": "application/json" },
267
+ body: JSON.stringify({
268
+ grant_type: "refresh_token",
269
+ refresh_token: refreshToken,
270
+ client_id: CLIENT_ID2
271
+ })
272
+ });
273
+ if (!response.ok) {
274
+ const text = await response.text();
275
+ throw new TokenExchangeError(
276
+ `Token refresh failed: ${response.status} ${text}`
277
+ );
278
+ }
279
+ const data = await response.json();
280
+ return this.mapTokenResponse(data);
281
+ }
282
+ generateCodeVerifier() {
283
+ const bytes = this.randomBytes(96);
284
+ return base64Encode(bytes).replace(/\+/g, "~").replace(/=/g, "_").replace(/\//g, "-");
285
+ }
286
+ generateState() {
287
+ const bytes = this.randomBytes(16);
288
+ return hexEncode(bytes);
289
+ }
290
+ buildAuthorizeUrl(redirectUri, scopes, codeVerifier, state) {
291
+ const codeChallenge = this.generateCodeChallengeSync(codeVerifier);
292
+ const url = new URL(AUTHORIZE_URL);
293
+ url.searchParams.set("code", "true");
294
+ url.searchParams.set("client_id", CLIENT_ID2);
295
+ url.searchParams.set("response_type", "code");
296
+ url.searchParams.set("redirect_uri", redirectUri);
297
+ url.searchParams.set("scope", scopes);
298
+ url.searchParams.set("code_challenge", codeChallenge);
299
+ url.searchParams.set("code_challenge_method", "S256");
300
+ url.searchParams.set("state", state);
301
+ return url.toString();
302
+ }
303
+ generateCodeChallengeSync(verifier) {
304
+ const hash = createHash("sha256").update(verifier).digest("base64");
305
+ return hash.split("=")[0].replace(/\+/g, "-").replace(/\//g, "_");
306
+ }
307
+ async exchangeCode(code, codeVerifier, state, redirectUri) {
308
+ const response = await this.fetchFn(TOKEN_URL, {
309
+ method: "POST",
310
+ headers: { "Content-Type": "application/json" },
311
+ body: JSON.stringify({
312
+ grant_type: "authorization_code",
313
+ code,
314
+ redirect_uri: redirectUri,
315
+ client_id: CLIENT_ID2,
316
+ code_verifier: codeVerifier,
317
+ state
318
+ })
319
+ });
320
+ if (!response.ok) {
321
+ const text = await response.text();
322
+ throw new TokenExchangeError(
323
+ `Token exchange failed: ${response.status} ${text}`
324
+ );
325
+ }
326
+ const data = await response.json();
327
+ return this.mapTokenResponse(data);
328
+ }
329
+ mapTokenResponse(data) {
330
+ return {
331
+ accessToken: data.access_token,
332
+ tokenType: data.token_type ?? "bearer",
333
+ expiresIn: data.expires_in,
334
+ obtainedAt: Date.now(),
335
+ refreshToken: data.refresh_token,
336
+ scopes: data.scope?.split(" ") ?? []
337
+ };
338
+ }
339
+ };
340
+ function defaultRandomBytes(size) {
341
+ return new Uint8Array(randomBytes(size));
342
+ }
343
+ function base64Encode(bytes) {
344
+ return Buffer.from(bytes).toString("base64");
345
+ }
346
+ function hexEncode(bytes) {
347
+ return Buffer.from(bytes).toString("hex");
348
+ }
349
+
350
+ export { AccessDeniedError, AuthError, ClaudeAuth, CopilotAuth, DeviceCodeExpiredError, TokenExchangeError };
351
+ //# sourceMappingURL=index.js.map
352
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/auth/types.ts","../../src/auth/copilot-auth.ts","../../src/auth/claude-auth.ts"],"names":["CLIENT_ID","DEFAULT_SCOPES","nodeRandomBytes"],"mappings":"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","file":"index.js","sourcesContent":["// ─── Auth Token Types ──────────────────────────────────────────\n\n/**\n * Base auth token returned by all auth providers.\n *\n * @example\n * ```ts\n * import type { AuthToken } from \"@witqq/agent-sdk/auth\";\n *\n * const token: AuthToken = {\n * accessToken: \"gho_abc123...\",\n * tokenType: \"bearer\",\n * obtainedAt: Date.now(),\n * };\n * ```\n */\nexport interface AuthToken {\n /** The access token string */\n accessToken: string;\n /** Token type (e.g. \"bearer\") */\n tokenType: string;\n /** Seconds until token expires (undefined = long-lived) */\n expiresIn?: number;\n /** Timestamp when the token was obtained */\n obtainedAt: number;\n}\n\n/**\n * Copilot-specific token (GitHub OAuth, long-lived).\n *\n * @example\n * ```ts\n * import type { CopilotAuthToken } from \"@witqq/agent-sdk/auth\";\n *\n * const token: CopilotAuthToken = {\n * accessToken: \"gho_abc123...\",\n * tokenType: \"bearer\",\n * obtainedAt: Date.now(),\n * login: \"octocat\",\n * };\n * ```\n */\nexport interface CopilotAuthToken extends AuthToken {\n /** GitHub user login associated with the token */\n login?: string;\n}\n\n/**\n * Claude-specific token (OAuth+PKCE, expires in 8h).\n *\n * @example\n * ```ts\n * import type { ClaudeAuthToken } from \"@witqq/agent-sdk/auth\";\n *\n * const token: ClaudeAuthToken = {\n * accessToken: \"sk-ant-oat01-...\",\n * tokenType: \"bearer\",\n * expiresIn: 28800,\n * obtainedAt: Date.now(),\n * refreshToken: \"sk-ant-rt01-...\",\n * scopes: [\"user:inference\", \"user:profile\"],\n * };\n * ```\n */\nexport interface ClaudeAuthToken extends AuthToken {\n /** Refresh token for obtaining new access tokens */\n refreshToken: string;\n /** OAuth scopes granted */\n scopes: string[];\n}\n\n// ─── Device Flow Types (Copilot) ──────────────────────────────\n\n/**\n * Result of initiating a GitHub Device Flow.\n *\n * @example\n * ```ts\n * import { CopilotAuth } from \"@witqq/agent-sdk/auth\";\n *\n * const auth = new CopilotAuth();\n * const { userCode, verificationUrl, waitForToken } = await auth.startDeviceFlow();\n * console.log(`Open ${verificationUrl} and enter code: ${userCode}`);\n * const token = await waitForToken();\n * ```\n */\nexport interface DeviceFlowResult {\n /** The code the user must enter at the verification URL */\n userCode: string;\n /** URL where the user enters the code */\n verificationUrl: string;\n /** Polls GitHub until user authorizes; resolves with token */\n waitForToken: (signal?: AbortSignal) => Promise<CopilotAuthToken>;\n}\n\n// ─── OAuth Flow Types (Claude) ────────────────────────────────\n\n/** Options for starting a Claude OAuth flow */\nexport interface OAuthFlowOptions {\n /** The redirect URI registered with the OAuth app */\n redirectUri?: string;\n /** OAuth scopes to request (defaults to user:profile user:inference) */\n scopes?: string;\n}\n\n/**\n * Result of initiating a Claude OAuth flow.\n *\n * @example\n * ```ts\n * import type { OAuthFlowResult } from \"@witqq/agent-sdk/auth\";\n *\n * const result: OAuthFlowResult = {\n * authorizeUrl: \"https://claude.ai/oauth/authorize?...\",\n * completeAuth: async (code) => ({ ... }),\n * };\n * // Open result.authorizeUrl in browser, get code from redirect\n * const token = await result.completeAuth(code);\n * ```\n */\nexport interface OAuthFlowResult {\n /** URL to open in browser for user authorization */\n authorizeUrl: string;\n /** Exchange the authorization code (or full redirect URL) for tokens */\n completeAuth: (codeOrUrl: string) => Promise<ClaudeAuthToken>;\n}\n\n// ─── Auth Errors ──────────────────────────────────────────────\n\n/** Base error for auth operations.\n * @param message - Error description\n * @param options - Standard ErrorOptions (e.g. cause)\n */\nexport class AuthError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"AuthError\";\n }\n}\n\n/** Device code expired before user authorized */\nexport class DeviceCodeExpiredError extends AuthError {\n constructor() {\n super(\"Device code expired. Please restart the auth flow.\");\n this.name = \"DeviceCodeExpiredError\";\n }\n}\n\n/** User denied access during OAuth flow */\nexport class AccessDeniedError extends AuthError {\n constructor() {\n super(\"Access was denied by the user.\");\n this.name = \"AccessDeniedError\";\n }\n}\n\n/** Token exchange or refresh failed.\n * @param message - Error description\n * @param options - Standard ErrorOptions (e.g. cause)\n */\nexport class TokenExchangeError extends AuthError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"TokenExchangeError\";\n }\n}\n","import type { CopilotAuthToken, DeviceFlowResult } from \"./types.js\";\nimport {\n AuthError,\n DeviceCodeExpiredError,\n AccessDeniedError,\n} from \"./types.js\";\n\nconst CLIENT_ID = \"Ov23ctDVkRmgkPke0Mmm\";\nconst DEVICE_CODE_URL = \"https://github.com/login/device/code\";\nconst ACCESS_TOKEN_URL = \"https://github.com/login/oauth/access_token\";\nconst USER_API_URL = \"https://api.github.com/user\";\nconst DEFAULT_SCOPES = \"read:user,read:org,repo,gist\";\nconst GRANT_TYPE = \"urn:ietf:params:oauth:grant-type:device_code\";\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n expires_in: number;\n interval: number;\n}\n\ninterface TokenPollResponse {\n access_token?: string;\n token_type?: string;\n scope?: string;\n error?: string;\n error_description?: string;\n interval?: number;\n}\n\ninterface GitHubUser {\n login: string;\n}\n\n/** Fetch function type for dependency injection in tests */\ntype FetchFn = typeof globalThis.fetch;\n\n/**\n * Programmatic GitHub Device Flow authentication for Copilot SDK.\n *\n * @example\n * ```ts\n * const auth = new CopilotAuth();\n * const flow = await auth.startDeviceFlow();\n * console.log(`Open ${flow.verificationUrl} and enter ${flow.userCode}`);\n * const token = await flow.waitForToken();\n * // Use token.accessToken with CopilotBackendOptions.githubToken\n * ```\n */\nexport class CopilotAuth {\n private readonly fetchFn: FetchFn;\n\n /** @param options - Optional configuration with custom fetch for testing */\n constructor(options?: { fetch?: FetchFn }) {\n this.fetchFn = options?.fetch ?? globalThis.fetch;\n }\n\n /**\n * Start the GitHub Device Flow.\n * Returns a device code result with user code, verification URL,\n * and a `waitForToken()` function that polls until the user authorizes.\n *\n * @param options - Optional scopes and abort signal\n * @returns Device flow result with user code, verification URL, and waitForToken poller\n * @throws {AuthError} If the device code request fails\n * @throws {DeviceCodeExpiredError} If the device code expires before user authorizes\n * @throws {AccessDeniedError} If the user denies access\n *\n * @example\n * ```ts\n * const auth = new CopilotAuth();\n * const { userCode, verificationUrl, waitForToken } = await auth.startDeviceFlow();\n * console.log(`Open ${verificationUrl} and enter code: ${userCode}`);\n * const token = await waitForToken();\n * ```\n */\n async startDeviceFlow(options?: {\n scopes?: string;\n signal?: AbortSignal;\n }): Promise<DeviceFlowResult> {\n const scopes = options?.scopes ?? DEFAULT_SCOPES;\n const response = await this.fetchFn(DEVICE_CODE_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n scope: scopes,\n }),\n signal: options?.signal,\n });\n\n if (!response.ok) {\n throw new AuthError(\n `Failed to request device code: ${response.status} ${response.statusText}`,\n );\n }\n\n const data = (await response.json()) as DeviceCodeResponse;\n\n return {\n userCode: data.user_code,\n verificationUrl: data.verification_uri,\n waitForToken: (signal?: AbortSignal) =>\n this.pollForToken(data.device_code, data.interval, signal),\n };\n }\n\n private async pollForToken(\n deviceCode: string,\n interval: number,\n signal?: AbortSignal,\n ): Promise<CopilotAuthToken> {\n let pollIntervalMs = interval * 1000;\n\n while (true) {\n if (signal?.aborted) {\n throw new AuthError(\"Authentication was aborted.\");\n }\n\n await this.delay(pollIntervalMs, signal);\n\n const response = await this.fetchFn(ACCESS_TOKEN_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: new URLSearchParams({\n client_id: CLIENT_ID,\n device_code: deviceCode,\n grant_type: GRANT_TYPE,\n }),\n signal,\n });\n\n if (!response.ok) {\n throw new AuthError(\n `Token poll failed: ${response.status} ${response.statusText}`,\n );\n }\n\n const data = (await response.json()) as TokenPollResponse;\n\n if (data.access_token) {\n const token: CopilotAuthToken = {\n accessToken: data.access_token,\n tokenType: data.token_type ?? \"bearer\",\n obtainedAt: Date.now(),\n };\n\n // Try to fetch user login\n try {\n const login = await this.fetchUserLogin(data.access_token, signal);\n if (login) token.login = login;\n } catch {\n // Non-critical: login is optional\n }\n\n return token;\n }\n\n if (data.error === \"authorization_pending\") {\n continue;\n }\n\n if (data.error === \"slow_down\") {\n pollIntervalMs = (data.interval ?? interval + 5) * 1000;\n continue;\n }\n\n if (data.error === \"expired_token\") {\n throw new DeviceCodeExpiredError();\n }\n\n if (data.error === \"access_denied\") {\n throw new AccessDeniedError();\n }\n\n throw new AuthError(\n data.error_description ?? `Unexpected error: ${data.error}`,\n );\n }\n }\n\n private async fetchUserLogin(\n token: string,\n signal?: AbortSignal,\n ): Promise<string | undefined> {\n const response = await this.fetchFn(USER_API_URL, {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"application/json\",\n },\n signal,\n });\n\n if (!response.ok) return undefined;\n\n const user = (await response.json()) as GitHubUser;\n return user.login;\n }\n\n private delay(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n reject(new AuthError(\"Authentication was aborted.\"));\n },\n { once: true },\n );\n });\n }\n}\n","import type { ClaudeAuthToken, OAuthFlowResult, OAuthFlowOptions } from \"./types.js\";\nimport { TokenExchangeError } from \"./types.js\";\nimport { createHash, randomBytes as nodeRandomBytes } from \"node:crypto\";\n\nconst CLIENT_ID = \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\";\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://platform.claude.com/v1/oauth/token\";\nconst DEFAULT_REDIRECT_URI =\n \"https://platform.claude.com/oauth/code/callback\";\nconst DEFAULT_SCOPES =\n \"user:profile user:inference user:sessions:claude_code user:mcp_servers\";\n\n/** Fetch function type for dependency injection in tests */\ntype FetchFn = typeof globalThis.fetch;\n\n/** Random bytes generator for dependency injection in tests */\ntype RandomBytesFn = (size: number) => Uint8Array;\n\n/**\n * Programmatic OAuth+PKCE authentication for Claude SDK.\n *\n * @example\n * ```ts\n * const auth = new ClaudeAuth();\n * const { authorizeUrl, completeAuth } = auth.startOAuthFlow({\n * redirectUri: \"https://platform.claude.com/oauth/code/callback\",\n * });\n * // Open authorizeUrl in browser, get code from redirect\n * const token = await completeAuth(code);\n * // Use token with ClaudeBackendOptions: env.CLAUDE_CODE_OAUTH_TOKEN\n * ```\n */\nexport class ClaudeAuth {\n private readonly fetchFn: FetchFn;\n private readonly randomBytes: RandomBytesFn;\n\n /**\n * @param options - Optional configuration with custom fetch and random bytes for testing\n */\n constructor(options?: {\n fetch?: FetchFn;\n randomBytes?: RandomBytesFn;\n }) {\n this.fetchFn = options?.fetch ?? globalThis.fetch;\n this.randomBytes = options?.randomBytes ?? defaultRandomBytes;\n }\n\n /**\n * Start the Claude OAuth+PKCE flow.\n * Generates PKCE code verifier/challenge and returns an authorize URL\n * plus a `completeAuth(code)` function for token exchange.\n *\n * @param options - Redirect URI and optional scopes\n * @returns OAuth flow result with authorize URL and completeAuth function\n * @throws {AuthError} If PKCE generation fails\n *\n * @example\n * ```ts\n * const auth = new ClaudeAuth();\n * const { authorizeUrl, completeAuth } = auth.startOAuthFlow({\n * redirectUri: \"https://platform.claude.com/oauth/code/callback\",\n * });\n * console.log(`Open: ${authorizeUrl}`);\n * const token = await completeAuth(authorizationCode);\n * ```\n */\n startOAuthFlow(options?: OAuthFlowOptions): OAuthFlowResult {\n const redirectUri = options?.redirectUri ?? DEFAULT_REDIRECT_URI;\n const scopes = options?.scopes ?? DEFAULT_SCOPES;\n\n const codeVerifier = this.generateCodeVerifier();\n const state = this.generateState();\n\n const authorizeUrl = this.buildAuthorizeUrl(\n redirectUri,\n scopes,\n codeVerifier,\n state,\n );\n\n return {\n authorizeUrl,\n completeAuth: (codeOrUrl: string) => {\n const code = ClaudeAuth.extractCode(codeOrUrl);\n return this.exchangeCode(code, codeVerifier, state, redirectUri);\n },\n };\n }\n\n /**\n * Extract an authorization code from user input.\n * Accepts a raw code string or a full redirect URL containing a `code` query parameter.\n *\n * @param input - Raw authorization code or redirect URL\n * @returns The extracted authorization code\n *\n * @example\n * ```ts\n * ClaudeAuth.extractCode(\"abc123\"); // \"abc123\"\n * ClaudeAuth.extractCode(\"https://platform.claude.com/oauth/code/callback?code=abc123&state=xyz\"); // \"abc123\"\n * ```\n */\n static extractCode(input: string): string {\n const trimmed = input.trim();\n try {\n const url = new URL(trimmed);\n const code = url.searchParams.get(\"code\");\n if (code) return code;\n } catch {\n // Not a URL — treat as raw code\n }\n // Handle code#state format from redirect page\n const hashIdx = trimmed.indexOf(\"#\");\n if (hashIdx > 0) {\n return trimmed.substring(0, hashIdx);\n }\n return trimmed;\n }\n\n /**\n * Refresh an expired Claude token.\n *\n * @param refreshToken - The refresh token from a previous authentication\n * @returns New auth token with refreshed access token\n * @throws {TokenExchangeError} If the refresh request fails\n *\n * @example\n * ```ts\n * const auth = new ClaudeAuth();\n * const newToken = await auth.refreshToken(oldToken.refreshToken);\n * ```\n */\n async refreshToken(refreshToken: string): Promise<ClaudeAuthToken> {\n const response = await this.fetchFn(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: CLIENT_ID,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new TokenExchangeError(\n `Token refresh failed: ${response.status} ${text}`,\n );\n }\n\n const data = (await response.json()) as TokenResponse;\n return this.mapTokenResponse(data);\n }\n\n private generateCodeVerifier(): string {\n const bytes = this.randomBytes(96);\n return base64Encode(bytes)\n .replace(/\\+/g, \"~\")\n .replace(/=/g, \"_\")\n .replace(/\\//g, \"-\");\n }\n\n private generateState(): string {\n const bytes = this.randomBytes(16);\n return hexEncode(bytes);\n }\n\n private buildAuthorizeUrl(\n redirectUri: string,\n scopes: string,\n codeVerifier: string,\n state: string,\n ): string {\n const codeChallenge = this.generateCodeChallengeSync(codeVerifier);\n\n const url = new URL(AUTHORIZE_URL);\n url.searchParams.set(\"code\", \"true\");\n url.searchParams.set(\"client_id\", CLIENT_ID);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"redirect_uri\", redirectUri);\n url.searchParams.set(\"scope\", scopes);\n url.searchParams.set(\"code_challenge\", codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"state\", state);\n\n return url.toString();\n }\n\n private generateCodeChallengeSync(verifier: string): string {\n const hash = createHash(\"sha256\").update(verifier).digest(\"base64\");\n return hash.split(\"=\")[0].replace(/\\+/g, \"-\").replace(/\\//g, \"_\");\n }\n\n private async exchangeCode(\n code: string,\n codeVerifier: string,\n state: string,\n redirectUri: string,\n ): Promise<ClaudeAuthToken> {\n const response = await this.fetchFn(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: CLIENT_ID,\n code_verifier: codeVerifier,\n state,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new TokenExchangeError(\n `Token exchange failed: ${response.status} ${text}`,\n );\n }\n\n const data = (await response.json()) as TokenResponse;\n return this.mapTokenResponse(data);\n }\n\n private mapTokenResponse(data: TokenResponse): ClaudeAuthToken {\n return {\n accessToken: data.access_token,\n tokenType: data.token_type ?? \"bearer\",\n expiresIn: data.expires_in,\n obtainedAt: Date.now(),\n refreshToken: data.refresh_token,\n scopes: data.scope?.split(\" \") ?? [],\n };\n }\n}\n\n// ─── Internal Types ───────────────────────────────────────────\n\ninterface TokenResponse {\n access_token: string;\n token_type?: string;\n expires_in?: number;\n scope?: string;\n refresh_token: string;\n}\n\n// ─── Utility Functions ────────────────────────────────────────\n\nfunction defaultRandomBytes(size: number): Uint8Array {\n return new Uint8Array(nodeRandomBytes(size));\n}\n\nfunction base64Encode(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString(\"base64\");\n}\n\nfunction hexEncode(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString(\"hex\");\n}\n"]}
@@ -43,6 +43,10 @@ var BaseAgent = class {
43
43
  state = "idle";
44
44
  abortController = null;
45
45
  config;
46
+ /** CLI session ID for persistent mode. Override in backends that support it. */
47
+ get sessionId() {
48
+ return void 0;
49
+ }
46
50
  constructor(config) {
47
51
  this.config = Object.freeze({ ...config });
48
52
  }
@@ -337,12 +341,9 @@ function _injectSDK(mock) {
337
341
  function _resetSDK() {
338
342
  sdkModule = null;
339
343
  }
340
- var CLAUDE_KNOWN_MODELS = [
341
- { id: "claude-sonnet-4-5-20250514", name: "Claude Sonnet 4.5" },
342
- { id: "claude-haiku-3-5-20241022", name: "Claude 3.5 Haiku" },
343
- { id: "claude-opus-4-20250514", name: "Claude Opus 4" },
344
- { id: "claude-sonnet-4-20250514", name: "Claude Sonnet 4" }
345
- ];
344
+ var ANTHROPIC_MODELS_URL = "https://api.anthropic.com/v1/models";
345
+ var ANTHROPIC_API_VERSION = "2023-06-01";
346
+ var ANTHROPIC_OAUTH_BETA = "oauth-2025-04-20";
346
347
  function buildMcpServer(sdk, tools, toolResultCapture) {
347
348
  if (tools.length === 0) return void 0;
348
349
  const mcpTools = tools.map(
@@ -586,17 +587,27 @@ var ClaudeAgent = class extends BaseAgent {
586
587
  options;
587
588
  tools;
588
589
  canUseTool;
590
+ isPersistent;
591
+ _sessionId;
589
592
  constructor(config, options) {
590
593
  super(config);
591
594
  this.options = options;
592
595
  this.tools = config.tools;
593
596
  this.canUseTool = buildCanUseTool(config);
597
+ this.isPersistent = config.sessionMode === "persistent";
594
598
  if (config.supervisor?.onAskUser) {
595
599
  console.warn(
596
600
  "[agent-sdk/claude] supervisor.onAskUser is not supported by the Claude CLI backend. User interaction requests from the model will not be forwarded."
597
601
  );
598
602
  }
599
603
  }
604
+ get sessionId() {
605
+ return this._sessionId;
606
+ }
607
+ /** Clear persistent session state after an error so next call starts fresh */
608
+ clearPersistentSession() {
609
+ this._sessionId = void 0;
610
+ }
600
611
  buildQueryOptions(signal) {
601
612
  const ac = new AbortController();
602
613
  signal.addEventListener("abort", () => ac.abort(), { once: true });
@@ -606,13 +617,19 @@ var ClaudeAgent = class extends BaseAgent {
606
617
  maxTurns: this.options.maxTurns,
607
618
  cwd: this.options.workingDirectory,
608
619
  pathToClaudeCodeExecutable: this.options.cliPath,
609
- persistSession: false,
620
+ persistSession: this.isPersistent,
610
621
  includePartialMessages: true,
611
622
  canUseTool: this.canUseTool
612
623
  };
624
+ if (this.isPersistent && this._sessionId) {
625
+ opts.resume = this._sessionId;
626
+ }
613
627
  if (this.config.systemPrompt) {
614
628
  opts.systemPrompt = this.config.systemPrompt;
615
629
  }
630
+ if (this.options.oauthToken) {
631
+ opts.env = { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: this.options.oauthToken };
632
+ }
616
633
  return opts;
617
634
  }
618
635
  async buildMcpConfig(opts, toolResultCapture) {
@@ -630,7 +647,8 @@ var ClaudeAgent = class extends BaseAgent {
630
647
  async executeRun(messages, _options, signal) {
631
648
  this.checkAbort(signal);
632
649
  const sdk = await loadSDK();
633
- const prompt = extractLastUserPrompt(messages);
650
+ const isResuming = this.isPersistent && this._sessionId !== void 0;
651
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
634
652
  let opts = this.buildQueryOptions(signal);
635
653
  const toolResultCapture = /* @__PURE__ */ new Map();
636
654
  opts = await this.buildMcpConfig(opts, toolResultCapture);
@@ -669,6 +687,9 @@ var ClaudeAgent = class extends BaseAgent {
669
687
  const r = msg;
670
688
  output = r.result;
671
689
  usage = aggregateUsage(r.modelUsage);
690
+ if (this.isPersistent && r.session_id) {
691
+ this._sessionId = r.session_id;
692
+ }
672
693
  } else if (msg.is_error) {
673
694
  const r = msg;
674
695
  throw new Error(
@@ -678,6 +699,7 @@ var ClaudeAgent = class extends BaseAgent {
678
699
  }
679
700
  }
680
701
  } catch (e) {
702
+ if (this.isPersistent) this.clearPersistentSession();
681
703
  if (signal.aborted) throw new AbortError();
682
704
  throw e;
683
705
  }
@@ -696,7 +718,8 @@ var ClaudeAgent = class extends BaseAgent {
696
718
  async executeRunStructured(messages, schema, _options, signal) {
697
719
  this.checkAbort(signal);
698
720
  const sdk = await loadSDK();
699
- const prompt = extractLastUserPrompt(messages);
721
+ const isResuming = this.isPersistent && this._sessionId !== void 0;
722
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
700
723
  let opts = this.buildQueryOptions(signal);
701
724
  opts = await this.buildMcpConfig(opts);
702
725
  const jsonSchema = zodToJsonSchema(schema.schema);
@@ -732,6 +755,9 @@ var ClaudeAgent = class extends BaseAgent {
732
755
  }
733
756
  }
734
757
  usage = aggregateUsage(r.modelUsage);
758
+ if (this.isPersistent && r.session_id) {
759
+ this._sessionId = r.session_id;
760
+ }
735
761
  } else if (msg.type === "result" && msg.is_error) {
736
762
  const r = msg;
737
763
  throw new Error(
@@ -740,6 +766,7 @@ var ClaudeAgent = class extends BaseAgent {
740
766
  }
741
767
  }
742
768
  } catch (e) {
769
+ if (this.isPersistent) this.clearPersistentSession();
743
770
  if (signal.aborted) throw new AbortError();
744
771
  throw e;
745
772
  }
@@ -758,7 +785,8 @@ var ClaudeAgent = class extends BaseAgent {
758
785
  async *executeStream(messages, _options, signal) {
759
786
  this.checkAbort(signal);
760
787
  const sdk = await loadSDK();
761
- const prompt = extractLastUserPrompt(messages);
788
+ const isResuming = this.isPersistent && this._sessionId !== void 0;
789
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
762
790
  let opts = this.buildQueryOptions(signal);
763
791
  opts = await this.buildMcpConfig(opts);
764
792
  const q = sdk.query({ prompt, options: opts });
@@ -777,15 +805,20 @@ var ClaudeAgent = class extends BaseAgent {
777
805
  }
778
806
  if (msg.type === "result" && msg.subtype === "success") {
779
807
  const r = msg;
808
+ if (this.isPersistent && r.session_id) {
809
+ this._sessionId = r.session_id;
810
+ }
780
811
  yield { type: "done", finalOutput: r.result };
781
812
  }
782
813
  }
783
814
  } catch (e) {
815
+ if (this.isPersistent) this.clearPersistentSession();
784
816
  if (signal.aborted) throw new AbortError();
785
817
  throw e;
786
818
  }
787
819
  }
788
820
  dispose() {
821
+ this._sessionId = void 0;
789
822
  super.dispose();
790
823
  }
791
824
  };
@@ -798,6 +831,20 @@ function extractLastUserPrompt(messages) {
798
831
  }
799
832
  return "";
800
833
  }
834
+ function buildContextualPrompt(messages) {
835
+ if (messages.length <= 1) {
836
+ return extractLastUserPrompt(messages);
837
+ }
838
+ const history = messages.slice(0, -1).map((msg) => {
839
+ const text = msg.content ? getTextContent(msg.content) : "";
840
+ return msg.role === "user" ? `User: ${text}` : `Assistant: ${text}`;
841
+ }).join("\n");
842
+ const lastPrompt = extractLastUserPrompt(messages);
843
+ return `Conversation history:
844
+ ${history}
845
+
846
+ User: ${lastPrompt}`;
847
+ }
801
848
  var ClaudeAgentService = class {
802
849
  name = "claude";
803
850
  disposed = false;
@@ -813,9 +860,30 @@ var ClaudeAgentService = class {
813
860
  async listModels() {
814
861
  if (this.disposed) throw new DisposedError("ClaudeAgentService");
815
862
  if (this.cachedModels) return this.cachedModels;
816
- this.cachedModels = CLAUDE_KNOWN_MODELS.map((m) => ({
863
+ const token = this.options.oauthToken;
864
+ if (!token) {
865
+ return [];
866
+ }
867
+ const res = await globalThis.fetch(
868
+ `${ANTHROPIC_MODELS_URL}?limit=100`,
869
+ {
870
+ headers: {
871
+ Authorization: `Bearer ${token}`,
872
+ "anthropic-version": ANTHROPIC_API_VERSION,
873
+ "anthropic-beta": ANTHROPIC_OAUTH_BETA
874
+ }
875
+ }
876
+ );
877
+ if (!res.ok) {
878
+ return [];
879
+ }
880
+ const body = await res.json();
881
+ if (!body.data || body.data.length === 0) {
882
+ return [];
883
+ }
884
+ this.cachedModels = body.data.map((m) => ({
817
885
  id: m.id,
818
- name: m.name,
886
+ name: m.display_name,
819
887
  provider: "claude"
820
888
  }));
821
889
  return this.cachedModels;
@@ -836,7 +904,7 @@ var ClaudeAgentService = class {
836
904
  const q = sdk.query({
837
905
  prompt: "echo test",
838
906
  options: {
839
- model: CLAUDE_KNOWN_MODELS[0].id,
907
+ model: "claude-sonnet-4-20250514",
840
908
  pathToClaudeCodeExecutable: this.options.cliPath,
841
909
  cwd: this.options.workingDirectory,
842
910
  persistSession: false,