droid-patch 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +490 -142
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -29,9 +29,14 @@ const fs = require('fs');
|
|
|
29
29
|
const DEBUG = process.env.DROID_SEARCH_DEBUG === '1';
|
|
30
30
|
const PORT = parseInt(process.env.SEARCH_PROXY_PORT || '0');
|
|
31
31
|
const FACTORY_API = 'https://api.factory.ai';
|
|
32
|
+
const SEARCH_ROUTE_ALIASES = new Set(['/api/tools/web-search', '/api/tools/exa/search']);
|
|
32
33
|
|
|
33
34
|
function log() { if (DEBUG) console.error.apply(console, ['[websearch]'].concat(Array.from(arguments))); }
|
|
34
35
|
|
|
36
|
+
function isSearchRequest(url, method) {
|
|
37
|
+
return method === 'POST' && SEARCH_ROUTE_ALIASES.has(url.pathname);
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
// === External Search Providers ===
|
|
36
41
|
|
|
37
42
|
async function searchSmitheryExa(query, numResults) {
|
|
@@ -204,7 +209,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
204
209
|
return;
|
|
205
210
|
}
|
|
206
211
|
|
|
207
|
-
if (url
|
|
212
|
+
if (isSearchRequest(url, req.method)) {
|
|
208
213
|
let body = '';
|
|
209
214
|
req.on('data', function(c) { body += c; });
|
|
210
215
|
req.on('end', async function() {
|
|
@@ -281,7 +286,6 @@ function generateNativeSearchProxyServer(factoryApiUrl = "https://api.factory.ai
|
|
|
281
286
|
|
|
282
287
|
const http = require('http');
|
|
283
288
|
const https = require('https');
|
|
284
|
-
const { execSync } = require('child_process');
|
|
285
289
|
const fs = require('fs');
|
|
286
290
|
const path = require('path');
|
|
287
291
|
const os = require('os');
|
|
@@ -289,13 +293,18 @@ const os = require('os');
|
|
|
289
293
|
const DEBUG = process.env.DROID_SEARCH_DEBUG === '1';
|
|
290
294
|
const PORT = parseInt(process.env.SEARCH_PROXY_PORT || '0');
|
|
291
295
|
const FACTORY_API = '${factoryApiUrl}';
|
|
296
|
+
const SEARCH_ROUTE_ALIASES = new Set(['/api/tools/web-search', '/api/tools/exa/search']);
|
|
297
|
+
const SUPPORTED_PROVIDERS = new Set(['anthropic', 'openai']);
|
|
292
298
|
|
|
293
299
|
function log(...args) { if (DEBUG) console.error('[websearch]', ...args); }
|
|
294
300
|
|
|
295
|
-
|
|
301
|
+
function isSearchRequest(url, method) {
|
|
302
|
+
return method === 'POST' && SEARCH_ROUTE_ALIASES.has(url.pathname);
|
|
303
|
+
}
|
|
296
304
|
|
|
297
305
|
let cachedSettings = null;
|
|
298
306
|
let settingsLastModified = 0;
|
|
307
|
+
let lastObservedProvider = null;
|
|
299
308
|
|
|
300
309
|
function getFactorySettings() {
|
|
301
310
|
const settingsPath = path.join(os.homedir(), '.factory', 'settings.json');
|
|
@@ -311,151 +320,409 @@ function getFactorySettings() {
|
|
|
311
320
|
}
|
|
312
321
|
}
|
|
313
322
|
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
function listCustomModels(settings) {
|
|
324
|
+
return Array.isArray(settings && settings.customModels) ? settings.customModels : [];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function isSupportedModel(modelConfig, preferredProvider) {
|
|
328
|
+
return !!(
|
|
329
|
+
modelConfig &&
|
|
330
|
+
SUPPORTED_PROVIDERS.has(modelConfig.provider) &&
|
|
331
|
+
(!preferredProvider || modelConfig.provider === preferredProvider) &&
|
|
332
|
+
modelConfig.id &&
|
|
333
|
+
modelConfig.baseUrl &&
|
|
334
|
+
modelConfig.apiKey &&
|
|
335
|
+
modelConfig.model
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function buildCandidateModelIds(settings) {
|
|
340
|
+
const candidates = [
|
|
341
|
+
process.env.DROID_SEARCH_MODEL_ID,
|
|
342
|
+
settings && settings.sessionDefaultSettings && settings.sessionDefaultSettings.model,
|
|
343
|
+
settings && settings.missionModelSettings && settings.missionModelSettings.workerModel,
|
|
344
|
+
settings && settings.missionModelSettings && settings.missionModelSettings.validationWorkerModel,
|
|
345
|
+
settings && settings.missionModelSettings && settings.missionModelSettings.orchestratorModel,
|
|
346
|
+
];
|
|
347
|
+
const unique = [];
|
|
348
|
+
for (const candidate of candidates) {
|
|
349
|
+
if (candidate && !unique.includes(candidate)) unique.push(candidate);
|
|
327
350
|
}
|
|
328
|
-
|
|
329
|
-
if (!currentModelId.startsWith('custom:')) return null;
|
|
330
|
-
log('Model not found:', currentModelId);
|
|
331
|
-
return null;
|
|
351
|
+
return unique;
|
|
332
352
|
}
|
|
333
353
|
|
|
334
|
-
|
|
354
|
+
function summarizeSupportedModels(settings, preferredProvider) {
|
|
355
|
+
return listCustomModels(settings)
|
|
356
|
+
.filter(function(modelConfig) { return isSupportedModel(modelConfig, preferredProvider); })
|
|
357
|
+
.map(function(modelConfig) { return modelConfig.id + ' [' + modelConfig.provider + ']'; })
|
|
358
|
+
.join(', ');
|
|
359
|
+
}
|
|
335
360
|
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
max_tokens: 4096,
|
|
343
|
-
stream: false,
|
|
344
|
-
system: 'You are a web search assistant. Use the web_search tool to find relevant information and return the results.',
|
|
345
|
-
tools: [{ type: 'web_search_20250305', name: 'web_search', max_uses: 1 }],
|
|
346
|
-
tool_choice: { type: 'tool', name: 'web_search' },
|
|
347
|
-
messages: [{ role: 'user', content: 'Search the web for: ' + query + '\\n\\nReturn up to ' + numResults + ' relevant results.' }]
|
|
361
|
+
function getCurrentModelConfig(preferredProvider) {
|
|
362
|
+
const settings = getFactorySettings();
|
|
363
|
+
if (!settings) {
|
|
364
|
+
return {
|
|
365
|
+
error: 'Failed to load ~/.factory/settings.json for native websearch',
|
|
366
|
+
statusCode: 500,
|
|
348
367
|
};
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const customModels = listCustomModels(settings);
|
|
371
|
+
const candidateIds = buildCandidateModelIds(settings);
|
|
372
|
+
for (const candidateId of candidateIds) {
|
|
373
|
+
const modelConfig = customModels.find(function(model) {
|
|
374
|
+
return model.id === candidateId && isSupportedModel(model, preferredProvider);
|
|
375
|
+
});
|
|
376
|
+
if (modelConfig) {
|
|
377
|
+
lastObservedProvider = modelConfig.provider;
|
|
378
|
+
log('Resolved model:', modelConfig.id, '| Provider:', modelConfig.provider);
|
|
379
|
+
return { modelConfig: modelConfig, source: candidateId };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const fallbackModels = customModels.filter(function(modelConfig) {
|
|
384
|
+
return isSupportedModel(modelConfig, preferredProvider);
|
|
385
|
+
});
|
|
386
|
+
if (fallbackModels.length === 1) {
|
|
387
|
+
lastObservedProvider = fallbackModels[0].provider;
|
|
388
|
+
log('Falling back to only supported model:', fallbackModels[0].id);
|
|
389
|
+
return { modelConfig: fallbackModels[0], source: 'single-supported-model' };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const providerLabel = preferredProvider || 'anthropic/openai';
|
|
393
|
+
const supported = summarizeSupportedModels(settings, preferredProvider);
|
|
394
|
+
const message = supported
|
|
395
|
+
? 'Could not resolve an active ' + providerLabel + ' custom model for native websearch. Available models: ' + supported
|
|
396
|
+
: 'No supported ' + providerLabel + ' custom models found in ~/.factory/settings.json';
|
|
397
|
+
|
|
398
|
+
return { error: message, statusCode: 400 };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function normalizeEndpoint(baseUrl, suffix) {
|
|
402
|
+
return baseUrl.endsWith(suffix) ? baseUrl : baseUrl.replace(/\\/$/, '') + suffix;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function extractErrorMessage(value) {
|
|
406
|
+
if (!value) return 'Unknown upstream error';
|
|
407
|
+
if (typeof value === 'string') return value;
|
|
408
|
+
if (typeof value.message === 'string') return value.message;
|
|
409
|
+
return JSON.stringify(value);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function sleep(ms) {
|
|
413
|
+
return new Promise(function(resolve) { setTimeout(resolve, ms); });
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function getRetryDelayMs(attempt) {
|
|
417
|
+
return Math.min(250 * Math.pow(2, attempt - 1), 2000);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function isRetryableSearchFailure(statusCode, message) {
|
|
421
|
+
if (statusCode === 408 || statusCode === 425 || statusCode === 429) return true;
|
|
422
|
+
if (statusCode >= 500 && statusCode <= 599) return true;
|
|
423
|
+
|
|
424
|
+
const normalized = String(message || '').toLowerCase();
|
|
425
|
+
return normalized.includes('proxy failed:') ||
|
|
426
|
+
normalized.includes('client network socket disconnected before secure tls connection was established') ||
|
|
427
|
+
normalized.includes('fetch failed') ||
|
|
428
|
+
normalized.includes('socket hang up') ||
|
|
429
|
+
normalized.includes('request timed out') ||
|
|
430
|
+
normalized.includes('timed out') ||
|
|
431
|
+
normalized.includes('econnreset') ||
|
|
432
|
+
normalized.includes('ecconnreset') ||
|
|
433
|
+
normalized.includes('econnrefused') ||
|
|
434
|
+
normalized.includes('ehostunreach') ||
|
|
435
|
+
normalized.includes('enotfound') ||
|
|
436
|
+
normalized.includes('eai_again');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function pushUniqueResult(results, result) {
|
|
440
|
+
if (!result || !result.url) return;
|
|
441
|
+
if (results.some(function(existing) { return existing.url === result.url; })) return;
|
|
442
|
+
results.push({
|
|
443
|
+
title: result.title || result.url,
|
|
444
|
+
url: result.url,
|
|
445
|
+
content: result.content || '',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function parseOpenAITextResults(text, numResults) {
|
|
450
|
+
const results = [];
|
|
451
|
+
const lines = String(text || '').split(/\\r?\\n/);
|
|
452
|
+
let current = null;
|
|
453
|
+
|
|
454
|
+
function flushCurrent() {
|
|
455
|
+
if (!current || !current.url) {
|
|
456
|
+
current = null;
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
pushUniqueResult(results, {
|
|
460
|
+
title: current.title,
|
|
461
|
+
url: current.url,
|
|
462
|
+
content: current.content.join(' ').trim(),
|
|
463
|
+
});
|
|
464
|
+
current = null;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
for (const rawLine of lines) {
|
|
468
|
+
const line = rawLine.trim();
|
|
469
|
+
if (!line) continue;
|
|
470
|
+
|
|
471
|
+
const titleMatch = line.match(/^\\d+\\.\\s+(?:\\*\\*(.+?)\\*\\*|(.+))$/);
|
|
472
|
+
if (titleMatch) {
|
|
473
|
+
flushCurrent();
|
|
474
|
+
current = {
|
|
475
|
+
title: (titleMatch[1] || titleMatch[2] || '').trim(),
|
|
476
|
+
url: '',
|
|
477
|
+
content: [],
|
|
478
|
+
};
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const urlMatch = line.match(/^(?:[-*]\\s+)?(https?:\\/\\/\\S+)/);
|
|
483
|
+
if (urlMatch) {
|
|
484
|
+
if (!current) {
|
|
485
|
+
current = { title: urlMatch[1], url: '', content: [] };
|
|
486
|
+
}
|
|
487
|
+
current.url = urlMatch[1].replace(/[),.;]+$/, '');
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const bulletTextMatch = line.match(/^[-*]\\s+(.+)/);
|
|
492
|
+
const contentText = (bulletTextMatch ? bulletTextMatch[1] : line).trim();
|
|
493
|
+
if (!current) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (contentText) {
|
|
497
|
+
current.content.push(contentText);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
flushCurrent();
|
|
502
|
+
return results.slice(0, numResults);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function postJson(endpoint, headers, requestBody) {
|
|
506
|
+
const maxAttempts = 5;
|
|
507
|
+
let lastError = null;
|
|
508
|
+
|
|
509
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
510
|
+
const controller = new AbortController();
|
|
511
|
+
const timeoutId = setTimeout(function() { controller.abort(); }, 60000);
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
const response = await fetch(endpoint, {
|
|
515
|
+
method: 'POST',
|
|
516
|
+
headers: headers,
|
|
517
|
+
body: JSON.stringify(requestBody),
|
|
518
|
+
signal: controller.signal,
|
|
519
|
+
});
|
|
520
|
+
const responseText = await response.text();
|
|
521
|
+
let payload = {};
|
|
522
|
+
if (responseText) {
|
|
523
|
+
try {
|
|
524
|
+
payload = JSON.parse(responseText);
|
|
525
|
+
} catch {
|
|
526
|
+
throw new Error('Invalid JSON response from ' + endpoint);
|
|
374
527
|
}
|
|
375
528
|
}
|
|
529
|
+
|
|
530
|
+
if (!response.ok) {
|
|
531
|
+
const message = extractErrorMessage(payload && payload.error) || ('HTTP ' + response.status);
|
|
532
|
+
if (attempt < maxAttempts && isRetryableSearchFailure(response.status, message)) {
|
|
533
|
+
lastError = new Error(message);
|
|
534
|
+
const delayMs = getRetryDelayMs(attempt);
|
|
535
|
+
log('Retrying request after upstream error:', response.status, message, '| next attempt', attempt + 1, 'of', maxAttempts);
|
|
536
|
+
await sleep(delayMs);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
throw new Error(message);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (payload && payload.error) {
|
|
543
|
+
const message = extractErrorMessage(payload.error);
|
|
544
|
+
if (attempt < maxAttempts && isRetryableSearchFailure(undefined, message)) {
|
|
545
|
+
lastError = new Error(message);
|
|
546
|
+
const delayMs = getRetryDelayMs(attempt);
|
|
547
|
+
log('Retrying request after payload error:', message, '| next attempt', attempt + 1, 'of', maxAttempts);
|
|
548
|
+
await sleep(delayMs);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
throw new Error(message);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return payload;
|
|
555
|
+
} catch (e) {
|
|
556
|
+
const message = e && e.name === 'AbortError' ? 'Request timed out' : (e && e.message ? e.message : String(e));
|
|
557
|
+
if (attempt < maxAttempts && isRetryableSearchFailure(undefined, message)) {
|
|
558
|
+
lastError = new Error(message);
|
|
559
|
+
const delayMs = getRetryDelayMs(attempt);
|
|
560
|
+
log('Retrying request after transport error:', message, '| next attempt', attempt + 1, 'of', maxAttempts);
|
|
561
|
+
await sleep(delayMs);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
throw new Error(message);
|
|
565
|
+
} finally {
|
|
566
|
+
clearTimeout(timeoutId);
|
|
376
567
|
}
|
|
377
|
-
|
|
378
|
-
log('Results:', results.length);
|
|
379
|
-
return results.length > 0 ? results.slice(0, numResults) : null;
|
|
380
|
-
} catch (e) {
|
|
381
|
-
log('Anthropic error:', e.message);
|
|
382
|
-
return null;
|
|
383
568
|
}
|
|
569
|
+
|
|
570
|
+
throw lastError || new Error('Request failed');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function searchAnthropicNative(query, numResults, modelConfig) {
|
|
574
|
+
const endpoint = normalizeEndpoint(modelConfig.baseUrl, '/v1/messages');
|
|
575
|
+
const requestBody = {
|
|
576
|
+
model: modelConfig.model,
|
|
577
|
+
max_tokens: 4096,
|
|
578
|
+
stream: false,
|
|
579
|
+
system: 'You are a web search assistant. Use the web_search tool to find relevant information and return the results.',
|
|
580
|
+
tools: [{ type: 'web_search_20250305', name: 'web_search', max_uses: 1 }],
|
|
581
|
+
tool_choice: { type: 'tool', name: 'web_search' },
|
|
582
|
+
messages: [{ role: 'user', content: 'Search the web for: ' + query + '\\n\\nReturn up to ' + numResults + ' relevant results.' }],
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
log('Anthropic search:', query, '→', endpoint);
|
|
586
|
+
const response = await postJson(endpoint, {
|
|
587
|
+
'Content-Type': 'application/json',
|
|
588
|
+
'anthropic-version': '2023-06-01',
|
|
589
|
+
'x-api-key': modelConfig.apiKey,
|
|
590
|
+
}, requestBody);
|
|
591
|
+
|
|
592
|
+
const results = [];
|
|
593
|
+
for (const block of (response.content || [])) {
|
|
594
|
+
if (block.type !== 'web_search_tool_result') continue;
|
|
595
|
+
for (const result of (block.content || [])) {
|
|
596
|
+
if (result.type !== 'web_search_result') continue;
|
|
597
|
+
results.push({
|
|
598
|
+
title: result.title || '',
|
|
599
|
+
url: result.url || '',
|
|
600
|
+
content: result.snippet || result.page_content || '',
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
log('Anthropic results:', results.length);
|
|
606
|
+
return results.slice(0, numResults);
|
|
384
607
|
}
|
|
385
608
|
|
|
386
609
|
async function searchOpenAINative(query, numResults, modelConfig) {
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
610
|
+
const endpoint = normalizeEndpoint(modelConfig.baseUrl, '/responses');
|
|
611
|
+
const input = 'Search the web for: ' + query + '\\n\\nReturn up to ' + numResults + ' relevant results.';
|
|
612
|
+
const requestVariants = [
|
|
613
|
+
{
|
|
614
|
+
label: 'web_search',
|
|
615
|
+
body: {
|
|
616
|
+
model: modelConfig.model,
|
|
617
|
+
stream: false,
|
|
618
|
+
tools: [{ type: 'web_search' }],
|
|
619
|
+
tool_choice: 'required',
|
|
620
|
+
input: input,
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
label: 'web_search_preview',
|
|
625
|
+
body: {
|
|
626
|
+
model: modelConfig.model,
|
|
627
|
+
stream: false,
|
|
628
|
+
tools: [{ type: 'web_search_preview' }],
|
|
629
|
+
input: input,
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
];
|
|
633
|
+
|
|
634
|
+
let lastError = null;
|
|
635
|
+
for (const variant of requestVariants) {
|
|
636
|
+
try {
|
|
637
|
+
log('OpenAI search:', query, '→', endpoint, '(' + variant.label + ')');
|
|
638
|
+
const response = await postJson(endpoint, {
|
|
639
|
+
'Content-Type': 'application/json',
|
|
640
|
+
Authorization: 'Bearer ' + modelConfig.apiKey,
|
|
641
|
+
}, variant.body);
|
|
642
|
+
|
|
643
|
+
const results = [];
|
|
644
|
+
const textBlocks = [];
|
|
645
|
+
for (const item of (response.output || [])) {
|
|
646
|
+
if (item.type !== 'message' || !Array.isArray(item.content)) continue;
|
|
416
647
|
for (const content of item.content) {
|
|
417
|
-
if (content.type
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
648
|
+
if (content.type !== 'output_text') continue;
|
|
649
|
+
if (content.text) {
|
|
650
|
+
textBlocks.push(content.text);
|
|
651
|
+
}
|
|
652
|
+
if (!Array.isArray(content.annotations)) continue;
|
|
653
|
+
for (const annotation of content.annotations) {
|
|
654
|
+
if (annotation.type !== 'url_citation' || !annotation.url) continue;
|
|
655
|
+
pushUniqueResult(results, {
|
|
656
|
+
title: annotation.title || '',
|
|
657
|
+
url: annotation.url || '',
|
|
658
|
+
content: annotation.title || '',
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (results.length === 0) {
|
|
665
|
+
for (const textBlock of textBlocks) {
|
|
666
|
+
for (const parsedResult of parseOpenAITextResults(textBlock, numResults)) {
|
|
667
|
+
pushUniqueResult(results, parsedResult);
|
|
427
668
|
}
|
|
669
|
+
if (results.length >= numResults) break;
|
|
428
670
|
}
|
|
429
671
|
}
|
|
672
|
+
|
|
673
|
+
log('OpenAI results:', results.length, 'via', variant.label);
|
|
674
|
+
return results.slice(0, numResults);
|
|
675
|
+
} catch (e) {
|
|
676
|
+
lastError = e;
|
|
677
|
+
log('OpenAI variant failed:', variant.label, '-', e.message);
|
|
430
678
|
}
|
|
431
|
-
|
|
432
|
-
log('Results:', results.length);
|
|
433
|
-
return results.length > 0 ? results.slice(0, numResults) : null;
|
|
434
|
-
} catch (e) {
|
|
435
|
-
log('OpenAI error:', e.message);
|
|
436
|
-
return null;
|
|
437
679
|
}
|
|
680
|
+
|
|
681
|
+
throw lastError || new Error('OpenAI web search failed');
|
|
438
682
|
}
|
|
439
683
|
|
|
440
684
|
async function search(query, numResults) {
|
|
441
685
|
numResults = numResults || 10;
|
|
442
686
|
log('Search:', query);
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
if (!modelConfig) {
|
|
446
|
-
|
|
447
|
-
|
|
687
|
+
|
|
688
|
+
const resolved = getCurrentModelConfig(lastObservedProvider);
|
|
689
|
+
if (!resolved.modelConfig) {
|
|
690
|
+
return {
|
|
691
|
+
results: [],
|
|
692
|
+
source: 'none',
|
|
693
|
+
error: resolved.error,
|
|
694
|
+
statusCode: resolved.statusCode || 400,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
try {
|
|
699
|
+
let results = [];
|
|
700
|
+
if (resolved.modelConfig.provider === 'anthropic') {
|
|
701
|
+
results = await searchAnthropicNative(query, numResults, resolved.modelConfig);
|
|
702
|
+
} else if (resolved.modelConfig.provider === 'openai') {
|
|
703
|
+
results = await searchOpenAINative(query, numResults, resolved.modelConfig);
|
|
704
|
+
} else {
|
|
705
|
+
return {
|
|
706
|
+
results: [],
|
|
707
|
+
source: 'none',
|
|
708
|
+
error: 'Unsupported provider: ' + resolved.modelConfig.provider,
|
|
709
|
+
statusCode: 400,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return {
|
|
714
|
+
results: results,
|
|
715
|
+
source: 'native-' + resolved.modelConfig.provider,
|
|
716
|
+
modelId: resolved.modelConfig.id,
|
|
717
|
+
};
|
|
718
|
+
} catch (e) {
|
|
719
|
+
return {
|
|
720
|
+
results: [],
|
|
721
|
+
source: 'none',
|
|
722
|
+
error: e && e.message ? e.message : String(e),
|
|
723
|
+
statusCode: 502,
|
|
724
|
+
};
|
|
448
725
|
}
|
|
449
|
-
|
|
450
|
-
const provider = modelConfig.provider;
|
|
451
|
-
let results = null;
|
|
452
|
-
|
|
453
|
-
if (provider === 'anthropic') results = await searchAnthropicNative(query, numResults, modelConfig);
|
|
454
|
-
else if (provider === 'openai') results = await searchOpenAINative(query, numResults, modelConfig);
|
|
455
|
-
else log('Unsupported provider:', provider);
|
|
456
|
-
|
|
457
|
-
if (results && results.length > 0) return { results: results, source: 'native-' + provider };
|
|
458
|
-
return { results: [], source: 'none' };
|
|
459
726
|
}
|
|
460
727
|
|
|
461
728
|
// === HTTP Proxy Server ===
|
|
@@ -469,14 +736,20 @@ const server = http.createServer(async (req, res) => {
|
|
|
469
736
|
return;
|
|
470
737
|
}
|
|
471
738
|
|
|
472
|
-
if (url
|
|
739
|
+
if (isSearchRequest(url, req.method)) {
|
|
473
740
|
let body = '';
|
|
474
741
|
req.on('data', function(c) { body += c; });
|
|
475
742
|
req.on('end', async function() {
|
|
476
743
|
try {
|
|
477
744
|
const parsed = JSON.parse(body);
|
|
478
745
|
const result = await search(parsed.query, parsed.numResults || 10);
|
|
479
|
-
|
|
746
|
+
if (result.error) {
|
|
747
|
+
log('Search failed:', result.error);
|
|
748
|
+
res.writeHead(result.statusCode || 500, { 'Content-Type': 'application/json' });
|
|
749
|
+
res.end(JSON.stringify({ error: result.error, results: [] }));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
log('Results:', result.results.length, 'from', result.source, 'model', result.modelId || 'unknown');
|
|
480
753
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
481
754
|
res.end(JSON.stringify({ results: result.results }));
|
|
482
755
|
} catch (e) {
|
|
@@ -511,6 +784,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
511
784
|
}
|
|
512
785
|
|
|
513
786
|
// Simple proxy - no SSE transformation (handled by proxy plugin)
|
|
787
|
+
if (url.pathname.startsWith('/api/llm/a/')) lastObservedProvider = 'anthropic';
|
|
788
|
+
if (url.pathname.startsWith('/api/llm/o/')) lastObservedProvider = 'openai';
|
|
514
789
|
log('Proxy:', req.method, url.pathname);
|
|
515
790
|
const proxyUrl = new URL(FACTORY_API + url.pathname + url.search);
|
|
516
791
|
const proxyModule = proxyUrl.protocol === 'https:' ? https : http;
|
|
@@ -571,7 +846,7 @@ should_passthrough() {
|
|
|
571
846
|
for arg in "$@"; do
|
|
572
847
|
if [ "$arg" = "--" ]; then end_opts=1; continue; fi
|
|
573
848
|
if [ "$end_opts" -eq 0 ] && [[ "$arg" == -* ]]; then continue; fi
|
|
574
|
-
case "$arg" in help|version|completion|completions|
|
|
849
|
+
case "$arg" in help|version|completion|completions|plugin) return 0 ;; esac
|
|
575
850
|
break
|
|
576
851
|
done
|
|
577
852
|
return 1
|
|
@@ -625,6 +900,9 @@ rm -f "$PORT_FILE"
|
|
|
625
900
|
|
|
626
901
|
export FACTORY_API_BASE_URL_OVERRIDE="http://127.0.0.1:$ACTUAL_PORT"
|
|
627
902
|
export FACTORY_API_BASE_URL="http://127.0.0.1:$ACTUAL_PORT"
|
|
903
|
+
export FACTORY_APP_BASE_URL_OVERRIDE="http://127.0.0.1:$ACTUAL_PORT"
|
|
904
|
+
export FACTORY_APP_BASE_URL="http://127.0.0.1:$ACTUAL_PORT"
|
|
905
|
+
export TOOLS_WEBSEARCH_BASE_URL="http://127.0.0.1:$ACTUAL_PORT"
|
|
628
906
|
"$DROID_BIN" "$@"
|
|
629
907
|
DROID_EXIT_CODE=$?
|
|
630
908
|
exit $DROID_EXIT_CODE
|
|
@@ -654,7 +932,6 @@ for %%a in (%*) do (
|
|
|
654
932
|
if "%%a"=="version" set "PASSTHROUGH=1"
|
|
655
933
|
if "%%a"=="completion" set "PASSTHROUGH=1"
|
|
656
934
|
if "%%a"=="completions" set "PASSTHROUGH=1"
|
|
657
|
-
if "%%a"=="exec" set "PASSTHROUGH=1"
|
|
658
935
|
if "%%a"=="plugin" set "PASSTHROUGH=1"
|
|
659
936
|
)
|
|
660
937
|
if "%PASSTHROUGH%"=="1" (
|
|
@@ -708,6 +985,9 @@ del "%PORT_FILE%" 2>nul
|
|
|
708
985
|
|
|
709
986
|
set "FACTORY_API_BASE_URL_OVERRIDE=http://127.0.0.1:%ACTUAL_PORT%"
|
|
710
987
|
set "FACTORY_API_BASE_URL=http://127.0.0.1:%ACTUAL_PORT%"
|
|
988
|
+
set "FACTORY_APP_BASE_URL_OVERRIDE=http://127.0.0.1:%ACTUAL_PORT%"
|
|
989
|
+
set "FACTORY_APP_BASE_URL=http://127.0.0.1:%ACTUAL_PORT%"
|
|
990
|
+
set "TOOLS_WEBSEARCH_BASE_URL=http://127.0.0.1:%ACTUAL_PORT%"
|
|
711
991
|
"%DROID_BIN%" %*
|
|
712
992
|
set "DROID_EXIT_CODE=%ERRORLEVEL%"
|
|
713
993
|
|
|
@@ -1061,12 +1341,38 @@ function compareSemver(left, right) {
|
|
|
1061
1341
|
}
|
|
1062
1342
|
return 0;
|
|
1063
1343
|
}
|
|
1344
|
+
function regexMatchesText(text, regex) {
|
|
1345
|
+
const flags = regex.flags.includes("g") ? regex.flags : `${regex.flags}g`;
|
|
1346
|
+
return new RegExp(regex.source, flags).test(text);
|
|
1347
|
+
}
|
|
1348
|
+
function patchMatchesBinary(binaryBuffer, binaryText, patch) {
|
|
1349
|
+
if (patch.regexPattern) return regexMatchesText(binaryText, patch.regexPattern) || !!patch.alreadyPatchedRegexPattern && regexMatchesText(binaryText, patch.alreadyPatchedRegexPattern);
|
|
1350
|
+
return [{
|
|
1351
|
+
pattern: patch.pattern,
|
|
1352
|
+
replacement: patch.replacement
|
|
1353
|
+
}, ...patch.variants || []].some((variant) => !!variant.pattern.length && binaryBuffer.includes(variant.pattern) || !!variant.replacement.length && binaryBuffer.includes(variant.replacement));
|
|
1354
|
+
}
|
|
1355
|
+
function inferVersionedPatchRuleFromBinary(droidPath, rules) {
|
|
1356
|
+
if (!droidPath || !existsSync(droidPath)) return;
|
|
1357
|
+
try {
|
|
1358
|
+
const binaryBuffer = readFileSync(droidPath);
|
|
1359
|
+
const binaryText = binaryBuffer.toString("utf-8");
|
|
1360
|
+
const matchingRules = rules.filter((rule) => rule.buildPatches().every((patch) => patchMatchesBinary(binaryBuffer, binaryText, patch)));
|
|
1361
|
+
if (matchingRules.length === 1) return matchingRules[0];
|
|
1362
|
+
} catch {}
|
|
1363
|
+
}
|
|
1064
1364
|
function matchesVersionRule(droidVersion, rule) {
|
|
1065
1365
|
if (rule.minVersion && compareSemver(droidVersion, rule.minVersion) < 0) return false;
|
|
1066
1366
|
if (rule.maxVersion && compareSemver(droidVersion, rule.maxVersion) >= 0) return false;
|
|
1067
1367
|
return true;
|
|
1068
1368
|
}
|
|
1069
|
-
function resolveVersionedPatches(featureName, droidVersion, rules) {
|
|
1369
|
+
function resolveVersionedPatches(featureName, droidVersion, rules, droidPath) {
|
|
1370
|
+
if (droidVersion && parseSemver(droidVersion)) {
|
|
1371
|
+
const matchedRule = rules.find((rule) => matchesVersionRule(droidVersion, rule));
|
|
1372
|
+
if (matchedRule) return matchedRule.buildPatches();
|
|
1373
|
+
}
|
|
1374
|
+
const inferredRule = inferVersionedPatchRuleFromBinary(droidPath, rules);
|
|
1375
|
+
if (inferredRule) return inferredRule.buildPatches();
|
|
1070
1376
|
if (!droidVersion) throw new Error(`Unable to detect droid version for ${featureName}`);
|
|
1071
1377
|
if (!parseSemver(droidVersion)) throw new Error(`Unsupported droid version format "${droidVersion}" for ${featureName}`);
|
|
1072
1378
|
const matchedRule = rules.find((rule) => matchesVersionRule(droidVersion, rule));
|
|
@@ -1103,6 +1409,40 @@ const SKIP_LOGIN_PATCH_RULES = [{
|
|
|
1103
1409
|
alreadyPatchedRegexPattern: SKIP_LOGIN_V068_PLUS_PATCHED_REGEX
|
|
1104
1410
|
}]
|
|
1105
1411
|
}];
|
|
1412
|
+
const FACTORYD_SELF_PATH_REGEX = /(function ([A-Za-z$_][A-Za-z0-9$_]*)\(([A-Za-z$_][A-Za-z0-9$_]*)\)\{if\()([A-Za-z$_][A-Za-z0-9$_]*)\.basename\(process\.execPath\)\.includes\("droid"\)(\)return process\.execPath;return \3\?"droid-dev":"droid"\})/g;
|
|
1413
|
+
const FACTORYD_SELF_PATH_PATCHED_REGEX = /function ([A-Za-z$_][A-Za-z0-9$_]*)\(([A-Za-z$_][A-Za-z0-9$_]*)\)\{if\(\(1\|\|([A-Za-z$_][A-Za-z0-9$_]*)\.basename\(process\.execPath\)\.includes\(""\)\)\)return process\.execPath;return \2\?"droid-dev":"droid"\}/g;
|
|
1414
|
+
const FACTORYD_SKIP_LOGIN_AUTH_REGEX = /async function ([A-Za-z$_][A-Za-z0-9$_]*)\(([A-Za-z$_][A-Za-z0-9$_]*)\)\{let ([A-Za-z$_][A-Za-z0-9$_]*)=([A-Za-z$_][A-Za-z0-9$_]*)\(\)\.apiBaseUrl,([A-Za-z$_][A-Za-z0-9$_]*)=await fetch\(`\$\{\3\}\/api\/cli\/whoami`,\{method:"GET",headers:\{Authorization:`Bearer \$\{\2\}`\}\}\),([A-Za-z$_][A-Za-z0-9$_]*)=await \5\.text\(\);if\(!\5\.ok\)throw new ([A-Za-z$_][A-Za-z0-9$_]*)\("API key verification failed",\{statusCode:\5\.status,body:\6\}\);let ([A-Za-z$_][A-Za-z0-9$_]*)=([A-Za-z$_][A-Za-z0-9$_]*)\(\6,([A-Za-z$_][A-Za-z0-9$_]*),"whoami response"\);return\{userId:\8\.userId,email:"",orgId:\8\.orgId\}\}/g;
|
|
1415
|
+
const FACTORYD_SKIP_LOGIN_AUTH_PATCHED_REGEX = /async function [A-Za-z$_][A-Za-z0-9$_]*\(([A-Za-z$_][A-Za-z0-9$_]*)\)\{if\(\/\^fk\/\.test\(\1\)\)return\{userId:"f",orgId:"f"\};let ([A-Za-z$_][A-Za-z0-9$_]*)=await fetch\(`\$\{([A-Za-z$_][A-Za-z0-9$_]*)\(\)\.apiBaseUrl\}\/api\/cli\/whoami`,\{headers:\{Authorization:`Bearer \$\{\1\}`\}\}\);if\(!\2\.ok\)throw new [A-Za-z$_][A-Za-z0-9$_]*\("API key verification failed"\);\2=[A-Za-z$_][A-Za-z0-9$_]*\(await \2\.text\(\),([A-Za-z$_][A-Za-z0-9$_]*),"whoami response"\);return\{userId:\2\.userId,email:"",orgId:\2\.orgId\}\s+\}/g;
|
|
1416
|
+
function createFactorydSelfPathPatch() {
|
|
1417
|
+
return {
|
|
1418
|
+
name: "factorydSelfPath",
|
|
1419
|
+
description: "Force factoryd auto-start to reuse the current executable instead of falling back to plain droid",
|
|
1420
|
+
pattern: Buffer.from(""),
|
|
1421
|
+
replacement: Buffer.from(""),
|
|
1422
|
+
regexPattern: FACTORYD_SELF_PATH_REGEX,
|
|
1423
|
+
regexReplacement: "$1(1||$4.basename(process.execPath).includes(\"\"))$5",
|
|
1424
|
+
alreadyPatchedRegexPattern: FACTORYD_SELF_PATH_PATCHED_REGEX
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
function createFactorydSkipLoginAuthPatch() {
|
|
1428
|
+
return {
|
|
1429
|
+
name: "factorydSkipLoginAuth",
|
|
1430
|
+
description: "Allow mission/factoryd auth to reuse fk- API key sessions via the shared /api/cli/whoami helper",
|
|
1431
|
+
pattern: Buffer.from(""),
|
|
1432
|
+
replacement: Buffer.from(""),
|
|
1433
|
+
regexPattern: FACTORYD_SKIP_LOGIN_AUTH_REGEX,
|
|
1434
|
+
regexReplacement: "async function $1($2){if(/^fk/.test($2))return{userId:\"f\",orgId:\"f\"};let $3=await fetch(`${$4().apiBaseUrl}/api/cli/whoami`,{headers:{Authorization:`Bearer ${$2}`}});if(!$3.ok)throw new $7(\"API key verification failed\");$3=$9(await $3.text(),$10,\"whoami response\");return{userId:$3.userId,email:\"\",orgId:$3.orgId} }",
|
|
1435
|
+
alreadyPatchedRegexPattern: FACTORYD_SKIP_LOGIN_AUTH_PATCHED_REGEX
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
function needsBinaryPatches(config) {
|
|
1439
|
+
return config.isCustom || config.skipLogin || config.reasoningEffort || !!config.noTelemetry || !!config.apiBase && !config.websearch && !config.websearchProxy;
|
|
1440
|
+
}
|
|
1441
|
+
function createMissionFactorydPatches(config) {
|
|
1442
|
+
const patches = [createFactorydSelfPathPatch()];
|
|
1443
|
+
if (config.skipLogin) patches.push(createFactorydSkipLoginAuthPatch());
|
|
1444
|
+
return patches;
|
|
1445
|
+
}
|
|
1106
1446
|
function findDefaultDroidPath() {
|
|
1107
1447
|
const home = homedir();
|
|
1108
1448
|
if (IS_WINDOWS) {
|
|
@@ -1126,19 +1466,20 @@ function findDefaultDroidPath() {
|
|
|
1126
1466
|
for (const p of windowsPaths) if (existsSync(p)) return p;
|
|
1127
1467
|
return join(home, ".droid", "bin", "droid.exe");
|
|
1128
1468
|
}
|
|
1129
|
-
try {
|
|
1130
|
-
const
|
|
1469
|
+
for (const lookupCommand of ["command -v droid", "which droid"]) try {
|
|
1470
|
+
const firstResult = execSync(lookupCommand, {
|
|
1131
1471
|
encoding: "utf-8",
|
|
1132
1472
|
stdio: [
|
|
1133
1473
|
"pipe",
|
|
1134
1474
|
"pipe",
|
|
1135
1475
|
"pipe"
|
|
1136
1476
|
]
|
|
1137
|
-
}).trim();
|
|
1138
|
-
if (
|
|
1477
|
+
}).trim().split(/\r?\n/)[0];
|
|
1478
|
+
if (firstResult && existsSync(firstResult)) return firstResult;
|
|
1139
1479
|
} catch {}
|
|
1140
1480
|
const paths = [
|
|
1141
1481
|
join(home, ".droid", "bin", "droid"),
|
|
1482
|
+
join(home, ".local", "bin", "droid"),
|
|
1142
1483
|
"/opt/homebrew/bin/droid",
|
|
1143
1484
|
"/usr/local/bin/droid",
|
|
1144
1485
|
"/usr/bin/droid",
|
|
@@ -1165,7 +1506,15 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1165
1506
|
const verbose = options.verbose;
|
|
1166
1507
|
const droidVersion = getDroidVersion(path);
|
|
1167
1508
|
const outputPath = outputDir && alias ? join(outputDir, alias) : void 0;
|
|
1168
|
-
const needsBinaryPatch =
|
|
1509
|
+
const needsBinaryPatch = needsBinaryPatches({
|
|
1510
|
+
isCustom: !!isCustom,
|
|
1511
|
+
skipLogin: !!skipLogin,
|
|
1512
|
+
apiBase,
|
|
1513
|
+
websearch: !!websearch,
|
|
1514
|
+
websearchProxy: !!websearchProxy,
|
|
1515
|
+
reasoningEffort: !!reasoningEffort,
|
|
1516
|
+
noTelemetry: !!noTelemetry
|
|
1517
|
+
});
|
|
1169
1518
|
if (websearch && websearchProxy) {
|
|
1170
1519
|
console.log(styleText("red", "Error: Cannot use --websearch and --websearch-proxy together"));
|
|
1171
1520
|
console.log(styleText("gray", "Choose one:"));
|
|
@@ -1284,7 +1633,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1284
1633
|
console.log(styleText(["cyan", "bold"], " Droid Binary Patcher"));
|
|
1285
1634
|
console.log(styleText("cyan", "═".repeat(60)));
|
|
1286
1635
|
console.log();
|
|
1287
|
-
const patches =
|
|
1636
|
+
const patches = createMissionFactorydPatches({ skipLogin });
|
|
1288
1637
|
if (isCustom) patches.push({
|
|
1289
1638
|
name: "isCustom",
|
|
1290
1639
|
description: "Change isCustom:!0 to isCustom:!1",
|
|
@@ -1292,7 +1641,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1292
1641
|
replacement: Buffer.from("isCustom:!1")
|
|
1293
1642
|
});
|
|
1294
1643
|
if (skipLogin) try {
|
|
1295
|
-
patches.push(...resolveVersionedPatches("--skip-login", droidVersion, SKIP_LOGIN_PATCH_RULES));
|
|
1644
|
+
patches.push(...resolveVersionedPatches("--skip-login", droidVersion, SKIP_LOGIN_PATCH_RULES, path));
|
|
1296
1645
|
} catch (error) {
|
|
1297
1646
|
console.log(styleText("red", `Error: ${error.message}`));
|
|
1298
1647
|
if (!droidVersion) console.log(styleText("gray", "Please use -p to point to a runnable droid binary."));
|
|
@@ -1431,8 +1780,7 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1431
1780
|
console.log(styleText(["blue", "bold"], " DRY RUN COMPLETE"));
|
|
1432
1781
|
console.log(styleText("blue", "═".repeat(60)));
|
|
1433
1782
|
console.log();
|
|
1434
|
-
console.log(styleText("gray", "To apply the patches,
|
|
1435
|
-
console.log(styleText("cyan", ` npx droid-patch --is-custom ${alias || "<alias-name>"}`));
|
|
1783
|
+
console.log(styleText("gray", "To apply the patches, rerun the same command without --dry-run."));
|
|
1436
1784
|
process.exit(0);
|
|
1437
1785
|
}
|
|
1438
1786
|
if (outputDir && result.success && result.outputPath) {
|
|
@@ -1596,14 +1944,14 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1596
1944
|
continue;
|
|
1597
1945
|
}
|
|
1598
1946
|
try {
|
|
1599
|
-
const patches = [];
|
|
1947
|
+
const patches = needsBinaryPatches(meta.patches) ? createMissionFactorydPatches({ skipLogin: meta.patches.skipLogin }) : [];
|
|
1600
1948
|
if (meta.patches.isCustom) patches.push({
|
|
1601
1949
|
name: "isCustom",
|
|
1602
1950
|
description: "Change isCustom:!0 to isCustom:!1",
|
|
1603
1951
|
pattern: Buffer.from("isCustom:!0"),
|
|
1604
1952
|
replacement: Buffer.from("isCustom:!1")
|
|
1605
1953
|
});
|
|
1606
|
-
if (meta.patches.skipLogin) patches.push(...resolveVersionedPatches("skip-login", newDroidVersion, SKIP_LOGIN_PATCH_RULES));
|
|
1954
|
+
if (meta.patches.skipLogin) patches.push(...resolveVersionedPatches("skip-login", newDroidVersion, SKIP_LOGIN_PATCH_RULES, newBinaryPath));
|
|
1607
1955
|
if (meta.patches.apiBase && !meta.patches.websearch) {
|
|
1608
1956
|
const originalUrl = "https://api.factory.ai";
|
|
1609
1957
|
const paddedUrl = meta.patches.apiBase.padEnd(22, " ");
|
|
@@ -1732,9 +2080,9 @@ bin("droid-patch", "CLI tool to patch droid binary with various modifications").
|
|
|
1732
2080
|
}
|
|
1733
2081
|
}
|
|
1734
2082
|
let execTargetPath = patches.length > 0 ? outputPath : newBinaryPath;
|
|
1735
|
-
if (meta.patches.websearch || !!meta.patches.proxy) {
|
|
2083
|
+
if (meta.patches.websearch || !!meta.patches.websearchProxy || !!meta.patches.proxy) {
|
|
1736
2084
|
const forwardTarget = meta.patches.apiBase || meta.patches.proxy || "https://api.factory.ai";
|
|
1737
|
-
const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), execTargetPath, meta.name, forwardTarget, meta.patches.standalone || false);
|
|
2085
|
+
const { wrapperScript } = await createWebSearchUnifiedFiles(join(homedir(), ".droid-patch", "proxy"), execTargetPath, meta.name, forwardTarget, meta.patches.standalone || false, meta.patches.websearchProxy || false);
|
|
1738
2086
|
execTargetPath = wrapperScript;
|
|
1739
2087
|
if (verbose) {
|
|
1740
2088
|
console.log(styleText("gray", ` Regenerated websearch wrapper`));
|