mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
* 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
134 lines
4.3 KiB
TypeScript
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 }
|
|
}
|
|
}
|