neoagent 3.2.1-beta.1 → 3.2.1-beta.10
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/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +654 -90
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +111 -20
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +3 -0
- package/flutter_app/lib/main_operations.dart +334 -321
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +4 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +78 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
- package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
- package/flutter_app/windows/runner/flutter_window.cpp +143 -32
- package/landing/index.html +3 -1
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +115 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +2 -2
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +85 -40
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +11 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +73083 -72209
- package/server/routes/admin.js +1 -1
- package/server/routes/android.js +30 -34
- package/server/routes/browser.js +23 -15
- package/server/routes/desktop.js +18 -1
- package/server/routes/integrations.js +107 -1
- package/server/routes/memory.js +1 -0
- package/server/routes/settings.js +16 -5
- package/server/routes/social_reach.js +12 -3
- package/server/routes/social_video.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/capabilityHealth.js +62 -96
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +45 -6
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +390 -162
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +121 -5
- package/server/services/ai/loop/conversation_loop.js +510 -332
- package/server/services/ai/loop/messaging_delivery.js +129 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +20 -45
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +19 -8
- package/server/services/ai/loopPolicy.js +48 -21
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_failure_cache.js +108 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +68 -163
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +166 -0
- package/server/services/ai/providers/anthropic.js +2 -2
- package/server/services/ai/providers/claudeCode.js +21 -33
- package/server/services/ai/providers/githubCopilot.js +41 -20
- package/server/services/ai/providers/google.js +135 -97
- package/server/services/ai/providers/grok.js +4 -3
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +111 -84
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +39 -5
- package/server/services/ai/providers/openaiCodex.js +11 -4
- package/server/services/ai/providers/openrouter.js +29 -7
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/systemPrompt.js +19 -12
- package/server/services/ai/taskAnalysis.js +58 -10
- package/server/services/ai/terminal_reply.js +18 -0
- package/server/services/ai/toolEvidence.js +350 -29
- package/server/services/ai/tools.js +190 -111
- package/server/services/android/controller.js +770 -237
- package/server/services/android/process.js +140 -0
- package/server/services/android/sdk_download.js +143 -0
- package/server/services/android/uia.js +6 -5
- package/server/services/artifacts/store.js +24 -0
- package/server/services/browser/controller.js +843 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +15 -1
- package/server/services/browser/extension/provider.js +71 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- package/server/services/desktop/gateway.js +41 -4
- package/server/services/desktop/protocol.js +3 -0
- package/server/services/desktop/provider.js +39 -42
- package/server/services/desktop/registry.js +137 -52
- package/server/services/integrations/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- package/server/services/integrations/figma/provider.js +78 -12
- package/server/services/integrations/github/common.js +11 -6
- package/server/services/integrations/github/provider.js +52 -53
- package/server/services/integrations/google/provider.js +55 -19
- package/server/services/integrations/home_assistant/network.js +17 -20
- package/server/services/integrations/home_assistant/provider.js +7 -5
- package/server/services/integrations/home_assistant/tools.js +17 -5
- package/server/services/integrations/http.js +51 -0
- package/server/services/integrations/manager.js +159 -53
- package/server/services/integrations/microsoft/provider.js +80 -13
- package/server/services/integrations/neoarchive/provider.js +55 -29
- package/server/services/integrations/neorecall/client.js +17 -10
- package/server/services/integrations/neorecall/provider.js +20 -11
- package/server/services/integrations/notion/provider.js +16 -13
- package/server/services/integrations/oauth_provider.js +115 -51
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/slack/provider.js +98 -9
- package/server/services/integrations/spotify/provider.js +67 -71
- package/server/services/integrations/trello/provider.js +21 -7
- package/server/services/integrations/weather/provider.js +18 -12
- package/server/services/integrations/whatsapp/provider.js +76 -16
- package/server/services/manager.js +110 -1
- package/server/services/memory/embedding_index.js +20 -8
- package/server/services/memory/embeddings.js +151 -90
- package/server/services/memory/ingestion.js +50 -9
- package/server/services/memory/ingestion_documents.js +13 -3
- package/server/services/memory/manager.js +52 -19
- package/server/services/messaging/automation.js +85 -10
- package/server/services/messaging/formatting_guides.js +7 -4
- package/server/services/messaging/http_platforms.js +33 -13
- package/server/services/messaging/inbound_queue.js +78 -24
- package/server/services/messaging/inbound_store.js +224 -0
- package/server/services/messaging/manager.js +326 -51
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +22 -14
- package/server/services/network/http.js +210 -0
- package/server/services/network/safe_request.js +307 -0
- package/server/services/runtime/backends/local-vm.js +227 -67
- package/server/services/runtime/docker-vm-manager.js +9 -0
- package/server/services/runtime/guest_bootstrap.js +30 -4
- package/server/services/runtime/guest_image.js +43 -12
- package/server/services/runtime/manager.js +77 -23
- package/server/services/runtime/validation.js +7 -6
- package/server/services/security/tool_categories.js +6 -0
- package/server/services/social_reach/channels/github.js +10 -4
- package/server/services/social_reach/channels/reddit.js +4 -4
- package/server/services/social_reach/channels/rss.js +2 -2
- package/server/services/social_reach/channels/social_video.js +12 -7
- package/server/services/social_reach/channels/v2ex.js +21 -8
- package/server/services/social_reach/channels/x.js +2 -2
- package/server/services/social_reach/channels/xueqiu.js +5 -5
- package/server/services/social_reach/service.js +9 -6
- package/server/services/social_reach/utils.js +65 -14
- package/server/services/social_video/service.js +160 -50
- package/server/services/tasks/integration_runtime.js +18 -8
- package/server/services/tasks/runtime.js +39 -4
- package/server/services/voice/agentBridge.js +17 -4
- package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
- package/server/services/voice/liveSession.js +31 -0
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtime.js +2 -2
- package/server/services/voice/runtimeManager.js +118 -20
- package/server/services/voice/turnRunner.js +6 -0
- package/server/services/wearable/firmware_manifest.js +51 -13
- package/server/services/wearable/service.js +1 -0
- package/server/utils/abort.js +96 -0
- package/server/utils/cloud-security.js +110 -3
- package/server/utils/files.js +31 -0
- package/server/utils/image_payload.js +95 -0
- package/server/utils/retry.js +107 -0
|
@@ -34,6 +34,8 @@ const { DesktopCompanionRegistry } = require('./desktop/registry');
|
|
|
34
34
|
const { DesktopProvider } = require('./desktop/provider');
|
|
35
35
|
const { TimelineService } = require('./timeline/service');
|
|
36
36
|
const { WearableService } = require('./wearable/service');
|
|
37
|
+
const { BitwardenCli } = require('./credentials/bitwarden_cli');
|
|
38
|
+
const { CredentialBroker } = require('./credentials/broker');
|
|
37
39
|
const { getRuntimeValidation } = require('./runtime/validation');
|
|
38
40
|
const {
|
|
39
41
|
getErrorMessage,
|
|
@@ -142,6 +144,17 @@ function createIntegrationManager(app) {
|
|
|
142
144
|
return integrationManager;
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
function createCredentialBroker(app) {
|
|
148
|
+
const bitwardenCli = registerLocal(app, 'bitwardenCli', new BitwardenCli());
|
|
149
|
+
const credentialBroker = registerLocal(
|
|
150
|
+
app,
|
|
151
|
+
'credentialBroker',
|
|
152
|
+
new CredentialBroker({ bitwarden: bitwardenCli }),
|
|
153
|
+
);
|
|
154
|
+
logServiceReady('Credential broker ready');
|
|
155
|
+
return credentialBroker;
|
|
156
|
+
}
|
|
157
|
+
|
|
145
158
|
function createMemoryIngestionService(app, { memoryManager, integrationManager }) {
|
|
146
159
|
const memoryIngestionService = registerLocal(
|
|
147
160
|
app,
|
|
@@ -469,10 +482,12 @@ async function startServices(app, io) {
|
|
|
469
482
|
const memoryManager = createMemoryManager(app);
|
|
470
483
|
const mcpClient = createMcpClient(app);
|
|
471
484
|
createAuthProviderManager(app);
|
|
485
|
+
const credentialBroker = createCredentialBroker(app);
|
|
472
486
|
const integrationManager = createIntegrationManager(app);
|
|
473
487
|
createMemoryIngestionService(app, { memoryManager, integrationManager });
|
|
474
488
|
const browserController = createBrowserController(app, artifactStore);
|
|
475
489
|
const runtimeManager = createRuntimeManager(app);
|
|
490
|
+
credentialBroker.setRuntimeManager(runtimeManager);
|
|
476
491
|
const runtimeValidation = getRuntimeValidation(runtimeManager);
|
|
477
492
|
registerLocal(app, 'runtimeValidation', runtimeValidation);
|
|
478
493
|
if (!runtimeValidation.ready) {
|
|
@@ -566,7 +581,27 @@ async function stopServices(app) {
|
|
|
566
581
|
console.error('[ApprovalGate] Shutdown error:', getErrorMessage(err));
|
|
567
582
|
}
|
|
568
583
|
}
|
|
569
|
-
if (app.locals.agentEngine && typeof app.locals.agentEngine.
|
|
584
|
+
if (app.locals.agentEngine && typeof app.locals.agentEngine.shutdown === 'function') {
|
|
585
|
+
try {
|
|
586
|
+
tasks.push(
|
|
587
|
+
app.locals.agentEngine.shutdown().then((status) => {
|
|
588
|
+
if (status.timedOut) {
|
|
589
|
+
console.warn(
|
|
590
|
+
`[AgentEngine] Shutdown timed out with ${status.pendingCount} operation(s) still settling`,
|
|
591
|
+
);
|
|
592
|
+
} else {
|
|
593
|
+
logServiceReady('Agent engine shutdown complete');
|
|
594
|
+
}
|
|
595
|
+
}),
|
|
596
|
+
);
|
|
597
|
+
logServiceReady('Active runs and background work marked interrupted');
|
|
598
|
+
} catch (err) {
|
|
599
|
+
console.error('[AgentEngine] Shutdown error:', getErrorMessage(err));
|
|
600
|
+
}
|
|
601
|
+
} else if (
|
|
602
|
+
app.locals.agentEngine
|
|
603
|
+
&& typeof app.locals.agentEngine.interruptAllActiveRuns === 'function'
|
|
604
|
+
) {
|
|
570
605
|
try {
|
|
571
606
|
app.locals.agentEngine.interruptAllActiveRuns();
|
|
572
607
|
logServiceReady('Active runs marked interrupted');
|
|
@@ -595,6 +630,23 @@ async function stopServices(app) {
|
|
|
595
630
|
);
|
|
596
631
|
}
|
|
597
632
|
|
|
633
|
+
if (app.locals.messagingAutomationRuntime) {
|
|
634
|
+
tasks.push(
|
|
635
|
+
Promise.resolve()
|
|
636
|
+
.then(() => app.locals.messagingAutomationRuntime.shutdown())
|
|
637
|
+
.then((status) => {
|
|
638
|
+
if (status.timedOut) {
|
|
639
|
+
console.warn('[MessagingAutomation] Shutdown timed out while work was settling');
|
|
640
|
+
} else {
|
|
641
|
+
logServiceReady('Messaging automation shutdown complete');
|
|
642
|
+
}
|
|
643
|
+
})
|
|
644
|
+
.catch((err) => {
|
|
645
|
+
console.error('[MessagingAutomation] Shutdown error:', getErrorMessage(err));
|
|
646
|
+
}),
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
598
650
|
if (app.locals.streamHub) {
|
|
599
651
|
try {
|
|
600
652
|
await app.locals.streamHub.shutdown();
|
|
@@ -617,6 +669,22 @@ async function stopServices(app) {
|
|
|
617
669
|
);
|
|
618
670
|
}
|
|
619
671
|
|
|
672
|
+
if (
|
|
673
|
+
app.locals.integrationManager &&
|
|
674
|
+
typeof app.locals.integrationManager.shutdown === 'function'
|
|
675
|
+
) {
|
|
676
|
+
tasks.push(
|
|
677
|
+
Promise.resolve()
|
|
678
|
+
.then(() => app.locals.integrationManager.shutdown())
|
|
679
|
+
.then((status) => {
|
|
680
|
+
logServiceReady(`Official integrations shutdown complete (${status.state})`);
|
|
681
|
+
})
|
|
682
|
+
.catch((err) => {
|
|
683
|
+
console.error('[Integrations] Shutdown error:', getErrorMessage(err));
|
|
684
|
+
}),
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
620
688
|
if (app.locals.mcpClient) {
|
|
621
689
|
tasks.push(
|
|
622
690
|
app.locals.mcpClient.shutdown().catch((err) => {
|
|
@@ -641,6 +709,22 @@ async function stopServices(app) {
|
|
|
641
709
|
);
|
|
642
710
|
}
|
|
643
711
|
|
|
712
|
+
if (app.locals.browserExtensionGateway?.close) {
|
|
713
|
+
tasks.push(
|
|
714
|
+
app.locals.browserExtensionGateway.close().catch((err) => {
|
|
715
|
+
console.error('[BrowserExtensionGateway] Shutdown error:', getErrorMessage(err));
|
|
716
|
+
}),
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (app.locals.desktopCompanionGateway?.close) {
|
|
721
|
+
tasks.push(
|
|
722
|
+
app.locals.desktopCompanionGateway.close().catch((err) => {
|
|
723
|
+
console.error('[DesktopCompanionGateway] Shutdown error:', getErrorMessage(err));
|
|
724
|
+
}),
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
644
728
|
if (app.locals.browserControllers instanceof Map) {
|
|
645
729
|
for (const controller of app.locals.browserControllers.values()) {
|
|
646
730
|
tasks.push(
|
|
@@ -659,6 +743,23 @@ async function stopServices(app) {
|
|
|
659
743
|
);
|
|
660
744
|
}
|
|
661
745
|
|
|
746
|
+
if (
|
|
747
|
+
app.locals.voiceRuntimeManager
|
|
748
|
+
&& typeof app.locals.voiceRuntimeManager.shutdown === 'function'
|
|
749
|
+
) {
|
|
750
|
+
tasks.push(
|
|
751
|
+
app.locals.voiceRuntimeManager.shutdown().then((status) => {
|
|
752
|
+
if (status.timedOut) {
|
|
753
|
+
console.warn('[VoiceRuntime] Shutdown timed out while sessions were closing');
|
|
754
|
+
} else {
|
|
755
|
+
logServiceReady('Voice runtime shutdown complete');
|
|
756
|
+
}
|
|
757
|
+
}).catch((err) => {
|
|
758
|
+
console.error('[VoiceRuntime] Shutdown error:', getErrorMessage(err));
|
|
759
|
+
}),
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
662
763
|
if (app.locals.runtimeManager) {
|
|
663
764
|
tasks.push(
|
|
664
765
|
app.locals.runtimeManager.shutdown().catch((err) => {
|
|
@@ -667,6 +768,14 @@ async function stopServices(app) {
|
|
|
667
768
|
);
|
|
668
769
|
}
|
|
669
770
|
|
|
771
|
+
if (app.locals.bitwardenCli) {
|
|
772
|
+
tasks.push(
|
|
773
|
+
app.locals.bitwardenCli.shutdown().catch((err) => {
|
|
774
|
+
console.error('[Bitwarden] Shutdown error:', getErrorMessage(err));
|
|
775
|
+
}),
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
670
779
|
if (app.locals.widgetService) {
|
|
671
780
|
const widgetService = app.locals.widgetService;
|
|
672
781
|
const cleanupMethod = ['shutdown', 'close', 'stop', 'dispose'].find(
|
|
@@ -95,6 +95,8 @@ function findEmbeddingCandidates(db, {
|
|
|
95
95
|
userId,
|
|
96
96
|
agentId,
|
|
97
97
|
embedding,
|
|
98
|
+
embeddingProvider = null,
|
|
99
|
+
embeddingModel = null,
|
|
98
100
|
limit = DEFAULT_CANDIDATE_LIMIT,
|
|
99
101
|
}) {
|
|
100
102
|
const vector = typeof embedding === 'string'
|
|
@@ -104,17 +106,25 @@ function findEmbeddingCandidates(db, {
|
|
|
104
106
|
if (!probesByBand.length) return [];
|
|
105
107
|
|
|
106
108
|
const matches = new Map();
|
|
109
|
+
const embeddingSpace = embeddingProvider && embeddingModel
|
|
110
|
+
? `${embeddingProvider}:${embeddingModel}`
|
|
111
|
+
: '';
|
|
107
112
|
for (const band of probesByBand) {
|
|
108
113
|
const placeholders = band.values.map(() => '?').join(', ');
|
|
109
114
|
const rows = db.prepare(
|
|
110
|
-
`SELECT memory_id
|
|
111
|
-
FROM memory_embedding_bands
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
AND
|
|
115
|
-
AND
|
|
116
|
-
AND
|
|
117
|
-
AND
|
|
115
|
+
`SELECT bands.memory_id
|
|
116
|
+
FROM memory_embedding_bands bands
|
|
117
|
+
JOIN memories memory ON memory.id = bands.memory_id
|
|
118
|
+
WHERE bands.user_id = ?
|
|
119
|
+
AND bands.agent_id = ?
|
|
120
|
+
AND bands.dimension = ?
|
|
121
|
+
AND bands.index_version = ?
|
|
122
|
+
AND bands.band_index = ?
|
|
123
|
+
AND bands.band_value IN (${placeholders})
|
|
124
|
+
AND (
|
|
125
|
+
? = ''
|
|
126
|
+
OR (memory.embedding_provider || ':' || memory.embedding_model) = ?
|
|
127
|
+
)`
|
|
118
128
|
).all(
|
|
119
129
|
userId,
|
|
120
130
|
agentId || '',
|
|
@@ -122,6 +132,8 @@ function findEmbeddingCandidates(db, {
|
|
|
122
132
|
INDEX_VERSION,
|
|
123
133
|
band.bandIndex,
|
|
124
134
|
...band.values,
|
|
135
|
+
embeddingSpace,
|
|
136
|
+
embeddingSpace,
|
|
125
137
|
);
|
|
126
138
|
for (const row of rows) {
|
|
127
139
|
matches.set(row.memory_id, (matches.get(row.memory_id) || 0) + 1);
|
|
@@ -4,134 +4,192 @@
|
|
|
4
4
|
* Embedding helpers for the semantic memory system.
|
|
5
5
|
*
|
|
6
6
|
* Provider selection (in priority order):
|
|
7
|
-
* 1. Google (
|
|
7
|
+
* 1. Google (gemini-embedding-2, 768 dims) — when provider hint is 'google' and GOOGLE_AI_KEY is set
|
|
8
8
|
* 2. OpenAI (text-embedding-3-small, 1536 dims) — when OPENAI_API_KEY is set
|
|
9
9
|
* 3. Keyword fallback — when no API key is available
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const https = require('https');
|
|
13
|
+
const {
|
|
14
|
+
createAbortError,
|
|
15
|
+
isAbortError,
|
|
16
|
+
throwIfAborted,
|
|
17
|
+
} = require('../../utils/abort');
|
|
13
18
|
|
|
14
19
|
const OPENAI_MODEL = 'text-embedding-3-small';
|
|
15
20
|
const OPENAI_DIM = 1536;
|
|
16
|
-
const GOOGLE_MODEL = '
|
|
21
|
+
const GOOGLE_MODEL = 'gemini-embedding-2';
|
|
17
22
|
const GOOGLE_DIM = 768;
|
|
23
|
+
const EMBEDDING_TIMEOUT_MS = 15000;
|
|
24
|
+
const MAX_EMBEDDING_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
18
25
|
|
|
19
26
|
// Exported so callers can sanity-check stored vector dimensions if needed
|
|
20
27
|
const EMBED_DIM = OPENAI_DIM;
|
|
21
28
|
const EMBED_DIM_GOOGLE = GOOGLE_DIM;
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
function toEmbeddingVector(value, expectedDimensions) {
|
|
31
|
+
if (!Array.isArray(value) || value.length !== expectedDimensions) return null;
|
|
32
|
+
const vector = new Float32Array(value.length);
|
|
33
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
34
|
+
const number = Number(value[index]);
|
|
35
|
+
if (!Number.isFinite(number)) return null;
|
|
36
|
+
vector[index] = number;
|
|
37
|
+
}
|
|
38
|
+
return vector;
|
|
39
|
+
}
|
|
29
40
|
|
|
30
|
-
|
|
41
|
+
function requestEmbeddingJson({ hostname, path, headers, body, signal }) {
|
|
42
|
+
throwIfAborted(signal, 'Embedding request aborted.');
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
31
44
|
let settled = false;
|
|
32
|
-
|
|
45
|
+
let request = null;
|
|
46
|
+
let response = null;
|
|
47
|
+
let timer = null;
|
|
48
|
+
|
|
49
|
+
const cleanup = () => {
|
|
50
|
+
if (timer) clearTimeout(timer);
|
|
51
|
+
signal?.removeEventListener('abort', onAbort);
|
|
52
|
+
};
|
|
53
|
+
const finish = (error, value = null) => {
|
|
33
54
|
if (settled) return;
|
|
34
55
|
settled = true;
|
|
35
|
-
|
|
56
|
+
cleanup();
|
|
57
|
+
if (error) reject(error);
|
|
58
|
+
else resolve(value);
|
|
59
|
+
};
|
|
60
|
+
const onAbort = () => {
|
|
61
|
+
const error = createAbortError(signal, 'Embedding request aborted.');
|
|
62
|
+
response?.destroy(error);
|
|
63
|
+
request?.destroy(error);
|
|
64
|
+
finish(error);
|
|
36
65
|
};
|
|
66
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
37
67
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
68
|
+
timer = setTimeout(() => {
|
|
69
|
+
request?.destroy();
|
|
70
|
+
response?.destroy();
|
|
71
|
+
finish(null, null);
|
|
72
|
+
}, EMBEDDING_TIMEOUT_MS);
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
hostname: 'generativelanguage.googleapis.com',
|
|
74
|
+
request = https.request({
|
|
75
|
+
hostname,
|
|
46
76
|
path,
|
|
47
77
|
method: 'POST',
|
|
48
78
|
headers: {
|
|
79
|
+
...headers,
|
|
49
80
|
'Content-Type': 'application/json',
|
|
50
|
-
'Content-Length': Buffer.byteLength(body)
|
|
81
|
+
'Content-Length': Buffer.byteLength(body),
|
|
82
|
+
},
|
|
83
|
+
}, (incoming) => {
|
|
84
|
+
response = incoming;
|
|
85
|
+
if (settled) {
|
|
86
|
+
incoming.destroy();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (incoming.statusCode < 200 || incoming.statusCode >= 300) {
|
|
90
|
+
incoming.destroy();
|
|
91
|
+
finish(null, null);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const contentLength = Number(incoming.headers?.['content-length']);
|
|
95
|
+
if (
|
|
96
|
+
Number.isFinite(contentLength)
|
|
97
|
+
&& contentLength > MAX_EMBEDDING_RESPONSE_BYTES
|
|
98
|
+
) {
|
|
99
|
+
incoming.destroy();
|
|
100
|
+
finish(null, null);
|
|
101
|
+
return;
|
|
51
102
|
}
|
|
52
|
-
};
|
|
53
103
|
|
|
54
|
-
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
|
|
104
|
+
const chunks = [];
|
|
105
|
+
let totalBytes = 0;
|
|
106
|
+
incoming.on('data', (chunk) => {
|
|
107
|
+
if (settled) return;
|
|
108
|
+
const buffer = Buffer.from(chunk);
|
|
109
|
+
totalBytes += buffer.byteLength;
|
|
110
|
+
if (totalBytes > MAX_EMBEDDING_RESPONSE_BYTES) {
|
|
111
|
+
incoming.destroy();
|
|
112
|
+
finish(null, null);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
chunks.push(buffer);
|
|
116
|
+
});
|
|
117
|
+
incoming.on('end', () => {
|
|
118
|
+
if (settled) return;
|
|
58
119
|
try {
|
|
59
|
-
|
|
60
|
-
const vec = parsed.embedding?.values;
|
|
61
|
-
if (!vec) return done(null);
|
|
62
|
-
done(new Float32Array(vec));
|
|
120
|
+
finish(null, JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8')));
|
|
63
121
|
} catch {
|
|
64
|
-
|
|
122
|
+
finish(null, null);
|
|
65
123
|
}
|
|
66
124
|
});
|
|
125
|
+
incoming.on('error', (error) => {
|
|
126
|
+
if (isAbortError(error, signal)) finish(createAbortError(signal));
|
|
127
|
+
else finish(null, null);
|
|
128
|
+
});
|
|
129
|
+
incoming.on('aborted', () => {
|
|
130
|
+
if (signal?.aborted) finish(createAbortError(signal));
|
|
131
|
+
else finish(null, null);
|
|
132
|
+
});
|
|
67
133
|
});
|
|
68
134
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
done(null);
|
|
135
|
+
request.on('error', (error) => {
|
|
136
|
+
if (isAbortError(error, signal)) finish(createAbortError(signal));
|
|
137
|
+
else finish(null, null);
|
|
73
138
|
});
|
|
74
|
-
|
|
75
|
-
req.end();
|
|
139
|
+
request.end(body);
|
|
76
140
|
});
|
|
77
141
|
}
|
|
78
142
|
|
|
79
|
-
|
|
80
|
-
|
|
143
|
+
function formatGoogleEmbeddingInput(text, inputType) {
|
|
144
|
+
if (inputType === 'query') return `task: search result | query: ${text}`;
|
|
145
|
+
if (inputType === 'document') return `title: none | text: ${text}`;
|
|
146
|
+
return `task: sentence similarity | query: ${text}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function getGeminiEmbedding(text, options = {}) {
|
|
150
|
+
const apiKey = process.env.GOOGLE_AI_KEY;
|
|
81
151
|
if (!apiKey) return null;
|
|
82
152
|
if (!text || !text.trim()) return null;
|
|
83
153
|
|
|
84
|
-
const truncated =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
154
|
+
const truncated = formatGoogleEmbeddingInput(
|
|
155
|
+
text.slice(0, 25000),
|
|
156
|
+
options.inputType,
|
|
157
|
+
);
|
|
158
|
+
const body = JSON.stringify({
|
|
159
|
+
model: `models/${GOOGLE_MODEL}`,
|
|
160
|
+
content: { parts: [{ text: truncated }] },
|
|
161
|
+
output_dimensionality: GOOGLE_DIM,
|
|
162
|
+
});
|
|
163
|
+
const data = await requestEmbeddingJson({
|
|
164
|
+
hostname: 'generativelanguage.googleapis.com',
|
|
165
|
+
path: `/v1beta/models/${GOOGLE_MODEL}:embedContent`,
|
|
166
|
+
headers: { 'x-goog-api-key': apiKey },
|
|
167
|
+
body,
|
|
168
|
+
signal: options.signal,
|
|
169
|
+
});
|
|
170
|
+
return toEmbeddingVector(data?.embedding?.values, GOOGLE_DIM);
|
|
171
|
+
}
|
|
99
172
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
headers: {
|
|
105
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
106
|
-
'Content-Type': 'application/json',
|
|
107
|
-
'Content-Length': Buffer.byteLength(body)
|
|
108
|
-
}
|
|
109
|
-
};
|
|
173
|
+
async function getOpenAIEmbedding(text, options = {}) {
|
|
174
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
175
|
+
if (!apiKey) return null;
|
|
176
|
+
if (!text || !text.trim()) return null;
|
|
110
177
|
|
|
111
|
-
|
|
112
|
-
let data = '';
|
|
113
|
-
res.on('data', chunk => { data += chunk; });
|
|
114
|
-
res.on('end', () => {
|
|
115
|
-
try {
|
|
116
|
-
const parsed = JSON.parse(data);
|
|
117
|
-
if (parsed.error) return done(null);
|
|
118
|
-
const vec = parsed.data?.[0]?.embedding;
|
|
119
|
-
if (!vec) return done(null);
|
|
120
|
-
done(new Float32Array(vec));
|
|
121
|
-
} catch {
|
|
122
|
-
done(null);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
});
|
|
178
|
+
const truncated = text.slice(0, 25000);
|
|
126
179
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
180
|
+
const body = JSON.stringify({
|
|
181
|
+
model: OPENAI_MODEL,
|
|
182
|
+
input: truncated,
|
|
183
|
+
encoding_format: 'float',
|
|
184
|
+
});
|
|
185
|
+
const data = await requestEmbeddingJson({
|
|
186
|
+
hostname: 'api.openai.com',
|
|
187
|
+
path: '/v1/embeddings',
|
|
188
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
189
|
+
body,
|
|
190
|
+
signal: options.signal,
|
|
134
191
|
});
|
|
192
|
+
return toEmbeddingVector(data?.data?.[0]?.embedding, OPENAI_DIM);
|
|
135
193
|
}
|
|
136
194
|
|
|
137
195
|
/**
|
|
@@ -140,15 +198,16 @@ async function getOpenAIEmbedding(text) {
|
|
|
140
198
|
* @param {string} [provider] - 'google' to prefer Gemini embeddings
|
|
141
199
|
* @returns {Float32Array|null}
|
|
142
200
|
*/
|
|
143
|
-
async function getEmbedding(text, provider) {
|
|
144
|
-
const result = await getEmbeddingWithMetadata(text, provider);
|
|
201
|
+
async function getEmbedding(text, provider, options = {}) {
|
|
202
|
+
const result = await getEmbeddingWithMetadata(text, provider, options);
|
|
145
203
|
return result?.vector || null;
|
|
146
204
|
}
|
|
147
205
|
|
|
148
|
-
async function getEmbeddingWithMetadata(text, provider) {
|
|
206
|
+
async function getEmbeddingWithMetadata(text, provider, options = {}) {
|
|
149
207
|
if (!text || !text.trim()) return null;
|
|
208
|
+
throwIfAborted(options.signal, 'Embedding request aborted.');
|
|
150
209
|
if (provider === 'google' && process.env.GOOGLE_AI_KEY) {
|
|
151
|
-
const vec = await getGeminiEmbedding(text);
|
|
210
|
+
const vec = await getGeminiEmbedding(text, options);
|
|
152
211
|
if (vec) {
|
|
153
212
|
return {
|
|
154
213
|
vector: vec,
|
|
@@ -158,7 +217,7 @@ async function getEmbeddingWithMetadata(text, provider) {
|
|
|
158
217
|
};
|
|
159
218
|
}
|
|
160
219
|
}
|
|
161
|
-
const vec = await getOpenAIEmbedding(text);
|
|
220
|
+
const vec = await getOpenAIEmbedding(text, options);
|
|
162
221
|
if (!vec) return null;
|
|
163
222
|
return {
|
|
164
223
|
vector: vec,
|
|
@@ -228,5 +287,7 @@ module.exports = {
|
|
|
228
287
|
deserializeEmbedding,
|
|
229
288
|
keywordSimilarity,
|
|
230
289
|
EMBED_DIM,
|
|
231
|
-
EMBED_DIM_GOOGLE
|
|
290
|
+
EMBED_DIM_GOOGLE,
|
|
291
|
+
GOOGLE_MODEL,
|
|
292
|
+
OPENAI_MODEL,
|
|
232
293
|
};
|