Update from Assistant
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* Datacollect (Wiki/Datacollect 프로젝트)의 MCP Bridge HTTP 클라이언트.
|
||||
*
|
||||
* Bridge는 사용자가 별도로 띄우는 Node Express 서버(`npm run bridge`)이고
|
||||
* 기본 포트는 3002. Research(NotebookLM)/Web Benchmark(Playwright)/YouTube
|
||||
* (yt-dlp+transcript) 같은 무거운 기능을 노출하므로, Astra는 이 endpoint를
|
||||
* thin client로 호출만 한다 — Playwright/Chrome/NotebookLM-MCP 의존성을
|
||||
* Astra가 직접 들고 갈 필요 없음.
|
||||
*
|
||||
* URL은 `astra.datacollectBridgeUrl` VS Code 설정으로 override 가능, 기본값
|
||||
* `http://127.0.0.1:3002`. 사용자가 다른 머신/포트에서 띄우면 그쪽으로 가게.
|
||||
*/
|
||||
|
||||
export function getBridgeBaseUrl(): string {
|
||||
const raw = vscode.workspace.getConfiguration('g1nation').get<string>('datacollectBridgeUrl');
|
||||
const url = (raw && raw.trim()) || 'http://127.0.0.1:3002';
|
||||
return url.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
export interface BridgeFetchOptions {
|
||||
timeoutMs?: number;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridge endpoint를 호출하고 JSON 응답을 돌려준다. 5xx/4xx는 throw로 surface —
|
||||
* `/api/research/start` 같은 핸들러가 `{ success: false, stage, error }` 형식의
|
||||
* 풍부한 진단 정보를 body에 담기 시작했으므로(이전 라운드 추가), throw 메시지에
|
||||
* `[stage] error` 형태로 포함해 slashRouter 쪽에서 사용자에게 그대로 보여준다.
|
||||
*/
|
||||
export async function bridgeFetch<T = any>(
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
opts: BridgeFetchOptions = {},
|
||||
): Promise<T> {
|
||||
const base = getBridgeBaseUrl();
|
||||
const url = `${base}${path.startsWith('/') ? path : `/${path}`}`;
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutMs = opts.timeoutMs ?? 60_000;
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
|
||||
// 호출자 signal이 abort되면 그대로 forward.
|
||||
if (opts.signal) {
|
||||
if (opts.signal.aborted) controller.abort();
|
||||
else opts.signal.addEventListener('abort', () => controller.abort(), { once: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
...init,
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(init?.headers || {}),
|
||||
},
|
||||
});
|
||||
const text = await res.text();
|
||||
let body: any = text;
|
||||
try { body = JSON.parse(text); } catch { /* keep as text */ }
|
||||
|
||||
if (!res.ok) {
|
||||
const stage = body?.stage ? `[${body.stage}] ` : '';
|
||||
const errMsg = body?.error || body?.message || (typeof body === 'string' ? body : `HTTP ${res.status}`);
|
||||
throw new Error(`Datacollect ${path} 실패: ${stage}${errMsg}`);
|
||||
}
|
||||
return body as T;
|
||||
} catch (e: any) {
|
||||
if (e?.name === 'AbortError') {
|
||||
throw new Error(`Datacollect ${path} 시간 초과 (${timeoutMs}ms). Bridge가 떠 있는지 확인하세요 (${base}).`);
|
||||
}
|
||||
// ECONNREFUSED 등 connect 실패는 친절히 안내.
|
||||
const msg = String(e?.message || e);
|
||||
if (msg.includes('ECONNREFUSED') || msg.includes('fetch failed')) {
|
||||
throw new Error(
|
||||
`Datacollect Bridge(${base})에 연결할 수 없습니다. ` +
|
||||
`Wiki/Datacollect 프로젝트에서 \`npm run bridge\`를 실행하고 다시 시도하세요.`,
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user