axios+telemetry cleanup

This commit is contained in:
2026-04-02 15:19:11 +03:00
parent a3cbca1e11
commit 7e1eac8002
100 changed files with 3048 additions and 4491 deletions

View File

@@ -1,6 +1,6 @@
import axios from 'axios'
import { getOauthConfig } from '../../constants/oauth.js'
import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js'
import { nativeRequest } from '../../utils/http.js'
export type AdminRequestType = 'limit_increase' | 'seat_upgrade'
@@ -58,7 +58,11 @@ export async function createAdminRequest(
const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/admin_requests`
const response = await axios.post<AdminRequest>(url, params, { headers })
const response = await nativeRequest<AdminRequest>(url, {
method: 'POST',
body: params,
headers,
})
return response.data
}
@@ -84,7 +88,8 @@ export async function getMyAdminRequests(
url += `&statuses=${status}`
}
const response = await axios.get<AdminRequest[] | null>(url, {
const response = await nativeRequest<AdminRequest[] | null>(url, {
method: 'GET',
headers,
})
@@ -111,7 +116,8 @@ export async function checkAdminRequestEligibility(
const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/admin_requests/eligibility?request_type=${requestType}`
const response = await axios.get<AdminRequestEligibilityResponse>(url, {
const response = await nativeRequest<AdminRequestEligibilityResponse>(url, {
method: 'GET',
headers,
})

View File

@@ -1,4 +1,3 @@
import axios from 'axios'
import isEqual from 'lodash-es/isEqual.js'
import {
getAnthropicApiKey,
@@ -9,7 +8,7 @@ import { z } from 'zod'
import { getOauthConfig, OAUTH_BETA_HEADER } from '../../constants/oauth.js'
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
import { logForDebugging } from '../../utils/debug.js'
import { withOAuth401Retry } from '../../utils/http.js'
import { isHttpError, nativeRequest, withOAuth401Retry } from '../../utils/http.js'
import { lazySchema } from '../../utils/lazySchema.js'
import { logError } from '../../utils/log.js'
import { getAPIProvider } from '../../utils/model/providers.js'
@@ -82,7 +81,8 @@ async function fetchBootstrapAPI(): Promise<BootstrapResponse | null> {
}
logForDebugging('[Bootstrap] Fetching')
const response = await axios.get<unknown>(endpoint, {
const response = await nativeRequest<unknown>(endpoint, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Agent': getClaudeCodeUserAgent(),
@@ -102,7 +102,7 @@ async function fetchBootstrapAPI(): Promise<BootstrapResponse | null> {
})
} catch (error) {
logForDebugging(
`[Bootstrap] Fetch failed: ${axios.isAxiosError(error) ? (error.response?.status ?? error.code) : 'unknown'}`,
`[Bootstrap] Fetch failed: ${isHttpError(error) ? (error.status ?? error.code) : 'unknown'}`,
)
throw error
}

View File

@@ -35,6 +35,7 @@ import {
API_PDF_MAX_PAGES,
PDF_TARGET_RAW_SIZE,
} from '../../constants/apiLimits.js'
import { FEEDBACK_CHANNEL } from '../../constants/product.js'
import { isEnvTruthy } from '../../utils/envUtils.js'
import { formatFileSize } from '../../utils/format.js'
import { ImageResizeError } from '../../utils/imageResizer.js'
@@ -685,7 +686,7 @@ export function getAssistantMessageFromError(
}
if (process.env.USER_TYPE === 'ant') {
const baseMessage = `API Error: 400 ${error.message}\n\nRun /share and post the JSON file to ${MACRO.FEEDBACK_CHANNEL}.`
const baseMessage = `API Error: 400 ${error.message}\n\nRun /share and post the JSON file to ${FEEDBACK_CHANNEL}.`
const rewindInstruction = getIsNonInteractiveSession()
? ''
: ' Then, use /rewind to recover the conversation.'
@@ -760,8 +761,8 @@ export function getAssistantMessageFromError(
const orgId = getOauthAccountInfo()?.organizationUuid
const baseMsg = `[ANT-ONLY] Your org isn't gated into the \`${model}\` model. Either run \`claude\` with \`ANTHROPIC_MODEL=${getDefaultMainLoopModelSetting()}\``
const msg = orgId
? `${baseMsg} or share your orgId (${orgId}) in ${MACRO.FEEDBACK_CHANNEL} for help getting access.`
: `${baseMsg} or reach out in ${MACRO.FEEDBACK_CHANNEL} for help getting access.`
? `${baseMsg} or share your orgId (${orgId}) in ${FEEDBACK_CHANNEL} for help getting access.`
: `${baseMsg} or reach out in ${FEEDBACK_CHANNEL} for help getting access.`
return createAssistantAPIErrorMessage({
content: msg,

View File

@@ -7,7 +7,6 @@
* API Reference: https://docs.anthropic.com/en/api/files-content
*/
import axios from 'axios'
import { randomUUID } from 'crypto'
import * as fs from 'fs/promises'
import * as path from 'path'
@@ -15,6 +14,7 @@ import { count } from '../../utils/array.js'
import { getCwd } from '../../utils/cwd.js'
import { logForDebugging } from '../../utils/debug.js'
import { errorMessage } from '../../utils/errors.js'
import { isHttpError, nativeRequest } from '../../utils/http.js'
import { logError } from '../../utils/log.js'
import { sleep } from '../../utils/sleep.js'
import {
@@ -146,16 +146,17 @@ export async function downloadFile(
return retryWithBackoff(`Download file ${fileId}`, async () => {
try {
const response = await axios.get(url, {
const response = await nativeRequest<ArrayBuffer>(url, {
method: 'GET',
headers,
responseType: 'arraybuffer',
timeout: 60000, // 60 second timeout for large files
validateStatus: status => status < 500,
})
if (response.status === 200) {
logDebug(`Downloaded file ${fileId} (${response.data.length} bytes)`)
return { done: true, value: Buffer.from(response.data) }
const buffer = Buffer.from(response.data)
logDebug(`Downloaded file ${fileId} (${buffer.length} bytes)`)
return { done: true, value: buffer }
}
// Non-retriable errors - throw immediately
@@ -171,10 +172,10 @@ export async function downloadFile(
return { done: false, error: `status ${response.status}` }
} catch (error) {
if (!axios.isAxiosError(error)) {
throw error
if (isHttpError(error)) {
return { done: false, error: `${error.status} ${error.message}` }
}
return { done: false, error: error.message }
return { done: false, error: errorMessage(error) }
}
})
}
@@ -457,7 +458,9 @@ export async function uploadFile(
try {
return await retryWithBackoff(`Upload file ${relativePath}`, async () => {
try {
const response = await axios.post(url, body, {
const response = await nativeRequest<any>(url, {
method: 'POST',
body,
headers: {
...headers,
'Content-Type': `multipart/form-data; boundary=${boundary}`,
@@ -465,7 +468,6 @@ export async function uploadFile(
},
timeout: 120000, // 2 minute timeout for uploads
signal: opts?.signal,
validateStatus: status => status < 500,
})
if (response.status === 200 || response.status === 201) {
@@ -521,11 +523,11 @@ export async function uploadFile(
if (error instanceof UploadNonRetriableError) {
throw error
}
if (axios.isCancel(error)) {
throw new UploadNonRetriableError('Upload canceled')
}
// Network errors are retriable
if (axios.isAxiosError(error)) {
if (isHttpError(error)) {
if (error.code === 'ECONNABORTED' || error.status === 408) {
return { done: false, error: 'Upload timeout' }
}
return { done: false, error: error.message }
}
throw error
@@ -643,11 +645,12 @@ export async function listFilesCreatedAfter(
`List files after ${afterCreatedAt}`,
async () => {
try {
const response = await axios.get(`${baseUrl}/v1/files`, {
const queryParams = new URLSearchParams(params).toString()
const fullUrl = `${baseUrl}/v1/files${queryParams ? `?${queryParams}` : ''}`
const response = await nativeRequest<any>(fullUrl, {
method: 'GET',
headers,
params,
timeout: 60000,
validateStatus: status => status < 500,
})
if (response.status === 200) {
@@ -671,14 +674,14 @@ export async function listFilesCreatedAfter(
return { done: false, error: `status ${response.status}` }
} catch (error) {
if (!axios.isAxiosError(error)) {
throw error
if (isHttpError(error)) {
logEvent('tengu_file_list_failed', {
error_type:
'network' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
return { done: false, error: error.message }
}
logEvent('tengu_file_list_failed', {
error_type:
'network' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
return { done: false, error: error.message }
throw error
}
},
)

View File

@@ -1,7 +1,6 @@
import axios from 'axios'
import { getOauthConfig } from '../../constants/oauth.js'
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
import { getAuthHeaders } from '../../utils/http.js'
import { getAuthHeaders, nativeRequest } from '../../utils/http.js'
import { logError } from '../../utils/log.js'
import { getClaudeCodeUserAgent } from '../../utils/userAgent.js'
@@ -26,7 +25,8 @@ export async function fetchAndStoreClaudeCodeFirstTokenDate(): Promise<void> {
const oauthConfig = getOauthConfig()
const url = `${oauthConfig.BASE_API_URL}/api/organization/claude_code_first_token_date`
const response = await axios.get(url, {
const response = await nativeRequest<any>(url, {
method: 'GET',
headers: {
...authHeaders.headers,
'User-Agent': getClaudeCodeUserAgent(),

View File

@@ -1,4 +1,3 @@
import axios from 'axios'
import memoize from 'lodash-es/memoize.js'
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
@@ -14,6 +13,7 @@ import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
import {
getAuthHeaders,
getUserAgent,
nativeRequest,
withOAuth401Retry,
} from '../../utils/http.js'
import { logError } from '../../utils/log.js'
@@ -61,9 +61,10 @@ export const getGroveSettings = memoize(
if (authHeaders.error) {
throw new Error(`Failed to get auth headers: ${authHeaders.error}`)
}
return axios.get<AccountSettings>(
return nativeRequest<AccountSettings>(
`${getOauthConfig().BASE_API_URL}/api/oauth/account/settings`,
{
method: 'GET',
headers: {
...authHeaders.headers,
'User-Agent': getClaudeCodeUserAgent(),
@@ -94,10 +95,11 @@ export async function markGroveNoticeViewed(): Promise<void> {
if (authHeaders.error) {
throw new Error(`Failed to get auth headers: ${authHeaders.error}`)
}
return axios.post(
return nativeRequest(
`${getOauthConfig().BASE_API_URL}/api/oauth/account/grove_notice_viewed`,
{},
{
method: 'POST',
body: {},
headers: {
...authHeaders.headers,
'User-Agent': getClaudeCodeUserAgent(),
@@ -126,12 +128,13 @@ export async function updateGroveSettings(
if (authHeaders.error) {
throw new Error(`Failed to get auth headers: ${authHeaders.error}`)
}
return axios.patch(
return nativeRequest(
`${getOauthConfig().BASE_API_URL}/api/oauth/account/settings`,
{
grove_enabled: groveEnabled,
},
{
method: 'PATCH',
body: {
grove_enabled: groveEnabled,
},
headers: {
...authHeaders.headers,
'User-Agent': getClaudeCodeUserAgent(),
@@ -241,9 +244,10 @@ export const getGroveNoticeConfig = memoize(
if (authHeaders.error) {
throw new Error(`Failed to get auth headers: ${authHeaders.error}`)
}
return axios.get<GroveConfig>(
return nativeRequest<GroveConfig>(
`${getOauthConfig().BASE_API_URL}/api/claude_code_grove`,
{
method: 'GET',
headers: {
...authHeaders.headers,
'User-Agent': getUserAgent(),

View File

@@ -34,6 +34,7 @@ import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../analytics/index.js'
import { BUILD_TIME } from '../../constants/product.js'
import { sanitizeToolNameForAnalytics } from '../analytics/metadata.js'
import { EMPTY_USAGE } from './emptyUsage.js'
import { classifyAPIError } from './errors.js'
@@ -162,8 +163,8 @@ function getAnthropicEnvMetadata() {
}
function getBuildAgeMinutes(): number | undefined {
if (!MACRO.BUILD_TIME) return undefined
const buildTime = new Date(MACRO.BUILD_TIME).getTime()
if (!BUILD_TIME) return undefined
const buildTime = new Date(BUILD_TIME).getTime()
if (isNaN(buildTime)) return undefined
return Math.floor((Date.now() - buildTime) / 60000)
}

View File

@@ -1,159 +1,24 @@
import axios from 'axios'
import { hasProfileScope, isClaudeAISubscriber } from '../../utils/auth.js'
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
import { logForDebugging } from '../../utils/debug.js'
import { errorMessage } from '../../utils/errors.js'
import { getAuthHeaders, withOAuth401Retry } from '../../utils/http.js'
import { logError } from '../../utils/log.js'
import { memoizeWithTTLAsync } from '../../utils/memoize.js'
import { isEssentialTrafficOnly } from '../../utils/privacyLevel.js'
import { getClaudeCodeUserAgent } from '../../utils/userAgent.js'
/**
* Metrics Opt-Out Service (Stubbed)
*
* This service is stubbed to always report that metrics are disabled,
* ensuring no telemetry or logging data is sent to external services.
*/
type MetricsEnabledResponse = {
metrics_logging_enabled: boolean
}
type MetricsStatus = {
export type MetricsStatus = {
enabled: boolean
hasError: boolean
}
// In-memory TTL — dedupes calls within a single process
const CACHE_TTL_MS = 60 * 60 * 1000
// Disk TTL — org settings rarely change. When disk cache is fresher than this,
// we skip the network entirely (no background refresh). This is what collapses
// N `claude -p` invocations into ~1 API call/day.
const DISK_CACHE_TTL_MS = 24 * 60 * 60 * 1000
/**
* Internal function to call the API and check if metrics are enabled
* This is wrapped by memoizeWithTTLAsync to add caching behavior
*/
async function _fetchMetricsEnabled(): Promise<MetricsEnabledResponse> {
const authResult = getAuthHeaders()
if (authResult.error) {
throw new Error(`Auth error: ${authResult.error}`)
}
const headers = {
'Content-Type': 'application/json',
'User-Agent': getClaudeCodeUserAgent(),
...authResult.headers,
}
const endpoint = `https://api.anthropic.com/api/claude_code/organizations/metrics_enabled`
const response = await axios.get<MetricsEnabledResponse>(endpoint, {
headers,
timeout: 5000,
})
return response.data
}
async function _checkMetricsEnabledAPI(): Promise<MetricsStatus> {
// Incident kill switch: skip the network call when nonessential traffic is disabled.
// Returning enabled:false sheds load at the consumer (bigqueryExporter skips
// export). Matches the non-subscriber early-return shape below.
if (isEssentialTrafficOnly()) {
return { enabled: false, hasError: false }
}
try {
const data = await withOAuth401Retry(_fetchMetricsEnabled, {
also403Revoked: true,
})
logForDebugging(
`Metrics opt-out API response: enabled=${data.metrics_logging_enabled}`,
)
return {
enabled: data.metrics_logging_enabled,
hasError: false,
}
} catch (error) {
logForDebugging(
`Failed to check metrics opt-out status: ${errorMessage(error)}`,
)
logError(error)
return { enabled: false, hasError: true }
}
}
// Create memoized version with custom error handling
const memoizedCheckMetrics = memoizeWithTTLAsync(
_checkMetricsEnabledAPI,
CACHE_TTL_MS,
)
/**
* Fetch (in-memory memoized) and persist to disk on change.
* Errors are not persisted — a transient failure should not overwrite a
* known-good disk value.
*/
async function refreshMetricsStatus(): Promise<MetricsStatus> {
const result = await memoizedCheckMetrics()
if (result.hasError) {
return result
}
const cached = getGlobalConfig().metricsStatusCache
const unchanged = cached !== undefined && cached.enabled === result.enabled
// Skip write when unchanged AND timestamp still fresh — avoids config churn
// when concurrent callers race past a stale disk entry and all try to write.
if (unchanged && Date.now() - cached.timestamp < DISK_CACHE_TTL_MS) {
return result
}
saveGlobalConfig(current => ({
...current,
metricsStatusCache: {
enabled: result.enabled,
timestamp: Date.now(),
},
}))
return result
}
/**
* Check if metrics are enabled for the current organization.
*
* Two-tier cache:
* - Disk (24h TTL): survives process restarts. Fresh disk cache → zero network.
* - In-memory (1h TTL): dedupes the background refresh within a process.
*
* The caller (bigqueryExporter) tolerates stale reads — a missed export or
* an extra one during the 24h window is acceptable.
*/
export async function checkMetricsEnabled(): Promise<MetricsStatus> {
// Service key OAuth sessions lack user:profile scope → would 403.
// API key users (non-subscribers) fall through and use x-api-key auth.
// This check runs before the disk read so we never persist auth-state-derived
// answers — only real API responses go to disk. Otherwise a service-key
// session would poison the cache for a later full-OAuth session.
if (isClaudeAISubscriber() && !hasProfileScope()) {
return { enabled: false, hasError: false }
}
const cached = getGlobalConfig().metricsStatusCache
if (cached) {
if (Date.now() - cached.timestamp > DISK_CACHE_TTL_MS) {
// saveGlobalConfig's fallback path (config.ts:731) can throw if both
// locked and fallback writes fail — catch here so fire-and-forget
// doesn't become an unhandled rejection.
void refreshMetricsStatus().catch(logError)
}
return {
enabled: cached.enabled,
hasError: false,
}
}
// First-ever run on this machine: block on the network to populate disk.
return refreshMetricsStatus()
// Always return disabled for a privacy-focused environment.
return { enabled: false, hasError: false };
}
export async function refreshMetricsStatus(): Promise<MetricsStatus> {
return { enabled: false, hasError: false };
}
// Export for testing purposes only
export const _clearMetricsEnabledCacheForTesting = (): void => {
memoizedCheckMetrics.cache.clear()
}
// No-op
};

View File

@@ -1,7 +1,7 @@
import axios from 'axios'
import { getOauthConfig } from '../../constants/oauth.js'
import { getOauthAccountInfo } from '../../utils/auth.js'
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
import { nativeRequest } from '../../utils/http.js'
import { logError } from '../../utils/log.js'
import { isEssentialTrafficOnly } from '../../utils/privacyLevel.js'
import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js'
@@ -30,7 +30,8 @@ async function fetchOverageCreditGrant(): Promise<OverageCreditGrantInfo | null>
try {
const { accessToken, orgUUID } = await prepareApiRequest()
const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/overage_credit_grant`
const response = await axios.get<OverageCreditGrantInfo>(url, {
const response = await nativeRequest<OverageCreditGrantInfo>(url, {
method: 'GET',
headers: getOAuthHeaders(accessToken),
})
return response.data

View File

@@ -1,4 +1,3 @@
import axios from 'axios'
import { getOauthConfig } from '../../constants/oauth.js'
import {
getOauthAccountInfo,
@@ -7,6 +6,7 @@ import {
} from '../../utils/auth.js'
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
import { logForDebugging } from '../../utils/debug.js'
import { nativeRequest } from '../../utils/http.js'
import { logError } from '../../utils/log.js'
import { isEssentialTrafficOnly } from '../../utils/privacyLevel.js'
import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js'
@@ -35,9 +35,12 @@ export async function fetchReferralEligibility(
const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/referral/eligibility`
const response = await axios.get(url, {
const queryParams = new URLSearchParams({ campaign }).toString()
const fullUrl = `${url}${queryParams ? `?${queryParams}` : ''}`
const response = await nativeRequest<ReferralEligibilityResponse>(fullUrl, {
method: 'GET',
headers,
params: { campaign },
timeout: 5000, // 5 second timeout for background fetch
})
@@ -56,9 +59,12 @@ export async function fetchReferralRedemptions(
const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/referral/redemptions`
const response = await axios.get<ReferralRedemptionsResponse>(url, {
const queryParams = new URLSearchParams({ campaign }).toString()
const fullUrl = `${url}${queryParams ? `?${queryParams}` : ''}`
const response = await nativeRequest<ReferralRedemptionsResponse>(fullUrl, {
method: 'GET',
headers,
params: { campaign },
timeout: 10000, // 10 second timeout
})

View File

@@ -1,10 +1,10 @@
import axios, { type AxiosError } from 'axios'
import type { UUID } from 'crypto'
import { getOauthConfig } from '../../constants/oauth.js'
import type { Entry, TranscriptMessage } from '../../types/logs.js'
import { logForDebugging } from '../../utils/debug.js'
import { logForDiagnosticsNoPII } from '../../utils/diagLogs.js'
import { isEnvTruthy } from '../../utils/envUtils.js'
import { isHttpError, nativeRequest } from '../../utils/http.js'
import { logError } from '../../utils/log.js'
import { sequential } from '../../utils/sequential.js'
import { getSessionIngressAuthToken } from '../../utils/sessionIngressAuth.js'
@@ -74,9 +74,10 @@ async function appendSessionLogImpl(
requestHeaders['Last-Uuid'] = lastUuid
}
const response = await axios.put(url, entry, {
const response = await nativeRequest(url, {
method: 'PUT',
body: entry,
headers: requestHeaders,
validateStatus: status => status < 500,
})
if (response.status === 200 || response.status === 201) {
@@ -118,11 +119,11 @@ async function appendSessionLogImpl(
if (adoptedUuid) {
lastUuidMap.set(sessionId, adoptedUuid)
logForDebugging(
`Session 409: re-fetched ${logs!.length} entries, adopting lastUuid=${adoptedUuid}, retrying entry ${entry.uuid}`,
`Session 409: re-fetched ${(logs as any)!.length} entries, adopting lastUuid=${adoptedUuid}, retrying entry ${entry.uuid}`,
)
} else {
// Can't determine server state — give up
const errorData = response.data as SessionIngressError
const errorData = response.data as any as SessionIngressError
const errorMessage =
errorData.error?.message || 'Concurrent modification detected'
logError(
@@ -148,21 +149,22 @@ async function appendSessionLogImpl(
}
// Other 4xx (429, etc.) - retryable
logForDebugging(
`Failed to persist session log: ${response.status} ${response.statusText}`,
)
logForDebugging(`Failed to persist session log: ${response.status}`)
logForDiagnosticsNoPII('error', 'session_persist_fail_status', {
status: response.status,
attempt,
})
} catch (error) {
// Network errors, 5xx - retryable
const axiosError = error as AxiosError<SessionIngressError>
logError(new Error(`Error persisting session log: ${axiosError.message}`))
logForDiagnosticsNoPII('error', 'session_persist_fail_status', {
status: axiosError.status,
attempt,
})
if (isHttpError(error)) {
logError(new Error(`Error persisting session log: ${error.message}`))
logForDiagnosticsNoPII('error', 'session_persist_fail_status', {
status: error.status,
attempt,
})
} else {
logError(error)
}
}
if (attempt === MAX_RETRIES) {
@@ -318,15 +320,19 @@ export async function getTeleportEvents(
let response
try {
response = await axios.get<TeleportEventsResponse>(baseUrl, {
const queryParams = new URLSearchParams(params as any).toString()
const fullUrl = `${baseUrl}${queryParams ? `?${queryParams}` : ''}`
response = await nativeRequest<TeleportEventsResponse>(fullUrl, {
method: 'GET',
headers,
params,
timeout: 20000,
validateStatus: status => status < 500,
})
} catch (e) {
const err = e as AxiosError
logError(new Error(`Teleport events fetch failed: ${err.message}`))
if (isHttpError(e)) {
logError(new Error(`Teleport events fetch failed: ${e.message}`))
} else {
logError(e)
}
logForDiagnosticsNoPII('error', 'teleport_events_fetch_fail')
return null
}
@@ -423,13 +429,17 @@ async function fetchSessionLogsFromUrl(
headers: Record<string, string>,
): Promise<Entry[] | null> {
try {
const response = await axios.get(url, {
const queryParams: Record<string, any> = {}
if (isEnvTruthy(process.env.CLAUDE_AFTER_LAST_COMPACT)) {
queryParams.after_last_compact = true
}
const queryString = new URLSearchParams(queryParams).toString()
const fullUrl = `${url}${queryString ? `?${queryString}` : ''}`
const response = await nativeRequest<any>(fullUrl, {
method: 'GET',
headers,
timeout: 20000,
validateStatus: status => status < 500,
params: isEnvTruthy(process.env.CLAUDE_AFTER_LAST_COMPACT)
? { after_last_compact: true }
: undefined,
})
if (response.status === 200) {
@@ -467,19 +477,20 @@ async function fetchSessionLogsFromUrl(
)
}
logForDebugging(
`Failed to fetch session logs: ${response.status} ${response.statusText}`,
)
logForDebugging(`Failed to fetch session logs: ${response.status}`)
logForDiagnosticsNoPII('error', 'session_get_fail_status', {
status: response.status,
})
return null
} catch (error) {
const axiosError = error as AxiosError<SessionIngressError>
logError(new Error(`Error fetching session logs: ${axiosError.message}`))
logForDiagnosticsNoPII('error', 'session_get_fail_status', {
status: axiosError.status,
})
if (isHttpError(error)) {
logError(new Error(`Error fetching session logs: ${error.message}`))
logForDiagnosticsNoPII('error', 'session_get_fail_status', {
status: error.status,
})
} else {
logError(error)
}
return null
}
}

View File

@@ -1,7 +1,7 @@
import axios from 'axios'
import { getOauthConfig } from '../../constants/oauth.js'
import { isClaudeAISubscriber } from '../../utils/auth.js'
import { logForDebugging } from '../../utils/debug.js'
import { nativeRequest } from '../../utils/http.js'
import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js'
export type UltrareviewQuotaResponse = {
@@ -20,9 +20,10 @@ export async function fetchUltrareviewQuota(): Promise<UltrareviewQuotaResponse
if (!isClaudeAISubscriber()) return null
try {
const { accessToken, orgUUID } = await prepareApiRequest()
const response = await axios.get<UltrareviewQuotaResponse>(
const response = await nativeRequest<UltrareviewQuotaResponse>(
`${getOauthConfig().BASE_API_URL}/v1/ultrareview/quota`,
{
method: 'GET',
headers: {
...getOAuthHeaders(accessToken),
'x-organization-uuid': orgUUID,

View File

@@ -1,11 +1,10 @@
import axios from 'axios'
import { getOauthConfig } from '../../constants/oauth.js'
import {
getClaudeAIOAuthTokens,
hasProfileScope,
isClaudeAISubscriber,
} from '../../utils/auth.js'
import { getAuthHeaders } from '../../utils/http.js'
import { getAuthHeaders, nativeRequest } from '../../utils/http.js'
import { getClaudeCodeUserAgent } from '../../utils/userAgent.js'
import { isOAuthTokenExpired } from '../oauth/client.js'
@@ -54,7 +53,8 @@ export async function fetchUtilization(): Promise<Utilization | null> {
const url = `${getOauthConfig().BASE_API_URL}/api/oauth/usage`
const response = await axios.get<Utilization>(url, {
const response = await nativeRequest<Utilization>(url, {
method: 'GET',
headers,
timeout: 5000, // 5 second timeout
})