init claude-code
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Shared attachment validation + resolution for SendUserMessage and
|
||||
* SendUserFile. Lives in BriefTool/ so the dynamic `./upload.js` import
|
||||
* inside the feature('BRIDGE_MODE') guard stays relative and upload.ts
|
||||
* (axios, crypto, auth utils) remains tree-shakeable from non-bridge builds.
|
||||
*/
|
||||
|
||||
import { feature } from 'bun:bundle'
|
||||
import { stat } from 'fs/promises'
|
||||
|
||||
import type { ValidationResult } from '../../Tool.js'
|
||||
|
||||
import { getCwd } from '../../utils/cwd.js'
|
||||
import { isEnvTruthy } from '../../utils/envUtils.js'
|
||||
import { getErrnoCode } from '../../utils/errors.js'
|
||||
import { IMAGE_EXTENSION_REGEX } from '../../utils/imagePaste.js'
|
||||
import { expandPath } from '../../utils/path.js'
|
||||
|
||||
export type ResolvedAttachment = {
|
||||
path: string
|
||||
size: number
|
||||
isImage: boolean
|
||||
file_uuid?: string
|
||||
}
|
||||
|
||||
export async function validateAttachmentPaths(
|
||||
rawPaths: string[],
|
||||
): Promise<ValidationResult> {
|
||||
const cwd = getCwd()
|
||||
for (const rawPath of rawPaths) {
|
||||
const fullPath = expandPath(rawPath)
|
||||
try {
|
||||
const stats = await stat(fullPath)
|
||||
if (!stats.isFile()) {
|
||||
return {
|
||||
result: false,
|
||||
message: `Attachment "${rawPath}" is not a regular file.`,
|
||||
errorCode: 1,
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const code = getErrnoCode(e)
|
||||
if (code === 'ENOENT') {
|
||||
return {
|
||||
result: false,
|
||||
message: `Attachment "${rawPath}" does not exist. Current working directory: ${cwd}.`,
|
||||
errorCode: 1,
|
||||
}
|
||||
}
|
||||
if (code === 'EACCES' || code === 'EPERM') {
|
||||
return {
|
||||
result: false,
|
||||
message: `Attachment "${rawPath}" is not accessible (permission denied).`,
|
||||
errorCode: 1,
|
||||
}
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return { result: true }
|
||||
}
|
||||
|
||||
export async function resolveAttachments(
|
||||
rawPaths: string[],
|
||||
uploadCtx: { replBridgeEnabled: boolean; signal?: AbortSignal },
|
||||
): Promise<ResolvedAttachment[]> {
|
||||
// Stat serially (local, fast) to keep ordering deterministic, then upload
|
||||
// in parallel (network, slow). Upload failures resolve undefined — the
|
||||
// attachment still carries {path, size, isImage} for local renderers.
|
||||
const stated: ResolvedAttachment[] = []
|
||||
for (const rawPath of rawPaths) {
|
||||
const fullPath = expandPath(rawPath)
|
||||
// Single stat — we need size, so this is the operation, not a guard.
|
||||
// validateInput ran before us, but the file could have moved since
|
||||
// (TOCTOU); if it did, let the error propagate so the model sees it.
|
||||
const stats = await stat(fullPath)
|
||||
stated.push({
|
||||
path: fullPath,
|
||||
size: stats.size,
|
||||
isImage: IMAGE_EXTENSION_REGEX.test(fullPath),
|
||||
})
|
||||
}
|
||||
// Dynamic import inside the feature() guard so upload.ts (axios, crypto,
|
||||
// zod, auth utils, MIME map) is fully eliminated from non-BRIDGE_MODE
|
||||
// builds. A static import would force module-scope evaluation regardless
|
||||
// of the guard inside uploadBriefAttachment — CLAUDE.md: "helpers defined
|
||||
// outside remain in the build even if never called".
|
||||
if (feature('BRIDGE_MODE')) {
|
||||
// Headless/SDK callers never set appState.replBridgeEnabled (only the TTY
|
||||
// REPL does, at main.tsx init). CLAUDE_CODE_BRIEF_UPLOAD lets a host that
|
||||
// runs the CLI as a subprocess opt in — e.g. the cowork desktop bridge,
|
||||
// which already passes CLAUDE_CODE_OAUTH_TOKEN for auth.
|
||||
const shouldUpload =
|
||||
uploadCtx.replBridgeEnabled ||
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_BRIEF_UPLOAD)
|
||||
const { uploadBriefAttachment } = await import('./upload.js')
|
||||
const uuids = await Promise.all(
|
||||
stated.map(a =>
|
||||
uploadBriefAttachment(a.path, a.size, {
|
||||
replBridgeEnabled: shouldUpload,
|
||||
signal: uploadCtx.signal,
|
||||
}),
|
||||
),
|
||||
)
|
||||
return stated.map((a, i) =>
|
||||
uuids[i] === undefined ? a : { ...a, file_uuid: uuids[i] },
|
||||
)
|
||||
}
|
||||
return stated
|
||||
}
|
||||
Reference in New Issue
Block a user