outlet-orm 7.0.0 → 9.0.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 +130 -2
- package/docs/skills/outlet-orm/AI.md +452 -102
- package/docs/skills/outlet-orm/API.md +108 -0
- package/docs/skills/outlet-orm/QUERIES.md +64 -0
- package/docs/skills/outlet-orm/SEEDS.md +47 -0
- package/docs/skills/outlet-orm/SKILL.md +15 -7
- package/package.json +1 -1
- package/src/AI/AIPromptEnhancer.js +170 -0
- package/src/AI/AIQueryBuilder.js +234 -0
- package/src/AI/AIQueryOptimizer.js +185 -0
- package/src/AI/AISeeder.js +181 -0
- package/src/AI/AiBridgeManager.js +287 -0
- package/src/AI/Builders/TextBuilder.js +170 -0
- package/src/AI/Contracts/AudioProviderContract.js +29 -0
- package/src/AI/Contracts/ChatProviderContract.js +38 -0
- package/src/AI/Contracts/EmbeddingsProviderContract.js +19 -0
- package/src/AI/Contracts/ImageProviderContract.js +19 -0
- package/src/AI/Contracts/ModelsProviderContract.js +26 -0
- package/src/AI/Contracts/ToolContract.js +25 -0
- package/src/AI/Facades/AiBridge.js +79 -0
- package/src/AI/MCPServer.js +113 -0
- package/src/AI/Providers/ClaudeProvider.js +64 -0
- package/src/AI/Providers/CustomOpenAIProvider.js +238 -0
- package/src/AI/Providers/GeminiProvider.js +68 -0
- package/src/AI/Providers/GrokProvider.js +46 -0
- package/src/AI/Providers/MistralProvider.js +21 -0
- package/src/AI/Providers/OllamaProvider.js +249 -0
- package/src/AI/Providers/OllamaTurboProvider.js +32 -0
- package/src/AI/Providers/OnnProvider.js +46 -0
- package/src/AI/Providers/OpenAIProvider.js +471 -0
- package/src/AI/Support/AudioNormalizer.js +37 -0
- package/src/AI/Support/ChatNormalizer.js +42 -0
- package/src/AI/Support/Document.js +77 -0
- package/src/AI/Support/DocumentAttachmentMapper.js +101 -0
- package/src/AI/Support/EmbeddingsNormalizer.js +30 -0
- package/src/AI/Support/Exceptions/ProviderError.js +22 -0
- package/src/AI/Support/FileSecurity.js +56 -0
- package/src/AI/Support/ImageNormalizer.js +62 -0
- package/src/AI/Support/JsonSchemaValidator.js +73 -0
- package/src/AI/Support/Message.js +40 -0
- package/src/AI/Support/StreamChunk.js +45 -0
- package/src/AI/Support/ToolChatRunner.js +160 -0
- package/src/AI/Support/ToolRegistry.js +62 -0
- package/src/AI/Tools/SystemInfoTool.js +25 -0
- package/src/index.js +67 -1
- package/types/index.d.ts +326 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const OpenAIProvider = require('./Providers/OpenAIProvider');
|
|
4
|
+
const OllamaProvider = require('./Providers/OllamaProvider');
|
|
5
|
+
const OllamaTurboProvider = require('./Providers/OllamaTurboProvider');
|
|
6
|
+
const OnnProvider = require('./Providers/OnnProvider');
|
|
7
|
+
const GeminiProvider = require('./Providers/GeminiProvider');
|
|
8
|
+
const GrokProvider = require('./Providers/GrokProvider');
|
|
9
|
+
const ClaudeProvider = require('./Providers/ClaudeProvider');
|
|
10
|
+
const MistralProvider = require('./Providers/MistralProvider');
|
|
11
|
+
const CustomOpenAIProvider = require('./Providers/CustomOpenAIProvider');
|
|
12
|
+
const ProviderError = require('./Support/Exceptions/ProviderError');
|
|
13
|
+
const ToolRegistry = require('./Support/ToolRegistry');
|
|
14
|
+
const ToolChatRunner = require('./Support/ToolChatRunner');
|
|
15
|
+
|
|
16
|
+
const BEARER_PREFIX = 'Bearer ';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* AiBridgeManager
|
|
20
|
+
* Central orchestrator for all AI providers. Supports provider registration,
|
|
21
|
+
* per-call overrides, capability delegation (chat, stream, embeddings, images,
|
|
22
|
+
* audio, models), tool registration, and chatWithTools loop.
|
|
23
|
+
*/
|
|
24
|
+
class AiBridgeManager {
|
|
25
|
+
/**
|
|
26
|
+
* @param {Object} config
|
|
27
|
+
*/
|
|
28
|
+
constructor(config = {}) {
|
|
29
|
+
this._providers = {};
|
|
30
|
+
this._toolRegistry = new ToolRegistry();
|
|
31
|
+
this._options = config.options || {};
|
|
32
|
+
|
|
33
|
+
// Auto-register providers from config
|
|
34
|
+
if (config.openai?.api_key) {
|
|
35
|
+
this._providers.openai = new OpenAIProvider(config.openai.api_key);
|
|
36
|
+
}
|
|
37
|
+
if (config.ollama?.endpoint) {
|
|
38
|
+
this._providers.ollama = new OllamaProvider(config.ollama.endpoint);
|
|
39
|
+
}
|
|
40
|
+
if (config.ollama_turbo?.api_key) {
|
|
41
|
+
const ep = config.ollama_turbo.endpoint || 'https://ollama.com';
|
|
42
|
+
this._providers.ollama_turbo = new OllamaTurboProvider(config.ollama_turbo.api_key, ep);
|
|
43
|
+
}
|
|
44
|
+
if (config.onn?.api_key) {
|
|
45
|
+
this._providers.onn = new OnnProvider(config.onn.api_key);
|
|
46
|
+
}
|
|
47
|
+
if (config.gemini?.api_key) {
|
|
48
|
+
this._providers.gemini = new GeminiProvider(config.gemini.api_key);
|
|
49
|
+
}
|
|
50
|
+
if (config.grok?.api_key) {
|
|
51
|
+
this._providers.grok = new GrokProvider(config.grok.api_key);
|
|
52
|
+
}
|
|
53
|
+
if (config.claude?.api_key) {
|
|
54
|
+
this._providers.claude = new ClaudeProvider(config.claude.api_key);
|
|
55
|
+
}
|
|
56
|
+
if (config.mistral?.api_key) {
|
|
57
|
+
const ep = config.mistral.endpoint || 'https://api.mistral.ai/v1/chat/completions';
|
|
58
|
+
this._providers.mistral = new MistralProvider(config.mistral.api_key, ep);
|
|
59
|
+
}
|
|
60
|
+
if (config.openai_custom?.api_key && config.openai_custom?.base_url) {
|
|
61
|
+
const c = config.openai_custom;
|
|
62
|
+
this._providers.openai_custom = new CustomOpenAIProvider(
|
|
63
|
+
c.api_key, c.base_url, c.paths || {},
|
|
64
|
+
c.auth_header || 'Authorization', c.auth_prefix || BEARER_PREFIX,
|
|
65
|
+
c.extra_headers || {}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
// OpenRouter (OpenAI-compatible)
|
|
69
|
+
if (config.openrouter?.api_key) {
|
|
70
|
+
const base = config.openrouter.base_url || 'https://openrouter.ai/api/v1';
|
|
71
|
+
const hdrs = {};
|
|
72
|
+
if (config.openrouter.referer) hdrs['HTTP-Referer'] = config.openrouter.referer;
|
|
73
|
+
if (config.openrouter.title) hdrs['X-Title'] = config.openrouter.title;
|
|
74
|
+
this._providers.openrouter = new CustomOpenAIProvider(
|
|
75
|
+
config.openrouter.api_key, base,
|
|
76
|
+
{ chat: '/chat/completions', embeddings: '/embeddings', image: '/images/generations', tts: '/audio/speech', stt: '/audio/transcriptions' },
|
|
77
|
+
'Authorization', BEARER_PREFIX, hdrs
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── Provider resolution ───
|
|
83
|
+
|
|
84
|
+
/** @private */
|
|
85
|
+
_hasOverrides(options) {
|
|
86
|
+
const keys = ['api_key', 'endpoint', 'base_url', 'chat_endpoint', 'auth_header', 'auth_prefix', 'paths', 'extra_headers'];
|
|
87
|
+
return keys.some(k => options[k] !== undefined);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @private */
|
|
91
|
+
_buildProviderFromOptions(name, options) {
|
|
92
|
+
switch (name) {
|
|
93
|
+
case 'openai': {
|
|
94
|
+
const api = options.api_key;
|
|
95
|
+
if (api) return new OpenAIProvider(api, options.chat_endpoint || 'https://api.openai.com/v1/chat/completions');
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'ollama': return new OllamaProvider(options.endpoint || 'http://localhost:11434');
|
|
99
|
+
case 'ollama_turbo': {
|
|
100
|
+
const api = options.api_key;
|
|
101
|
+
if (api) return new OllamaTurboProvider(api, options.endpoint || 'https://ollama.com');
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case 'onn': {
|
|
105
|
+
const api = options.api_key;
|
|
106
|
+
if (api) return new OnnProvider(api, options.endpoint || 'https://api.onn.ai/v1/chat');
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'gemini': {
|
|
110
|
+
const api = options.api_key;
|
|
111
|
+
if (api) return new GeminiProvider(api, options.endpoint);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case 'grok': {
|
|
115
|
+
const api = options.api_key;
|
|
116
|
+
if (api) return new GrokProvider(api, options.endpoint);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case 'claude': {
|
|
120
|
+
const api = options.api_key;
|
|
121
|
+
if (api) return new ClaudeProvider(api, options.endpoint);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'mistral': {
|
|
125
|
+
const api = options.api_key;
|
|
126
|
+
if (api) return new MistralProvider(api, options.endpoint || 'https://api.mistral.ai/v1/chat/completions');
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'openai_custom': {
|
|
130
|
+
const api = options.api_key;
|
|
131
|
+
const base = options.base_url;
|
|
132
|
+
if (api && base) {
|
|
133
|
+
return new CustomOpenAIProvider(api, base, options.paths || {},
|
|
134
|
+
options.auth_header || 'Authorization', options.auth_prefix || BEARER_PREFIX,
|
|
135
|
+
options.extra_headers || {});
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case 'openrouter': {
|
|
140
|
+
const api = options.api_key;
|
|
141
|
+
if (api) {
|
|
142
|
+
const base = options.base_url || 'https://openrouter.ai/api/v1';
|
|
143
|
+
const hdrs = {};
|
|
144
|
+
if (options.referer) hdrs['HTTP-Referer'] = options.referer;
|
|
145
|
+
if (options.title) hdrs['X-Title'] = options.title;
|
|
146
|
+
return new CustomOpenAIProvider(api, base,
|
|
147
|
+
{ chat: '/chat/completions', embeddings: '/embeddings', image: '/images/generations', tts: '/audio/speech', stt: '/audio/transcriptions' },
|
|
148
|
+
'Authorization', BEARER_PREFIX, hdrs
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @private */
|
|
158
|
+
_resolveProvider(name, options = {}) {
|
|
159
|
+
if (this._hasOverrides(options)) {
|
|
160
|
+
const p = this._buildProviderFromOptions(name, options);
|
|
161
|
+
if (p) return p;
|
|
162
|
+
}
|
|
163
|
+
if (this._providers[name]) return this._providers[name];
|
|
164
|
+
const p = this._buildProviderFromOptions(name, options);
|
|
165
|
+
if (p) { this._providers[name] = p; }
|
|
166
|
+
return p || null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── Public API ───
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get a registered provider by name.
|
|
173
|
+
* @param {string} name
|
|
174
|
+
* @returns {Object|null}
|
|
175
|
+
*/
|
|
176
|
+
provider(name) {
|
|
177
|
+
return this._providers[name] || null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Register a provider instance.
|
|
182
|
+
* @param {string} name
|
|
183
|
+
* @param {Object} provider
|
|
184
|
+
* @returns {this}
|
|
185
|
+
*/
|
|
186
|
+
registerProvider(name, provider) {
|
|
187
|
+
this._providers[name] = provider;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── Chat ───
|
|
192
|
+
async chat(provider, messages, options = {}) {
|
|
193
|
+
const p = this._resolveProvider(provider, options);
|
|
194
|
+
if (!p || typeof p.chat !== 'function') throw ProviderError.unsupported(provider, 'chat');
|
|
195
|
+
return p.chat(messages, options);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ─── Stream ───
|
|
199
|
+
async *stream(provider, messages, options = {}) {
|
|
200
|
+
const p = this._resolveProvider(provider, options);
|
|
201
|
+
if (!p || typeof p.stream !== 'function') throw ProviderError.unsupported(provider, 'streaming');
|
|
202
|
+
yield* p.stream(messages, options);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ─── Stream Events ───
|
|
206
|
+
async *streamEvents(provider, messages, options = {}) {
|
|
207
|
+
const p = this._resolveProvider(provider, options);
|
|
208
|
+
if (!p || typeof p.supportsStreaming !== 'function' || !p.supportsStreaming()) {
|
|
209
|
+
throw ProviderError.unsupported(provider, 'streaming');
|
|
210
|
+
}
|
|
211
|
+
if (typeof p.streamEvents === 'function') {
|
|
212
|
+
yield* p.streamEvents(messages, options);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
for await (const chunk of p.stream(messages, options)) {
|
|
216
|
+
yield { type: 'delta', data: chunk };
|
|
217
|
+
}
|
|
218
|
+
yield { type: 'end', data: null };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ─── Embeddings ───
|
|
222
|
+
async embeddings(provider, inputs, options = {}) {
|
|
223
|
+
const p = this._resolveProvider(provider, options);
|
|
224
|
+
if (!p || typeof p.embeddings !== 'function') throw ProviderError.unsupported(provider, 'embeddings');
|
|
225
|
+
return p.embeddings(inputs, options);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─── Models ───
|
|
229
|
+
async models(provider) {
|
|
230
|
+
const p = this._providers[provider];
|
|
231
|
+
if (!p || typeof p.listModels !== 'function') throw ProviderError.unsupported(provider, 'models');
|
|
232
|
+
return p.listModels();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async model(provider, id) {
|
|
236
|
+
const p = this._providers[provider];
|
|
237
|
+
if (!p || typeof p.getModel !== 'function') throw ProviderError.unsupported(provider, 'model');
|
|
238
|
+
return p.getModel(id);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── Images ───
|
|
242
|
+
async image(provider, prompt, options = {}) {
|
|
243
|
+
const p = this._resolveProvider(provider, options);
|
|
244
|
+
if (!p || typeof p.generateImage !== 'function') throw ProviderError.unsupported(provider, 'image');
|
|
245
|
+
return p.generateImage(prompt, options);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ─── Audio ───
|
|
249
|
+
async tts(provider, text, options = {}) {
|
|
250
|
+
const p = this._resolveProvider(provider, options);
|
|
251
|
+
if (!p || typeof p.textToSpeech !== 'function') throw ProviderError.unsupported(provider, 'tts');
|
|
252
|
+
return p.textToSpeech(text, options);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async stt(provider, filePath, options = {}) {
|
|
256
|
+
const p = this._resolveProvider(provider, options);
|
|
257
|
+
if (!p || typeof p.speechToText !== 'function') throw ProviderError.unsupported(provider, 'stt');
|
|
258
|
+
return p.speechToText(filePath, options);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ─── Fluent builder ───
|
|
262
|
+
text() {
|
|
263
|
+
const TextBuilder = require('./Builders/TextBuilder');
|
|
264
|
+
return new TextBuilder(this);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ─── Tools API ───
|
|
268
|
+
registerTool(tool) {
|
|
269
|
+
this._toolRegistry.register(tool);
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
tool(name) {
|
|
274
|
+
return this._toolRegistry.get(name);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
tools() {
|
|
278
|
+
return this._toolRegistry.all();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async chatWithTools(provider, messages, options = {}) {
|
|
282
|
+
const runner = new ToolChatRunner(this);
|
|
283
|
+
return runner.run(provider, messages, options);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = AiBridgeManager;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ChatNormalizer = require('../Support/ChatNormalizer');
|
|
4
|
+
const StreamChunk = require('../Support/StreamChunk');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TextBuilder
|
|
8
|
+
* Fluent builder for text generation over AiBridge providers.
|
|
9
|
+
* Keeps method names short and explicit, reducing array option errors.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const result = await manager.text()
|
|
13
|
+
* .using('openai', 'gpt-4o-mini')
|
|
14
|
+
* .withPrompt('Explain quantum computing')
|
|
15
|
+
* .withMaxTokens(200)
|
|
16
|
+
* .asText();
|
|
17
|
+
*/
|
|
18
|
+
class TextBuilder {
|
|
19
|
+
static ERR_MISSING_USING = 'Provider and model must be set via using().';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {import('../AiBridgeManager')} manager
|
|
23
|
+
*/
|
|
24
|
+
constructor(manager) {
|
|
25
|
+
this._manager = manager;
|
|
26
|
+
this._provider = null;
|
|
27
|
+
this._model = null;
|
|
28
|
+
this._providerConfig = {};
|
|
29
|
+
this._messages = [];
|
|
30
|
+
this._systemPrompt = null;
|
|
31
|
+
this._maxTokens = null;
|
|
32
|
+
this._temperature = null;
|
|
33
|
+
this._topP = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Set the provider and model (required).
|
|
38
|
+
* @param {string} provider
|
|
39
|
+
* @param {string} model
|
|
40
|
+
* @param {Object} [providerConfig={}]
|
|
41
|
+
* @returns {this}
|
|
42
|
+
*/
|
|
43
|
+
using(provider, model, providerConfig = {}) {
|
|
44
|
+
this._provider = provider;
|
|
45
|
+
this._model = model;
|
|
46
|
+
this._providerConfig = providerConfig;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Add a user prompt message.
|
|
52
|
+
* @param {string} text
|
|
53
|
+
* @param {Array} [attachments=[]]
|
|
54
|
+
* @returns {this}
|
|
55
|
+
*/
|
|
56
|
+
withPrompt(text, attachments = []) {
|
|
57
|
+
const msg = { role: 'user', content: text };
|
|
58
|
+
if (attachments.length > 0) msg.attachments = attachments;
|
|
59
|
+
this._messages.push(msg);
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Alias for withPrompt */
|
|
64
|
+
prompt(text) { return this.withPrompt(text); }
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Set the system prompt.
|
|
68
|
+
* @param {string} text
|
|
69
|
+
* @returns {this}
|
|
70
|
+
*/
|
|
71
|
+
withSystemPrompt(text) {
|
|
72
|
+
this._systemPrompt = text;
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @param {number} tokens @returns {this} */
|
|
77
|
+
withMaxTokens(tokens) { this._maxTokens = tokens; return this; }
|
|
78
|
+
|
|
79
|
+
/** @param {number} t @returns {this} */
|
|
80
|
+
usingTemperature(t) { this._temperature = t; return this; }
|
|
81
|
+
|
|
82
|
+
/** @param {number} p @returns {this} */
|
|
83
|
+
usingTopP(p) { this._topP = p; return this; }
|
|
84
|
+
|
|
85
|
+
// ─── Override helpers ───
|
|
86
|
+
|
|
87
|
+
/** @param {string} key @returns {this} */
|
|
88
|
+
withApiKey(key) { this._providerConfig.api_key = key; return this; }
|
|
89
|
+
/** @param {string} ep @returns {this} */
|
|
90
|
+
withEndpoint(ep) { this._providerConfig.endpoint = ep; return this; }
|
|
91
|
+
/** @param {string} url @returns {this} */
|
|
92
|
+
withBaseUrl(url) { this._providerConfig.base_url = url; return this; }
|
|
93
|
+
/** @param {string} url @returns {this} */
|
|
94
|
+
withChatEndpoint(url) { this._providerConfig.chat_endpoint = url; return this; }
|
|
95
|
+
/** @param {string} header @param {string} [prefix='Bearer '] @returns {this} */
|
|
96
|
+
withAuthHeader(header, prefix = 'Bearer ') { this._providerConfig.auth_header = header; this._providerConfig.auth_prefix = prefix; return this; }
|
|
97
|
+
/** @param {Object} headers @returns {this} */
|
|
98
|
+
withExtraHeaders(headers) { this._providerConfig.extra_headers = headers; return this; }
|
|
99
|
+
/** @param {Object} paths @returns {this} */
|
|
100
|
+
withPaths(paths) { this._providerConfig.paths = paths; return this; }
|
|
101
|
+
|
|
102
|
+
// ─── Private helpers ───
|
|
103
|
+
|
|
104
|
+
/** @private */
|
|
105
|
+
_buildMessages() {
|
|
106
|
+
const msgs = [...this._messages];
|
|
107
|
+
if (this._systemPrompt) {
|
|
108
|
+
msgs.unshift({ role: 'system', content: this._systemPrompt });
|
|
109
|
+
}
|
|
110
|
+
return msgs;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @private */
|
|
114
|
+
_callOptions() {
|
|
115
|
+
const opts = { ...this._providerConfig };
|
|
116
|
+
if (this._model) opts.model = this._model;
|
|
117
|
+
if (this._maxTokens !== null) opts.max_tokens = this._maxTokens;
|
|
118
|
+
if (this._temperature !== null) opts.temperature = this._temperature;
|
|
119
|
+
if (this._topP !== null) opts.top_p = this._topP;
|
|
120
|
+
return opts;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─── Terminal methods ───
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Execute and return normalized text response.
|
|
127
|
+
* @returns {Promise<{text: string, raw: Object, usage: Object|null, finish_reason: string|null}>}
|
|
128
|
+
*/
|
|
129
|
+
async asText() {
|
|
130
|
+
if (!this._provider || !this._model) throw new Error(TextBuilder.ERR_MISSING_USING);
|
|
131
|
+
const res = await this._manager.chat(this._provider, this._buildMessages(), this._callOptions());
|
|
132
|
+
const norm = ChatNormalizer.normalize(res);
|
|
133
|
+
return {
|
|
134
|
+
text: norm.text || '',
|
|
135
|
+
raw: res,
|
|
136
|
+
usage: norm.usage || null,
|
|
137
|
+
finish_reason: norm.finish_reason || null,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Execute and return raw provider response.
|
|
143
|
+
* @returns {Promise<Object>}
|
|
144
|
+
*/
|
|
145
|
+
async asRaw() {
|
|
146
|
+
if (!this._provider || !this._model) throw new Error(TextBuilder.ERR_MISSING_USING);
|
|
147
|
+
return this._manager.chat(this._provider, this._buildMessages(), this._callOptions());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Execute as a streaming generator of StreamChunk objects.
|
|
152
|
+
* @returns {AsyncGenerator<StreamChunk>}
|
|
153
|
+
*/
|
|
154
|
+
async *asStream() {
|
|
155
|
+
if (!this._provider || !this._model) throw new Error(TextBuilder.ERR_MISSING_USING);
|
|
156
|
+
for await (const chunk of this._manager.stream(this._provider, this._buildMessages(), this._callOptions())) {
|
|
157
|
+
if (typeof chunk === 'string') {
|
|
158
|
+
yield StreamChunk.delta(chunk);
|
|
159
|
+
} else if (chunk && typeof chunk === 'object') {
|
|
160
|
+
const text = String(chunk.delta || chunk.text || '');
|
|
161
|
+
yield new StreamChunk(text, chunk.usage || null, chunk.finish_reason || null,
|
|
162
|
+
chunk.type || 'delta', chunk.tool_calls || [], chunk.tool_results || []);
|
|
163
|
+
} else {
|
|
164
|
+
yield StreamChunk.delta(String(chunk));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = TextBuilder;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AudioProviderContract
|
|
5
|
+
* Base class for providers that support text-to-speech and speech-to-text.
|
|
6
|
+
*/
|
|
7
|
+
class AudioProviderContract {
|
|
8
|
+
/**
|
|
9
|
+
* Text to speech.
|
|
10
|
+
* @param {string} text
|
|
11
|
+
* @param {Object} [options={}]
|
|
12
|
+
* @returns {Promise<{audio: string, mime: string}>} audio is base64-encoded
|
|
13
|
+
*/
|
|
14
|
+
async textToSpeech(text, options = {}) {
|
|
15
|
+
throw new Error('Not implemented: textToSpeech()');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Speech to text.
|
|
20
|
+
* @param {string} filePath
|
|
21
|
+
* @param {Object} [options={}]
|
|
22
|
+
* @returns {Promise<{text: string, raw?: Object}>}
|
|
23
|
+
*/
|
|
24
|
+
async speechToText(filePath, options = {}) {
|
|
25
|
+
throw new Error('Not implemented: speechToText()');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = AudioProviderContract;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ChatProviderContract
|
|
5
|
+
* Base class defining the chat provider interface.
|
|
6
|
+
* All chat providers must extend this and implement its methods.
|
|
7
|
+
*/
|
|
8
|
+
class ChatProviderContract {
|
|
9
|
+
/**
|
|
10
|
+
* Send a chat message and return raw provider response object.
|
|
11
|
+
* @param {Array<{role: string, content: string}>} messages
|
|
12
|
+
* @param {Object} [options={}]
|
|
13
|
+
* @returns {Promise<Object>}
|
|
14
|
+
*/
|
|
15
|
+
async chat(messages, options = {}) {
|
|
16
|
+
throw new Error('Not implemented: chat()');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Stream a chat completion (async generator yielding chunks of text or objects).
|
|
21
|
+
* @param {Array<{role: string, content: string}>} messages
|
|
22
|
+
* @param {Object} [options={}]
|
|
23
|
+
* @yields {string|Object}
|
|
24
|
+
*/
|
|
25
|
+
async *stream(messages, options = {}) {
|
|
26
|
+
throw new Error('Not implemented: stream()');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether this provider supports streaming.
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
supportsStreaming() {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = ChatProviderContract;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EmbeddingsProviderContract
|
|
5
|
+
* Base class for providers that support embeddings generation.
|
|
6
|
+
*/
|
|
7
|
+
class EmbeddingsProviderContract {
|
|
8
|
+
/**
|
|
9
|
+
* Generate embeddings for one or multiple inputs.
|
|
10
|
+
* @param {string[]} inputs
|
|
11
|
+
* @param {Object} [options={}]
|
|
12
|
+
* @returns {Promise<{embeddings: number[][], usage?: Object, raw?: Object}>}
|
|
13
|
+
*/
|
|
14
|
+
async embeddings(inputs, options = {}) {
|
|
15
|
+
throw new Error('Not implemented: embeddings()');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = EmbeddingsProviderContract;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ImageProviderContract
|
|
5
|
+
* Base class for providers that support image generation.
|
|
6
|
+
*/
|
|
7
|
+
class ImageProviderContract {
|
|
8
|
+
/**
|
|
9
|
+
* Generate images from a prompt.
|
|
10
|
+
* @param {string} prompt
|
|
11
|
+
* @param {Object} [options={}]
|
|
12
|
+
* @returns {Promise<{images: Array, meta?: Object, raw?: Object}>}
|
|
13
|
+
*/
|
|
14
|
+
async generateImage(prompt, options = {}) {
|
|
15
|
+
throw new Error('Not implemented: generateImage()');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = ImageProviderContract;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ModelsProviderContract
|
|
5
|
+
* Base class for providers that support listing models.
|
|
6
|
+
*/
|
|
7
|
+
class ModelsProviderContract {
|
|
8
|
+
/**
|
|
9
|
+
* List models metadata as returned by the provider.
|
|
10
|
+
* @returns {Promise<Array>}
|
|
11
|
+
*/
|
|
12
|
+
async listModels() {
|
|
13
|
+
throw new Error('Not implemented: listModels()');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Retrieve a single model metadata by id/name.
|
|
18
|
+
* @param {string} id
|
|
19
|
+
* @returns {Promise<Object>}
|
|
20
|
+
*/
|
|
21
|
+
async getModel(id) {
|
|
22
|
+
throw new Error('Not implemented: getModel()');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = ModelsProviderContract;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ToolContract
|
|
5
|
+
* Base class for tools usable by AI providers (function calling).
|
|
6
|
+
*/
|
|
7
|
+
class ToolContract {
|
|
8
|
+
/** @returns {string} */
|
|
9
|
+
name() { throw new Error('Not implemented: name()'); }
|
|
10
|
+
|
|
11
|
+
/** @returns {string} */
|
|
12
|
+
description() { throw new Error('Not implemented: description()'); }
|
|
13
|
+
|
|
14
|
+
/** @returns {Object} JSON Schema of parameters */
|
|
15
|
+
schema() { throw new Error('Not implemented: schema()'); }
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Execute the tool with given arguments. Must return a string result.
|
|
19
|
+
* @param {Object} args
|
|
20
|
+
* @returns {Promise<string>|string}
|
|
21
|
+
*/
|
|
22
|
+
execute(args) { throw new Error('Not implemented: execute()'); }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = ToolContract;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AiBridge Facade
|
|
5
|
+
*
|
|
6
|
+
* Convenience entry-point mirroring AiBridge\Facades\AiBridge in PHP.
|
|
7
|
+
* Provides static-like helpers that delegate to an AiBridgeManager instance.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const ImageNormalizer = require('../Support/ImageNormalizer');
|
|
11
|
+
const AudioNormalizer = require('../Support/AudioNormalizer');
|
|
12
|
+
const EmbeddingsNormalizer = require('../Support/EmbeddingsNormalizer');
|
|
13
|
+
|
|
14
|
+
let _manager = null;
|
|
15
|
+
|
|
16
|
+
const AiBridge = {
|
|
17
|
+
/**
|
|
18
|
+
* Bind an AiBridgeManager instance so all helpers delegate to it.
|
|
19
|
+
* @param {import('../AiBridgeManager')} manager
|
|
20
|
+
*/
|
|
21
|
+
setManager(manager) {
|
|
22
|
+
_manager = manager;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Return the bound manager (or null).
|
|
27
|
+
* @returns {import('../AiBridgeManager')|null}
|
|
28
|
+
*/
|
|
29
|
+
getManager() {
|
|
30
|
+
return _manager;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/* ─── Normalization helpers (static, no manager needed) ────── */
|
|
34
|
+
|
|
35
|
+
normalizeImages(raw) {
|
|
36
|
+
return ImageNormalizer.normalize(raw);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
normalizeTTSAudio(raw) {
|
|
40
|
+
return AudioNormalizer.normalizeTTS(raw);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
normalizeSTTAudio(raw) {
|
|
44
|
+
return AudioNormalizer.normalizeSTT(raw);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
normalizeEmbeddings(raw) {
|
|
48
|
+
return EmbeddingsNormalizer.normalize(raw);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/* ─── Delegated helpers (require bound manager) ────────────── */
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create a new TextBuilder via the bound manager.
|
|
55
|
+
* @returns {import('../Builders/TextBuilder')}
|
|
56
|
+
*/
|
|
57
|
+
text() {
|
|
58
|
+
if (!_manager) throw new Error('AiBridge facade: no manager bound. Call AiBridge.setManager(manager) first.');
|
|
59
|
+
return _manager.text();
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Shorthand for manager.chat()
|
|
64
|
+
*/
|
|
65
|
+
async chat(messages, opts) {
|
|
66
|
+
if (!_manager) throw new Error('AiBridge facade: no manager bound.');
|
|
67
|
+
return _manager.chat(messages, opts);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Shorthand for manager.provider()
|
|
72
|
+
*/
|
|
73
|
+
provider(name) {
|
|
74
|
+
if (!_manager) throw new Error('AiBridge facade: no manager bound.');
|
|
75
|
+
return _manager.provider(name);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
module.exports = AiBridge;
|