mirror of
https://github.com/yamadashy/repomix.git
synced 2026-05-30 11:18:53 +02:00
fa06e5059c
Six items from gemini, claude initial review, and claude follow-up: - turnstile.ts: Update misleading comment that claimed the metric filters on `event=turnstile_siteverify` and `outcome=success`. The actual Cloud Monitoring metrics in `monitoring/metrics/` filter on `siteverifyDurationMs` field presence, which uniformly captures both the parallel success log (event=turnstile_siteverify) and the four rejectAndLog failure paths (event=pack_completed). The comment contradicted README and YAML and would mislead future readers. - turnstile.ts: Wrap rejectAndLog in a local `rejectWithDuration` helper so every post-siteverify branch automatically carries `siteverifyDurationMs`. Prevents drift if a fifth reject reason gets added later. - client.ts: Split the wire-protocol `PackProgressStage` (server-emitted SSE values) from the display-only `DisplayProgressStage` superset that adds `verifying`. Keeping the synthetic stage out of the wire type prevents silent divergence with the server's `PackProgressStage`. - usePackRequest.ts, TryItLoading.vue, TryItResult.vue: Switch the display-side type to `DisplayProgressStage`. `onProgress` callbacks still take the wire `PackProgressStage`. - usePackRequest.ts: Clear `progressStage` on token-acquisition failure branches (aborted / error). Functionally invisible since loading=false hides the loading UI, but prevents the next submit's verifying flash from briefly showing the previous run's stale state. - monitoring/metrics/turnstile_siteverify_duration.yaml: Retune the exponential bucket layout for the 100ms-1s SLO band where decisions get made. Doubling buckets only placed ~3 boundaries between 100ms and 1s; growthFactor=1.5 with scale=10 places ~8 boundaries there. 18 finite buckets cover 10ms to ~9.85s, comfortably above the 5s siteverify timeout so timeouts don't land in overflow. - monitoring/README.md: Document that pre-network rejections (secret_missing, missing_token, token_too_long) intentionally don't carry siteverifyDurationMs, so they're excluded from both metrics but still appear in the existing pack_requests metric. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
181 lines
4.7 KiB
TypeScript
181 lines
4.7 KiB
TypeScript
export interface PackOptions {
|
|
removeComments: boolean;
|
|
removeEmptyLines: boolean;
|
|
showLineNumbers: boolean;
|
|
fileSummary?: boolean;
|
|
directoryStructure?: boolean;
|
|
includePatterns?: string;
|
|
ignorePatterns?: string;
|
|
outputParsable?: boolean;
|
|
compress?: boolean;
|
|
}
|
|
|
|
export interface FileInfo {
|
|
path: string;
|
|
charCount: number;
|
|
selected?: boolean;
|
|
}
|
|
|
|
export interface PackRequest {
|
|
url: string;
|
|
format: 'xml' | 'markdown' | 'plain';
|
|
options: PackOptions;
|
|
file?: File;
|
|
}
|
|
|
|
export interface SuspiciousFile {
|
|
filePath: string;
|
|
messages: string[];
|
|
}
|
|
|
|
export interface PackResult {
|
|
content: string;
|
|
format: string;
|
|
metadata: {
|
|
repository: string;
|
|
timestamp: string;
|
|
summary: {
|
|
totalFiles: number;
|
|
totalCharacters: number;
|
|
totalTokens: number;
|
|
};
|
|
topFiles: {
|
|
path: string;
|
|
charCount: number;
|
|
tokenCount: number;
|
|
}[];
|
|
allFiles?: FileInfo[];
|
|
suspiciousFiles?: SuspiciousFile[];
|
|
};
|
|
}
|
|
|
|
export interface ErrorResponse {
|
|
error: string;
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
constructor(message: string) {
|
|
super(message);
|
|
this.name = 'ApiError';
|
|
}
|
|
}
|
|
|
|
// Wire-protocol stages — must stay aligned with the server-emitted SSE
|
|
// values (`website/server/src/types.ts:PackProgressStage`). `onProgress`
|
|
// callbacks receive only these, so any new server stage requires a
|
|
// deliberate type update on both sides.
|
|
export type PackProgressStage = 'cache-check' | 'cloning' | 'repository-fetch' | 'extracting' | 'processing';
|
|
|
|
// Display-only superset. `verifying` is a client-only synthetic stage
|
|
// shown while the server runs Turnstile siteverify (before any SSE event
|
|
// arrives); usePackRequest sets it locally between `takeToken()` returning
|
|
// and the first onProgress callback firing, so the loading UI shows a
|
|
// meaningful step instead of a generic "...". Keeping it out of
|
|
// `PackProgressStage` prevents the wire contract from drifting silently
|
|
// when display-only stages get added or renamed.
|
|
export type DisplayProgressStage = PackProgressStage | 'verifying';
|
|
|
|
export interface PackStreamCallbacks {
|
|
onProgress?: (stage: PackProgressStage, message?: string) => void;
|
|
signal?: AbortSignal;
|
|
}
|
|
|
|
const API_BASE_URL = import.meta.env.PROD ? 'https://api.repomix.com' : 'http://localhost:8080';
|
|
|
|
// NDJSON stream event types
|
|
interface ProgressEvent {
|
|
type: 'progress';
|
|
stage: PackProgressStage;
|
|
message?: string;
|
|
}
|
|
|
|
interface ResultEvent {
|
|
type: 'result';
|
|
data: PackResult;
|
|
}
|
|
|
|
interface StreamErrorEvent {
|
|
type: 'error';
|
|
message: string;
|
|
}
|
|
|
|
type StreamEvent = ProgressEvent | ResultEvent | StreamErrorEvent;
|
|
|
|
export async function packRepository(
|
|
request: PackRequest,
|
|
callbacks?: PackStreamCallbacks,
|
|
turnstileToken?: string,
|
|
): Promise<PackResult> {
|
|
const formData = new FormData();
|
|
|
|
if (request.file) {
|
|
formData.append('file', request.file);
|
|
} else {
|
|
formData.append('url', request.url);
|
|
}
|
|
formData.append('format', request.format);
|
|
formData.append('options', JSON.stringify(request.options));
|
|
|
|
// Token rides as a header rather than a form field to keep packRequestSchema
|
|
// free of cross-cutting concerns; the server-side turnstileMiddleware reads
|
|
// it before the schema validation runs.
|
|
const headers: HeadersInit = turnstileToken ? { 'X-Turnstile-Token': turnstileToken } : {};
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/pack`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: formData,
|
|
signal: callbacks?.signal,
|
|
});
|
|
|
|
// Handle non-streaming error responses (validation errors return JSON)
|
|
if (!response.ok) {
|
|
const data = await response.json();
|
|
throw new ApiError((data as ErrorResponse).error);
|
|
}
|
|
|
|
// Handle NDJSON stream
|
|
if (!response.body) {
|
|
throw new ApiError('No response body received');
|
|
}
|
|
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let buffer = '';
|
|
let result: PackResult | null = null;
|
|
|
|
try {
|
|
while (true) {
|
|
const { value, done } = await reader.read();
|
|
if (done) break;
|
|
|
|
buffer += decoder.decode(value, { stream: true });
|
|
|
|
// Parse complete lines from buffer
|
|
const lines = buffer.split('\n');
|
|
buffer = lines.pop() || '';
|
|
|
|
for (const line of lines) {
|
|
if (!line.trim()) continue;
|
|
|
|
const event = JSON.parse(line) as StreamEvent;
|
|
if (event.type === 'progress') {
|
|
callbacks?.onProgress?.(event.stage, event.message);
|
|
} else if (event.type === 'result') {
|
|
result = event.data;
|
|
} else if (event.type === 'error') {
|
|
throw new ApiError(event.message);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
|
|
if (!result) {
|
|
throw new ApiError('No result received from server');
|
|
}
|
|
|
|
return result;
|
|
}
|