@fgv/ts-extras 5.1.0-26 → 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/keystore/keyStore.js +2 -1
- package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
- package/dist/ts-extras.d.ts +595 -119
- 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/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/package.json +13 -13
|
@@ -32,9 +32,11 @@
|
|
|
32
32
|
import { isJsonObject } from '@fgv/ts-json-base';
|
|
33
33
|
import { fail, mapResults, succeed, Validators } from '@fgv/ts-utils';
|
|
34
34
|
import { resolveModel } from './model';
|
|
35
|
+
import { checkTemperatureConflict, mergeThinkingConfig, providerDiscriminatorForId } from './thinkingOptionsResolver';
|
|
35
36
|
import { buildAnthropicMessages, buildGeminiContents, buildMessages, buildOpenAiChatUserContent, buildOpenAiResponsesUserContent } from './chatRequestBuilders';
|
|
36
37
|
import { bearerAuthHeader, resolveEffectiveBaseUrl } from './endpoint';
|
|
37
38
|
import { DEFAULT_MODEL_CAPABILITY_CONFIG, resolveImageCapability, supportsImageGeneration } from './registry';
|
|
39
|
+
import { resolveImageOptions, validateResolvedOptions } from './imageOptionsResolver';
|
|
38
40
|
import { toAnthropicTools, toGeminiTools, toResponsesApiTools } from './toolFormats';
|
|
39
41
|
// ============================================================================
|
|
40
42
|
// Shared helpers
|
|
@@ -104,6 +106,7 @@ async function fetchMultipart(url, headers, body, logger, signal) {
|
|
|
104
106
|
});
|
|
105
107
|
}
|
|
106
108
|
catch (err) {
|
|
109
|
+
/* c8 ignore next 1 - defensive: fetch errors are always Error instances in practice */
|
|
107
110
|
const detail = err instanceof Error ? err.message : String(err);
|
|
108
111
|
/* c8 ignore next 1 - optional logger */
|
|
109
112
|
logger === null || logger === void 0 ? void 0 : logger.error(`AI API request failed: ${detail}`);
|
|
@@ -121,13 +124,12 @@ async function fetchMultipart(url, headers, body, logger, signal) {
|
|
|
121
124
|
try {
|
|
122
125
|
json = await response.json();
|
|
123
126
|
}
|
|
124
|
-
catch (_a) {
|
|
125
|
-
/* c8 ignore next 1 - optional logger */
|
|
127
|
+
catch /* c8 ignore start - defensive: response.json() failure on a 2xx */ (_a) {
|
|
126
128
|
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned invalid JSON response');
|
|
127
129
|
return fail('AI API returned invalid JSON response');
|
|
128
|
-
}
|
|
130
|
+
} /* c8 ignore stop */
|
|
131
|
+
/* c8 ignore next 5 - defensive: provider returning non-object JSON on a 2xx */
|
|
129
132
|
if (!isJsonObject(json)) {
|
|
130
|
-
/* c8 ignore next 1 - optional logger */
|
|
131
133
|
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned non-object JSON response');
|
|
132
134
|
return fail('AI API returned non-object JSON response');
|
|
133
135
|
}
|
|
@@ -193,6 +195,7 @@ async function fetchGetJson(url, headers, logger, signal) {
|
|
|
193
195
|
response = await fetch(url, { method: 'GET', headers, signal });
|
|
194
196
|
}
|
|
195
197
|
catch (err) {
|
|
198
|
+
/* c8 ignore next 1 - defensive: fetch errors are always Error instances in practice */
|
|
196
199
|
const detail = err instanceof Error ? err.message : String(err);
|
|
197
200
|
/* c8 ignore next 1 - optional logger */
|
|
198
201
|
logger === null || logger === void 0 ? void 0 : logger.error(`AI API request failed: ${detail}`);
|
|
@@ -210,13 +213,12 @@ async function fetchGetJson(url, headers, logger, signal) {
|
|
|
210
213
|
try {
|
|
211
214
|
json = await response.json();
|
|
212
215
|
}
|
|
213
|
-
catch (_a) {
|
|
214
|
-
/* c8 ignore next 1 - optional logger */
|
|
216
|
+
catch /* c8 ignore start - defensive: response.json() failure on a 2xx */ (_a) {
|
|
215
217
|
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned invalid JSON response');
|
|
216
218
|
return fail('AI API returned invalid JSON response');
|
|
217
|
-
}
|
|
219
|
+
} /* c8 ignore stop */
|
|
220
|
+
/* c8 ignore next 5 - defensive: provider returning non-object JSON on a 2xx */
|
|
218
221
|
if (!isJsonObject(json)) {
|
|
219
|
-
/* c8 ignore next 1 - optional logger */
|
|
220
222
|
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned non-object JSON response');
|
|
221
223
|
return fail('AI API returned non-object JSON response');
|
|
222
224
|
}
|
|
@@ -246,13 +248,6 @@ const responsesApiResponse = Validators.object({
|
|
|
246
248
|
output: Validators.arrayOf(responsesApiOutputItem).withConstraint((arr) => arr.length > 0),
|
|
247
249
|
status: Validators.string
|
|
248
250
|
});
|
|
249
|
-
const anthropicContentBlock = Validators.object({
|
|
250
|
-
text: Validators.string
|
|
251
|
-
});
|
|
252
|
-
const anthropicResponse = Validators.object({
|
|
253
|
-
content: Validators.arrayOf(anthropicContentBlock).withConstraint((arr) => arr.length > 0),
|
|
254
|
-
stop_reason: Validators.string
|
|
255
|
-
});
|
|
256
251
|
const geminiPart = Validators.object({
|
|
257
252
|
text: Validators.string
|
|
258
253
|
});
|
|
@@ -274,12 +269,17 @@ const geminiResponse = Validators.object({
|
|
|
274
269
|
* Works for xAI Grok, OpenAI, Groq, and Mistral.
|
|
275
270
|
* @internal
|
|
276
271
|
*/
|
|
277
|
-
async function callOpenAiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, signal) {
|
|
272
|
+
async function callOpenAiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, signal, resolvedThinking) {
|
|
273
|
+
var _a;
|
|
278
274
|
const url = `${config.baseUrl}/chat/completions`;
|
|
279
275
|
const messages = buildMessages(prompt.system, buildOpenAiChatUserContent(prompt), {
|
|
280
276
|
tail: additionalMessages
|
|
281
277
|
});
|
|
282
|
-
const
|
|
278
|
+
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;
|
|
279
|
+
const body = Object.assign(Object.assign({ model: config.model, messages }, (effort === undefined || effort === 'none' ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning_effort: effort } : {}));
|
|
280
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
281
|
+
Object.assign(body, resolvedThinking.otherParams);
|
|
282
|
+
}
|
|
283
283
|
const headers = bearerAuthHeader(config.apiKey);
|
|
284
284
|
/* c8 ignore next 1 - optional logger */
|
|
285
285
|
logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI completion: model=${config.model}`);
|
|
@@ -322,17 +322,17 @@ function extractResponsesApiText(output) {
|
|
|
322
322
|
* Used when tools are configured for an openai-format provider.
|
|
323
323
|
* @internal
|
|
324
324
|
*/
|
|
325
|
-
async function callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature = 0.7, logger, signal) {
|
|
325
|
+
async function callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature = 0.7, logger, signal, resolvedThinking) {
|
|
326
|
+
var _a;
|
|
326
327
|
const url = `${config.baseUrl}/responses`;
|
|
327
328
|
const input = buildMessages(prompt.system, buildOpenAiResponsesUserContent(prompt), {
|
|
328
329
|
tail: additionalMessages
|
|
329
330
|
});
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
};
|
|
331
|
+
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;
|
|
332
|
+
const body = Object.assign(Object.assign({ model: config.model, input, tools: toResponsesApiTools(tools) }, (effort === undefined || effort === 'none' ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning: { effort } } : {}));
|
|
333
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
334
|
+
Object.assign(body, resolvedThinking.otherParams);
|
|
335
|
+
}
|
|
336
336
|
const headers = bearerAuthHeader(config.apiKey);
|
|
337
337
|
/* c8 ignore next 1 - optional logger */
|
|
338
338
|
logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI Responses API: model=${config.model}, tools=${tools.map((t) => t.type).join(',')}`);
|
|
@@ -375,23 +375,18 @@ function extractAnthropicText(content) {
|
|
|
375
375
|
}
|
|
376
376
|
return succeed(textParts.join(''));
|
|
377
377
|
}
|
|
378
|
-
/**
|
|
379
|
-
|
|
380
|
-
* When tools are configured, includes them in the request and handles
|
|
381
|
-
* mixed content block responses.
|
|
382
|
-
* @internal
|
|
383
|
-
*/
|
|
384
|
-
async function callAnthropicCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal) {
|
|
378
|
+
/** Calls the Anthropic Messages API with optional tool support. @internal */
|
|
379
|
+
async function callAnthropicCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal, resolvedThinking) {
|
|
385
380
|
const url = `${config.baseUrl}/messages`;
|
|
386
|
-
// Anthropic uses system as a top-level field, not in messages
|
|
387
381
|
const messages = buildAnthropicMessages(prompt, { tail: additionalMessages });
|
|
388
|
-
const body = {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
382
|
+
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 } : {}));
|
|
383
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.anthropicEffort) !== undefined) {
|
|
384
|
+
body.thinking = { type: 'enabled' };
|
|
385
|
+
body.output_config = { effort: resolvedThinking.anthropicEffort };
|
|
386
|
+
}
|
|
387
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
388
|
+
Object.assign(body, resolvedThinking.otherParams);
|
|
389
|
+
}
|
|
395
390
|
if (tools && tools.length > 0) {
|
|
396
391
|
body.tools = toAnthropicTools(tools);
|
|
397
392
|
/* c8 ignore next 3 - optional logger diagnostic output */
|
|
@@ -410,28 +405,18 @@ async function callAnthropicCompletion(config, prompt, additionalMessages, tempe
|
|
|
410
405
|
if (jsonResult.isFailure()) {
|
|
411
406
|
return fail(jsonResult.message);
|
|
412
407
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
const stopReason = jsonResult.value.stop_reason;
|
|
418
|
-
if (!Array.isArray(rawContent)) {
|
|
419
|
-
return fail('Anthropic API response: content is not an array');
|
|
420
|
-
}
|
|
421
|
-
return extractAnthropicText(rawContent).onSuccess((text) => succeed({
|
|
422
|
-
content: text,
|
|
423
|
-
truncated: stopReason === 'max_tokens'
|
|
424
|
-
}));
|
|
408
|
+
const rawContent = jsonResult.value.content;
|
|
409
|
+
const stopReason = jsonResult.value.stop_reason;
|
|
410
|
+
if (!Array.isArray(rawContent)) {
|
|
411
|
+
return fail('Anthropic API response: content is not an array');
|
|
425
412
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
});
|
|
434
|
-
});
|
|
413
|
+
if (typeof stopReason !== 'string') {
|
|
414
|
+
return fail('Anthropic API response: stop_reason is missing or not a string');
|
|
415
|
+
}
|
|
416
|
+
return extractAnthropicText(rawContent).onSuccess((text) => succeed({
|
|
417
|
+
content: text,
|
|
418
|
+
truncated: stopReason === 'max_tokens'
|
|
419
|
+
}));
|
|
435
420
|
}
|
|
436
421
|
// ============================================================================
|
|
437
422
|
// Google Gemini adapter
|
|
@@ -441,14 +426,20 @@ async function callAnthropicCompletion(config, prompt, additionalMessages, tempe
|
|
|
441
426
|
* When tools are configured, includes Google Search grounding.
|
|
442
427
|
* @internal
|
|
443
428
|
*/
|
|
444
|
-
async function callGeminiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal) {
|
|
429
|
+
async function callGeminiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal, resolvedThinking) {
|
|
445
430
|
const url = `${config.baseUrl}/models/${config.model}:generateContent`;
|
|
446
|
-
// Gemini uses 'contents' with 'parts', and 'model' role instead of 'assistant'
|
|
447
431
|
const contents = buildGeminiContents(prompt, { tail: additionalMessages });
|
|
432
|
+
const generationConfig = { temperature };
|
|
433
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.geminiThinkingBudget) !== undefined) {
|
|
434
|
+
generationConfig.thinkingConfig = { thinkingBudget: resolvedThinking.geminiThinkingBudget };
|
|
435
|
+
}
|
|
436
|
+
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
437
|
+
Object.assign(generationConfig, resolvedThinking.otherParams);
|
|
438
|
+
}
|
|
448
439
|
const body = {
|
|
449
440
|
systemInstruction: { parts: [{ text: prompt.system }] },
|
|
450
441
|
contents,
|
|
451
|
-
generationConfig
|
|
442
|
+
generationConfig
|
|
452
443
|
};
|
|
453
444
|
if (tools && tools.length > 0) {
|
|
454
445
|
body.tools = toGeminiTools(tools);
|
|
@@ -482,23 +473,15 @@ async function callGeminiCompletion(config, prompt, additionalMessages, temperat
|
|
|
482
473
|
// ============================================================================
|
|
483
474
|
/**
|
|
484
475
|
* Calls the appropriate chat completion API for a given provider.
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* - `'openai'` for xAI, OpenAI, Groq, Mistral
|
|
488
|
-
* - `'anthropic'` for Anthropic Claude
|
|
489
|
-
* - `'gemini'` for Google Gemini
|
|
490
|
-
*
|
|
491
|
-
* When tools are provided and the provider supports them:
|
|
492
|
-
* - OpenAI-format providers switch to the Responses API
|
|
493
|
-
* - Anthropic includes tools in the Messages API request
|
|
494
|
-
* - Gemini includes Google Search grounding
|
|
495
|
-
*
|
|
476
|
+
* Routes by `apiFormat`: `'openai'` (xAI/OpenAI/Groq/Mistral — switches to Responses API when
|
|
477
|
+
* tools are set), `'anthropic'`, or `'gemini'`.
|
|
496
478
|
* @param params - Request parameters including descriptor, API key, prompt, and optional tools
|
|
497
479
|
* @returns The completion response with content and truncation status, or a failure
|
|
498
480
|
* @public
|
|
499
481
|
*/
|
|
500
482
|
export async function callProviderCompletion(params) {
|
|
501
|
-
|
|
483
|
+
var _a;
|
|
484
|
+
const { descriptor, apiKey, prompt, additionalMessages, temperature, modelOverride, logger, tools, signal, endpoint, thinking } = params;
|
|
502
485
|
const baseUrlResult = resolveEffectiveBaseUrl(descriptor, endpoint);
|
|
503
486
|
if (baseUrlResult.isFailure()) {
|
|
504
487
|
return fail(baseUrlResult.message);
|
|
@@ -507,11 +490,31 @@ export async function callProviderCompletion(params) {
|
|
|
507
490
|
return fail(`provider "${descriptor.id}" does not accept image input`);
|
|
508
491
|
}
|
|
509
492
|
const hasTools = tools !== undefined && tools.length > 0;
|
|
510
|
-
const
|
|
493
|
+
const discriminator = providerDiscriminatorForId(descriptor.id);
|
|
494
|
+
const hasThinkingConfig = discriminator !== undefined &&
|
|
495
|
+
((thinking === null || thinking === void 0 ? void 0 : thinking.effort) !== undefined ||
|
|
496
|
+
((_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);
|
|
497
|
+
const modelContext = hasThinkingConfig ? 'thinking' : hasTools ? 'tools' : undefined;
|
|
511
498
|
const model = resolveModel(modelOverride !== null && modelOverride !== void 0 ? modelOverride : descriptor.defaultModel, modelContext);
|
|
512
499
|
if (model.length === 0) {
|
|
513
500
|
return fail(`provider "${descriptor.id}": no model resolved; pass modelOverride or set descriptor.defaultModel`);
|
|
514
501
|
}
|
|
502
|
+
let resolvedThinking;
|
|
503
|
+
if (thinking !== undefined) {
|
|
504
|
+
if (discriminator !== undefined) {
|
|
505
|
+
const mergeResult = mergeThinkingConfig(thinking, model, discriminator);
|
|
506
|
+
/* c8 ignore next 3 - mergeThinkingConfig always succeeds; defensive guard */
|
|
507
|
+
if (mergeResult.isFailure()) {
|
|
508
|
+
return fail(mergeResult.message);
|
|
509
|
+
}
|
|
510
|
+
resolvedThinking = mergeResult.value;
|
|
511
|
+
const conflictResult = checkTemperatureConflict(resolvedThinking, discriminator, temperature);
|
|
512
|
+
if (conflictResult.isFailure()) {
|
|
513
|
+
return fail(conflictResult.message);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const effectiveTemperature = temperature !== null && temperature !== void 0 ? temperature : 0.7;
|
|
515
518
|
const config = {
|
|
516
519
|
baseUrl: baseUrlResult.value,
|
|
517
520
|
apiKey,
|
|
@@ -527,13 +530,13 @@ export async function callProviderCompletion(params) {
|
|
|
527
530
|
switch (descriptor.apiFormat) {
|
|
528
531
|
case 'openai':
|
|
529
532
|
if (hasTools) {
|
|
530
|
-
return callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages,
|
|
533
|
+
return callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, effectiveTemperature, logger, signal, resolvedThinking);
|
|
531
534
|
}
|
|
532
|
-
return callOpenAiCompletion(config, prompt, additionalMessages,
|
|
535
|
+
return callOpenAiCompletion(config, prompt, additionalMessages, effectiveTemperature, logger, signal, resolvedThinking);
|
|
533
536
|
case 'anthropic':
|
|
534
|
-
return callAnthropicCompletion(config, prompt, additionalMessages,
|
|
537
|
+
return callAnthropicCompletion(config, prompt, additionalMessages, effectiveTemperature, logger, tools, signal, resolvedThinking);
|
|
535
538
|
case 'gemini':
|
|
536
|
-
return callGeminiCompletion(config, prompt, additionalMessages,
|
|
539
|
+
return callGeminiCompletion(config, prompt, additionalMessages, effectiveTemperature, logger, tools, signal, resolvedThinking);
|
|
537
540
|
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
538
541
|
default: {
|
|
539
542
|
const _exhaustive = descriptor.apiFormat;
|
|
@@ -584,7 +587,13 @@ const proxiedImageGenerationResponse = Validators.object({
|
|
|
584
587
|
});
|
|
585
588
|
const proxiedListModelsEntry = Validators.object({
|
|
586
589
|
id: Validators.string,
|
|
587
|
-
capabilities: Validators.arrayOf(Validators.enumeratedValue([
|
|
590
|
+
capabilities: Validators.arrayOf(Validators.enumeratedValue([
|
|
591
|
+
'chat',
|
|
592
|
+
'tools',
|
|
593
|
+
'vision',
|
|
594
|
+
'image-generation',
|
|
595
|
+
'thinking'
|
|
596
|
+
])),
|
|
588
597
|
displayName: Validators.string.optional()
|
|
589
598
|
});
|
|
590
599
|
const proxiedListModelsResponse = Validators.object({
|
|
@@ -593,101 +602,160 @@ const proxiedListModelsResponse = Validators.object({
|
|
|
593
602
|
// ============================================================================
|
|
594
603
|
// Image generation — adapters
|
|
595
604
|
// ============================================================================
|
|
596
|
-
/**
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
*
|
|
601
|
-
* When `request.referenceImages` is non-empty, routes to `/images/edits`
|
|
602
|
-
* (multipart) instead of `/images/generations` (JSON). Per-model edit support
|
|
603
|
-
* is not validated here (e.g. dall-e-3 does not support edits) — the
|
|
604
|
-
* provider's 400 surfaces through the failure path.
|
|
605
|
-
*
|
|
606
|
-
* @internal
|
|
607
|
-
*/
|
|
608
|
-
async function callOpenAiImageGeneration(config, request, defaultMimeType, logger, signal) {
|
|
609
|
-
var _a, _b, _c;
|
|
610
|
-
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
611
|
-
const refs = (_b = request.referenceImages) !== null && _b !== void 0 ? _b : [];
|
|
605
|
+
/** Routes to /images/generations or /images/edits; handles outputParamStyle. @internal */
|
|
606
|
+
async function callOpenAiImageGeneration(config, request, capability, resolved, logger, signal) {
|
|
607
|
+
var _a, _b;
|
|
608
|
+
const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
|
|
612
609
|
const headers = bearerAuthHeader(config.apiKey);
|
|
613
|
-
const
|
|
610
|
+
const effectiveMimeType = resolved.outputFormat !== undefined
|
|
611
|
+
? `image/${resolved.outputFormat}`
|
|
612
|
+
: (_b = capability.defaultOutputMimeType) !== null && _b !== void 0 ? _b : 'image/png';
|
|
614
613
|
const fetched = refs.length > 0
|
|
615
|
-
? await callOpenAiImagesEdits(config, request, headers,
|
|
616
|
-
: await callOpenAiImagesGenerations(config, request, headers,
|
|
614
|
+
? await callOpenAiImagesEdits(config, capability, request, headers, resolved, logger, signal)
|
|
615
|
+
: await callOpenAiImagesGenerations(config, request, headers, resolved, capability, logger, signal);
|
|
617
616
|
return fetched.onSuccess((json) => openAiImageResponse
|
|
618
617
|
.validate(json)
|
|
619
618
|
.withErrorFormat((msg) => `OpenAI images API response: ${msg}`)
|
|
620
619
|
.onSuccess((response) => succeed({
|
|
621
|
-
images: response.data.map((item) => (Object.assign({ mimeType:
|
|
620
|
+
images: response.data.map((item) => (Object.assign({ mimeType: effectiveMimeType, base64: item.b64_json }, (item.revised_prompt !== undefined ? { revisedPrompt: item.revised_prompt } : {}))))
|
|
622
621
|
})));
|
|
623
622
|
}
|
|
624
|
-
/**
|
|
625
|
-
|
|
626
|
-
* @internal
|
|
627
|
-
*/
|
|
628
|
-
function callOpenAiImagesGenerations(config, request, headers, n, logger, signal) {
|
|
623
|
+
/** Builds the JSON /images/generations request; handles outputParamStyle. @internal */
|
|
624
|
+
function callOpenAiImagesGenerations(config, request, headers, resolved, capability, logger, signal) {
|
|
629
625
|
var _a;
|
|
630
|
-
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
631
626
|
const body = {
|
|
632
627
|
model: config.model,
|
|
633
628
|
prompt: request.prompt,
|
|
634
|
-
n
|
|
635
|
-
response_format: 'b64_json'
|
|
629
|
+
n: resolved.n
|
|
636
630
|
};
|
|
637
|
-
|
|
638
|
-
|
|
631
|
+
// Output format param — conditional on model capability
|
|
632
|
+
if (capability.outputParamStyle === 'response-format') {
|
|
633
|
+
body.response_format = 'b64_json';
|
|
634
|
+
}
|
|
635
|
+
else if (capability.outputParamStyle === 'output-format') {
|
|
636
|
+
body.output_format = (_a = resolved.outputFormat) !== null && _a !== void 0 ? _a : 'png';
|
|
637
|
+
}
|
|
638
|
+
if (resolved.size !== undefined) {
|
|
639
|
+
body.size = resolved.size;
|
|
640
|
+
}
|
|
641
|
+
if (capability.supportsQualityParam && resolved.quality !== undefined) {
|
|
642
|
+
body.quality = resolved.quality;
|
|
639
643
|
}
|
|
640
|
-
if (
|
|
641
|
-
body.
|
|
644
|
+
if (resolved.seed !== undefined) {
|
|
645
|
+
body.seed = resolved.seed;
|
|
642
646
|
}
|
|
643
|
-
if (
|
|
644
|
-
body.
|
|
647
|
+
if (resolved.style !== undefined) {
|
|
648
|
+
body.style = resolved.style;
|
|
649
|
+
}
|
|
650
|
+
if (resolved.background !== undefined) {
|
|
651
|
+
body.background = resolved.background;
|
|
652
|
+
}
|
|
653
|
+
if (resolved.moderation !== undefined) {
|
|
654
|
+
body.moderation = resolved.moderation;
|
|
655
|
+
}
|
|
656
|
+
if (resolved.outputCompression !== undefined) {
|
|
657
|
+
body.output_compression = resolved.outputCompression;
|
|
658
|
+
}
|
|
659
|
+
if (resolved.otherParams !== undefined) {
|
|
660
|
+
Object.assign(body, resolved.otherParams);
|
|
645
661
|
}
|
|
646
662
|
/* c8 ignore next 1 - optional logger */
|
|
647
|
-
logger === null || logger === void 0 ? void 0 : logger.info(`Image generation: model=${config.model}, n=${n}`);
|
|
663
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`Image generation: model=${config.model}, n=${resolved.n}`);
|
|
648
664
|
return fetchJson(`${config.baseUrl}/images/generations`, headers, body, logger, signal);
|
|
649
665
|
}
|
|
650
|
-
/**
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
*/
|
|
654
|
-
async function callOpenAiImagesEdits(config, request, headers, n, refs, logger, signal) {
|
|
655
|
-
var _a;
|
|
666
|
+
/** Builds the multipart /images/edits request with ref images. @internal */
|
|
667
|
+
async function callOpenAiImagesEdits(config, capability, request, headers, resolved, logger, signal) {
|
|
668
|
+
const refs = request.referenceImages; // callers verify refs.length > 0 before calling this function
|
|
656
669
|
const blobsResult = mapResults(refs.map((ref, i) => attachmentToBlob(ref).withErrorFormat((msg) => `reference image ${i}: ${msg}`)));
|
|
657
670
|
/* c8 ignore next 3 - decode failure unreachable via Node's Buffer.from (silently strips invalid input) */
|
|
658
671
|
if (blobsResult.isFailure()) {
|
|
659
672
|
return fail(blobsResult.message);
|
|
660
673
|
}
|
|
661
|
-
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
662
674
|
const form = new FormData();
|
|
663
675
|
form.append('model', config.model);
|
|
664
676
|
form.append('prompt', request.prompt);
|
|
665
|
-
form.append('n', String(n));
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
form.append('size', opts.size);
|
|
677
|
+
form.append('n', String(resolved.n));
|
|
678
|
+
if (capability.outputParamStyle !== 'output-format') {
|
|
679
|
+
form.append('response_format', 'b64_json');
|
|
669
680
|
}
|
|
670
|
-
if (
|
|
671
|
-
form.append('
|
|
672
|
-
}
|
|
673
|
-
if (opts.seed !== undefined) {
|
|
674
|
-
form.append('seed', String(opts.seed));
|
|
681
|
+
if (resolved.size !== undefined) {
|
|
682
|
+
form.append('size', resolved.size);
|
|
675
683
|
}
|
|
676
684
|
blobsResult.value.forEach((blob, i) => {
|
|
677
685
|
form.append('image[]', blob, `ref-${i}.${extensionForMimeType(refs[i].mimeType)}`);
|
|
678
686
|
});
|
|
679
687
|
/* c8 ignore next 1 - optional logger */
|
|
680
|
-
logger === null || logger === void 0 ? void 0 : logger.info(`Image edit: model=${config.model}, n=${n}, refs=${refs.length}`);
|
|
688
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`Image edit: model=${config.model}, n=${resolved.n}, refs=${refs.length}`);
|
|
681
689
|
return fetchMultipart(`${config.baseUrl}/images/edits`, headers, form, logger, signal);
|
|
682
690
|
}
|
|
683
|
-
/**
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
+
/** Calls xAI /images/edits with JSON body (not multipart); up to 3 source images. @internal */
|
|
692
|
+
async function callXaiImagesEdits(config, request, resolved, logger, signal) {
|
|
693
|
+
var _a;
|
|
694
|
+
/* c8 ignore next 1 - defensive: referenceImages always defined when this function is called */
|
|
695
|
+
const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
|
|
696
|
+
if (refs.length > 3) {
|
|
697
|
+
return fail(`xAI image edits supports at most 3 reference images; got ${refs.length}`);
|
|
698
|
+
}
|
|
699
|
+
const images = refs.map((ref) => ({
|
|
700
|
+
type: 'image_url',
|
|
701
|
+
url: `data:${ref.mimeType};base64,${ref.base64}`
|
|
702
|
+
}));
|
|
703
|
+
const body = {
|
|
704
|
+
model: config.model,
|
|
705
|
+
prompt: request.prompt,
|
|
706
|
+
n: resolved.n,
|
|
707
|
+
response_format: 'b64_json',
|
|
708
|
+
image: images
|
|
709
|
+
};
|
|
710
|
+
if (resolved.aspectRatio !== undefined) {
|
|
711
|
+
body.aspect_ratio = resolved.aspectRatio;
|
|
712
|
+
}
|
|
713
|
+
if (resolved.resolution !== undefined) {
|
|
714
|
+
body.resolution = resolved.resolution;
|
|
715
|
+
}
|
|
716
|
+
if (resolved.otherParams !== undefined) {
|
|
717
|
+
Object.assign(body, resolved.otherParams);
|
|
718
|
+
}
|
|
719
|
+
/* c8 ignore next 1 - optional logger */
|
|
720
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`xAI image edit: model=${config.model}, n=${resolved.n}, refs=${refs.length}`);
|
|
721
|
+
return fetchJson(`${config.baseUrl}/images/edits`, bearerAuthHeader(config.apiKey), body, logger, signal);
|
|
722
|
+
}
|
|
723
|
+
/** Calls xAI /images/generations; uses aspect_ratio instead of size. @internal */
|
|
724
|
+
async function callXaiImageGeneration(config, request, capability, resolved, logger, signal) {
|
|
725
|
+
const headers = bearerAuthHeader(config.apiKey);
|
|
726
|
+
const body = {
|
|
727
|
+
model: config.model,
|
|
728
|
+
prompt: request.prompt,
|
|
729
|
+
n: resolved.n,
|
|
730
|
+
response_format: 'b64_json'
|
|
731
|
+
};
|
|
732
|
+
if (resolved.aspectRatio !== undefined) {
|
|
733
|
+
body.aspect_ratio = resolved.aspectRatio;
|
|
734
|
+
}
|
|
735
|
+
if (resolved.resolution !== undefined) {
|
|
736
|
+
body.resolution = resolved.resolution;
|
|
737
|
+
}
|
|
738
|
+
if (resolved.otherParams !== undefined) {
|
|
739
|
+
Object.assign(body, resolved.otherParams);
|
|
740
|
+
}
|
|
741
|
+
/* c8 ignore next 1 - optional logger */
|
|
742
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`xAI image generation: model=${config.model}, n=${resolved.n}`);
|
|
743
|
+
const fetched = await fetchJson(`${config.baseUrl}/images/generations`, headers, body, logger, signal);
|
|
744
|
+
return fetched.onSuccess((json) => openAiImageResponse
|
|
745
|
+
.validate(json)
|
|
746
|
+
.withErrorFormat((msg) => `xAI images API response: ${msg}`)
|
|
747
|
+
.onSuccess((response) => succeed({
|
|
748
|
+
images: response.data.map((item) => {
|
|
749
|
+
var _a;
|
|
750
|
+
return ({
|
|
751
|
+
mimeType: (_a = capability.defaultOutputMimeType) !== null && _a !== void 0 ? _a : 'image/jpeg',
|
|
752
|
+
base64: item.b64_json
|
|
753
|
+
});
|
|
754
|
+
})
|
|
755
|
+
})));
|
|
756
|
+
}
|
|
757
|
+
/** Calls Gemini :generateContent for image output; accepts ref images as inlineData. @internal */
|
|
758
|
+
async function callGeminiImageOutGeneration(config, request, resolved, logger, signal) {
|
|
691
759
|
var _a;
|
|
692
760
|
const url = `${config.baseUrl}/models/${config.model}:generateContent`;
|
|
693
761
|
const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
|
|
@@ -695,9 +763,17 @@ async function callGeminiImageOutGeneration(config, request, logger, signal) {
|
|
|
695
763
|
for (const ref of refs) {
|
|
696
764
|
parts.push({ inlineData: { mimeType: ref.mimeType, data: ref.base64 } });
|
|
697
765
|
}
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
766
|
+
const generationConfig = {};
|
|
767
|
+
if (resolved.geminiAspectRatio !== undefined) {
|
|
768
|
+
generationConfig.imageConfig = { aspectRatio: resolved.geminiAspectRatio };
|
|
769
|
+
}
|
|
770
|
+
if (resolved.otherParams !== undefined) {
|
|
771
|
+
Object.assign(generationConfig, resolved.otherParams);
|
|
772
|
+
}
|
|
773
|
+
const body = { contents: [{ role: 'user', parts }] };
|
|
774
|
+
if (Object.keys(generationConfig).length > 0) {
|
|
775
|
+
body.generationConfig = generationConfig;
|
|
776
|
+
}
|
|
701
777
|
const headers = {
|
|
702
778
|
'x-goog-api-key': config.apiKey
|
|
703
779
|
};
|
|
@@ -724,33 +800,48 @@ async function callGeminiImageOutGeneration(config, request, logger, signal) {
|
|
|
724
800
|
return succeed({ images });
|
|
725
801
|
}));
|
|
726
802
|
}
|
|
727
|
-
/**
|
|
728
|
-
|
|
729
|
-
* @internal
|
|
730
|
-
*/
|
|
731
|
-
async function callImagenGeneration(config, request, logger, signal) {
|
|
732
|
-
var _a, _b, _c, _d;
|
|
803
|
+
/** Calls the Gemini Imagen :predict endpoint with Imagen 4 params. @internal */
|
|
804
|
+
async function callImagenGeneration(config, request, resolved, logger, signal) {
|
|
733
805
|
const url = `${config.baseUrl}/models/${config.model}:predict`;
|
|
734
|
-
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
735
806
|
const parameters = {
|
|
736
|
-
sampleCount:
|
|
807
|
+
sampleCount: resolved.n
|
|
737
808
|
};
|
|
738
|
-
if (
|
|
739
|
-
parameters.aspectRatio =
|
|
809
|
+
if (resolved.imagenAspectRatio !== undefined) {
|
|
810
|
+
parameters.aspectRatio = resolved.imagenAspectRatio;
|
|
740
811
|
}
|
|
741
|
-
if (
|
|
742
|
-
parameters.
|
|
812
|
+
if (resolved.imageSize !== undefined) {
|
|
813
|
+
parameters.imageSize = resolved.imageSize;
|
|
743
814
|
}
|
|
744
|
-
if (
|
|
745
|
-
parameters.
|
|
815
|
+
if (resolved.addWatermark !== undefined) {
|
|
816
|
+
parameters.addWatermark = resolved.addWatermark;
|
|
817
|
+
}
|
|
818
|
+
if (resolved.enhancePrompt !== undefined) {
|
|
819
|
+
parameters.enhancePrompt = resolved.enhancePrompt;
|
|
820
|
+
}
|
|
821
|
+
if (resolved.imagenOutputMimeType !== undefined || resolved.imagenOutputCompressionQuality !== undefined) {
|
|
822
|
+
const outputOptions = {};
|
|
823
|
+
if (resolved.imagenOutputMimeType !== undefined) {
|
|
824
|
+
outputOptions.mimeType = resolved.imagenOutputMimeType;
|
|
825
|
+
}
|
|
826
|
+
if (resolved.imagenOutputCompressionQuality !== undefined) {
|
|
827
|
+
outputOptions.compressionQuality = resolved.imagenOutputCompressionQuality;
|
|
828
|
+
}
|
|
829
|
+
parameters.outputOptions = outputOptions;
|
|
830
|
+
}
|
|
831
|
+
if (resolved.personGeneration !== undefined) {
|
|
832
|
+
parameters.personGeneration = resolved.personGeneration;
|
|
833
|
+
}
|
|
834
|
+
if (resolved.seed !== undefined) {
|
|
835
|
+
parameters.seed = resolved.seed;
|
|
836
|
+
}
|
|
837
|
+
if (resolved.otherParams !== undefined) {
|
|
838
|
+
Object.assign(parameters, resolved.otherParams);
|
|
746
839
|
}
|
|
747
840
|
const body = {
|
|
748
841
|
instances: [{ prompt: request.prompt }],
|
|
749
842
|
parameters
|
|
750
843
|
};
|
|
751
|
-
const headers = {
|
|
752
|
-
'x-goog-api-key': config.apiKey
|
|
753
|
-
};
|
|
844
|
+
const headers = { 'x-goog-api-key': config.apiKey };
|
|
754
845
|
/* c8 ignore next 1 - optional logger */
|
|
755
846
|
logger === null || logger === void 0 ? void 0 : logger.info(`Imagen generation: model=${config.model}, n=${parameters.sampleCount}`);
|
|
756
847
|
const jsonResult = await fetchJson(url, headers, body, logger, signal);
|
|
@@ -776,25 +867,16 @@ async function callImagenGeneration(config, request, logger, signal) {
|
|
|
776
867
|
// ============================================================================
|
|
777
868
|
/**
|
|
778
869
|
* Calls the appropriate image-generation API for a given provider.
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
* - `'openai-images'` for OpenAI (DALL-E, gpt-image-1)
|
|
784
|
-
* - `'xai-images'` for xAI Grok image models
|
|
785
|
-
* - `'gemini-imagen'` for Google Imagen `:predict`
|
|
786
|
-
* - `'gemini-image-out'` for Gemini chat-style image output (Nano Banana)
|
|
787
|
-
*
|
|
788
|
-
* Image-model selection reuses the existing `'image'` {@link ModelSpecKey}.
|
|
789
|
-
* When `request.referenceImages` is non-empty, the call is rejected up front
|
|
790
|
-
* unless the resolved capability declares `acceptsImageReferenceInput`.
|
|
791
|
-
*
|
|
870
|
+
* Routes by the `format` field of the resolved {@link IAiImageModelCapability}:
|
|
871
|
+
* `'openai-images'`, `'xai-images'`, `'xai-images-edits'`, `'gemini-imagen'`,
|
|
872
|
+
* or `'gemini-image-out'`. Rejects up front if `referenceImages` is set but the
|
|
873
|
+
* capability does not declare `acceptsImageReferenceInput`.
|
|
792
874
|
* @param params - Request parameters including descriptor, API key, and prompt
|
|
793
875
|
* @returns The generated images, or a failure
|
|
794
876
|
* @public
|
|
795
877
|
*/
|
|
796
878
|
export async function callProviderImageGeneration(params) {
|
|
797
|
-
var _a, _b;
|
|
879
|
+
var _a, _b, _c;
|
|
798
880
|
const { descriptor, apiKey, params: request, modelOverride, logger, signal, endpoint } = params;
|
|
799
881
|
if (!supportsImageGeneration(descriptor)) {
|
|
800
882
|
return fail(`provider "${descriptor.id}" does not support image generation`);
|
|
@@ -816,6 +898,11 @@ export async function callProviderImageGeneration(params) {
|
|
|
816
898
|
if (((_b = (_a = request.referenceImages) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 && !capability.acceptsImageReferenceInput) {
|
|
817
899
|
return fail(`model "${model}" does not support reference images`);
|
|
818
900
|
}
|
|
901
|
+
const resolved = resolveImageOptions(model, capability, request.options);
|
|
902
|
+
const validationResult = validateResolvedOptions(model, capability, resolved);
|
|
903
|
+
if (validationResult.isFailure()) {
|
|
904
|
+
return fail(validationResult.message);
|
|
905
|
+
}
|
|
819
906
|
const config = {
|
|
820
907
|
baseUrl: baseUrlResult.value,
|
|
821
908
|
apiKey,
|
|
@@ -828,13 +915,32 @@ export async function callProviderImageGeneration(params) {
|
|
|
828
915
|
}
|
|
829
916
|
switch (capability.format) {
|
|
830
917
|
case 'openai-images':
|
|
831
|
-
return callOpenAiImageGeneration(config, request,
|
|
918
|
+
return callOpenAiImageGeneration(config, request, capability, resolved, logger, signal);
|
|
832
919
|
case 'xai-images':
|
|
833
|
-
return
|
|
920
|
+
return callXaiImageGeneration(config, request, capability, resolved, logger, signal);
|
|
921
|
+
case 'xai-images-edits': {
|
|
922
|
+
const refs = (_c = request.referenceImages) !== null && _c !== void 0 ? _c : [];
|
|
923
|
+
if (refs.length > 0) {
|
|
924
|
+
const editsResult = await callXaiImagesEdits(config, request, resolved, logger, signal);
|
|
925
|
+
return editsResult.onSuccess((json) => openAiImageResponse
|
|
926
|
+
.validate(json)
|
|
927
|
+
.withErrorFormat((msg) => `xAI images API response: ${msg}`)
|
|
928
|
+
.onSuccess((response) => succeed({
|
|
929
|
+
images: response.data.map((item) => {
|
|
930
|
+
var _a;
|
|
931
|
+
return ({
|
|
932
|
+
mimeType: (_a = capability.defaultOutputMimeType) !== null && _a !== void 0 ? _a : 'image/jpeg',
|
|
933
|
+
base64: item.b64_json
|
|
934
|
+
});
|
|
935
|
+
})
|
|
936
|
+
})));
|
|
937
|
+
}
|
|
938
|
+
return callXaiImageGeneration(config, request, capability, resolved, logger, signal);
|
|
939
|
+
}
|
|
834
940
|
case 'gemini-imagen':
|
|
835
|
-
return callImagenGeneration(config, request, logger, signal);
|
|
941
|
+
return callImagenGeneration(config, request, resolved, logger, signal);
|
|
836
942
|
case 'gemini-image-out':
|
|
837
|
-
return callGeminiImageOutGeneration(config, request, logger, signal);
|
|
943
|
+
return callGeminiImageOutGeneration(config, request, resolved, logger, signal);
|
|
838
944
|
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
839
945
|
default: {
|
|
840
946
|
const _exhaustive = capability.format;
|
|
@@ -888,6 +994,7 @@ function geminiMethodsToCapabilities(methods) {
|
|
|
888
994
|
* @internal
|
|
889
995
|
*/
|
|
890
996
|
function geminiBareId(name) {
|
|
997
|
+
/* c8 ignore next 1 - defensive: Gemini API always returns names prefixed with 'models/' */
|
|
891
998
|
return name.startsWith('models/') ? name.substring('models/'.length) : name;
|
|
892
999
|
}
|
|
893
1000
|
/**
|
|
@@ -1020,12 +1127,8 @@ async function callGeminiListModels(config, providerId, capabilityConfig, logger
|
|
|
1020
1127
|
// List models — dispatcher
|
|
1021
1128
|
// ============================================================================
|
|
1022
1129
|
/**
|
|
1023
|
-
* Lists models available from a provider,
|
|
1024
|
-
* native provider info
|
|
1025
|
-
*
|
|
1026
|
-
* Routes based on `descriptor.apiFormat` — listing reuses the existing
|
|
1027
|
-
* format dispatch and does not require a separate descriptor field.
|
|
1028
|
-
*
|
|
1130
|
+
* Lists models available from a provider, routing by `descriptor.apiFormat`.
|
|
1131
|
+
* Capabilities are resolved from native provider info and a configurable rule set.
|
|
1029
1132
|
* @param params - Request parameters including descriptor, API key, and optional capability filter
|
|
1030
1133
|
* @returns The resolved model list, or a failure
|
|
1031
1134
|
* @public
|
|
@@ -1072,18 +1175,9 @@ export async function callProviderListModels(params) {
|
|
|
1072
1175
|
// ============================================================================
|
|
1073
1176
|
/**
|
|
1074
1177
|
* Calls the model-listing endpoint on a proxy server.
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1078
|
-
* - Endpoint: `POST ${proxyUrl}/api/ai/list-models`
|
|
1079
|
-
* - Request body: `{providerId, apiKey, capability?}`. Capability config is
|
|
1080
|
-
* not forwarded — the proxy applies its own (typically the same default
|
|
1081
|
-
* the library ships).
|
|
1082
|
-
* - Success response body: an `IAiModelInfo[]` (under key `models`) where
|
|
1083
|
-
* `capabilities` is serialized as a string array (not Set, which doesn't
|
|
1084
|
-
* round-trip through JSON).
|
|
1085
|
-
* - Error response body: `{error: string}`, surfaced as `proxy: ${error}`.
|
|
1086
|
-
*
|
|
1178
|
+
* Endpoint: `POST ${proxyUrl}/api/ai/list-models`. Capability config is not
|
|
1179
|
+
* forwarded. `capabilities` is serialized as a string array. Error body
|
|
1180
|
+
* `{error: string}` is surfaced as `proxy: ${error}`.
|
|
1087
1181
|
* @public
|
|
1088
1182
|
*/
|
|
1089
1183
|
export async function callProxiedListModels(proxyUrl, params) {
|
|
@@ -1131,7 +1225,7 @@ export async function callProxiedListModels(proxyUrl, params) {
|
|
|
1131
1225
|
* @public
|
|
1132
1226
|
*/
|
|
1133
1227
|
export async function callProxiedCompletion(proxyUrl, params) {
|
|
1134
|
-
const { descriptor, apiKey, prompt, additionalMessages, temperature, modelOverride, logger, tools, signal } = params;
|
|
1228
|
+
const { descriptor, apiKey, prompt, additionalMessages, temperature, modelOverride, logger, tools, signal, thinking } = params;
|
|
1135
1229
|
const promptBody = { system: prompt.system, user: prompt.user };
|
|
1136
1230
|
if (prompt.attachments.length > 0) {
|
|
1137
1231
|
promptBody.attachments = prompt.attachments;
|
|
@@ -1151,6 +1245,9 @@ export async function callProxiedCompletion(proxyUrl, params) {
|
|
|
1151
1245
|
if (tools && tools.length > 0) {
|
|
1152
1246
|
body.tools = tools;
|
|
1153
1247
|
}
|
|
1248
|
+
if (thinking !== undefined) {
|
|
1249
|
+
body.thinking = thinking;
|
|
1250
|
+
}
|
|
1154
1251
|
/* c8 ignore next 1 - optional logger */
|
|
1155
1252
|
logger === null || logger === void 0 ? void 0 : logger.info(`AI proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
|
|
1156
1253
|
const url = `${proxyUrl}/api/ai/completion`;
|
|
@@ -1158,7 +1255,6 @@ export async function callProxiedCompletion(proxyUrl, params) {
|
|
|
1158
1255
|
if (jsonResult.isFailure()) {
|
|
1159
1256
|
return fail(jsonResult.message);
|
|
1160
1257
|
}
|
|
1161
|
-
// Check for error response from proxy
|
|
1162
1258
|
const response = jsonResult.value;
|
|
1163
1259
|
if (typeof response.error === 'string') {
|
|
1164
1260
|
return fail(`proxy: ${response.error}`);
|
|
@@ -1177,20 +1273,11 @@ export async function callProxiedCompletion(proxyUrl, params) {
|
|
|
1177
1273
|
/**
|
|
1178
1274
|
* Calls the image-generation endpoint on a proxy server instead of calling
|
|
1179
1275
|
* the provider API directly from the browser.
|
|
1180
|
-
*
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
* - Success response body: an {@link IAiImageGenerationResponse}
|
|
1186
|
-
* - Error response body: `{error: string}` (surfaced as `proxy: ${error}`)
|
|
1187
|
-
*
|
|
1188
|
-
* The proxy server is responsible for descriptor lookup, model resolution,
|
|
1189
|
-
* provider dispatch, and response normalization. When `params.referenceImages`
|
|
1190
|
-
* is present, the proxy is also responsible for repackaging it into the
|
|
1191
|
-
* upstream wire format (e.g. multipart/form-data for OpenAI `/images/edits`,
|
|
1192
|
-
* `inlineData` parts for Gemini `:generateContent`).
|
|
1193
|
-
*
|
|
1276
|
+
* Endpoint: `POST ${proxyUrl}/api/ai/image-generation`. Request body:
|
|
1277
|
+
* `{providerId, apiKey, params, modelOverride?}`. The proxy handles descriptor
|
|
1278
|
+
* lookup, model resolution, provider dispatch, and response normalization
|
|
1279
|
+
* (including repackaging `referenceImages` for the upstream wire format).
|
|
1280
|
+
* Error body `{error: string}` is surfaced as `proxy: ${error}`.
|
|
1194
1281
|
* @param proxyUrl - Base URL of the proxy server (e.g. `http://localhost:3001`)
|
|
1195
1282
|
* @param params - Same parameters as {@link callProviderImageGeneration}
|
|
1196
1283
|
* @returns The generated images, or a failure
|