import { createHash, randomBytes } from 'crypto';

// src/errors.ts
var AgentSDKError = class extends Error {
  /** @internal Marker for cross-bundle identity checks */
  _agentSDKError = true;
  /** Machine-readable error code. Prefer values from the ErrorCode enum. */
  code;
  /** Whether this error is safe to retry */
  retryable;
  /** HTTP status code hint for error classification */
  httpStatus;
  constructor(message, options) {
    super(message, options);
    this.name = "AgentSDKError";
    this.code = options?.code;
    this.retryable = options?.retryable ?? false;
    this.httpStatus = options?.httpStatus;
  }
  /** Check if an error is an AgentSDKError (works across bundled copies) */
  static is(error) {
    return error instanceof Error && "_agentSDKError" in error && error._agentSDKError === true;
  }
};

// src/auth/types.ts
var AuthError = class extends AgentSDKError {
  constructor(message, options) {
    super(message, { ...options, code: "AUTH_EXPIRED" /* AUTH_EXPIRED */ });
    this.name = "AuthError";
  }
};
var DeviceCodeExpiredError = class extends AuthError {
  constructor() {
    super("Device code expired. Please restart the auth flow.");
    this.name = "DeviceCodeExpiredError";
  }
};
var AccessDeniedError = class extends AuthError {
  constructor() {
    super("Access was denied by the user.");
    this.name = "AccessDeniedError";
  }
};
var TokenExchangeError = class extends AuthError {
  constructor(message, options) {
    super(message, options);
    this.name = "TokenExchangeError";
  }
};

