axios+telemetry cleanup
This commit is contained in:
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user