Remove internal-only tooling from the external build (#352)

* Remove internal-only tooling without changing external runtime contracts

This trims the lowest-risk internal-only surfaces first: deleted internal
modules are replaced by build-time no-op stubs, the bundled stuck skill is
removed, and the insights S3 upload path now stays local-only. The privacy
verifier is expanded and the remaining bundled internal Slack/Artifactory
strings are neutralized without broad repo-wide renames.

Constraint: Keep the first PR deletion-heavy and avoid mass rewrites of USER_TYPE, tengu, or claude_code identifiers
Rejected: One-shot DMCA cleanup branch | too much semantic risk for a first PR
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Treat full-repo typecheck as a baseline issue on this upstream snapshot; do not claim this commit introduced the existing non-Phase-A errors without isolating them first
Tested: bun run build
Tested: bun run smoke
Tested: bun run verify:privacy
Not-tested: Full repo typecheck (currently fails on widespread pre-existing upstream errors outside this change set)

* Keep minimal source shims so CI can import Phase A cleanup paths

The first PR removed internal-only source files entirely, but CI provider
and context tests import those modules directly from source rather than
through the build-time no-telemetry stubs. This restores tiny no-op source
shims so tests and local source imports resolve while preserving the same
external runtime behavior.

Constraint: GitHub Actions runs source-level tests in addition to bundled build/privacy checks
Rejected: Revert the entire deletion pass | unnecessary once the import contract is satisfied by small shims
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: For later cleanup phases, treat build-time stubs and source-test imports as separate compatibility surfaces
Tested: bun run build
Tested: bun run smoke
Tested: bun run verify:privacy
Tested: bun run test:provider
Tested: bun run test:provider-recommendation
Not-tested: Full repo typecheck (still noisy on this upstream snapshot)

---------

Co-authored-by: anandh8x <test@example.com>
This commit is contained in:
Anandan
2026-04-04 23:04:34 +05:30
committed by GitHub
parent 75d2543854
commit 9e84d2fddc
17 changed files with 148 additions and 1729 deletions

View File

@@ -1,226 +1,26 @@
import type { ClientOptions } from '@anthropic-ai/sdk'
import { createHash } from 'crypto'
import { promises as fs } from 'fs'
import { dirname, join } from 'path'
import { getSessionId } from 'src/bootstrap/state.js'
import { getClaudeConfigHomeDir } from '../../utils/envUtils.js'
import { jsonParse, jsonStringify } from '../../utils/slowOperations.js'
function hashString(str: string): string {
return createHash('sha256').update(str).digest('hex')
}
// Cache last few API requests for ant users (e.g., for /issue command)
const MAX_CACHED_REQUESTS = 5
const cachedApiRequests: Array<{ timestamp: string; request: unknown }> = []
type DumpState = {
initialized: boolean
messageCountSeen: number
lastInitDataHash: string
// Cheap proxy for change detection — skips the expensive stringify+hash
// when model/tools/system are structurally identical to the last call.
lastInitFingerprint: string
}
// Track state per session to avoid duplicating data
const dumpState = new Map<string, DumpState>()
export function getLastApiRequests(): Array<{
timestamp: string
request: unknown
}> {
return [...cachedApiRequests]
return []
}
export function clearApiRequestCache(): void {
cachedApiRequests.length = 0
}
export function clearApiRequestCache(): void {}
export function clearDumpState(agentIdOrSessionId: string): void {
dumpState.delete(agentIdOrSessionId)
}
export function clearDumpState(_agentIdOrSessionId?: string): void {}
export function clearAllDumpState(): void {
dumpState.clear()
}
export function clearAllDumpState(): void {}
export function addApiRequestToCache(requestData: unknown): void {
if (process.env.USER_TYPE !== 'ant') return
cachedApiRequests.push({
timestamp: new Date().toISOString(),
request: requestData,
})
if (cachedApiRequests.length > MAX_CACHED_REQUESTS) {
cachedApiRequests.shift()
}
}
export function addApiRequestToCache(_requestData: unknown): void {}
export function getDumpPromptsPath(agentIdOrSessionId?: string): string {
return join(
getClaudeConfigHomeDir(),
'dump-prompts',
`${agentIdOrSessionId ?? getSessionId()}.jsonl`,
)
}
function appendToFile(filePath: string, entries: string[]): void {
if (entries.length === 0) return
fs.mkdir(dirname(filePath), { recursive: true })
.then(() => fs.appendFile(filePath, entries.join('\n') + '\n'))
.catch(() => {})
}
function initFingerprint(req: Record<string, unknown>): string {
const tools = req.tools as Array<{ name?: string }> | undefined
const system = req.system as unknown[] | string | undefined
const sysLen =
typeof system === 'string'
? system.length
: Array.isArray(system)
? system.reduce(
(n: number, b) => n + ((b as { text?: string }).text?.length ?? 0),
0,
)
: 0
const toolNames = tools?.map(t => t.name ?? '').join(',') ?? ''
return `${req.model}|${toolNames}|${sysLen}`
}
function dumpRequest(
body: string,
ts: string,
state: DumpState,
filePath: string,
): void {
try {
const req = jsonParse(body) as Record<string, unknown>
addApiRequestToCache(req)
if (process.env.USER_TYPE !== 'ant') return
const entries: string[] = []
const messages = (req.messages ?? []) as Array<{ role?: string }>
// Write init data (system, tools, metadata) on first request,
// and a system_update entry whenever it changes.
// Cheap fingerprint first: system+tools don't change between turns,
// so skip the 300ms stringify when the shape is unchanged.
const fingerprint = initFingerprint(req)
if (!state.initialized || fingerprint !== state.lastInitFingerprint) {
const { messages: _, ...initData } = req
const initDataStr = jsonStringify(initData)
const initDataHash = hashString(initDataStr)
state.lastInitFingerprint = fingerprint
if (!state.initialized) {
state.initialized = true
state.lastInitDataHash = initDataHash
// Reuse initDataStr rather than re-serializing initData inside a wrapper.
// timestamp from toISOString() contains no chars needing JSON escaping.
entries.push(
`{"type":"init","timestamp":"${ts}","data":${initDataStr}}`,
)
} else if (initDataHash !== state.lastInitDataHash) {
state.lastInitDataHash = initDataHash
entries.push(
`{"type":"system_update","timestamp":"${ts}","data":${initDataStr}}`,
)
}
}
// Write only new user messages (assistant messages captured in response)
for (const msg of messages.slice(state.messageCountSeen)) {
if (msg.role === 'user') {
entries.push(
jsonStringify({ type: 'message', timestamp: ts, data: msg }),
)
}
}
state.messageCountSeen = messages.length
appendToFile(filePath, entries)
} catch {
// Ignore parsing errors
}
export function getDumpPromptsPath(_agentIdOrSessionId?: string): string {
return ''
}
export function createDumpPromptsFetch(
agentIdOrSessionId: string,
_agentIdOrSessionId: string,
): ClientOptions['fetch'] {
const filePath = getDumpPromptsPath(agentIdOrSessionId)
return async (input: RequestInfo | URL, init?: RequestInit) => {
const state = dumpState.get(agentIdOrSessionId) ?? {
initialized: false,
messageCountSeen: 0,
lastInitDataHash: '',
lastInitFingerprint: '',
}
dumpState.set(agentIdOrSessionId, state)
let timestamp: string | undefined
if (init?.method === 'POST' && init.body) {
timestamp = new Date().toISOString()
// Parsing + stringifying the request (system prompt + tool schemas = MBs)
// takes hundreds of ms. Defer so it doesn't block the actual API call —
// this is debug tooling for /issue, not on the critical path.
setImmediate(dumpRequest, init.body as string, timestamp, state, filePath)
}
// eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins
const response = await globalThis.fetch(input, init)
// Save response async
if (timestamp && response.ok && process.env.USER_TYPE === 'ant') {
const cloned = response.clone()
void (async () => {
try {
const isStreaming = cloned.headers
.get('content-type')
?.includes('text/event-stream')
let data: unknown
if (isStreaming && cloned.body) {
// Parse SSE stream into chunks
const reader = cloned.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
}
} finally {
reader.releaseLock()
}
const chunks: unknown[] = []
for (const event of buffer.split('\n\n')) {
for (const line of event.split('\n')) {
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
try {
chunks.push(jsonParse(line.slice(6)))
} catch {
// Ignore parse errors
}
}
}
}
data = { stream: true, chunks }
} else {
data = await cloned.json()
}
await fs.appendFile(
filePath,
jsonStringify({ type: 'response', timestamp, data }) + '\n',
)
} catch {
// Best effort
}
})()
}
return response
}
return undefined
}