antigravity2api2 / src /AntigravityRequester.js
lin7zhi's picture
Upload folder using huggingface_hub
97ec0e5 verified
import { spawn } from 'child_process';
import os from 'os';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
class antigravityRequester {
constructor(options = {}) {
this.binPath = options.binPath;
this.executablePath = options.executablePath || this._getExecutablePath();
this.proc = null;
this.requestId = 0;
this.pendingRequests = new Map();
this.buffer = '';
this.writeQueue = Promise.resolve();
}
_getExecutablePath() {
const platform = os.platform();
const arch = os.arch();
let filename;
if (platform === 'win32') {
filename = 'antigravity_requester_windows_amd64.exe';
} else if (platform === 'android') {
filename = 'antigravity_requester_android_arm64';
} else if (platform === 'linux') {
filename = 'antigravity_requester_linux_amd64';
} else {
throw new Error(`Unsupported platform: ${platform}`);
}
const binPath = this.binPath || path.join(__dirname, 'bin');
const requester_execPath = path.join(binPath, filename);
// 设置执行权限(非Windows平台)
if (platform !== 'win32') {
try {
fs.chmodSync(requester_execPath, 0o755);
} catch (error) {
console.warn(`Could not set executable permissions: ${error.message}`);
}
}
return requester_execPath;
}
_ensureProcess() {
if (this.proc) return;
this.proc = spawn(this.executablePath, [], {
stdio: ['pipe', 'pipe', 'pipe']
});
// 设置 stdin 为非阻塞模式
if (this.proc.stdin.setDefaultEncoding) {
this.proc.stdin.setDefaultEncoding('utf8');
}
// 增大 stdout 缓冲区以减少背压
if (this.proc.stdout.setEncoding) {
this.proc.stdout.setEncoding('utf8');
}
// 使用 setImmediate 异步处理数据,避免阻塞
this.proc.stdout.on('data', (data) => {
this.buffer += data.toString();
// 使用 setImmediate 异步处理,避免阻塞 stdout 读取
setImmediate(() => {
const lines = this.buffer.split('\n');
this.buffer = lines.pop();
for (const line of lines) {
if (!line.trim()) continue;
try {
const response = JSON.parse(line);
const pending = this.pendingRequests.get(response.id);
if (!pending) continue;
if (pending.streamResponse) {
pending.streamResponse._handleChunk(response);
if (response.type === 'end' || response.type === 'error') {
this.pendingRequests.delete(response.id);
}
} else {
this.pendingRequests.delete(response.id);
if (response.ok) {
pending.resolve(new antigravityResponse(response));
} else {
pending.reject(new Error(response.error || 'Request failed'));
}
}
} catch (e) {
console.error('Failed to parse response:', e, 'Line:', line);
}
}
});
});
this.proc.stderr.on('data', (data) => {
console.error('antigravityRequester stderr:', data.toString());
});
this.proc.on('close', () => {
this.proc = null;
for (const [id, pending] of this.pendingRequests) {
if (pending.reject) {
pending.reject(new Error('Process closed'));
} else if (pending.streamResponse && pending.streamResponse._onError) {
pending.streamResponse._onError(new Error('Process closed'));
}
}
this.pendingRequests.clear();
});
}
async antigravity_fetch(url, options = {}) {
this._ensureProcess();
const id = `req-${++this.requestId}`;
const request = {
id,
url,
method: options.method || 'GET',
headers: options.headers,
body: options.body,
timeout_ms: options.timeout || 30000,
proxy: options.proxy,
response_format: 'text',
...options
};
return new Promise((resolve, reject) => {
this.pendingRequests.set(id, { resolve, reject });
this._writeRequest(request);
});
}
antigravity_fetchStream(url, options = {}) {
this._ensureProcess();
const id = `req-${++this.requestId}`;
const request = {
id,
url,
method: options.method || 'GET',
headers: options.headers,
body: options.body,
timeout_ms: options.timeout || 30000,
proxy: options.proxy,
stream: true,
...options
};
const streamResponse = new StreamResponse(id);
this.pendingRequests.set(id, { streamResponse });
this._writeRequest(request);
return streamResponse;
}
_writeRequest(request) {
this.writeQueue = this.writeQueue.then(() => {
return new Promise((resolve, reject) => {
const data = JSON.stringify(request) + '\n';
const canWrite = this.proc.stdin.write(data);
if (canWrite) {
resolve();
} else {
// 等待 drain 事件
this.proc.stdin.once('drain', resolve);
this.proc.stdin.once('error', reject);
}
});
}).catch(err => {
console.error('Write request failed:', err);
});
}
close() {
if (this.proc) {
this.proc.stdin.end();
this.proc = null;
}
}
}
class StreamResponse {
constructor(id) {
this.id = id;
this.status = null;
this.statusText = null;
this.headers = null;
this.chunks = [];
this._onStart = null;
this._onData = null;
this._onEnd = null;
this._onError = null;
this._ended = false;
this._error = null;
this._textPromiseResolve = null;
this._textPromiseReject = null;
}
_handleChunk(chunk) {
if (chunk.type === 'start') {
this.status = chunk.status;
this.headers = new Map(Object.entries(chunk.headers || {}));
if (this._onStart) this._onStart({ status: chunk.status, headers: this.headers });
} else if (chunk.type === 'data') {
const data = chunk.encoding === 'base64'
? Buffer.from(chunk.data, 'base64').toString('utf8')
: chunk.data;
this.chunks.push(data);
if (this._onData) this._onData(data);
} else if (chunk.type === 'end') {
this._ended = true;
if (this._textPromiseResolve) this._textPromiseResolve(this.chunks.join(''));
if (this._onEnd) this._onEnd();
} else if (chunk.type === 'error') {
this._ended = true;
this._error = new Error(chunk.error);
if (this._textPromiseReject) this._textPromiseReject(this._error);
if (this._onError) this._onError(this._error);
}
}
onStart(callback) {
this._onStart = callback;
return this;
}
onData(callback) {
this._onData = callback;
return this;
}
onEnd(callback) {
this._onEnd = callback;
return this;
}
onError(callback) {
this._onError = callback;
return this;
}
async text() {
if (this._ended) {
if (this._error) throw this._error;
return this.chunks.join('');
}
return new Promise((resolve, reject) => {
this._textPromiseResolve = resolve;
this._textPromiseReject = reject;
});
}
}
class antigravityResponse {
constructor(response) {
this._response = response;
this.ok = response.ok;
this.status = response.status;
this.statusText = response.status_text;
this.url = response.url;
this.headers = new Map(Object.entries(response.headers || {}));
this.redirected = response.redirected;
}
async text() {
if (this._response.body_encoding === 'base64') {
return Buffer.from(this._response.body, 'base64').toString('utf8');
}
return this._response.body;
}
async json() {
const text = await this.text();
return JSON.parse(text);
}
async buffer() {
if (this._response.body_encoding === 'base64') {
return Buffer.from(this._response.body, 'base64');
}
return Buffer.from(this._response.body, 'utf8');
}
}
export default antigravityRequester;