// src/auth/copilot-auth.ts
var CLIENT_ID = "Ov23ctDVkRmgkPke0Mmm";
var DEVICE_CODE_URL = "https://github.com/login/device/code";
var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
var USER_API_URL = "https://api.github.com/user";
var DEFAULT_SCOPES = "read:user,read:org,repo,gist";
var GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
var CopilotAuth = class {
  fetchFn;
  /** @param options - Optional configuration with custom fetch for testing */
  constructor(options) {
    this.fetchFn = options?.fetch ?? globalThis.fetch;
  }
  /**
   * Start the GitHub Device Flow.
   * Returns a device code result with user code, verification URL,
   * and a `waitForToken()` function that polls until the user authorizes.
   *
   * @param options - Optional scopes and abort signal
   * @returns Device flow result with user code, verification URL, and waitForToken poller
   * @throws {AuthError} If the device code request fails
   * @throws {DeviceCodeExpiredError} If the device code expires before user authorizes
   * @throws {AccessDeniedError} If the user denies access
   *
   * @example
   * ```ts
   * const auth = new CopilotAuth();
   * const { userCode, verificationUrl, waitForToken } = await auth.startDeviceFlow();
   * console.log(`Open ${verificationUrl} and enter code: ${userCode}`);
   * const token = await waitForToken();
   * ```
   */
  async startDeviceFlow(options) {
    const scopes = options?.scopes ?? DEFAULT_SCOPES;
    const response = await this.fetchFn(DEVICE_CODE_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Accept: "application/json"
      },
      body: new URLSearchParams({
        client_id: CLIENT_ID,
        scope: scopes
      }),
      signal: options?.signal
    });
    if (!response.ok) {
      throw new AuthError(
        `Failed to request device code: ${response.status} ${response.statusText}`
      );
    }
    const data = await response.json();
    return {
      userCode: data.user_code,
      verificationUrl: data.verification_uri,
      waitForToken: (signal) => this.pollForToken(data.device_code, data.interval, signal)
    };
  }
  async pollForToken(deviceCode, interval, signal) {
    let pollIntervalMs = interval * 1e3;
    while (true) {
      if (signal?.aborted) {
        throw new AuthError("Authentication was aborted.");
      }
      await this.delay(pollIntervalMs, signal);
      const response = await this.fetchFn(ACCESS_TOKEN_URL, {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          Accept: "application/json"
        },
        body: new URLSearchParams({
          client_id: CLIENT_ID,
          device_code: deviceCode,
          grant_type: GRANT_TYPE
        }),
        signal
      });
      if (!response.ok) {
        throw new AuthError(
          `Token poll failed: ${response.status} ${response.statusText}`
        );
      }
      const data = await response.json();
      if (data.access_token) {
        const token = {
          accessToken: data.access_token,
          tokenType: data.token_type ?? "bearer",
          obtainedAt: Date.now()
        };
        if (data.refresh_token) {
          token.refreshToken = data.refresh_token;
        }
        if (data.expires_in) {
          token.expiresIn = data.expires_in;
        }
        try {
          const login = await this.fetchUserLogin(data.access_token, signal);
          if (login) token.login = login;
        } catch {
        }
        return token;
      }
      if (data.error === "authorization_pending") {
        continue;
      }
      if (data.error === "slow_down") {
        pollIntervalMs = (data.interval ?? interval + 5) * 1e3;
        continue;
      }
      if (data.error === "expired_token") {
        throw new DeviceCodeExpiredError();
      }
      if (data.error === "access_denied") {
        throw new AccessDeniedError();
      }
      throw new AuthError(
        data.error_description ?? `Unexpected error: ${data.error}`
      );
    }
  }
  async fetchUserLogin(token, signal) {
    const response = await this.fetchFn(USER_API_URL, {
      headers: {
        Authorization: `Bearer ${token}`,
        Accept: "application/json"
      },
      signal
    });
    if (!response.ok) return void 0;
    const user = await response.json();
    return user.login;
  }
  /**
   * Refresh an expired Copilot token using a refresh token.
   * Only works for GitHub App tokens that include a refresh_token.
   *
   * @param refreshToken - The refresh token from the original auth flow
   * @param signal - Optional abort signal
   * @returns Fresh CopilotAuthToken with new access and refresh tokens
   * @throws {TokenExchangeError} If the refresh request fails
   *
   * @example
   * ```ts
   * const auth = new CopilotAuth();
   * const newToken = await auth.refreshToken(oldToken.refreshToken!);
   * ```
   */
  async refreshToken(refreshToken, signal) {
    const response = await this.fetchFn(ACCESS_TOKEN_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Accept: "application/json"
      },
      body: new URLSearchParams({
        client_id: CLIENT_ID,
        grant_type: "refresh_token",
        refresh_token: refreshToken
      }),
      signal
    });
    if (!response.ok) {
      throw new TokenExchangeError(
        `Token refresh failed: ${response.status} ${response.statusText}`
      );
    }
    const data = await response.json();
    if (data.error) {
      throw new TokenExchangeError(
        data.error_description ?? `Token refresh error: ${data.error}`
      );
    }
    if (!data.access_token) {
      throw new TokenExchangeError("Token refresh response missing access_token");
    }
    const token = {
      accessToken: data.access_token,
      tokenType: data.token_type ?? "bearer",
      obtainedAt: Date.now()
    };
    if (data.refresh_token) {
      token.refreshToken = data.refresh_token;
    }
    if (data.expires_in) {
      token.expiresIn = data.expires_in;
    }
    return token;
  }
  delay(ms, signal) {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(resolve, ms);
      signal?.addEventListener(
        "abort",
        () => {
          clearTimeout(timer);
          reject(new AuthError("Authentication was aborted."));
        },
        { once: true }
      );
    });
  }
};
var CLIENT_ID2 = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
var TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
var DEFAULT_REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
var DEFAULT_SCOPES2 = "user:profile user:inference user:sessions:claude_code user:mcp_servers";
var ClaudeAuth = class _ClaudeAuth {
  fetchFn;
  randomBytes;
  /**
   * @param options - Optional configuration with custom fetch and random bytes for testing
   */
  constructor(options) {
    this.fetchFn = options?.fetch ?? globalThis.fetch;
    this.randomBytes = options?.randomBytes ?? defaultRandomBytes;
  }
  /**
   * Start the Claude OAuth+PKCE flow.
   * Generates PKCE code verifier/challenge and returns an authorize URL
   * plus a `completeAuth(code)` function for token exchange.
   *
   * @param options - Redirect URI and optional scopes
   * @returns OAuth flow result with authorize URL and completeAuth function
   * @throws {AuthError} If PKCE generation fails
   *
   * @example
   * ```ts
   * const auth = new ClaudeAuth();
   * const { authorizeUrl, completeAuth } = auth.startOAuthFlow({
   *   redirectUri: "https://platform.claude.com/oauth/code/callback",
   * });
   * console.log(`Open: ${authorizeUrl}`);
   * const token = await completeAuth(authorizationCode);
   * ```
   */
  startOAuthFlow(options) {
    const redirectUri = options?.redirectUri ?? DEFAULT_REDIRECT_URI;
    const scopes = options?.scopes ?? DEFAULT_SCOPES2;
    const codeVerifier = this.generateCodeVerifier();
    const state = this.generateState();
    const authorizeUrl = this.buildAuthorizeUrl(
      redirectUri,
      scopes,
      codeVerifier,
      state
    );
    return {
      authorizeUrl,
      completeAuth: (codeOrUrl) => {
        const code = _ClaudeAuth.extractCode(codeOrUrl);
        return this.exchangeCode(code, codeVerifier, state, redirectUri);
      }
    };
  }
  /**
   * Extract an authorization code from user input.
   * Accepts a raw code string or a full redirect URL containing a `code` query parameter.
   *
   * @param input - Raw authorization code or redirect URL
   * @returns The extracted authorization code
   *
   * @example
   * ```ts
   * ClaudeAuth.extractCode("abc123"); // "abc123"
   * ClaudeAuth.extractCode("https://platform.claude.com/oauth/code/callback?code=abc123&state=xyz"); // "abc123"
   * ```
   */
  static extractCode(input) {
    const trimmed = input.trim();
    try {
      const url = new URL(trimmed);
      const code = url.searchParams.get("code");
      if (code) return code;
    } catch {
    }
    const hashIdx = trimmed.indexOf("#");
    if (hashIdx > 0) {
      return trimmed.substring(0, hashIdx);
    }
    return trimmed;
  }
  /**
   * Refresh an expired Claude token.
   *
   * @param refreshToken - The refresh token from a previous authentication
   * @returns New auth token with refreshed access token
   * @throws {TokenExchangeError} If the refresh request fails
   *
   * @example
   * ```ts
   * const auth = new ClaudeAuth();
   * const newToken = await auth.refreshToken(oldToken.refreshToken);
   * ```
   */
  async refreshToken(refreshToken) {
    const response = await this.fetchFn(TOKEN_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        grant_type: "refresh_token",
        refresh_token: refreshToken,
        client_id: CLIENT_ID2
      })
    });
    if (!response.ok) {
      const text = await response.text();
      throw new TokenExchangeError(
        `Token refresh failed: ${response.status} ${text}`
      );
    }
    const data = await response.json();
    return this.mapTokenResponse(data);
  }
  generateCodeVerifier() {
    const bytes = this.randomBytes(96);
    return base64Encode(bytes).replace(/\+/g, "~").replace(/=/g, "_").replace(/\//g, "-");
  }
  generateState() {
    const bytes = this.randomBytes(16);
    return hexEncode(bytes);
  }
  buildAuthorizeUrl(redirectUri, scopes, codeVerifier, state) {
    const codeChallenge = this.generateCodeChallengeSync(codeVerifier);
    const url = new URL(AUTHORIZE_URL);
    url.searchParams.set("code", "true");
    url.searchParams.set("client_id", CLIENT_ID2);
    url.searchParams.set("response_type", "code");
    url.searchParams.set("redirect_uri", redirectUri);
    url.searchParams.set("scope", scopes);
    url.searchParams.set("code_challenge", codeChallenge);
    url.searchParams.set("code_challenge_method", "S256");
    url.searchParams.set("state", state);
    return url.toString();
  }
  generateCodeChallengeSync(verifier) {
    const hash = createHash("sha256").update(verifier).digest("base64");
    return hash.split("=")[0].replace(/\+/g, "-").replace(/\//g, "_");
  }
  async exchangeCode(code, codeVerifier, state, redirectUri) {
    const response = await this.fetchFn(TOKEN_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        grant_type: "authorization_code",
        code,
        redirect_uri: redirectUri,
        client_id: CLIENT_ID2,
        code_verifier: codeVerifier,
        state
      })
    });
    if (!response.ok) {
      const text = await response.text();
      throw new TokenExchangeError(
        `Token exchange failed: ${response.status} ${text}`
      );
    }
    const data = await response.json();
    return this.mapTokenResponse(data);
  }
  mapTokenResponse(data) {
    return {
      accessToken: data.access_token,
      tokenType: data.token_type ?? "bearer",
      expiresIn: data.expires_in,
      obtainedAt: Date.now(),
      refreshToken: data.refresh_token,
      scopes: data.scope?.split(" ") ?? []
    };
  }
};
function defaultRandomBytes(size) {
  return new Uint8Array(randomBytes(size));
}
function base64Encode(bytes) {
  return Buffer.from(bytes).toString("base64");
}
function hexEncode(bytes) {
  return Buffer.from(bytes).toString("hex");
}

