Files
ccs/src/cursor/cursor-runtime-probe.ts
T

263 lines
7.4 KiB
TypeScript

import type { CursorConfig } from '../config/unified-config-types';
import { checkAuthStatus } from './cursor-auth';
import { isDaemonRunning, startDaemon } from './cursor-daemon';
import { getModelsForDaemon, resolveCursorRequestModel } from './cursor-models';
export interface CursorProbeResult {
ok: boolean;
stage: 'config' | 'auth' | 'daemon' | 'runtime';
status: number;
duration_ms: number;
model?: string;
error_type?: string | null;
message: string;
}
const PROBE_PROMPT = 'Reply with OK only.';
const PROBE_TIMEOUT_MS = 15_000;
const PROBE_SUCCESS_PATTERN = /^ok[.!]?$/i;
function isDaemonReachabilityError(error: unknown): boolean {
if (!(error instanceof Error)) return false;
const message = error.message.toLowerCase();
if (
message.includes('fetch failed') ||
message.includes('econnrefused') ||
message.includes('econnreset') ||
message.includes('socket hang up') ||
message.includes('connection refused')
) {
return true;
}
const cause = (error as Error & { cause?: unknown }).cause;
if (!cause || typeof cause !== 'object' || !('code' in cause)) {
return false;
}
const code = String((cause as { code?: unknown }).code ?? '').toUpperCase();
return ['ECONNREFUSED', 'ECONNRESET', 'ECONNABORTED', 'EPIPE', 'UND_ERR_SOCKET'].includes(code);
}
function parseProbeError(text: string): { errorType: string | null; message: string } {
try {
const parsed = JSON.parse(text) as {
error?: { type?: string; message?: string };
type?: string;
};
if (parsed.error?.message) {
return {
errorType: parsed.error.type ?? null,
message: parsed.error.message,
};
}
if (parsed.type === 'error') {
return {
errorType: null,
message: text,
};
}
} catch {
// fall through to raw text
}
return {
errorType: null,
message: text || 'Unknown probe failure',
};
}
function parseProbeSuccess(text: string): { ok: boolean; message: string } {
try {
const parsed = JSON.parse(text) as {
choices?: Array<{
message?: { content?: string | null };
}>;
error?: { message?: string };
};
if (parsed.error?.message) {
return { ok: false, message: parsed.error.message };
}
const content = parsed.choices?.[0]?.message?.content;
if (typeof content !== 'string' || !content.trim()) {
return { ok: false, message: 'Probe response was missing assistant content.' };
}
return {
ok: PROBE_SUCCESS_PATTERN.test(content.trim()),
message: PROBE_SUCCESS_PATTERN.test(content.trim())
? 'Live probe succeeded.'
: `Probe returned unexpected assistant content: ${content.trim()}`,
};
} catch {
return { ok: false, message: 'Probe response was not valid JSON.' };
}
}
export async function probeCursorRuntime(config: CursorConfig): Promise<CursorProbeResult> {
const startedAt = Date.now();
if (!config.enabled) {
return {
ok: false,
stage: 'config',
status: 400,
duration_ms: Date.now() - startedAt,
message: 'Cursor integration is disabled.',
error_type: 'configuration_error',
};
}
const authStatus = checkAuthStatus();
if (!authStatus.authenticated || !authStatus.credentials) {
return {
ok: false,
stage: 'auth',
status: 401,
duration_ms: Date.now() - startedAt,
message: 'Cursor credentials not found. Run `ccs legacy cursor auth` first.',
error_type: 'authentication_error',
};
}
if (authStatus.expired) {
return {
ok: false,
stage: 'auth',
status: 401,
duration_ms: Date.now() - startedAt,
message: 'Cursor credentials expired. Run `ccs legacy cursor auth` again.',
error_type: 'authentication_error',
};
}
let daemonRunning = await isDaemonRunning(config.port);
if (!daemonRunning && config.auto_start) {
const startResult = await startDaemon({
port: config.port,
ghost_mode: config.ghost_mode,
});
if (!startResult.success) {
daemonRunning = await isDaemonRunning(config.port);
} else {
daemonRunning = true;
}
if (!daemonRunning) {
return {
ok: false,
stage: 'daemon',
status: 503,
duration_ms: Date.now() - startedAt,
message: startResult.error || 'Failed to start Cursor daemon for live probe.',
error_type: 'daemon_start_failed',
};
}
}
if (!daemonRunning) {
return {
ok: false,
stage: 'daemon',
status: 503,
duration_ms: Date.now() - startedAt,
message:
'Cursor daemon is not running. Start it with `ccs legacy cursor start` or enable auto_start.',
error_type: 'daemon_not_running',
};
}
try {
const credentials = {
accessToken: authStatus.credentials.accessToken,
machineId: authStatus.credentials.machineId,
ghostMode: config.ghost_mode,
};
const availableModels = await getModelsForDaemon({ credentials });
const model = resolveCursorRequestModel(config.model, availableModels);
const abortController = new AbortController();
const timeout = setTimeout(() => abortController.abort(), PROBE_TIMEOUT_MS);
try {
const response = await fetch(`http://127.0.0.1:${config.port}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
max_tokens: 8,
messages: [{ role: 'user', content: PROBE_PROMPT }],
}),
signal: abortController.signal,
});
const text = await response.text();
const duration = Date.now() - startedAt;
if (!response.ok) {
const error = parseProbeError(text);
return {
ok: false,
stage: 'runtime',
status: response.status,
duration_ms: duration,
model,
error_type: error.errorType,
message: error.message,
};
}
const success = parseProbeSuccess(text);
return {
ok: success.ok,
stage: 'runtime',
status: success.ok ? response.status : 502,
duration_ms: duration,
model,
error_type: success.ok ? null : 'probe_validation_failed',
message: success.message,
};
} finally {
clearTimeout(timeout);
}
} catch (error) {
const daemonReachabilityError = isDaemonReachabilityError(error);
return {
ok: false,
stage:
error instanceof Error && error.name === 'AbortError'
? 'runtime'
: daemonReachabilityError
? 'daemon'
: 'runtime',
status:
error instanceof Error && error.name === 'AbortError'
? 504
: daemonReachabilityError
? 503
: 500,
duration_ms: Date.now() - startedAt,
error_type:
error instanceof Error && error.name === 'AbortError'
? 'probe_timeout'
: daemonReachabilityError
? 'daemon_unreachable'
: 'runtime_error',
message:
error instanceof Error && error.name === 'AbortError'
? `Live probe timed out after ${PROBE_TIMEOUT_MS}ms.`
: daemonReachabilityError
? 'Cursor daemon became unreachable during the live probe. Start it again and retry.'
: error instanceof Error
? error.message
: 'Unknown runtime probe failure.',
};
}
}