@fgv/ts-extras 5.1.0-25 → 5.1.0-27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +2 -2
- package/dist/index.browser.js.map +1 -1
- package/dist/packlets/ai-assist/apiClient.js +300 -213
- package/dist/packlets/ai-assist/apiClient.js.map +1 -1
- package/dist/packlets/ai-assist/chatRequestBuilders.js +6 -0
- package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
- package/dist/packlets/ai-assist/imageOptionsResolver.js +212 -0
- package/dist/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
- package/dist/packlets/ai-assist/index.js +1 -0
- package/dist/packlets/ai-assist/index.js.map +1 -1
- package/dist/packlets/ai-assist/model.js +1 -1
- package/dist/packlets/ai-assist/model.js.map +1 -1
- package/dist/packlets/ai-assist/registry.js +120 -22
- package/dist/packlets/ai-assist/registry.js.map +1 -1
- package/dist/packlets/ai-assist/sseParser.js +1 -0
- package/dist/packlets/ai-assist/sseParser.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +17 -12
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/common.js +2 -0
- package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js +17 -5
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +19 -4
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +20 -5
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js +9 -3
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
- package/dist/packlets/ai-assist/streamingClient.js +28 -6
- package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
- package/dist/packlets/ai-assist/thinkingOptionsResolver.js +265 -0
- package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
- package/dist/packlets/conversion/converters.js +1 -0
- package/dist/packlets/conversion/converters.js.map +1 -1
- package/dist/packlets/crypto-utils/index.browser.js +2 -0
- package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
- package/dist/packlets/crypto-utils/index.js +2 -0
- package/dist/packlets/crypto-utils/index.js.map +1 -1
- package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js +8 -0
- package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/keyStore.js +2 -1
- package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
- package/dist/packlets/crypto-utils/model.js +2 -1
- package/dist/packlets/crypto-utils/model.js.map +1 -1
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js +25 -0
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
- package/dist/packlets/crypto-utils/spkiHelpers.js +130 -0
- package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -0
- package/dist/ts-extras.d.ts +695 -126
- package/lib/index.browser.d.ts +2 -2
- package/lib/index.browser.d.ts.map +1 -1
- package/lib/index.browser.js +4 -3
- package/lib/index.browser.js.map +1 -1
- package/lib/packlets/ai-assist/apiClient.d.ts +29 -85
- package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/apiClient.js +300 -213
- package/lib/packlets/ai-assist/apiClient.js.map +1 -1
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
- package/lib/packlets/ai-assist/chatRequestBuilders.js +6 -0
- package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
- package/lib/packlets/ai-assist/imageOptionsResolver.d.ts +74 -0
- package/lib/packlets/ai-assist/imageOptionsResolver.d.ts.map +1 -0
- package/lib/packlets/ai-assist/imageOptionsResolver.js +216 -0
- package/lib/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
- package/lib/packlets/ai-assist/index.d.ts +2 -1
- package/lib/packlets/ai-assist/index.d.ts.map +1 -1
- package/lib/packlets/ai-assist/index.js +4 -1
- package/lib/packlets/ai-assist/index.js.map +1 -1
- package/lib/packlets/ai-assist/model.d.ts +410 -35
- package/lib/packlets/ai-assist/model.d.ts.map +1 -1
- package/lib/packlets/ai-assist/model.js +1 -1
- package/lib/packlets/ai-assist/model.js.map +1 -1
- package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
- package/lib/packlets/ai-assist/registry.js +120 -22
- package/lib/packlets/ai-assist/registry.js.map +1 -1
- package/lib/packlets/ai-assist/sseParser.d.ts.map +1 -1
- package/lib/packlets/ai-assist/sseParser.js +1 -0
- package/lib/packlets/ai-assist/sseParser.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +2 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +17 -12
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +5 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.js +2 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +2 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js +17 -5
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +2 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +19 -4
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +2 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +20 -5
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/proxy.js +9 -3
- package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.js +28 -6
- package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
- package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +71 -0
- package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -0
- package/lib/packlets/ai-assist/thinkingOptionsResolver.js +270 -0
- package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
- package/lib/packlets/conversion/converters.d.ts.map +1 -1
- package/lib/packlets/conversion/converters.js +1 -0
- package/lib/packlets/conversion/converters.js.map +1 -1
- package/lib/packlets/crypto-utils/index.browser.d.ts +1 -0
- package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/index.browser.js +7 -1
- package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
- package/lib/packlets/crypto-utils/index.d.ts +1 -0
- package/lib/packlets/crypto-utils/index.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/index.js +7 -1
- package/lib/packlets/crypto-utils/index.js.map +1 -1
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts +10 -6
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js +8 -0
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/keyStore.js +2 -1
- package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
- package/lib/packlets/crypto-utils/model.d.ts +19 -1
- package/lib/packlets/crypto-utils/model.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/model.js +2 -1
- package/lib/packlets/crypto-utils/model.js.map +1 -1
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +13 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js +25 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -1
- package/lib/packlets/crypto-utils/spkiHelpers.d.ts +53 -0
- package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/spkiHelpers.js +136 -0
- package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -0
- package/package.json +13 -13
|
@@ -40,9 +40,11 @@ exports.callProxiedImageGeneration = callProxiedImageGeneration;
|
|
|
40
40
|
const ts_json_base_1 = require("@fgv/ts-json-base");
|
|
41
41
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
42
42
|
const model_1 = require("./model");
|
|
43
|
+
const thinkingOptionsResolver_1 = require("./thinkingOptionsResolver");
|
|
43
44
|
const chatRequestBuilders_1 = require("./chatRequestBuilders");
|
|
44
45
|
const endpoint_1 = require("./endpoint");
|
|
45
46
|
const registry_1 = require("./registry");
|
|
47
|
+
const imageOptionsResolver_1 = require("./imageOptionsResolver");
|
|
46
48
|
const toolFormats_1 = require("./toolFormats");
|
|
47
49
|
// ============================================================================
|
|
48
50
|
// Shared helpers
|
|
@@ -112,6 +114,7 @@ async function fetchMultipart(url, headers, body, logger, signal) {
|
|
|
112
114
|
});
|
|
113
115
|
}
|
|
114
116
|
catch (err) {
|
|
117
|
+
/* c8 ignore next 1 - defensive: fetch errors are always Error instances in practice */
|
|
115
118
|
const detail = err instanceof Error ? err.message : String(err);
|
|
116
119
|
/* c8 ignore next 1 - optional logger */
|
|
117
120
|
logger === null || logger === void 0 ? void 0 : logger.error(`AI API request failed: ${detail}`);
|
|
@@ -129,13 +132,12 @@ async function fetchMultipart(url, headers, body, logger, signal) {
|
|
|
129
132
|
try {
|
|
130
133
|
json = await response.json();
|
|
131
134
|
}
|
|
132
|
-
catch (_a) {
|
|
133
|
-
/* c8 ignore next 1 - optional logger */
|
|
135
|
+
catch /* c8 ignore start - defensive: response.json() failure on a 2xx */ (_a) {
|
|
134
136
|
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned invalid JSON response');
|
|
135
137
|
return (0, ts_utils_1.fail)('AI API returned invalid JSON response');
|
|
136
|
-
}
|
|
138
|
+
} /* c8 ignore stop */
|
|
139
|
+
/* c8 ignore next 5 - defensive: provider returning non-object JSON on a 2xx */
|
|
137
140
|
if (!(0, ts_json_base_1.isJsonObject)(json)) {
|
|
138
|
-
/* c8 ignore next 1 - optional logger */
|
|
139
141
|
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned non-object JSON response');
|
|
140
142
|
return (0, ts_utils_1.fail)('AI API returned non-object JSON response');
|
|
141
143
|
}
|
|
@@ -201,6 +203,7 @@ async function fetchGetJson(url, headers, logger, signal) {
|
|
|
201
203
|
response = await fetch(url, { method: 'GET', headers, signal });
|
|
202
204
|
}
|
|
203
205
|
catch (err) {
|
|
206
|
+
/* c8 ignore next 1 - defensive: fetch errors are always Error instances in practice */
|
|
204
207
|
const detail = err instanceof Error ? err.message : String(err);
|
|
205
208
|
/* c8 ignore next 1 - optional logger */
|
|
206
209
|
logger === null || logger === void 0 ? void 0 : logger.error(`AI API request failed: ${detail}`);
|
|
@@ -218,13 +221,12 @@ async function fetchGetJson(url, headers, logger, signal) {
|
|
|
218
221
|
try {
|
|
219
222
|
json = await response.json();
|
|
220
223
|
}
|
|
221
|
-
catch (_a) {
|
|
222
|
-
/* c8 ignore next 1 - optional logger */
|
|
224
|
+
catch /* c8 ignore start - defensive: response.json() failure on a 2xx */ (_a) {
|
|
223
225
|
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned invalid JSON response');
|
|
224
226
|
return (0, ts_utils_1.fail)('AI API returned invalid JSON response');
|
|
225
|
-
}
|
|
227
|
+
} /* c8 ignore stop */
|
|
228
|
+
/* c8 ignore next 5 - defensive: provider returning non-object JSON on a 2xx */
|
|
226
229
|
if (!(0, ts_json_base_1.isJsonObject)(json)) {
|
|
227
|
-
/* c8 ignore next 1 - optional logger */
|
|
228
230
|
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned non-object JSON response');
|
|
229
231
|
return (0, ts_utils_1.fail)('AI API returned non-object JSON response');
|
|
230
232
|
}
|
|
@@ -254,13 +256,6 @@ const responsesApiResponse = ts_utils_1.Validators.object({
|
|
|
254
256
|
output: ts_utils_1.Validators.arrayOf(responsesApiOutputItem).withConstraint((arr) => arr.length > 0),
|
|
255
257
|
status: ts_utils_1.Validators.string
|
|
256
258
|
});
|
|
257
|
-
const anthropicContentBlock = ts_utils_1.Validators.object({
|
|
258
|
-
text: ts_utils_1.Validators.string
|
|
259
|
-
});
|
|
260
|
-
const anthropicResponse = ts_utils_1.Validators.object({
|
|
261
|
-
content: ts_utils_1.Validators.arrayOf(anthropicContentBlock).withConstraint((arr) => arr.length > 0),
|
|
262
|
-
stop_reason: ts_utils_1.Validators.string
|
|
263
|
-
});
|
|
264
259
|
const geminiPart = ts_utils_1.Validators.object({
|
|
265
260
|
text: ts_utils_1.Validators.string
|
|
266
261
|
});
|
|
@@ -282,12 +277,17 @@ const geminiResponse = ts_utils_1.Validators.object({
|
|
|
282
277
|
* Works for xAI Grok, OpenAI, Groq, and Mistral.
|
|
283
278
|
* @internal
|
|
284
279
|
*/
|
|
285
|
-
async function callOpenAiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, signal) {
|
|
280
|
+
async function callOpenAiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, signal, resolvedThinking) {
|
|
281
|
+
var _a;
|
|
286
282
|
const url = `${config.baseUrl}/chat/completions`;
|
|
287
283
|
const messages = (0, chatRequestBuilders_1.buildMessages)(prompt.system, (0, chatRequestBuilders_1.buildOpenAiChatUserContent)(prompt), {
|
|
288
284
|
tail: additionalMessages
|
|
289
285
|
});
|
|
290
|
-
const
|
|
286
|
+
const effort = (_a = resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.openAiEffort) !== null && _a !== void 0 ? _a : resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.xaiEffort;
|
|
287
|
+
const body = Object.assign(Object.assign({ model: config.model, messages }, (effort === undefined || effort === 'none' ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning_effort: effort } : {}));
|
|
288
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
289
|
+
Object.assign(body, resolvedThinking.otherParams);
|
|
290
|
+
}
|
|
291
291
|
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
292
292
|
/* c8 ignore next 1 - optional logger */
|
|
293
293
|
logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI completion: model=${config.model}`);
|
|
@@ -330,17 +330,17 @@ function extractResponsesApiText(output) {
|
|
|
330
330
|
* Used when tools are configured for an openai-format provider.
|
|
331
331
|
* @internal
|
|
332
332
|
*/
|
|
333
|
-
async function callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature = 0.7, logger, signal) {
|
|
333
|
+
async function callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature = 0.7, logger, signal, resolvedThinking) {
|
|
334
|
+
var _a;
|
|
334
335
|
const url = `${config.baseUrl}/responses`;
|
|
335
336
|
const input = (0, chatRequestBuilders_1.buildMessages)(prompt.system, (0, chatRequestBuilders_1.buildOpenAiResponsesUserContent)(prompt), {
|
|
336
337
|
tail: additionalMessages
|
|
337
338
|
});
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
};
|
|
339
|
+
const effort = (_a = resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.openAiEffort) !== null && _a !== void 0 ? _a : resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.xaiEffort;
|
|
340
|
+
const body = Object.assign(Object.assign({ model: config.model, input, tools: (0, toolFormats_1.toResponsesApiTools)(tools) }, (effort === undefined || effort === 'none' ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning: { effort } } : {}));
|
|
341
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
342
|
+
Object.assign(body, resolvedThinking.otherParams);
|
|
343
|
+
}
|
|
344
344
|
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
345
345
|
/* c8 ignore next 1 - optional logger */
|
|
346
346
|
logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI Responses API: model=${config.model}, tools=${tools.map((t) => t.type).join(',')}`);
|
|
@@ -383,23 +383,18 @@ function extractAnthropicText(content) {
|
|
|
383
383
|
}
|
|
384
384
|
return (0, ts_utils_1.succeed)(textParts.join(''));
|
|
385
385
|
}
|
|
386
|
-
/**
|
|
387
|
-
|
|
388
|
-
* When tools are configured, includes them in the request and handles
|
|
389
|
-
* mixed content block responses.
|
|
390
|
-
* @internal
|
|
391
|
-
*/
|
|
392
|
-
async function callAnthropicCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal) {
|
|
386
|
+
/** Calls the Anthropic Messages API with optional tool support. @internal */
|
|
387
|
+
async function callAnthropicCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal, resolvedThinking) {
|
|
393
388
|
const url = `${config.baseUrl}/messages`;
|
|
394
|
-
// Anthropic uses system as a top-level field, not in messages
|
|
395
389
|
const messages = (0, chatRequestBuilders_1.buildAnthropicMessages)(prompt, { tail: additionalMessages });
|
|
396
|
-
const body = {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
390
|
+
const body = Object.assign({ model: config.model, system: prompt.system, messages, max_tokens: 4096 }, ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.anthropicEffort) === undefined ? { temperature } : {}));
|
|
391
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.anthropicEffort) !== undefined) {
|
|
392
|
+
body.thinking = { type: 'enabled' };
|
|
393
|
+
body.output_config = { effort: resolvedThinking.anthropicEffort };
|
|
394
|
+
}
|
|
395
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
396
|
+
Object.assign(body, resolvedThinking.otherParams);
|
|
397
|
+
}
|
|
403
398
|
if (tools && tools.length > 0) {
|
|
404
399
|
body.tools = (0, toolFormats_1.toAnthropicTools)(tools);
|
|
405
400
|
/* c8 ignore next 3 - optional logger diagnostic output */
|
|
@@ -418,28 +413,18 @@ async function callAnthropicCompletion(config, prompt, additionalMessages, tempe
|
|
|
418
413
|
if (jsonResult.isFailure()) {
|
|
419
414
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
420
415
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
const stopReason = jsonResult.value.stop_reason;
|
|
426
|
-
if (!Array.isArray(rawContent)) {
|
|
427
|
-
return (0, ts_utils_1.fail)('Anthropic API response: content is not an array');
|
|
428
|
-
}
|
|
429
|
-
return extractAnthropicText(rawContent).onSuccess((text) => (0, ts_utils_1.succeed)({
|
|
430
|
-
content: text,
|
|
431
|
-
truncated: stopReason === 'max_tokens'
|
|
432
|
-
}));
|
|
416
|
+
const rawContent = jsonResult.value.content;
|
|
417
|
+
const stopReason = jsonResult.value.stop_reason;
|
|
418
|
+
if (!Array.isArray(rawContent)) {
|
|
419
|
+
return (0, ts_utils_1.fail)('Anthropic API response: content is not an array');
|
|
433
420
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
});
|
|
442
|
-
});
|
|
421
|
+
if (typeof stopReason !== 'string') {
|
|
422
|
+
return (0, ts_utils_1.fail)('Anthropic API response: stop_reason is missing or not a string');
|
|
423
|
+
}
|
|
424
|
+
return extractAnthropicText(rawContent).onSuccess((text) => (0, ts_utils_1.succeed)({
|
|
425
|
+
content: text,
|
|
426
|
+
truncated: stopReason === 'max_tokens'
|
|
427
|
+
}));
|
|
443
428
|
}
|
|
444
429
|
// ============================================================================
|
|
445
430
|
// Google Gemini adapter
|
|
@@ -449,14 +434,20 @@ async function callAnthropicCompletion(config, prompt, additionalMessages, tempe
|
|
|
449
434
|
* When tools are configured, includes Google Search grounding.
|
|
450
435
|
* @internal
|
|
451
436
|
*/
|
|
452
|
-
async function callGeminiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal) {
|
|
437
|
+
async function callGeminiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal, resolvedThinking) {
|
|
453
438
|
const url = `${config.baseUrl}/models/${config.model}:generateContent`;
|
|
454
|
-
// Gemini uses 'contents' with 'parts', and 'model' role instead of 'assistant'
|
|
455
439
|
const contents = (0, chatRequestBuilders_1.buildGeminiContents)(prompt, { tail: additionalMessages });
|
|
440
|
+
const generationConfig = { temperature };
|
|
441
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.geminiThinkingBudget) !== undefined) {
|
|
442
|
+
generationConfig.thinkingConfig = { thinkingBudget: resolvedThinking.geminiThinkingBudget };
|
|
443
|
+
}
|
|
444
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
445
|
+
Object.assign(generationConfig, resolvedThinking.otherParams);
|
|
446
|
+
}
|
|
456
447
|
const body = {
|
|
457
448
|
systemInstruction: { parts: [{ text: prompt.system }] },
|
|
458
449
|
contents,
|
|
459
|
-
generationConfig
|
|
450
|
+
generationConfig
|
|
460
451
|
};
|
|
461
452
|
if (tools && tools.length > 0) {
|
|
462
453
|
body.tools = (0, toolFormats_1.toGeminiTools)(tools);
|
|
@@ -490,23 +481,15 @@ async function callGeminiCompletion(config, prompt, additionalMessages, temperat
|
|
|
490
481
|
// ============================================================================
|
|
491
482
|
/**
|
|
492
483
|
* Calls the appropriate chat completion API for a given provider.
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
* - `'openai'` for xAI, OpenAI, Groq, Mistral
|
|
496
|
-
* - `'anthropic'` for Anthropic Claude
|
|
497
|
-
* - `'gemini'` for Google Gemini
|
|
498
|
-
*
|
|
499
|
-
* When tools are provided and the provider supports them:
|
|
500
|
-
* - OpenAI-format providers switch to the Responses API
|
|
501
|
-
* - Anthropic includes tools in the Messages API request
|
|
502
|
-
* - Gemini includes Google Search grounding
|
|
503
|
-
*
|
|
484
|
+
* Routes by `apiFormat`: `'openai'` (xAI/OpenAI/Groq/Mistral — switches to Responses API when
|
|
485
|
+
* tools are set), `'anthropic'`, or `'gemini'`.
|
|
504
486
|
* @param params - Request parameters including descriptor, API key, prompt, and optional tools
|
|
505
487
|
* @returns The completion response with content and truncation status, or a failure
|
|
506
488
|
* @public
|
|
507
489
|
*/
|
|
508
490
|
async function callProviderCompletion(params) {
|
|
509
|
-
|
|
491
|
+
var _a;
|
|
492
|
+
const { descriptor, apiKey, prompt, additionalMessages, temperature, modelOverride, logger, tools, signal, endpoint, thinking } = params;
|
|
510
493
|
const baseUrlResult = (0, endpoint_1.resolveEffectiveBaseUrl)(descriptor, endpoint);
|
|
511
494
|
if (baseUrlResult.isFailure()) {
|
|
512
495
|
return (0, ts_utils_1.fail)(baseUrlResult.message);
|
|
@@ -515,11 +498,31 @@ async function callProviderCompletion(params) {
|
|
|
515
498
|
return (0, ts_utils_1.fail)(`provider "${descriptor.id}" does not accept image input`);
|
|
516
499
|
}
|
|
517
500
|
const hasTools = tools !== undefined && tools.length > 0;
|
|
518
|
-
const
|
|
501
|
+
const discriminator = (0, thinkingOptionsResolver_1.providerDiscriminatorForId)(descriptor.id);
|
|
502
|
+
const hasThinkingConfig = discriminator !== undefined &&
|
|
503
|
+
((thinking === null || thinking === void 0 ? void 0 : thinking.effort) !== undefined ||
|
|
504
|
+
((_a = thinking === null || thinking === void 0 ? void 0 : thinking.providers) === null || _a === void 0 ? void 0 : _a.some((b) => b.provider === 'other' || b.provider === discriminator)) === true);
|
|
505
|
+
const modelContext = hasThinkingConfig ? 'thinking' : hasTools ? 'tools' : undefined;
|
|
519
506
|
const model = (0, model_1.resolveModel)(modelOverride !== null && modelOverride !== void 0 ? modelOverride : descriptor.defaultModel, modelContext);
|
|
520
507
|
if (model.length === 0) {
|
|
521
508
|
return (0, ts_utils_1.fail)(`provider "${descriptor.id}": no model resolved; pass modelOverride or set descriptor.defaultModel`);
|
|
522
509
|
}
|
|
510
|
+
let resolvedThinking;
|
|
511
|
+
if (thinking !== undefined) {
|
|
512
|
+
if (discriminator !== undefined) {
|
|
513
|
+
const mergeResult = (0, thinkingOptionsResolver_1.mergeThinkingConfig)(thinking, model, discriminator);
|
|
514
|
+
/* c8 ignore next 3 - mergeThinkingConfig always succeeds; defensive guard */
|
|
515
|
+
if (mergeResult.isFailure()) {
|
|
516
|
+
return (0, ts_utils_1.fail)(mergeResult.message);
|
|
517
|
+
}
|
|
518
|
+
resolvedThinking = mergeResult.value;
|
|
519
|
+
const conflictResult = (0, thinkingOptionsResolver_1.checkTemperatureConflict)(resolvedThinking, discriminator, temperature);
|
|
520
|
+
if (conflictResult.isFailure()) {
|
|
521
|
+
return (0, ts_utils_1.fail)(conflictResult.message);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const effectiveTemperature = temperature !== null && temperature !== void 0 ? temperature : 0.7;
|
|
523
526
|
const config = {
|
|
524
527
|
baseUrl: baseUrlResult.value,
|
|
525
528
|
apiKey,
|
|
@@ -535,13 +538,13 @@ async function callProviderCompletion(params) {
|
|
|
535
538
|
switch (descriptor.apiFormat) {
|
|
536
539
|
case 'openai':
|
|
537
540
|
if (hasTools) {
|
|
538
|
-
return callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages,
|
|
541
|
+
return callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, effectiveTemperature, logger, signal, resolvedThinking);
|
|
539
542
|
}
|
|
540
|
-
return callOpenAiCompletion(config, prompt, additionalMessages,
|
|
543
|
+
return callOpenAiCompletion(config, prompt, additionalMessages, effectiveTemperature, logger, signal, resolvedThinking);
|
|
541
544
|
case 'anthropic':
|
|
542
|
-
return callAnthropicCompletion(config, prompt, additionalMessages,
|
|
545
|
+
return callAnthropicCompletion(config, prompt, additionalMessages, effectiveTemperature, logger, tools, signal, resolvedThinking);
|
|
543
546
|
case 'gemini':
|
|
544
|
-
return callGeminiCompletion(config, prompt, additionalMessages,
|
|
547
|
+
return callGeminiCompletion(config, prompt, additionalMessages, effectiveTemperature, logger, tools, signal, resolvedThinking);
|
|
545
548
|
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
546
549
|
default: {
|
|
547
550
|
const _exhaustive = descriptor.apiFormat;
|
|
@@ -592,7 +595,13 @@ const proxiedImageGenerationResponse = ts_utils_1.Validators.object({
|
|
|
592
595
|
});
|
|
593
596
|
const proxiedListModelsEntry = ts_utils_1.Validators.object({
|
|
594
597
|
id: ts_utils_1.Validators.string,
|
|
595
|
-
capabilities: ts_utils_1.Validators.arrayOf(ts_utils_1.Validators.enumeratedValue([
|
|
598
|
+
capabilities: ts_utils_1.Validators.arrayOf(ts_utils_1.Validators.enumeratedValue([
|
|
599
|
+
'chat',
|
|
600
|
+
'tools',
|
|
601
|
+
'vision',
|
|
602
|
+
'image-generation',
|
|
603
|
+
'thinking'
|
|
604
|
+
])),
|
|
596
605
|
displayName: ts_utils_1.Validators.string.optional()
|
|
597
606
|
});
|
|
598
607
|
const proxiedListModelsResponse = ts_utils_1.Validators.object({
|
|
@@ -601,101 +610,160 @@ const proxiedListModelsResponse = ts_utils_1.Validators.object({
|
|
|
601
610
|
// ============================================================================
|
|
602
611
|
// Image generation — adapters
|
|
603
612
|
// ============================================================================
|
|
604
|
-
/**
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
*
|
|
609
|
-
* When `request.referenceImages` is non-empty, routes to `/images/edits`
|
|
610
|
-
* (multipart) instead of `/images/generations` (JSON). Per-model edit support
|
|
611
|
-
* is not validated here (e.g. dall-e-3 does not support edits) — the
|
|
612
|
-
* provider's 400 surfaces through the failure path.
|
|
613
|
-
*
|
|
614
|
-
* @internal
|
|
615
|
-
*/
|
|
616
|
-
async function callOpenAiImageGeneration(config, request, defaultMimeType, logger, signal) {
|
|
617
|
-
var _a, _b, _c;
|
|
618
|
-
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
619
|
-
const refs = (_b = request.referenceImages) !== null && _b !== void 0 ? _b : [];
|
|
613
|
+
/** Routes to /images/generations or /images/edits; handles outputParamStyle. @internal */
|
|
614
|
+
async function callOpenAiImageGeneration(config, request, capability, resolved, logger, signal) {
|
|
615
|
+
var _a, _b;
|
|
616
|
+
const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
|
|
620
617
|
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
621
|
-
const
|
|
618
|
+
const effectiveMimeType = resolved.outputFormat !== undefined
|
|
619
|
+
? `image/${resolved.outputFormat}`
|
|
620
|
+
: (_b = capability.defaultOutputMimeType) !== null && _b !== void 0 ? _b : 'image/png';
|
|
622
621
|
const fetched = refs.length > 0
|
|
623
|
-
? await callOpenAiImagesEdits(config, request, headers,
|
|
624
|
-
: await callOpenAiImagesGenerations(config, request, headers,
|
|
622
|
+
? await callOpenAiImagesEdits(config, capability, request, headers, resolved, logger, signal)
|
|
623
|
+
: await callOpenAiImagesGenerations(config, request, headers, resolved, capability, logger, signal);
|
|
625
624
|
return fetched.onSuccess((json) => openAiImageResponse
|
|
626
625
|
.validate(json)
|
|
627
626
|
.withErrorFormat((msg) => `OpenAI images API response: ${msg}`)
|
|
628
627
|
.onSuccess((response) => (0, ts_utils_1.succeed)({
|
|
629
|
-
images: response.data.map((item) => (Object.assign({ mimeType:
|
|
628
|
+
images: response.data.map((item) => (Object.assign({ mimeType: effectiveMimeType, base64: item.b64_json }, (item.revised_prompt !== undefined ? { revisedPrompt: item.revised_prompt } : {}))))
|
|
630
629
|
})));
|
|
631
630
|
}
|
|
632
|
-
/**
|
|
633
|
-
|
|
634
|
-
* @internal
|
|
635
|
-
*/
|
|
636
|
-
function callOpenAiImagesGenerations(config, request, headers, n, logger, signal) {
|
|
631
|
+
/** Builds the JSON /images/generations request; handles outputParamStyle. @internal */
|
|
632
|
+
function callOpenAiImagesGenerations(config, request, headers, resolved, capability, logger, signal) {
|
|
637
633
|
var _a;
|
|
638
|
-
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
639
634
|
const body = {
|
|
640
635
|
model: config.model,
|
|
641
636
|
prompt: request.prompt,
|
|
642
|
-
n
|
|
643
|
-
response_format: 'b64_json'
|
|
637
|
+
n: resolved.n
|
|
644
638
|
};
|
|
645
|
-
|
|
646
|
-
|
|
639
|
+
// Output format param — conditional on model capability
|
|
640
|
+
if (capability.outputParamStyle === 'response-format') {
|
|
641
|
+
body.response_format = 'b64_json';
|
|
642
|
+
}
|
|
643
|
+
else if (capability.outputParamStyle === 'output-format') {
|
|
644
|
+
body.output_format = (_a = resolved.outputFormat) !== null && _a !== void 0 ? _a : 'png';
|
|
645
|
+
}
|
|
646
|
+
if (resolved.size !== undefined) {
|
|
647
|
+
body.size = resolved.size;
|
|
648
|
+
}
|
|
649
|
+
if (capability.supportsQualityParam && resolved.quality !== undefined) {
|
|
650
|
+
body.quality = resolved.quality;
|
|
647
651
|
}
|
|
648
|
-
if (
|
|
649
|
-
body.
|
|
652
|
+
if (resolved.seed !== undefined) {
|
|
653
|
+
body.seed = resolved.seed;
|
|
650
654
|
}
|
|
651
|
-
if (
|
|
652
|
-
body.
|
|
655
|
+
if (resolved.style !== undefined) {
|
|
656
|
+
body.style = resolved.style;
|
|
657
|
+
}
|
|
658
|
+
if (resolved.background !== undefined) {
|
|
659
|
+
body.background = resolved.background;
|
|
660
|
+
}
|
|
661
|
+
if (resolved.moderation !== undefined) {
|
|
662
|
+
body.moderation = resolved.moderation;
|
|
663
|
+
}
|
|
664
|
+
if (resolved.outputCompression !== undefined) {
|
|
665
|
+
body.output_compression = resolved.outputCompression;
|
|
666
|
+
}
|
|
667
|
+
if (resolved.otherParams !== undefined) {
|
|
668
|
+
Object.assign(body, resolved.otherParams);
|
|
653
669
|
}
|
|
654
670
|
/* c8 ignore next 1 - optional logger */
|
|
655
|
-
logger === null || logger === void 0 ? void 0 : logger.info(`Image generation: model=${config.model}, n=${n}`);
|
|
671
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`Image generation: model=${config.model}, n=${resolved.n}`);
|
|
656
672
|
return fetchJson(`${config.baseUrl}/images/generations`, headers, body, logger, signal);
|
|
657
673
|
}
|
|
658
|
-
/**
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
*/
|
|
662
|
-
async function callOpenAiImagesEdits(config, request, headers, n, refs, logger, signal) {
|
|
663
|
-
var _a;
|
|
674
|
+
/** Builds the multipart /images/edits request with ref images. @internal */
|
|
675
|
+
async function callOpenAiImagesEdits(config, capability, request, headers, resolved, logger, signal) {
|
|
676
|
+
const refs = request.referenceImages; // callers verify refs.length > 0 before calling this function
|
|
664
677
|
const blobsResult = (0, ts_utils_1.mapResults)(refs.map((ref, i) => attachmentToBlob(ref).withErrorFormat((msg) => `reference image ${i}: ${msg}`)));
|
|
665
678
|
/* c8 ignore next 3 - decode failure unreachable via Node's Buffer.from (silently strips invalid input) */
|
|
666
679
|
if (blobsResult.isFailure()) {
|
|
667
680
|
return (0, ts_utils_1.fail)(blobsResult.message);
|
|
668
681
|
}
|
|
669
|
-
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
670
682
|
const form = new FormData();
|
|
671
683
|
form.append('model', config.model);
|
|
672
684
|
form.append('prompt', request.prompt);
|
|
673
|
-
form.append('n', String(n));
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
form.append('size', opts.size);
|
|
685
|
+
form.append('n', String(resolved.n));
|
|
686
|
+
if (capability.outputParamStyle !== 'output-format') {
|
|
687
|
+
form.append('response_format', 'b64_json');
|
|
677
688
|
}
|
|
678
|
-
if (
|
|
679
|
-
form.append('
|
|
680
|
-
}
|
|
681
|
-
if (opts.seed !== undefined) {
|
|
682
|
-
form.append('seed', String(opts.seed));
|
|
689
|
+
if (resolved.size !== undefined) {
|
|
690
|
+
form.append('size', resolved.size);
|
|
683
691
|
}
|
|
684
692
|
blobsResult.value.forEach((blob, i) => {
|
|
685
693
|
form.append('image[]', blob, `ref-${i}.${extensionForMimeType(refs[i].mimeType)}`);
|
|
686
694
|
});
|
|
687
695
|
/* c8 ignore next 1 - optional logger */
|
|
688
|
-
logger === null || logger === void 0 ? void 0 : logger.info(`Image edit: model=${config.model}, n=${n}, refs=${refs.length}`);
|
|
696
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`Image edit: model=${config.model}, n=${resolved.n}, refs=${refs.length}`);
|
|
689
697
|
return fetchMultipart(`${config.baseUrl}/images/edits`, headers, form, logger, signal);
|
|
690
698
|
}
|
|
691
|
-
/**
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
+
/** Calls xAI /images/edits with JSON body (not multipart); up to 3 source images. @internal */
|
|
700
|
+
async function callXaiImagesEdits(config, request, resolved, logger, signal) {
|
|
701
|
+
var _a;
|
|
702
|
+
/* c8 ignore next 1 - defensive: referenceImages always defined when this function is called */
|
|
703
|
+
const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
|
|
704
|
+
if (refs.length > 3) {
|
|
705
|
+
return (0, ts_utils_1.fail)(`xAI image edits supports at most 3 reference images; got ${refs.length}`);
|
|
706
|
+
}
|
|
707
|
+
const images = refs.map((ref) => ({
|
|
708
|
+
type: 'image_url',
|
|
709
|
+
url: `data:${ref.mimeType};base64,${ref.base64}`
|
|
710
|
+
}));
|
|
711
|
+
const body = {
|
|
712
|
+
model: config.model,
|
|
713
|
+
prompt: request.prompt,
|
|
714
|
+
n: resolved.n,
|
|
715
|
+
response_format: 'b64_json',
|
|
716
|
+
image: images
|
|
717
|
+
};
|
|
718
|
+
if (resolved.aspectRatio !== undefined) {
|
|
719
|
+
body.aspect_ratio = resolved.aspectRatio;
|
|
720
|
+
}
|
|
721
|
+
if (resolved.resolution !== undefined) {
|
|
722
|
+
body.resolution = resolved.resolution;
|
|
723
|
+
}
|
|
724
|
+
if (resolved.otherParams !== undefined) {
|
|
725
|
+
Object.assign(body, resolved.otherParams);
|
|
726
|
+
}
|
|
727
|
+
/* c8 ignore next 1 - optional logger */
|
|
728
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`xAI image edit: model=${config.model}, n=${resolved.n}, refs=${refs.length}`);
|
|
729
|
+
return fetchJson(`${config.baseUrl}/images/edits`, (0, endpoint_1.bearerAuthHeader)(config.apiKey), body, logger, signal);
|
|
730
|
+
}
|
|
731
|
+
/** Calls xAI /images/generations; uses aspect_ratio instead of size. @internal */
|
|
732
|
+
async function callXaiImageGeneration(config, request, capability, resolved, logger, signal) {
|
|
733
|
+
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
734
|
+
const body = {
|
|
735
|
+
model: config.model,
|
|
736
|
+
prompt: request.prompt,
|
|
737
|
+
n: resolved.n,
|
|
738
|
+
response_format: 'b64_json'
|
|
739
|
+
};
|
|
740
|
+
if (resolved.aspectRatio !== undefined) {
|
|
741
|
+
body.aspect_ratio = resolved.aspectRatio;
|
|
742
|
+
}
|
|
743
|
+
if (resolved.resolution !== undefined) {
|
|
744
|
+
body.resolution = resolved.resolution;
|
|
745
|
+
}
|
|
746
|
+
if (resolved.otherParams !== undefined) {
|
|
747
|
+
Object.assign(body, resolved.otherParams);
|
|
748
|
+
}
|
|
749
|
+
/* c8 ignore next 1 - optional logger */
|
|
750
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`xAI image generation: model=${config.model}, n=${resolved.n}`);
|
|
751
|
+
const fetched = await fetchJson(`${config.baseUrl}/images/generations`, headers, body, logger, signal);
|
|
752
|
+
return fetched.onSuccess((json) => openAiImageResponse
|
|
753
|
+
.validate(json)
|
|
754
|
+
.withErrorFormat((msg) => `xAI images API response: ${msg}`)
|
|
755
|
+
.onSuccess((response) => (0, ts_utils_1.succeed)({
|
|
756
|
+
images: response.data.map((item) => {
|
|
757
|
+
var _a;
|
|
758
|
+
return ({
|
|
759
|
+
mimeType: (_a = capability.defaultOutputMimeType) !== null && _a !== void 0 ? _a : 'image/jpeg',
|
|
760
|
+
base64: item.b64_json
|
|
761
|
+
});
|
|
762
|
+
})
|
|
763
|
+
})));
|
|
764
|
+
}
|
|
765
|
+
/** Calls Gemini :generateContent for image output; accepts ref images as inlineData. @internal */
|
|
766
|
+
async function callGeminiImageOutGeneration(config, request, resolved, logger, signal) {
|
|
699
767
|
var _a;
|
|
700
768
|
const url = `${config.baseUrl}/models/${config.model}:generateContent`;
|
|
701
769
|
const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
|
|
@@ -703,9 +771,17 @@ async function callGeminiImageOutGeneration(config, request, logger, signal) {
|
|
|
703
771
|
for (const ref of refs) {
|
|
704
772
|
parts.push({ inlineData: { mimeType: ref.mimeType, data: ref.base64 } });
|
|
705
773
|
}
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
774
|
+
const generationConfig = {};
|
|
775
|
+
if (resolved.geminiAspectRatio !== undefined) {
|
|
776
|
+
generationConfig.imageConfig = { aspectRatio: resolved.geminiAspectRatio };
|
|
777
|
+
}
|
|
778
|
+
if (resolved.otherParams !== undefined) {
|
|
779
|
+
Object.assign(generationConfig, resolved.otherParams);
|
|
780
|
+
}
|
|
781
|
+
const body = { contents: [{ role: 'user', parts }] };
|
|
782
|
+
if (Object.keys(generationConfig).length > 0) {
|
|
783
|
+
body.generationConfig = generationConfig;
|
|
784
|
+
}
|
|
709
785
|
const headers = {
|
|
710
786
|
'x-goog-api-key': config.apiKey
|
|
711
787
|
};
|
|
@@ -732,33 +808,48 @@ async function callGeminiImageOutGeneration(config, request, logger, signal) {
|
|
|
732
808
|
return (0, ts_utils_1.succeed)({ images });
|
|
733
809
|
}));
|
|
734
810
|
}
|
|
735
|
-
/**
|
|
736
|
-
|
|
737
|
-
* @internal
|
|
738
|
-
*/
|
|
739
|
-
async function callImagenGeneration(config, request, logger, signal) {
|
|
740
|
-
var _a, _b, _c, _d;
|
|
811
|
+
/** Calls the Gemini Imagen :predict endpoint with Imagen 4 params. @internal */
|
|
812
|
+
async function callImagenGeneration(config, request, resolved, logger, signal) {
|
|
741
813
|
const url = `${config.baseUrl}/models/${config.model}:predict`;
|
|
742
|
-
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
743
814
|
const parameters = {
|
|
744
|
-
sampleCount:
|
|
815
|
+
sampleCount: resolved.n
|
|
745
816
|
};
|
|
746
|
-
if (
|
|
747
|
-
parameters.aspectRatio =
|
|
817
|
+
if (resolved.imagenAspectRatio !== undefined) {
|
|
818
|
+
parameters.aspectRatio = resolved.imagenAspectRatio;
|
|
748
819
|
}
|
|
749
|
-
if (
|
|
750
|
-
parameters.
|
|
820
|
+
if (resolved.imageSize !== undefined) {
|
|
821
|
+
parameters.imageSize = resolved.imageSize;
|
|
751
822
|
}
|
|
752
|
-
if (
|
|
753
|
-
parameters.
|
|
823
|
+
if (resolved.addWatermark !== undefined) {
|
|
824
|
+
parameters.addWatermark = resolved.addWatermark;
|
|
825
|
+
}
|
|
826
|
+
if (resolved.enhancePrompt !== undefined) {
|
|
827
|
+
parameters.enhancePrompt = resolved.enhancePrompt;
|
|
828
|
+
}
|
|
829
|
+
if (resolved.imagenOutputMimeType !== undefined || resolved.imagenOutputCompressionQuality !== undefined) {
|
|
830
|
+
const outputOptions = {};
|
|
831
|
+
if (resolved.imagenOutputMimeType !== undefined) {
|
|
832
|
+
outputOptions.mimeType = resolved.imagenOutputMimeType;
|
|
833
|
+
}
|
|
834
|
+
if (resolved.imagenOutputCompressionQuality !== undefined) {
|
|
835
|
+
outputOptions.compressionQuality = resolved.imagenOutputCompressionQuality;
|
|
836
|
+
}
|
|
837
|
+
parameters.outputOptions = outputOptions;
|
|
838
|
+
}
|
|
839
|
+
if (resolved.personGeneration !== undefined) {
|
|
840
|
+
parameters.personGeneration = resolved.personGeneration;
|
|
841
|
+
}
|
|
842
|
+
if (resolved.seed !== undefined) {
|
|
843
|
+
parameters.seed = resolved.seed;
|
|
844
|
+
}
|
|
845
|
+
if (resolved.otherParams !== undefined) {
|
|
846
|
+
Object.assign(parameters, resolved.otherParams);
|
|
754
847
|
}
|
|
755
848
|
const body = {
|
|
756
849
|
instances: [{ prompt: request.prompt }],
|
|
757
850
|
parameters
|
|
758
851
|
};
|
|
759
|
-
const headers = {
|
|
760
|
-
'x-goog-api-key': config.apiKey
|
|
761
|
-
};
|
|
852
|
+
const headers = { 'x-goog-api-key': config.apiKey };
|
|
762
853
|
/* c8 ignore next 1 - optional logger */
|
|
763
854
|
logger === null || logger === void 0 ? void 0 : logger.info(`Imagen generation: model=${config.model}, n=${parameters.sampleCount}`);
|
|
764
855
|
const jsonResult = await fetchJson(url, headers, body, logger, signal);
|
|
@@ -784,25 +875,16 @@ async function callImagenGeneration(config, request, logger, signal) {
|
|
|
784
875
|
// ============================================================================
|
|
785
876
|
/**
|
|
786
877
|
* Calls the appropriate image-generation API for a given provider.
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
* - `'openai-images'` for OpenAI (DALL-E, gpt-image-1)
|
|
792
|
-
* - `'xai-images'` for xAI Grok image models
|
|
793
|
-
* - `'gemini-imagen'` for Google Imagen `:predict`
|
|
794
|
-
* - `'gemini-image-out'` for Gemini chat-style image output (Nano Banana)
|
|
795
|
-
*
|
|
796
|
-
* Image-model selection reuses the existing `'image'` {@link ModelSpecKey}.
|
|
797
|
-
* When `request.referenceImages` is non-empty, the call is rejected up front
|
|
798
|
-
* unless the resolved capability declares `acceptsImageReferenceInput`.
|
|
799
|
-
*
|
|
878
|
+
* Routes by the `format` field of the resolved {@link IAiImageModelCapability}:
|
|
879
|
+
* `'openai-images'`, `'xai-images'`, `'xai-images-edits'`, `'gemini-imagen'`,
|
|
880
|
+
* or `'gemini-image-out'`. Rejects up front if `referenceImages` is set but the
|
|
881
|
+
* capability does not declare `acceptsImageReferenceInput`.
|
|
800
882
|
* @param params - Request parameters including descriptor, API key, and prompt
|
|
801
883
|
* @returns The generated images, or a failure
|
|
802
884
|
* @public
|
|
803
885
|
*/
|
|
804
886
|
async function callProviderImageGeneration(params) {
|
|
805
|
-
var _a, _b;
|
|
887
|
+
var _a, _b, _c;
|
|
806
888
|
const { descriptor, apiKey, params: request, modelOverride, logger, signal, endpoint } = params;
|
|
807
889
|
if (!(0, registry_1.supportsImageGeneration)(descriptor)) {
|
|
808
890
|
return (0, ts_utils_1.fail)(`provider "${descriptor.id}" does not support image generation`);
|
|
@@ -824,6 +906,11 @@ async function callProviderImageGeneration(params) {
|
|
|
824
906
|
if (((_b = (_a = request.referenceImages) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 && !capability.acceptsImageReferenceInput) {
|
|
825
907
|
return (0, ts_utils_1.fail)(`model "${model}" does not support reference images`);
|
|
826
908
|
}
|
|
909
|
+
const resolved = (0, imageOptionsResolver_1.resolveImageOptions)(model, capability, request.options);
|
|
910
|
+
const validationResult = (0, imageOptionsResolver_1.validateResolvedOptions)(model, capability, resolved);
|
|
911
|
+
if (validationResult.isFailure()) {
|
|
912
|
+
return (0, ts_utils_1.fail)(validationResult.message);
|
|
913
|
+
}
|
|
827
914
|
const config = {
|
|
828
915
|
baseUrl: baseUrlResult.value,
|
|
829
916
|
apiKey,
|
|
@@ -836,13 +923,32 @@ async function callProviderImageGeneration(params) {
|
|
|
836
923
|
}
|
|
837
924
|
switch (capability.format) {
|
|
838
925
|
case 'openai-images':
|
|
839
|
-
return callOpenAiImageGeneration(config, request,
|
|
926
|
+
return callOpenAiImageGeneration(config, request, capability, resolved, logger, signal);
|
|
840
927
|
case 'xai-images':
|
|
841
|
-
return
|
|
928
|
+
return callXaiImageGeneration(config, request, capability, resolved, logger, signal);
|
|
929
|
+
case 'xai-images-edits': {
|
|
930
|
+
const refs = (_c = request.referenceImages) !== null && _c !== void 0 ? _c : [];
|
|
931
|
+
if (refs.length > 0) {
|
|
932
|
+
const editsResult = await callXaiImagesEdits(config, request, resolved, logger, signal);
|
|
933
|
+
return editsResult.onSuccess((json) => openAiImageResponse
|
|
934
|
+
.validate(json)
|
|
935
|
+
.withErrorFormat((msg) => `xAI images API response: ${msg}`)
|
|
936
|
+
.onSuccess((response) => (0, ts_utils_1.succeed)({
|
|
937
|
+
images: response.data.map((item) => {
|
|
938
|
+
var _a;
|
|
939
|
+
return ({
|
|
940
|
+
mimeType: (_a = capability.defaultOutputMimeType) !== null && _a !== void 0 ? _a : 'image/jpeg',
|
|
941
|
+
base64: item.b64_json
|
|
942
|
+
});
|
|
943
|
+
})
|
|
944
|
+
})));
|
|
945
|
+
}
|
|
946
|
+
return callXaiImageGeneration(config, request, capability, resolved, logger, signal);
|
|
947
|
+
}
|
|
842
948
|
case 'gemini-imagen':
|
|
843
|
-
return callImagenGeneration(config, request, logger, signal);
|
|
949
|
+
return callImagenGeneration(config, request, resolved, logger, signal);
|
|
844
950
|
case 'gemini-image-out':
|
|
845
|
-
return callGeminiImageOutGeneration(config, request, logger, signal);
|
|
951
|
+
return callGeminiImageOutGeneration(config, request, resolved, logger, signal);
|
|
846
952
|
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
847
953
|
default: {
|
|
848
954
|
const _exhaustive = capability.format;
|
|
@@ -896,6 +1002,7 @@ function geminiMethodsToCapabilities(methods) {
|
|
|
896
1002
|
* @internal
|
|
897
1003
|
*/
|
|
898
1004
|
function geminiBareId(name) {
|
|
1005
|
+
/* c8 ignore next 1 - defensive: Gemini API always returns names prefixed with 'models/' */
|
|
899
1006
|
return name.startsWith('models/') ? name.substring('models/'.length) : name;
|
|
900
1007
|
}
|
|
901
1008
|
/**
|
|
@@ -1028,12 +1135,8 @@ async function callGeminiListModels(config, providerId, capabilityConfig, logger
|
|
|
1028
1135
|
// List models — dispatcher
|
|
1029
1136
|
// ============================================================================
|
|
1030
1137
|
/**
|
|
1031
|
-
* Lists models available from a provider,
|
|
1032
|
-
* native provider info
|
|
1033
|
-
*
|
|
1034
|
-
* Routes based on `descriptor.apiFormat` — listing reuses the existing
|
|
1035
|
-
* format dispatch and does not require a separate descriptor field.
|
|
1036
|
-
*
|
|
1138
|
+
* Lists models available from a provider, routing by `descriptor.apiFormat`.
|
|
1139
|
+
* Capabilities are resolved from native provider info and a configurable rule set.
|
|
1037
1140
|
* @param params - Request parameters including descriptor, API key, and optional capability filter
|
|
1038
1141
|
* @returns The resolved model list, or a failure
|
|
1039
1142
|
* @public
|
|
@@ -1080,18 +1183,9 @@ async function callProviderListModels(params) {
|
|
|
1080
1183
|
// ============================================================================
|
|
1081
1184
|
/**
|
|
1082
1185
|
* Calls the model-listing endpoint on a proxy server.
|
|
1083
|
-
*
|
|
1084
|
-
*
|
|
1085
|
-
*
|
|
1086
|
-
* - Endpoint: `POST ${proxyUrl}/api/ai/list-models`
|
|
1087
|
-
* - Request body: `{providerId, apiKey, capability?}`. Capability config is
|
|
1088
|
-
* not forwarded — the proxy applies its own (typically the same default
|
|
1089
|
-
* the library ships).
|
|
1090
|
-
* - Success response body: an `IAiModelInfo[]` (under key `models`) where
|
|
1091
|
-
* `capabilities` is serialized as a string array (not Set, which doesn't
|
|
1092
|
-
* round-trip through JSON).
|
|
1093
|
-
* - Error response body: `{error: string}`, surfaced as `proxy: ${error}`.
|
|
1094
|
-
*
|
|
1186
|
+
* Endpoint: `POST ${proxyUrl}/api/ai/list-models`. Capability config is not
|
|
1187
|
+
* forwarded. `capabilities` is serialized as a string array. Error body
|
|
1188
|
+
* `{error: string}` is surfaced as `proxy: ${error}`.
|
|
1095
1189
|
* @public
|
|
1096
1190
|
*/
|
|
1097
1191
|
async function callProxiedListModels(proxyUrl, params) {
|
|
@@ -1139,7 +1233,7 @@ async function callProxiedListModels(proxyUrl, params) {
|
|
|
1139
1233
|
* @public
|
|
1140
1234
|
*/
|
|
1141
1235
|
async function callProxiedCompletion(proxyUrl, params) {
|
|
1142
|
-
const { descriptor, apiKey, prompt, additionalMessages, temperature, modelOverride, logger, tools, signal } = params;
|
|
1236
|
+
const { descriptor, apiKey, prompt, additionalMessages, temperature, modelOverride, logger, tools, signal, thinking } = params;
|
|
1143
1237
|
const promptBody = { system: prompt.system, user: prompt.user };
|
|
1144
1238
|
if (prompt.attachments.length > 0) {
|
|
1145
1239
|
promptBody.attachments = prompt.attachments;
|
|
@@ -1159,6 +1253,9 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
1159
1253
|
if (tools && tools.length > 0) {
|
|
1160
1254
|
body.tools = tools;
|
|
1161
1255
|
}
|
|
1256
|
+
if (thinking !== undefined) {
|
|
1257
|
+
body.thinking = thinking;
|
|
1258
|
+
}
|
|
1162
1259
|
/* c8 ignore next 1 - optional logger */
|
|
1163
1260
|
logger === null || logger === void 0 ? void 0 : logger.info(`AI proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
|
|
1164
1261
|
const url = `${proxyUrl}/api/ai/completion`;
|
|
@@ -1166,7 +1263,6 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
1166
1263
|
if (jsonResult.isFailure()) {
|
|
1167
1264
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
1168
1265
|
}
|
|
1169
|
-
// Check for error response from proxy
|
|
1170
1266
|
const response = jsonResult.value;
|
|
1171
1267
|
if (typeof response.error === 'string') {
|
|
1172
1268
|
return (0, ts_utils_1.fail)(`proxy: ${response.error}`);
|
|
@@ -1185,20 +1281,11 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
1185
1281
|
/**
|
|
1186
1282
|
* Calls the image-generation endpoint on a proxy server instead of calling
|
|
1187
1283
|
* the provider API directly from the browser.
|
|
1188
|
-
*
|
|
1189
|
-
*
|
|
1190
|
-
*
|
|
1191
|
-
*
|
|
1192
|
-
*
|
|
1193
|
-
* - Success response body: an {@link IAiImageGenerationResponse}
|
|
1194
|
-
* - Error response body: `{error: string}` (surfaced as `proxy: ${error}`)
|
|
1195
|
-
*
|
|
1196
|
-
* The proxy server is responsible for descriptor lookup, model resolution,
|
|
1197
|
-
* provider dispatch, and response normalization. When `params.referenceImages`
|
|
1198
|
-
* is present, the proxy is also responsible for repackaging it into the
|
|
1199
|
-
* upstream wire format (e.g. multipart/form-data for OpenAI `/images/edits`,
|
|
1200
|
-
* `inlineData` parts for Gemini `:generateContent`).
|
|
1201
|
-
*
|
|
1284
|
+
* Endpoint: `POST ${proxyUrl}/api/ai/image-generation`. Request body:
|
|
1285
|
+
* `{providerId, apiKey, params, modelOverride?}`. The proxy handles descriptor
|
|
1286
|
+
* lookup, model resolution, provider dispatch, and response normalization
|
|
1287
|
+
* (including repackaging `referenceImages` for the upstream wire format).
|
|
1288
|
+
* Error body `{error: string}` is surfaced as `proxy: ${error}`.
|
|
1202
1289
|
* @param proxyUrl - Base URL of the proxy server (e.g. `http://localhost:3001`)
|
|
1203
1290
|
* @param params - Same parameters as {@link callProviderImageGeneration}
|
|
1204
1291
|
* @returns The generated images, or a failure
|