// src/auth/refresh-manager.ts
var TokenRefreshManager = class {
  currentToken;
  refreshFn;
  threshold;
  maxRetries;
  retryDelayMs;
  minDelayMs;
  timerId = null;
  running = false;
  disposed = false;
  listeners = {
    refreshed: /* @__PURE__ */ new Set(),
    error: /* @__PURE__ */ new Set(),
    expired: /* @__PURE__ */ new Set(),
    disposed: /* @__PURE__ */ new Set()
  };
  constructor(options) {
    this.currentToken = { ...options.token };
    this.refreshFn = options.refresh;
    this.threshold = options.refreshThreshold ?? 0.8;
    this.maxRetries = options.maxRetries ?? 3;
    this.retryDelayMs = options.retryDelayMs ?? 1e3;
    this.minDelayMs = options.minDelayMs ?? 1e3;
  }
  /** Register an event listener */
  on(event, listener) {
    this.listeners[event].add(listener);
    return this;
  }
  /** Remove an event listener */
  off(event, listener) {
    this.listeners[event].delete(listener);
    return this;
  }
  /** Current token managed by this instance */
  get token() {
    return { ...this.currentToken };
  }
  /** Whether the manager is currently running */
  get isRunning() {
    return this.running;
  }
  /** Whether the manager has been disposed */
  get isDisposed() {
    return this.disposed;
  }
  /**
   * Start automatic refresh scheduling.
   * If the token is already expired, emits "expired" immediately.
   * If the token has no expiresIn, does nothing (long-lived token).
   */
  start() {
    if (this.disposed) return;
    if (this.running) return;
    this.running = true;
    this.schedule();
  }
  /** Stop automatic refresh (can be restarted with start()) */
  stop() {
    this.running = false;
    this.clearTimer();
  }
  /**
   * Update the managed token (e.g. after manual refresh).
   * Reschedules automatic refresh if running.
   */
  updateToken(token) {
    if (this.disposed) return;
    this.currentToken = { ...token };
    if (this.running) {
      this.clearTimer();
      this.schedule();
    }
  }
  /** Stop and clean up all resources */
  dispose() {
    if (this.disposed) return;
    this.stop();
    this.disposed = true;
    this.emit("disposed");
    for (const set of Object.values(this.listeners)) {
      set.clear();
    }
  }
  // ─── Private ──────────────────────────────────────────────────
  schedule() {
    if (!this.running || this.disposed) return;
    const delayMs = this.computeRefreshDelay();
    if (delayMs === null) {
      return;
    }
    if (delayMs <= 0) {
      this.timerId = setTimeout(() => {
        this.timerId = null;
        if (!this.running || this.disposed) return;
        void this.performRefresh();
      }, 0);
      return;
    }
    this.timerId = setTimeout(() => {
      this.timerId = null;
      if (!this.running || this.disposed) return;
      void this.performRefresh();
    }, Math.max(delayMs, this.minDelayMs));
  }
  async performRefresh(attempt = 1) {
    if (!this.running || this.disposed) return;
    try {
      const newToken = await this.refreshFn(this.currentToken);
      if (!this.running || this.disposed) return;
      this.currentToken = { ...newToken };
      this.emit("refreshed", newToken);
      this.schedule();
    } catch (err) {
      if (!this.running || this.disposed) return;
      const error = err instanceof Error ? err : new Error(String(err));
      this.emit("error", error, attempt);
      if (attempt < this.maxRetries) {
        const delay = this.retryDelayMs * Math.pow(2, attempt - 1);
        this.timerId = setTimeout(() => {
          this.timerId = null;
          if (!this.running || this.disposed) return;
          void this.performRefresh(attempt + 1);
        }, delay);
      } else {
        if (this.isTokenExpired()) {
          this.running = false;
          this.emit("expired");
        } else {
          const expiresIn = this.currentToken.expiresIn;
          if (expiresIn == null) return;
          const expiresAt = this.currentToken.obtainedAt + expiresIn * 1e3;
          const waitMs = Math.max(expiresAt - Date.now(), this.minDelayMs);
          this.timerId = setTimeout(() => {
            this.timerId = null;
            if (!this.running || this.disposed) return;
            void this.performRefresh();
          }, waitMs);
        }
      }
    }
  }
  computeRefreshDelay() {
    if (this.currentToken.expiresIn == null) return null;
    const lifetimeMs = this.currentToken.expiresIn * 1e3;
    const refreshAtMs = this.currentToken.obtainedAt + lifetimeMs * this.threshold;
    const now = Date.now();
    const delay = refreshAtMs - now;
    return delay;
  }
  isTokenExpired() {
    if (this.currentToken.expiresIn == null) return false;
    const expiresAt = this.currentToken.obtainedAt + this.currentToken.expiresIn * 1e3;
    return Date.now() >= expiresAt;
  }
  clearTimer() {
    if (this.timerId !== null) {
      clearTimeout(this.timerId);
      this.timerId = null;
    }
  }
  emit(event, ...args) {
    for (const listener of this.listeners[event]) {
      try {
        listener(...args);
      } catch {
      }
    }
  }
};

export { AccessDeniedError, AuthError, ClaudeAuth, CopilotAuth, DeviceCodeExpiredError, TokenExchangeError, TokenRefreshManager };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map