Files
openclaude/src/components/FeedbackSurvey/submitTranscriptShare.ts
T
JATMNandGitHub 46e80568be fix(provider): support custom Anthropic bearer auth (#1929)
* fix(provider): support custom Anthropic bearer auth

* feat(provider): add custom Anthropic profile flow

* fix(provider): restore custom Anthropic tokens on startup

* fix(provider): preserve custom Anthropic env setup

* fix(provider): clear stale custom Anthropic tokens

* fix(provider): preserve custom Anthropic API-key env setup

* fix(provider): cover custom Anthropic auth routing

* test(api): isolate custom Anthropic client routing

* test(api): cache-bust client provider imports

* feat(provider): clarify custom provider presets

* fix(provider): address custom Anthropic review feedback

* fix(provider): preserve custom Anthropic headers

* fix(provider): require custom Anthropic token

* fix(provider): isolate custom Anthropic credentials

* fix(provider): guard custom Anthropic setup

* fix(provider): complete custom Anthropic integration

* fix(provider): classify custom Anthropic proxies

* fix(provider): gate proxy cache extensions

* fix(provider): preserve custom Anthropic isolation

* fix(provider): retain direct proxy model option

* fix(provider): honor custom endpoint boundaries

* fix(provider): keep proxy credentials local

* fix(provider): disable proxy fast mode

* fix(provider): preserve first-party route identity

* fix(provider): isolate custom Anthropic endpoints

* fix(provider): gate remaining first-party features

* fix(provider): isolate custom Anthropic proxy features

* test(web-search): make Brave timeout mock abort-aware

* fix(provider): address custom Anthropic review feedback

* test(provider): cover first-party beta gates

* fix(provider): complete custom Anthropic isolation

* fix(provider): complete custom Anthropic routing

* fix(provider): address custom Anthropic review followups

* fix(provider): close custom Anthropic review gaps

* test(provider): keep custom Anthropic mock helpers isolated

* test(provider): isolate model options gateway mocks

* fix(provider): stabilize custom Anthropic model option display

* fix(provider): address remaining review threads

* fix(provider): synchronize active profile persistence

* fix(provider): preserve custom Anthropic API key auth

* fix(provider): avoid forwarding inherited Anthropic keys

* fix(provider): guard custom auth selection

* fix(provider): require first-party Anthropic port

* test(web-search): avoid duplicate shared lock

* fix(provider): resolve remaining review findings

* fix(provider): harden custom Anthropic routing

* fix(provider): simplify Anthropic thinking gate

* fix(provider): preserve custom proxy routing and secret permissions

* fix(mcp): isolate Claude.ai config cache by provider

* fix(model): keep custom endpoints out of first-party UX

* fix(provider): scope Opus off switch to Anthropic

* fix(provider): disable tool search for custom proxies

* fix(provider): close custom Anthropic review gaps

* fix(provider): reject Anthropic staging custom profiles

* fix(webfetch): classify custom Anthropic endpoints

* fix(provider): block bearer auth at Anthropic origin

* fix(provider): keep custom auth off staging OAuth

* test(provider): strengthen auth regression coverage
2026-07-16 08:14:22 +08:00

134 lines
4.3 KiB
TypeScript

import axios from 'axios'
import { readFile, stat } from 'fs/promises'
import type { Message } from '../../types/message.js'
import { checkAndRefreshOAuthTokenIfNeeded } from '../../utils/auth.js'
import { logForDebugging } from '../../utils/debug.js'
import { errorMessage } from '../../utils/errors.js'
import { getAuthHeaders, getUserAgent } from '../../utils/http.js'
import { isFirstPartyAnthropicProvider } from '../../utils/model/providers.js'
import { normalizeMessagesForAPI } from '../../utils/messages.js'
import {
extractAgentIdsFromMessages,
getTranscriptPath,
loadSubagentTranscripts,
MAX_TRANSCRIPT_READ_BYTES,
} from '../../utils/sessionStorage.js'
import { jsonStringify } from '../../utils/slowOperations.js'
import { jsonRedactor, redactJsonLines, redactSensitiveInfo } from '../../utils/redaction.js'
type TranscriptShareResult = {
success: boolean
transcriptId?: string
}
export type TranscriptShareTrigger =
| 'bad_feedback_survey'
| 'good_feedback_survey'
| 'frustration'
| 'memory_survey'
export async function submitTranscriptShare(
messages: Message[],
trigger: TranscriptShareTrigger,
appearanceId: string,
): Promise<TranscriptShareResult> {
if (!isFirstPartyAnthropicProvider()) {
return { success: false }
}
try {
logForDebugging('Collecting transcript for sharing', { level: 'info' })
const transcript = normalizeMessagesForAPI(messages)
// Collect subagent transcripts
const agentIds = extractAgentIdsFromMessages(messages)
const subagentTranscripts = await loadSubagentTranscripts(agentIds)
// Read raw JSONL transcript (with size guard to prevent OOM)
let rawTranscriptJsonl: string | undefined
try {
const transcriptPath = getTranscriptPath()
const { size } = await stat(transcriptPath)
if (size <= MAX_TRANSCRIPT_READ_BYTES) {
rawTranscriptJsonl = await readFile(transcriptPath, 'utf-8')
} else {
logForDebugging(
`Skipping raw transcript read: file too large (${size} bytes)`,
{ level: 'warn' },
)
}
} catch {
// File may not exist
}
// Pre-redact JSONL lines so nested keys like "auth" are caught by
// jsonRedactor (which can't see inside pre-serialized string values).
const redactedTranscriptJsonl = rawTranscriptJsonl
? redactJsonLines(rawTranscriptJsonl)
: undefined
const data = {
trigger,
version: MACRO.VERSION,
platform: process.platform,
transcript,
subagentTranscripts:
Object.keys(subagentTranscripts).length > 0
? subagentTranscripts
: undefined,
rawTranscriptJsonl: redactedTranscriptJsonl,
}
// Two-pass redaction:
// 1. `jsonRedactor` runs as the JSON.stringify replacer so the
// key-aware check applies during serialization — a credential
// field whose value is an unknown shape (object, array) gets
// collapsed to `'[REDACTED]'` instead of being serialized and
// then re-parsed by a regex over the text.
// 2. `redactSensitiveInfo` runs over the final string as a
// defense-in-depth second pass — catches secrets embedded in
// free-form text (log lines, error messages) inside any field,
// even when the field name isn't on the credential-substring
// list.
const content = redactSensitiveInfo(jsonStringify(data, jsonRedactor))
await checkAndRefreshOAuthTokenIfNeeded()
const authResult = getAuthHeaders()
if (authResult.error) {
return { success: false }
}
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'User-Agent': getUserAgent(),
...authResult.headers,
}
const response = await axios.post(
'https://api.anthropic.com/api/claude_code_shared_session_transcripts',
{ content, appearance_id: appearanceId },
{
headers,
timeout: 30000,
},
)
if (response.status === 200 || response.status === 201) {
const result = response.data
logForDebugging('Transcript shared successfully', { level: 'info' })
return {
success: true,
transcriptId: result?.transcript_id,
}
}
return { success: false }
} catch (err) {
logForDebugging(errorMessage(err), {
level: 'error',
})
return { success: false }
}
}