opensentinel 2.1.1 → 3.1.1
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/README.md +354 -283
- package/dist/archiver-AVNBYCKQ.js +15340 -0
- package/dist/archiver-AVNBYCKQ.js.map +1 -0
- package/dist/audit-logger-OBPR7CRO.js +22 -0
- package/dist/auth-UOX5K2BE.js +18 -0
- package/dist/autonomy-ZXDBDQUJ.js +86 -0
- package/dist/autonomy-ZXDBDQUJ.js.map +1 -0
- package/dist/aws-s3-Q4LLZZPD.js +146 -0
- package/dist/aws-s3-Q4LLZZPD.js.map +1 -0
- package/dist/backup-restore-PZ7CYYB7.js +16 -0
- package/dist/blocks-R3PODY47.js +23 -0
- package/dist/bot-QRARP4UN.js +36 -0
- package/dist/brain-7XLLM3KC.js +56 -0
- package/dist/camera-monitor-M5CYKUU4.js +335 -0
- package/dist/camera-monitor-M5CYKUU4.js.map +1 -0
- package/dist/{charts-MMXM6BWW.js → charts-V7ARZNKF.js} +2 -2
- package/dist/chunk-22VGGA7S.js +330 -0
- package/dist/chunk-22VGGA7S.js.map +1 -0
- package/dist/chunk-35WYTA3C.js +382 -0
- package/dist/chunk-35WYTA3C.js.map +1 -0
- package/dist/chunk-3E2PSU2C.js +146 -0
- package/dist/chunk-3E2PSU2C.js.map +1 -0
- package/dist/{chunk-L3F43VPB.js → chunk-4GLYY4NN.js} +2 -2
- package/dist/{chunk-L3F43VPB.js.map → chunk-4GLYY4NN.js.map} +1 -1
- package/dist/{chunk-L3PDU3XN.js → chunk-4UOE5TUZ.js} +4 -4
- package/dist/{chunk-6SNHU3CY.js → chunk-66OJ3WB4.js} +2 -2
- package/dist/chunk-6KONMXQ6.js +297 -0
- package/dist/chunk-6KONMXQ6.js.map +1 -0
- package/dist/chunk-6PMVAAA7.js +196 -0
- package/dist/chunk-6PMVAAA7.js.map +1 -0
- package/dist/chunk-766ASQWE.js +32620 -0
- package/dist/chunk-766ASQWE.js.map +1 -0
- package/dist/chunk-7WQO5J2M.js +29 -0
- package/dist/chunk-7WQO5J2M.js.map +1 -0
- package/dist/chunk-APHSRMBS.js +148 -0
- package/dist/chunk-APHSRMBS.js.map +1 -0
- package/dist/{chunk-4LVWXUNC.js → chunk-AYUKPTSM.js} +57 -39
- package/dist/chunk-AYUKPTSM.js.map +1 -0
- package/dist/chunk-BIPYADGB.js +84 -0
- package/dist/chunk-BIPYADGB.js.map +1 -0
- package/dist/chunk-BRBWNV65.js +457 -0
- package/dist/chunk-BRBWNV65.js.map +1 -0
- package/dist/chunk-BXZ6EA52.js +382 -0
- package/dist/chunk-BXZ6EA52.js.map +1 -0
- package/dist/chunk-EVE7MIIY.js +290 -0
- package/dist/chunk-EVE7MIIY.js.map +1 -0
- package/dist/chunk-F3TTNID2.js +138 -0
- package/dist/chunk-F3TTNID2.js.map +1 -0
- package/dist/chunk-H5RQOFO2.js +190 -0
- package/dist/chunk-H5RQOFO2.js.map +1 -0
- package/dist/chunk-HN3F4WSW.js +145 -0
- package/dist/chunk-HN3F4WSW.js.map +1 -0
- package/dist/{chunk-6DRDKB45.js → chunk-I6BDYQIG.js} +20 -9
- package/dist/chunk-I6BDYQIG.js.map +1 -0
- package/dist/chunk-IZJMVV7O.js +347 -0
- package/dist/chunk-IZJMVV7O.js.map +1 -0
- package/dist/chunk-KM22GV7G.js +211 -0
- package/dist/chunk-KM22GV7G.js.map +1 -0
- package/dist/chunk-MGFBLVR7.js +103 -0
- package/dist/chunk-MGFBLVR7.js.map +1 -0
- package/dist/chunk-MQJ2ECQT.js +228 -0
- package/dist/chunk-MQJ2ECQT.js.map +1 -0
- package/dist/{chunk-F6QUZQGI.js → chunk-MXAPLSJ5.js} +2 -2
- package/dist/{chunk-GK3E2I7A.js → chunk-NHMBTUMW.js} +2 -2
- package/dist/chunk-NPRTSZIF.js +131 -0
- package/dist/chunk-NPRTSZIF.js.map +1 -0
- package/dist/chunk-O7IH7JTI.js +1898 -0
- package/dist/chunk-O7IH7JTI.js.map +1 -0
- package/dist/chunk-OCVQGBJK.js +293 -0
- package/dist/chunk-OCVQGBJK.js.map +1 -0
- package/dist/chunk-P6QINGFL.js +332 -0
- package/dist/chunk-P6QINGFL.js.map +1 -0
- package/dist/chunk-PHDZKPNE.js +91 -0
- package/dist/chunk-PHDZKPNE.js.map +1 -0
- package/dist/chunk-PLDDJCW6.js +49 -0
- package/dist/chunk-PTGTGXV2.js +164 -0
- package/dist/chunk-PTGTGXV2.js.map +1 -0
- package/dist/chunk-REMIY4U2.js +171 -0
- package/dist/chunk-REMIY4U2.js.map +1 -0
- package/dist/chunk-RZ4YESBG.js +141 -0
- package/dist/chunk-RZ4YESBG.js.map +1 -0
- package/dist/chunk-SAX5MHK4.js +111 -0
- package/dist/chunk-SAX5MHK4.js.map +1 -0
- package/dist/{chunk-GVJVEWHI.js → chunk-SJSUSJ47.js} +2 -2
- package/dist/chunk-SPPMCAKG.js +777 -0
- package/dist/chunk-SPPMCAKG.js.map +1 -0
- package/dist/chunk-SVAPX2XN.js +2441 -0
- package/dist/chunk-SVAPX2XN.js.map +1 -0
- package/dist/chunk-TVEWKIK3.js +452 -0
- package/dist/chunk-TVEWKIK3.js.map +1 -0
- package/dist/{chunk-HH2HBTQM.js → chunk-TYAGMJNV.js} +5 -5
- package/dist/{chunk-JXUP2X7V.js → chunk-VEHFVBLI.js} +2 -2
- package/dist/chunk-VNX5GMTN.js +128 -0
- package/dist/chunk-VNX5GMTN.js.map +1 -0
- package/dist/chunk-VRD5CYRL.js +1568 -0
- package/dist/chunk-VRD5CYRL.js.map +1 -0
- package/dist/chunk-WLUHNG6X.js +122 -0
- package/dist/chunk-WLUHNG6X.js.map +1 -0
- package/dist/chunk-WRAKK6K6.js +265 -0
- package/dist/chunk-WRAKK6K6.js.map +1 -0
- package/dist/chunk-XKYRH4FM.js +681 -0
- package/dist/chunk-XKYRH4FM.js.map +1 -0
- package/dist/{chunk-GUBEEYDW.js → chunk-XMCVRVTF.js} +2 -2
- package/dist/{chunk-GUBEEYDW.js.map → chunk-XMCVRVTF.js.map} +1 -1
- package/dist/chunk-ZLZKF2PM.js +310 -0
- package/dist/chunk-ZLZKF2PM.js.map +1 -0
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-ZQSFPMOB.js +21 -0
- package/dist/clipboard-manager-TEO2GEDN.js +24 -0
- package/dist/commands/setup.js +3 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.js +3 -3
- package/dist/commands/status.js +2 -2
- package/dist/commands/stop.js +2 -2
- package/dist/commands/utils.js +2 -2
- package/dist/cron-explain-HHQKPD3M.js +16 -0
- package/dist/crypto-4AP47IKC.js +14 -0
- package/dist/crypto-4AP47IKC.js.map +1 -0
- package/dist/databases-37X4CI2Y.js +21 -0
- package/dist/databases-37X4CI2Y.js.map +1 -0
- package/dist/discord-B3HUPGQ6.js +70 -0
- package/dist/discord-B3HUPGQ6.js.map +1 -0
- package/dist/dist-UISMLMFN.js +21847 -0
- package/dist/dist-UISMLMFN.js.map +1 -0
- package/dist/email-K7LO2IPB.js +268 -0
- package/dist/email-K7LO2IPB.js.map +1 -0
- package/dist/enhanced-retrieval-DNLLEM4Z.js +753 -0
- package/dist/enhanced-retrieval-DNLLEM4Z.js.map +1 -0
- package/dist/enrichment-pipeline-MNHNW65K.js +13 -0
- package/dist/enrichment-pipeline-MNHNW65K.js.map +1 -0
- package/dist/entity-resolution-Y3IUWEAT.js +24 -0
- package/dist/entity-resolution-Y3IUWEAT.js.map +1 -0
- package/dist/env-IWXUVTCB.js +12 -0
- package/dist/env-IWXUVTCB.js.map +1 -0
- package/dist/google-workspace-DKWUVNGC.js +169 -0
- package/dist/google-workspace-DKWUVNGC.js.map +1 -0
- package/dist/hash-tool-ULQYD7B5.js +22 -0
- package/dist/hash-tool-ULQYD7B5.js.map +1 -0
- package/dist/heartbeat-monitor-GCISLXI3.js +22 -0
- package/dist/heartbeat-monitor-GCISLXI3.js.map +1 -0
- package/dist/image-generation-OSU7FP6F.js +486 -0
- package/dist/image-generation-OSU7FP6F.js.map +1 -0
- package/dist/imessage-NGA2XF2V.js +35 -0
- package/dist/imessage-NGA2XF2V.js.map +1 -0
- package/dist/inbox-summarizer-NRI4S7IF.js +47 -0
- package/dist/inbox-summarizer-NRI4S7IF.js.map +1 -0
- package/dist/incident-response-C5J7Q6DT.js +244 -0
- package/dist/incident-response-C5J7Q6DT.js.map +1 -0
- package/dist/inventory-manager-352OHXWD.js +24 -0
- package/dist/inventory-manager-352OHXWD.js.map +1 -0
- package/dist/jira-GSGDBMIG.js +199 -0
- package/dist/jira-GSGDBMIG.js.map +1 -0
- package/dist/json-tool-QE2SYHEG.js +26 -0
- package/dist/json-tool-QE2SYHEG.js.map +1 -0
- package/dist/key-rotation-DPHU4ZTB.js +18 -0
- package/dist/key-rotation-DPHU4ZTB.js.map +1 -0
- package/dist/lib.d.ts +603 -11
- package/dist/lib.js +161 -35
- package/dist/lib.js.map +1 -1
- package/dist/mailchimp-KKNF6QJ7.js +152 -0
- package/dist/mailchimp-KKNF6QJ7.js.map +1 -0
- package/dist/matrix-QVHG76I7.js +279 -0
- package/dist/matrix-QVHG76I7.js.map +1 -0
- package/dist/{mcp-LS7Q3Z5W.js → mcp-3JI6W7ZE.js} +3 -3
- package/dist/mcp-3JI6W7ZE.js.map +1 -0
- package/dist/microsoft365-UCBKJHNX.js +164 -0
- package/dist/microsoft365-UCBKJHNX.js.map +1 -0
- package/dist/ocr-AC7NPX33.js +22 -0
- package/dist/ocr-AC7NPX33.js.map +1 -0
- package/dist/ollama-BOAMSPLJ.js +8 -0
- package/dist/ollama-BOAMSPLJ.js.map +1 -0
- package/dist/pages-MI523RB7.js +26 -0
- package/dist/pages-MI523RB7.js.map +1 -0
- package/dist/pair-JDFTERIK.js +24 -0
- package/dist/pair-JDFTERIK.js.map +1 -0
- package/dist/pairing-IFQYCPNS.js +10 -0
- package/dist/pairing-IFQYCPNS.js.map +1 -0
- package/dist/pdf-ALQVOEJR.js +17 -0
- package/dist/pdf-ALQVOEJR.js.map +1 -0
- package/dist/presentations-DSV5IHG5.js +1002 -0
- package/dist/presentations-DSV5IHG5.js.map +1 -0
- package/dist/prometheus-JNT2BD4L.js +10 -0
- package/dist/prometheus-JNT2BD4L.js.map +1 -0
- package/dist/providers-J4LYPHDR.js +19 -0
- package/dist/providers-J4LYPHDR.js.map +1 -0
- package/dist/qr-code-WIX4PB4U.js +16 -0
- package/dist/qr-code-WIX4PB4U.js.map +1 -0
- package/dist/quickbooks-XB4NII2S.js +190 -0
- package/dist/quickbooks-XB4NII2S.js.map +1 -0
- package/dist/regex-tool-W4ABRKGK.js +24 -0
- package/dist/regex-tool-W4ABRKGK.js.map +1 -0
- package/dist/scheduler-VK4WFERV.js +63 -0
- package/dist/scheduler-VK4WFERV.js.map +1 -0
- package/dist/search-BCLBO5E3.js +25 -0
- package/dist/search-BCLBO5E3.js.map +1 -0
- package/dist/sendgrid-RNXCAFKM.js +152 -0
- package/dist/sendgrid-RNXCAFKM.js.map +1 -0
- package/dist/shopify-NCXYJB4R.js +171 -0
- package/dist/shopify-NCXYJB4R.js.map +1 -0
- package/dist/signal-6CGDFYL2.js +35 -0
- package/dist/signal-6CGDFYL2.js.map +1 -0
- package/dist/slack-IZQWIKOH.js +75 -0
- package/dist/slack-IZQWIKOH.js.map +1 -0
- package/dist/sms-M3JIOTCW.js +23 -0
- package/dist/sms-M3JIOTCW.js.map +1 -0
- package/dist/{src-K7GASHRH.js → src-VYUE6LRA.js} +138 -32
- package/dist/src-VYUE6LRA.js.map +1 -0
- package/dist/stocks-XXWBPOCU.js +14 -0
- package/dist/stocks-XXWBPOCU.js.map +1 -0
- package/dist/text-transform-6SGUA5Z4.js +22 -0
- package/dist/text-transform-6SGUA5Z4.js.map +1 -0
- package/dist/tools-2RLEI2N6.js +38 -0
- package/dist/tools-2RLEI2N6.js.map +1 -0
- package/dist/tunnel-IWMXUML4.js +301 -0
- package/dist/tunnel-IWMXUML4.js.map +1 -0
- package/dist/twilio-53GEW5JT.js +139 -0
- package/dist/twilio-53GEW5JT.js.map +1 -0
- package/dist/unit-converter-ZYXMEZOE.js +14 -0
- package/dist/unit-converter-ZYXMEZOE.js.map +1 -0
- package/dist/whatsapp-LFX6YKCM.js +35 -0
- package/dist/whatsapp-LFX6YKCM.js.map +1 -0
- package/dist/word-document-7B6SJMAY.js +902 -0
- package/dist/word-document-7B6SJMAY.js.map +1 -0
- package/dist/xero-QYO66D45.js +162 -0
- package/dist/xero-QYO66D45.js.map +1 -0
- package/dist/zapier-webhook-TBZ5YF2A.js +106 -0
- package/dist/zapier-webhook-TBZ5YF2A.js.map +1 -0
- package/drizzle/0002_mushy_master_mold.sql +140 -0
- package/drizzle/meta/0002_snapshot.json +3637 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +100 -98
- package/dist/bot-KJ26BG56.js +0 -15
- package/dist/chunk-4LVWXUNC.js.map +0 -1
- package/dist/chunk-4TG2IG5K.js +0 -5249
- package/dist/chunk-4TG2IG5K.js.map +0 -1
- package/dist/chunk-6DRDKB45.js.map +0 -1
- package/dist/chunk-CI6Q63MM.js +0 -1613
- package/dist/chunk-CI6Q63MM.js.map +0 -1
- package/dist/chunk-KHNYJY2Z.js +0 -178
- package/dist/chunk-KHNYJY2Z.js.map +0 -1
- package/dist/chunk-NSBPE2FW.js +0 -17
- package/dist/discord-ZOJFTVTB.js +0 -49
- package/dist/imessage-JFRB6EJ7.js +0 -14
- package/dist/scheduler-EZ7CZMCS.js +0 -42
- package/dist/signal-T3MCSULM.js +0 -14
- package/dist/slack-N2M4FHAJ.js +0 -54
- package/dist/src-K7GASHRH.js.map +0 -1
- package/dist/tools-24GZHYRF.js +0 -16
- package/dist/whatsapp-VCRUPAO5.js +0 -14
- /package/dist/{bot-KJ26BG56.js.map → audit-logger-OBPR7CRO.js.map} +0 -0
- /package/dist/{chunk-NSBPE2FW.js.map → auth-UOX5K2BE.js.map} +0 -0
- /package/dist/{discord-ZOJFTVTB.js.map → backup-restore-PZ7CYYB7.js.map} +0 -0
- /package/dist/{imessage-JFRB6EJ7.js.map → blocks-R3PODY47.js.map} +0 -0
- /package/dist/{mcp-LS7Q3Z5W.js.map → bot-QRARP4UN.js.map} +0 -0
- /package/dist/{scheduler-EZ7CZMCS.js.map → brain-7XLLM3KC.js.map} +0 -0
- /package/dist/{charts-MMXM6BWW.js.map → charts-V7ARZNKF.js.map} +0 -0
- /package/dist/{chunk-L3PDU3XN.js.map → chunk-4UOE5TUZ.js.map} +0 -0
- /package/dist/{chunk-6SNHU3CY.js.map → chunk-66OJ3WB4.js.map} +0 -0
- /package/dist/{chunk-F6QUZQGI.js.map → chunk-MXAPLSJ5.js.map} +0 -0
- /package/dist/{chunk-GK3E2I7A.js.map → chunk-NHMBTUMW.js.map} +0 -0
- /package/dist/{signal-T3MCSULM.js.map → chunk-PLDDJCW6.js.map} +0 -0
- /package/dist/{chunk-GVJVEWHI.js.map → chunk-SJSUSJ47.js.map} +0 -0
- /package/dist/{chunk-HH2HBTQM.js.map → chunk-TYAGMJNV.js.map} +0 -0
- /package/dist/{chunk-JXUP2X7V.js.map → chunk-VEHFVBLI.js.map} +0 -0
- /package/dist/{slack-N2M4FHAJ.js.map → client-ZQSFPMOB.js.map} +0 -0
- /package/dist/{tools-24GZHYRF.js.map → clipboard-manager-TEO2GEDN.js.map} +0 -0
- /package/dist/{whatsapp-VCRUPAO5.js.map → cron-explain-HHQKPD3M.js.map} +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
// src/core/providers/openai-compatible-provider.ts
|
|
2
|
+
import OpenAI from "openai";
|
|
3
|
+
var OpenAICompatibleProvider = class {
|
|
4
|
+
id;
|
|
5
|
+
name;
|
|
6
|
+
type;
|
|
7
|
+
client;
|
|
8
|
+
defaultModel;
|
|
9
|
+
capabilities;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.id = config.id;
|
|
12
|
+
this.name = config.name;
|
|
13
|
+
this.type = config.type;
|
|
14
|
+
this.defaultModel = config.defaultModel || "gpt-4o";
|
|
15
|
+
this.client = new OpenAI({
|
|
16
|
+
apiKey: config.apiKey || "not-needed",
|
|
17
|
+
baseURL: config.baseUrl
|
|
18
|
+
});
|
|
19
|
+
this.capabilities = {
|
|
20
|
+
supportsVision: true,
|
|
21
|
+
supportsToolUse: true,
|
|
22
|
+
supportsStreaming: true,
|
|
23
|
+
supportsExtendedThinking: false,
|
|
24
|
+
supportsSystemPrompt: true,
|
|
25
|
+
maxContextWindow: 128e3
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
getCapabilities() {
|
|
29
|
+
return { ...this.capabilities };
|
|
30
|
+
}
|
|
31
|
+
async createMessage(request) {
|
|
32
|
+
const messages = this.convertMessages(request);
|
|
33
|
+
const model = request.model || this.defaultModel;
|
|
34
|
+
const params = {
|
|
35
|
+
model,
|
|
36
|
+
messages,
|
|
37
|
+
max_tokens: request.max_tokens
|
|
38
|
+
};
|
|
39
|
+
if (request.tools?.length) {
|
|
40
|
+
params.tools = request.tools.map(llmToolToOpenAI);
|
|
41
|
+
}
|
|
42
|
+
if (request.thinking) {
|
|
43
|
+
console.warn(`[${this.name}] Extended thinking not supported, ignoring`);
|
|
44
|
+
}
|
|
45
|
+
const response = await this.client.chat.completions.create(params);
|
|
46
|
+
return this.convertResponse(response, model);
|
|
47
|
+
}
|
|
48
|
+
streamMessage(request) {
|
|
49
|
+
const messages = this.convertMessages(request);
|
|
50
|
+
const model = request.model || this.defaultModel;
|
|
51
|
+
const params = {
|
|
52
|
+
model,
|
|
53
|
+
messages,
|
|
54
|
+
max_tokens: request.max_tokens,
|
|
55
|
+
stream: true
|
|
56
|
+
};
|
|
57
|
+
if (request.tools?.length) {
|
|
58
|
+
params.tools = request.tools.map(llmToolToOpenAI);
|
|
59
|
+
}
|
|
60
|
+
if (request.thinking) {
|
|
61
|
+
console.warn(`[${this.name}] Extended thinking not supported, ignoring`);
|
|
62
|
+
}
|
|
63
|
+
let fullContent = "";
|
|
64
|
+
let toolCalls = [];
|
|
65
|
+
let finishReason = "stop";
|
|
66
|
+
let inputTokens = 0;
|
|
67
|
+
let outputTokens = 0;
|
|
68
|
+
const streamPromise = this.client.chat.completions.create(params);
|
|
69
|
+
const events = {
|
|
70
|
+
[Symbol.asyncIterator]() {
|
|
71
|
+
return {
|
|
72
|
+
streamDone: false,
|
|
73
|
+
async next() {
|
|
74
|
+
return { done: true, value: void 0 };
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const self = this;
|
|
80
|
+
return {
|
|
81
|
+
events: {
|
|
82
|
+
async *[Symbol.asyncIterator]() {
|
|
83
|
+
try {
|
|
84
|
+
const stream = await self.client.chat.completions.create({
|
|
85
|
+
...params,
|
|
86
|
+
stream: true
|
|
87
|
+
});
|
|
88
|
+
for await (const chunk of stream) {
|
|
89
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
90
|
+
if (delta?.content) {
|
|
91
|
+
fullContent += delta.content;
|
|
92
|
+
yield {
|
|
93
|
+
type: "content_block_delta",
|
|
94
|
+
delta: { type: "text_delta", text: delta.content }
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (delta?.tool_calls) {
|
|
98
|
+
for (const tc of delta.tool_calls) {
|
|
99
|
+
if (tc.index !== void 0) {
|
|
100
|
+
while (toolCalls.length <= tc.index) {
|
|
101
|
+
toolCalls.push({ id: "", name: "", arguments: "" });
|
|
102
|
+
}
|
|
103
|
+
if (tc.id) toolCalls[tc.index].id = tc.id;
|
|
104
|
+
if (tc.function?.name) toolCalls[tc.index].name = tc.function.name;
|
|
105
|
+
if (tc.function?.arguments) toolCalls[tc.index].arguments += tc.function.arguments;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (chunk.choices?.[0]?.finish_reason) {
|
|
110
|
+
finishReason = chunk.choices[0].finish_reason;
|
|
111
|
+
}
|
|
112
|
+
if (chunk.usage) {
|
|
113
|
+
inputTokens = chunk.usage.prompt_tokens || 0;
|
|
114
|
+
outputTokens = chunk.usage.completion_tokens || 0;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
const response = await self.client.chat.completions.create({
|
|
119
|
+
...params,
|
|
120
|
+
stream: false
|
|
121
|
+
});
|
|
122
|
+
const msg = response.choices?.[0]?.message;
|
|
123
|
+
if (msg?.content) {
|
|
124
|
+
fullContent = msg.content;
|
|
125
|
+
yield {
|
|
126
|
+
type: "content_block_delta",
|
|
127
|
+
delta: { type: "text_delta", text: msg.content }
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
finishReason = response.choices?.[0]?.finish_reason || "stop";
|
|
131
|
+
inputTokens = response.usage?.prompt_tokens || 0;
|
|
132
|
+
outputTokens = response.usage?.completion_tokens || 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
async finalMessage() {
|
|
137
|
+
const contentBlocks = [];
|
|
138
|
+
if (fullContent) {
|
|
139
|
+
contentBlocks.push({ type: "text", text: fullContent });
|
|
140
|
+
}
|
|
141
|
+
for (const tc of toolCalls) {
|
|
142
|
+
if (tc.name) {
|
|
143
|
+
let parsedArgs = {};
|
|
144
|
+
try {
|
|
145
|
+
parsedArgs = JSON.parse(tc.arguments || "{}");
|
|
146
|
+
} catch {
|
|
147
|
+
parsedArgs = {};
|
|
148
|
+
}
|
|
149
|
+
contentBlocks.push({
|
|
150
|
+
type: "tool_use",
|
|
151
|
+
id: tc.id || `call_${Date.now()}`,
|
|
152
|
+
name: tc.name,
|
|
153
|
+
input: parsedArgs
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
content: contentBlocks,
|
|
159
|
+
stop_reason: finishReason === "tool_calls" ? "tool_use" : mapFinishReason(finishReason),
|
|
160
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
161
|
+
model
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async listModels() {
|
|
167
|
+
try {
|
|
168
|
+
const models = await this.client.models.list();
|
|
169
|
+
return (models.data || []).map((m) => m.id);
|
|
170
|
+
} catch {
|
|
171
|
+
return [this.defaultModel];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async isAvailable() {
|
|
175
|
+
try {
|
|
176
|
+
await this.client.models.list();
|
|
177
|
+
return true;
|
|
178
|
+
} catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// ============================================
|
|
183
|
+
// Conversion helpers
|
|
184
|
+
// ============================================
|
|
185
|
+
convertMessages(request) {
|
|
186
|
+
const messages = [];
|
|
187
|
+
if (request.system) {
|
|
188
|
+
messages.push({ role: "system", content: request.system });
|
|
189
|
+
}
|
|
190
|
+
for (const msg of request.messages) {
|
|
191
|
+
if (msg.role === "system") continue;
|
|
192
|
+
if (typeof msg.content === "string") {
|
|
193
|
+
messages.push({ role: msg.role, content: msg.content });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const hasToolResult = msg.content.some((b) => b.type === "tool_result");
|
|
197
|
+
if (hasToolResult) {
|
|
198
|
+
for (const block of msg.content) {
|
|
199
|
+
if (block.type === "tool_result") {
|
|
200
|
+
messages.push({
|
|
201
|
+
role: "tool",
|
|
202
|
+
tool_call_id: block.tool_use_id,
|
|
203
|
+
content: block.content || ""
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const hasToolUse = msg.content.some((b) => b.type === "tool_use");
|
|
210
|
+
if (hasToolUse) {
|
|
211
|
+
const textParts = msg.content.filter((b) => b.type === "text");
|
|
212
|
+
const toolParts = msg.content.filter((b) => b.type === "tool_use");
|
|
213
|
+
messages.push({
|
|
214
|
+
role: "assistant",
|
|
215
|
+
content: textParts.map((b) => b.text).join("") || null,
|
|
216
|
+
tool_calls: toolParts.map((b) => ({
|
|
217
|
+
id: b.id || `call_${Date.now()}`,
|
|
218
|
+
type: "function",
|
|
219
|
+
function: {
|
|
220
|
+
name: b.name,
|
|
221
|
+
arguments: JSON.stringify(b.input || {})
|
|
222
|
+
}
|
|
223
|
+
}))
|
|
224
|
+
});
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const parts = [];
|
|
228
|
+
for (const block of msg.content) {
|
|
229
|
+
if (block.type === "text") {
|
|
230
|
+
parts.push({ type: "text", text: block.text });
|
|
231
|
+
} else if (block.type === "image") {
|
|
232
|
+
if (block.source?.type === "url" && block.source.url) {
|
|
233
|
+
parts.push({
|
|
234
|
+
type: "image_url",
|
|
235
|
+
image_url: { url: block.source.url }
|
|
236
|
+
});
|
|
237
|
+
} else if (block.source?.data) {
|
|
238
|
+
const mediaType = block.source.mediaType || "image/jpeg";
|
|
239
|
+
parts.push({
|
|
240
|
+
type: "image_url",
|
|
241
|
+
image_url: {
|
|
242
|
+
url: `data:${mediaType};base64,${block.source.data}`
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
messages.push({ role: msg.role, content: parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts });
|
|
249
|
+
}
|
|
250
|
+
return messages;
|
|
251
|
+
}
|
|
252
|
+
convertResponse(response, model) {
|
|
253
|
+
const choice = response.choices?.[0];
|
|
254
|
+
const message = choice?.message;
|
|
255
|
+
const contentBlocks = [];
|
|
256
|
+
if (message?.content) {
|
|
257
|
+
contentBlocks.push({ type: "text", text: message.content });
|
|
258
|
+
}
|
|
259
|
+
if (message?.tool_calls) {
|
|
260
|
+
for (const tc of message.tool_calls) {
|
|
261
|
+
let parsedArgs = {};
|
|
262
|
+
try {
|
|
263
|
+
parsedArgs = JSON.parse(tc.function?.arguments || "{}");
|
|
264
|
+
} catch {
|
|
265
|
+
parsedArgs = {};
|
|
266
|
+
}
|
|
267
|
+
contentBlocks.push({
|
|
268
|
+
type: "tool_use",
|
|
269
|
+
id: tc.id,
|
|
270
|
+
name: tc.function?.name || "",
|
|
271
|
+
input: parsedArgs
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const finishReason = choice?.finish_reason || "stop";
|
|
276
|
+
return {
|
|
277
|
+
content: contentBlocks,
|
|
278
|
+
stop_reason: finishReason === "tool_calls" ? "tool_use" : mapFinishReason(finishReason),
|
|
279
|
+
usage: {
|
|
280
|
+
input_tokens: response.usage?.prompt_tokens || 0,
|
|
281
|
+
output_tokens: response.usage?.completion_tokens || 0
|
|
282
|
+
},
|
|
283
|
+
model
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
function llmToolToOpenAI(tool) {
|
|
288
|
+
return {
|
|
289
|
+
type: "function",
|
|
290
|
+
function: {
|
|
291
|
+
name: tool.name,
|
|
292
|
+
description: tool.description,
|
|
293
|
+
parameters: tool.input_schema
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function mapFinishReason(reason) {
|
|
298
|
+
switch (reason) {
|
|
299
|
+
case "stop":
|
|
300
|
+
return "end_turn";
|
|
301
|
+
case "tool_calls":
|
|
302
|
+
return "tool_use";
|
|
303
|
+
case "length":
|
|
304
|
+
return "max_tokens";
|
|
305
|
+
default:
|
|
306
|
+
return "end_turn";
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/core/providers/ollama.ts
|
|
311
|
+
var OllamaProvider = class extends OpenAICompatibleProvider {
|
|
312
|
+
baseUrl;
|
|
313
|
+
constructor(baseUrl = "http://localhost:11434", defaultModel = "llama3.1") {
|
|
314
|
+
super({
|
|
315
|
+
id: "ollama",
|
|
316
|
+
name: "Ollama",
|
|
317
|
+
type: "openai-compatible",
|
|
318
|
+
apiKey: "ollama",
|
|
319
|
+
// Ollama doesn't require an API key
|
|
320
|
+
baseUrl: `${baseUrl}/v1`,
|
|
321
|
+
defaultModel,
|
|
322
|
+
enabled: true
|
|
323
|
+
});
|
|
324
|
+
this.baseUrl = baseUrl;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Return conservative capabilities since not all Ollama models
|
|
328
|
+
* support vision or tool use.
|
|
329
|
+
*/
|
|
330
|
+
getCapabilities() {
|
|
331
|
+
return {
|
|
332
|
+
supportsVision: false,
|
|
333
|
+
supportsToolUse: false,
|
|
334
|
+
supportsStreaming: true,
|
|
335
|
+
supportsExtendedThinking: false,
|
|
336
|
+
supportsSystemPrompt: true,
|
|
337
|
+
maxContextWindow: 8192
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* List available models using Ollama's native /api/tags endpoint.
|
|
342
|
+
* Falls back to the parent's OpenAI-compatible listing on error.
|
|
343
|
+
*/
|
|
344
|
+
async listModels() {
|
|
345
|
+
try {
|
|
346
|
+
const response = await fetch(`${this.baseUrl}/api/tags`);
|
|
347
|
+
if (!response.ok) {
|
|
348
|
+
throw new Error(`Ollama /api/tags returned ${response.status}`);
|
|
349
|
+
}
|
|
350
|
+
const data = await response.json();
|
|
351
|
+
if (data.models && data.models.length > 0) {
|
|
352
|
+
return data.models.map((m) => m.name);
|
|
353
|
+
}
|
|
354
|
+
return [this.defaultModel];
|
|
355
|
+
} catch {
|
|
356
|
+
return super.listModels();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Check if the local Ollama server is reachable by hitting
|
|
361
|
+
* the native /api/tags endpoint with a 3-second timeout.
|
|
362
|
+
*/
|
|
363
|
+
async isAvailable() {
|
|
364
|
+
try {
|
|
365
|
+
const controller = new AbortController();
|
|
366
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
367
|
+
const response = await fetch(`${this.baseUrl}/api/tags`, {
|
|
368
|
+
signal: controller.signal
|
|
369
|
+
});
|
|
370
|
+
clearTimeout(timeout);
|
|
371
|
+
return response.ok;
|
|
372
|
+
} catch {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
export {
|
|
379
|
+
OpenAICompatibleProvider,
|
|
380
|
+
OllamaProvider
|
|
381
|
+
};
|
|
382
|
+
//# sourceMappingURL=chunk-35WYTA3C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/providers/openai-compatible-provider.ts","../src/core/providers/ollama.ts"],"sourcesContent":["/**\n * OpenAI-Compatible Provider\n *\n * Works with OpenAI, OpenRouter, Groq, Mistral, Ollama, and any\n * OpenAI-compatible API endpoint. Converts between provider-agnostic\n * LLM types and OpenAI's chat completions format.\n */\n\nimport OpenAI from \"openai\";\nimport type { LLMProvider } from \"./provider\";\nimport type {\n LLMProviderCapabilities,\n LLMProviderConfig,\n LLMRequest,\n LLMResponse,\n LLMContentBlock,\n LLMStreamEvent,\n LLMStreamResult,\n LLMMessage,\n LLMTool,\n} from \"./types\";\n\nexport class OpenAICompatibleProvider implements LLMProvider {\n readonly id: string;\n readonly name: string;\n readonly type: string;\n protected client: OpenAI;\n protected defaultModel: string;\n protected capabilities: LLMProviderCapabilities;\n\n constructor(config: LLMProviderConfig) {\n this.id = config.id;\n this.name = config.name;\n this.type = config.type;\n this.defaultModel = config.defaultModel || \"gpt-4o\";\n\n this.client = new OpenAI({\n apiKey: config.apiKey || \"not-needed\",\n baseURL: config.baseUrl,\n });\n\n this.capabilities = {\n supportsVision: true,\n supportsToolUse: true,\n supportsStreaming: true,\n supportsExtendedThinking: false,\n supportsSystemPrompt: true,\n maxContextWindow: 128000,\n };\n }\n\n getCapabilities(): LLMProviderCapabilities {\n return { ...this.capabilities };\n }\n\n async createMessage(request: LLMRequest): Promise<LLMResponse> {\n const messages = this.convertMessages(request);\n const model = request.model || this.defaultModel;\n\n const params: any = {\n model,\n messages,\n max_tokens: request.max_tokens,\n };\n\n if (request.tools?.length) {\n params.tools = request.tools.map(llmToolToOpenAI);\n }\n\n // Extended thinking not supported — log warning and ignore\n if (request.thinking) {\n console.warn(`[${this.name}] Extended thinking not supported, ignoring`);\n }\n\n const response = await this.client.chat.completions.create(params);\n return this.convertResponse(response, model);\n }\n\n streamMessage(request: LLMRequest): LLMStreamResult {\n const messages = this.convertMessages(request);\n const model = request.model || this.defaultModel;\n\n const params: any = {\n model,\n messages,\n max_tokens: request.max_tokens,\n stream: true,\n };\n\n if (request.tools?.length) {\n params.tools = request.tools.map(llmToolToOpenAI);\n }\n\n if (request.thinking) {\n console.warn(`[${this.name}] Extended thinking not supported, ignoring`);\n }\n\n // Accumulate for finalMessage\n let fullContent = \"\";\n let toolCalls: any[] = [];\n let finishReason = \"stop\";\n let inputTokens = 0;\n let outputTokens = 0;\n\n const streamPromise = this.client.chat.completions.create(params) as Promise<any>;\n\n const events: AsyncIterable<LLMStreamEvent> = {\n [Symbol.asyncIterator]() {\n return {\n streamDone: false,\n async next(): Promise<IteratorResult<LLMStreamEvent>> {\n // We need a different approach for OpenAI streaming\n // Use the non-streaming call and emit chunks manually\n return { done: true, value: undefined as any };\n },\n };\n },\n };\n\n // For OpenAI-compatible, we use a simpler approach: create a non-streaming\n // call wrapped as a stream-like result. This avoids complex SSE parsing\n // differences across providers.\n const self = this;\n\n return {\n events: {\n async *[Symbol.asyncIterator]() {\n try {\n const stream = await self.client.chat.completions.create({\n ...params,\n stream: true,\n });\n\n for await (const chunk of stream as any) {\n const delta = chunk.choices?.[0]?.delta;\n if (delta?.content) {\n fullContent += delta.content;\n yield {\n type: \"content_block_delta\" as const,\n delta: { type: \"text_delta\" as const, text: delta.content },\n };\n }\n if (delta?.tool_calls) {\n for (const tc of delta.tool_calls) {\n if (tc.index !== undefined) {\n while (toolCalls.length <= tc.index) {\n toolCalls.push({ id: \"\", name: \"\", arguments: \"\" });\n }\n if (tc.id) toolCalls[tc.index].id = tc.id;\n if (tc.function?.name) toolCalls[tc.index].name = tc.function.name;\n if (tc.function?.arguments) toolCalls[tc.index].arguments += tc.function.arguments;\n }\n }\n }\n if (chunk.choices?.[0]?.finish_reason) {\n finishReason = chunk.choices[0].finish_reason;\n }\n if (chunk.usage) {\n inputTokens = chunk.usage.prompt_tokens || 0;\n outputTokens = chunk.usage.completion_tokens || 0;\n }\n }\n } catch (err) {\n // If streaming fails, fall back to non-streaming\n const response = await self.client.chat.completions.create({\n ...params,\n stream: false,\n });\n const msg = (response as any).choices?.[0]?.message;\n if (msg?.content) {\n fullContent = msg.content;\n yield {\n type: \"content_block_delta\" as const,\n delta: { type: \"text_delta\" as const, text: msg.content },\n };\n }\n finishReason = (response as any).choices?.[0]?.finish_reason || \"stop\";\n inputTokens = (response as any).usage?.prompt_tokens || 0;\n outputTokens = (response as any).usage?.completion_tokens || 0;\n }\n },\n },\n async finalMessage(): Promise<LLMResponse> {\n const contentBlocks: LLMContentBlock[] = [];\n\n if (fullContent) {\n contentBlocks.push({ type: \"text\", text: fullContent });\n }\n\n for (const tc of toolCalls) {\n if (tc.name) {\n let parsedArgs: unknown = {};\n try {\n parsedArgs = JSON.parse(tc.arguments || \"{}\");\n } catch {\n parsedArgs = {};\n }\n contentBlocks.push({\n type: \"tool_use\",\n id: tc.id || `call_${Date.now()}`,\n name: tc.name,\n input: parsedArgs,\n });\n }\n }\n\n return {\n content: contentBlocks,\n stop_reason: finishReason === \"tool_calls\" ? \"tool_use\" : mapFinishReason(finishReason),\n usage: { input_tokens: inputTokens, output_tokens: outputTokens },\n model,\n };\n },\n };\n }\n\n async listModels(): Promise<string[]> {\n try {\n const models = await this.client.models.list();\n return (models.data || []).map((m: any) => m.id);\n } catch {\n return [this.defaultModel];\n }\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n await this.client.models.list();\n return true;\n } catch {\n return false;\n }\n }\n\n // ============================================\n // Conversion helpers\n // ============================================\n\n protected convertMessages(request: LLMRequest): any[] {\n const messages: any[] = [];\n\n // Add system prompt as a system role message\n if (request.system) {\n messages.push({ role: \"system\", content: request.system });\n }\n\n for (const msg of request.messages) {\n if (msg.role === \"system\") continue; // Already handled above\n\n if (typeof msg.content === \"string\") {\n messages.push({ role: msg.role, content: msg.content });\n continue;\n }\n\n // Convert content blocks\n const hasToolResult = msg.content.some((b) => b.type === \"tool_result\");\n if (hasToolResult) {\n // OpenAI uses separate tool messages\n for (const block of msg.content) {\n if (block.type === \"tool_result\") {\n messages.push({\n role: \"tool\",\n tool_call_id: block.tool_use_id,\n content: block.content || \"\",\n });\n }\n }\n continue;\n }\n\n const hasToolUse = msg.content.some((b) => b.type === \"tool_use\");\n if (hasToolUse) {\n // Assistant message with tool calls\n const textParts = msg.content.filter((b) => b.type === \"text\");\n const toolParts = msg.content.filter((b) => b.type === \"tool_use\");\n\n messages.push({\n role: \"assistant\",\n content: textParts.map((b) => b.text).join(\"\") || null,\n tool_calls: toolParts.map((b) => ({\n id: b.id || `call_${Date.now()}`,\n type: \"function\",\n function: {\n name: b.name,\n arguments: JSON.stringify(b.input || {}),\n },\n })),\n });\n continue;\n }\n\n // Mixed content (text + images)\n const parts: any[] = [];\n for (const block of msg.content) {\n if (block.type === \"text\") {\n parts.push({ type: \"text\", text: block.text });\n } else if (block.type === \"image\") {\n if (block.source?.type === \"url\" && block.source.url) {\n parts.push({\n type: \"image_url\",\n image_url: { url: block.source.url },\n });\n } else if (block.source?.data) {\n const mediaType = block.source.mediaType || \"image/jpeg\";\n parts.push({\n type: \"image_url\",\n image_url: {\n url: `data:${mediaType};base64,${block.source.data}`,\n },\n });\n }\n }\n }\n\n messages.push({ role: msg.role, content: parts.length === 1 && parts[0].type === \"text\" ? parts[0].text : parts });\n }\n\n return messages;\n }\n\n protected convertResponse(response: any, model: string): LLMResponse {\n const choice = response.choices?.[0];\n const message = choice?.message;\n const contentBlocks: LLMContentBlock[] = [];\n\n if (message?.content) {\n contentBlocks.push({ type: \"text\", text: message.content });\n }\n\n // Convert tool calls\n if (message?.tool_calls) {\n for (const tc of message.tool_calls) {\n let parsedArgs: unknown = {};\n try {\n parsedArgs = JSON.parse(tc.function?.arguments || \"{}\");\n } catch {\n parsedArgs = {};\n }\n contentBlocks.push({\n type: \"tool_use\",\n id: tc.id,\n name: tc.function?.name || \"\",\n input: parsedArgs,\n });\n }\n }\n\n const finishReason = choice?.finish_reason || \"stop\";\n\n return {\n content: contentBlocks,\n stop_reason: finishReason === \"tool_calls\" ? \"tool_use\" : mapFinishReason(finishReason),\n usage: {\n input_tokens: response.usage?.prompt_tokens || 0,\n output_tokens: response.usage?.completion_tokens || 0,\n },\n model,\n };\n }\n}\n\n// ============================================\n// Tool conversion\n// ============================================\n\nfunction llmToolToOpenAI(tool: LLMTool): any {\n return {\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.input_schema,\n },\n };\n}\n\nfunction mapFinishReason(reason: string): LLMResponse[\"stop_reason\"] {\n switch (reason) {\n case \"stop\":\n return \"end_turn\";\n case \"tool_calls\":\n return \"tool_use\";\n case \"length\":\n return \"max_tokens\";\n default:\n return \"end_turn\";\n }\n}\n","/**\n * Ollama Provider\n *\n * Extends the OpenAI-compatible provider for local Ollama instances.\n * Ollama exposes an OpenAI-compatible API at /v1 and its own native\n * endpoints (e.g. /api/tags) for model management.\n */\n\nimport { OpenAICompatibleProvider } from \"./openai-compatible-provider\";\nimport type { LLMProviderCapabilities } from \"./types\";\n\nexport class OllamaProvider extends OpenAICompatibleProvider {\n private baseUrl: string;\n\n constructor(\n baseUrl: string = \"http://localhost:11434\",\n defaultModel: string = \"llama3.1\"\n ) {\n super({\n id: \"ollama\",\n name: \"Ollama\",\n type: \"openai-compatible\",\n apiKey: \"ollama\", // Ollama doesn't require an API key\n baseUrl: `${baseUrl}/v1`,\n defaultModel,\n enabled: true,\n });\n\n this.baseUrl = baseUrl;\n }\n\n /**\n * Return conservative capabilities since not all Ollama models\n * support vision or tool use.\n */\n override getCapabilities(): LLMProviderCapabilities {\n return {\n supportsVision: false,\n supportsToolUse: false,\n supportsStreaming: true,\n supportsExtendedThinking: false,\n supportsSystemPrompt: true,\n maxContextWindow: 8192,\n };\n }\n\n /**\n * List available models using Ollama's native /api/tags endpoint.\n * Falls back to the parent's OpenAI-compatible listing on error.\n */\n override async listModels(): Promise<string[]> {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n if (!response.ok) {\n throw new Error(`Ollama /api/tags returned ${response.status}`);\n }\n const data = (await response.json()) as {\n models?: Array<{ name: string }>;\n };\n if (data.models && data.models.length > 0) {\n return data.models.map((m) => m.name);\n }\n return [this.defaultModel];\n } catch {\n return super.listModels();\n }\n }\n\n /**\n * Check if the local Ollama server is reachable by hitting\n * the native /api/tags endpoint with a 3-second timeout.\n */\n override async isAvailable(): Promise<boolean> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 3000);\n\n const response = await fetch(`${this.baseUrl}/api/tags`, {\n signal: controller.signal,\n });\n\n clearTimeout(timeout);\n return response.ok;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// src/core/security/pairing.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
var PairingManager = class {
|
|
4
|
+
currentCode = null;
|
|
5
|
+
devices = /* @__PURE__ */ new Map();
|
|
6
|
+
codeLifetimeMs = 5 * 60 * 1e3;
|
|
7
|
+
// 5 minutes
|
|
8
|
+
// -----------------------------------------------------------------------
|
|
9
|
+
// Code lifetime configuration
|
|
10
|
+
// -----------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Set the lifetime of generated pairing codes.
|
|
13
|
+
* @param minutes - Lifetime in minutes.
|
|
14
|
+
*/
|
|
15
|
+
setCodeLifetime(minutes) {
|
|
16
|
+
this.codeLifetimeMs = minutes * 60 * 1e3;
|
|
17
|
+
}
|
|
18
|
+
// -----------------------------------------------------------------------
|
|
19
|
+
// Code generation & retrieval
|
|
20
|
+
// -----------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Generate a new 6-digit numeric pairing code.
|
|
23
|
+
* Replaces any existing active code.
|
|
24
|
+
* @returns The 6-digit code string.
|
|
25
|
+
*/
|
|
26
|
+
generateCode() {
|
|
27
|
+
const code = String(Math.floor(1e5 + Math.random() * 9e5));
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
this.currentCode = {
|
|
30
|
+
code,
|
|
31
|
+
createdAt: now,
|
|
32
|
+
expiresAt: now + this.codeLifetimeMs,
|
|
33
|
+
used: false
|
|
34
|
+
};
|
|
35
|
+
return code;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Return the current pairing code if it is still active (not expired and
|
|
39
|
+
* not yet used). Returns `null` otherwise.
|
|
40
|
+
*/
|
|
41
|
+
getActiveCode() {
|
|
42
|
+
if (!this.currentCode) return null;
|
|
43
|
+
if (this.currentCode.used) return null;
|
|
44
|
+
if (Date.now() > this.currentCode.expiresAt) return null;
|
|
45
|
+
return this.currentCode.code;
|
|
46
|
+
}
|
|
47
|
+
// -----------------------------------------------------------------------
|
|
48
|
+
// Pairing
|
|
49
|
+
// -----------------------------------------------------------------------
|
|
50
|
+
/**
|
|
51
|
+
* Attempt to pair a device using the provided code.
|
|
52
|
+
*
|
|
53
|
+
* On success the code is marked as used and a unique bearer token is
|
|
54
|
+
* returned that the device should store for future authenticated requests.
|
|
55
|
+
*/
|
|
56
|
+
pair(code, deviceInfo) {
|
|
57
|
+
if (!this.currentCode) {
|
|
58
|
+
return { success: false, error: "No active pairing code" };
|
|
59
|
+
}
|
|
60
|
+
if (this.currentCode.used) {
|
|
61
|
+
return { success: false, error: "Pairing code already used" };
|
|
62
|
+
}
|
|
63
|
+
if (Date.now() > this.currentCode.expiresAt) {
|
|
64
|
+
return { success: false, error: "Pairing code expired" };
|
|
65
|
+
}
|
|
66
|
+
if (this.currentCode.code !== code) {
|
|
67
|
+
return { success: false, error: "Invalid pairing code" };
|
|
68
|
+
}
|
|
69
|
+
this.currentCode.used = true;
|
|
70
|
+
const randomHex = crypto.randomBytes(36).toString("hex").slice(0, 48);
|
|
71
|
+
const token = `os_pair_${randomHex}`;
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
const device = {
|
|
74
|
+
token,
|
|
75
|
+
deviceInfo,
|
|
76
|
+
pairedAt: now,
|
|
77
|
+
lastSeen: now
|
|
78
|
+
};
|
|
79
|
+
this.devices.set(token, device);
|
|
80
|
+
return { success: true, token };
|
|
81
|
+
}
|
|
82
|
+
// -----------------------------------------------------------------------
|
|
83
|
+
// Token operations
|
|
84
|
+
// -----------------------------------------------------------------------
|
|
85
|
+
/**
|
|
86
|
+
* Check whether a token corresponds to a paired device.
|
|
87
|
+
*/
|
|
88
|
+
validateToken(token) {
|
|
89
|
+
return this.devices.has(token);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Retrieve the device record associated with a token.
|
|
93
|
+
*/
|
|
94
|
+
getDeviceByToken(token) {
|
|
95
|
+
return this.devices.get(token);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Revoke a previously issued token, effectively un-pairing the device.
|
|
99
|
+
* @returns `true` if the token existed and was removed.
|
|
100
|
+
*/
|
|
101
|
+
revokeToken(token) {
|
|
102
|
+
return this.devices.delete(token);
|
|
103
|
+
}
|
|
104
|
+
// -----------------------------------------------------------------------
|
|
105
|
+
// Device listing & bookkeeping
|
|
106
|
+
// -----------------------------------------------------------------------
|
|
107
|
+
/**
|
|
108
|
+
* Return a snapshot of all currently paired devices.
|
|
109
|
+
*/
|
|
110
|
+
listDevices() {
|
|
111
|
+
return Array.from(this.devices.values()).map((d) => ({
|
|
112
|
+
token: d.token,
|
|
113
|
+
deviceInfo: d.deviceInfo,
|
|
114
|
+
pairedAt: d.pairedAt,
|
|
115
|
+
lastSeen: d.lastSeen
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Bump the `lastSeen` timestamp for the given token.
|
|
120
|
+
*/
|
|
121
|
+
updateLastSeen(token) {
|
|
122
|
+
const device = this.devices.get(token);
|
|
123
|
+
if (device) {
|
|
124
|
+
device.lastSeen = Date.now();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// -----------------------------------------------------------------------
|
|
128
|
+
// Stats
|
|
129
|
+
// -----------------------------------------------------------------------
|
|
130
|
+
/**
|
|
131
|
+
* Return a small stats summary for display purposes.
|
|
132
|
+
*/
|
|
133
|
+
getStats() {
|
|
134
|
+
return {
|
|
135
|
+
activeCode: this.getActiveCode() !== null,
|
|
136
|
+
deviceCount: this.devices.size
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var pairingManager = new PairingManager();
|
|
141
|
+
|
|
142
|
+
export {
|
|
143
|
+
PairingManager,
|
|
144
|
+
pairingManager
|
|
145
|
+
};
|
|
146
|
+
//# sourceMappingURL=chunk-3E2PSU2C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/security/pairing.ts"],"sourcesContent":["/**\n * PairingManager — consumer-friendly device pairing via 6-digit codes.\n *\n * Flow:\n * 1. Server generates a short-lived 6-digit numeric code.\n * 2. User enters the code on their device/app.\n * 3. On match the device receives a bearer token for future requests.\n */\n\nimport crypto from \"crypto\";\n\n// ---------------------------------------------------------------------------\n// Interfaces\n// ---------------------------------------------------------------------------\n\nexport interface PairingCode {\n code: string;\n createdAt: number;\n expiresAt: number;\n used: boolean;\n}\n\nexport interface PairedDevice {\n token: string;\n deviceInfo: string;\n pairedAt: number;\n lastSeen: number;\n}\n\n// ---------------------------------------------------------------------------\n// PairingManager\n// ---------------------------------------------------------------------------\n\nexport class PairingManager {\n private currentCode: PairingCode | null = null;\n private devices: Map<string, PairedDevice> = new Map();\n private codeLifetimeMs: number = 5 * 60 * 1000; // 5 minutes\n\n // -----------------------------------------------------------------------\n // Code lifetime configuration\n // -----------------------------------------------------------------------\n\n /**\n * Set the lifetime of generated pairing codes.\n * @param minutes - Lifetime in minutes.\n */\n setCodeLifetime(minutes: number): void {\n this.codeLifetimeMs = minutes * 60 * 1000;\n }\n\n // -----------------------------------------------------------------------\n // Code generation & retrieval\n // -----------------------------------------------------------------------\n\n /**\n * Generate a new 6-digit numeric pairing code.\n * Replaces any existing active code.\n * @returns The 6-digit code string.\n */\n generateCode(): string {\n const code = String(Math.floor(100000 + Math.random() * 900000));\n const now = Date.now();\n\n this.currentCode = {\n code,\n createdAt: now,\n expiresAt: now + this.codeLifetimeMs,\n used: false,\n };\n\n return code;\n }\n\n /**\n * Return the current pairing code if it is still active (not expired and\n * not yet used). Returns `null` otherwise.\n */\n getActiveCode(): string | null {\n if (!this.currentCode) return null;\n if (this.currentCode.used) return null;\n if (Date.now() > this.currentCode.expiresAt) return null;\n return this.currentCode.code;\n }\n\n // -----------------------------------------------------------------------\n // Pairing\n // -----------------------------------------------------------------------\n\n /**\n * Attempt to pair a device using the provided code.\n *\n * On success the code is marked as used and a unique bearer token is\n * returned that the device should store for future authenticated requests.\n */\n pair(\n code: string,\n deviceInfo: string,\n ): { success: boolean; token?: string; error?: string } {\n if (!this.currentCode) {\n return { success: false, error: \"No active pairing code\" };\n }\n\n if (this.currentCode.used) {\n return { success: false, error: \"Pairing code already used\" };\n }\n\n if (Date.now() > this.currentCode.expiresAt) {\n return { success: false, error: \"Pairing code expired\" };\n }\n\n if (this.currentCode.code !== code) {\n return { success: false, error: \"Invalid pairing code\" };\n }\n\n // Mark the code as consumed.\n this.currentCode.used = true;\n\n // Generate a bearer token.\n const randomHex = crypto.randomBytes(36).toString(\"hex\").slice(0, 48);\n const token = `os_pair_${randomHex}`;\n\n const now = Date.now();\n const device: PairedDevice = {\n token,\n deviceInfo,\n pairedAt: now,\n lastSeen: now,\n };\n\n this.devices.set(token, device);\n\n return { success: true, token };\n }\n\n // -----------------------------------------------------------------------\n // Token operations\n // -----------------------------------------------------------------------\n\n /**\n * Check whether a token corresponds to a paired device.\n */\n validateToken(token: string): boolean {\n return this.devices.has(token);\n }\n\n /**\n * Retrieve the device record associated with a token.\n */\n getDeviceByToken(token: string): PairedDevice | undefined {\n return this.devices.get(token);\n }\n\n /**\n * Revoke a previously issued token, effectively un-pairing the device.\n * @returns `true` if the token existed and was removed.\n */\n revokeToken(token: string): boolean {\n return this.devices.delete(token);\n }\n\n // -----------------------------------------------------------------------\n // Device listing & bookkeeping\n // -----------------------------------------------------------------------\n\n /**\n * Return a snapshot of all currently paired devices.\n */\n listDevices(): Array<{\n token: string;\n deviceInfo: string;\n pairedAt: number;\n lastSeen: number;\n }> {\n return Array.from(this.devices.values()).map((d) => ({\n token: d.token,\n deviceInfo: d.deviceInfo,\n pairedAt: d.pairedAt,\n lastSeen: d.lastSeen,\n }));\n }\n\n /**\n * Bump the `lastSeen` timestamp for the given token.\n */\n updateLastSeen(token: string): void {\n const device = this.devices.get(token);\n if (device) {\n device.lastSeen = Date.now();\n }\n }\n\n // -----------------------------------------------------------------------\n // Stats\n // -----------------------------------------------------------------------\n\n /**\n * Return a small stats summary for display purposes.\n */\n getStats(): { activeCode: boolean; deviceCount: number } {\n return {\n activeCode: this.getActiveCode() !== null,\n deviceCount: this.devices.size,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Singleton export\n// ---------------------------------------------------------------------------\n\nexport const pairingManager = new PairingManager();\n"],"mappings":"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","names":[]}
|
|
@@ -246,7 +246,7 @@ var MCPClient = class {
|
|
|
246
246
|
},
|
|
247
247
|
clientInfo: {
|
|
248
248
|
name: "OpenSentinel",
|
|
249
|
-
version: "
|
|
249
|
+
version: "3.0.0"
|
|
250
250
|
}
|
|
251
251
|
};
|
|
252
252
|
const result = await this.request("initialize", params);
|
|
@@ -649,4 +649,4 @@ export {
|
|
|
649
649
|
getMCPToolSummary,
|
|
650
650
|
findMCPTool
|
|
651
651
|
};
|
|
652
|
-
//# sourceMappingURL=chunk-
|
|
652
|
+
//# sourceMappingURL=chunk-4GLYY4NN.js.map
|