@fgv/ts-extras 5.1.0-2 → 5.1.0-21
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 -1
- package/dist/index.browser.js.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/packlets/ai-assist/apiClient.js +807 -67
- package/dist/packlets/ai-assist/apiClient.js.map +1 -0
- package/dist/packlets/ai-assist/chatRequestBuilders.js +180 -0
- package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -0
- package/dist/packlets/ai-assist/converters.js +2 -1
- package/dist/packlets/ai-assist/converters.js.map +1 -0
- package/dist/packlets/ai-assist/endpoint.js +78 -0
- package/dist/packlets/ai-assist/endpoint.js.map +1 -0
- package/dist/packlets/ai-assist/index.js +4 -3
- package/dist/packlets/ai-assist/index.js.map +1 -0
- package/dist/packlets/ai-assist/model.js +20 -3
- package/dist/packlets/ai-assist/model.js.map +1 -0
- package/dist/packlets/ai-assist/registry.js +137 -10
- package/dist/packlets/ai-assist/registry.js.map +1 -0
- package/dist/packlets/ai-assist/sseParser.js +122 -0
- package/dist/packlets/ai-assist/sseParser.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +192 -0
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/common.js +77 -0
- package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js +160 -0
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +150 -0
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +164 -0
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js +157 -0
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
- package/dist/packlets/ai-assist/streamingClient.js +94 -0
- package/dist/packlets/ai-assist/streamingClient.js.map +1 -0
- package/dist/packlets/ai-assist/toolFormats.js.map +1 -0
- package/dist/packlets/conversion/converters.js +34 -1
- package/dist/packlets/conversion/converters.js.map +1 -0
- package/dist/packlets/conversion/index.js.map +1 -0
- package/dist/packlets/crypto-utils/constants.js.map +1 -0
- package/dist/packlets/crypto-utils/converters.js.map +1 -0
- package/dist/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
- package/dist/packlets/crypto-utils/encryptedFile.js.map +1 -0
- package/dist/packlets/crypto-utils/index.browser.js +2 -0
- package/dist/packlets/crypto-utils/index.browser.js.map +1 -0
- package/dist/packlets/crypto-utils/index.js +2 -0
- package/dist/packlets/crypto-utils/index.js.map +1 -0
- package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js +63 -0
- package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
- package/dist/packlets/crypto-utils/keystore/converters.js +101 -9
- package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -0
- package/dist/packlets/crypto-utils/keystore/index.js +1 -0
- package/dist/packlets/crypto-utils/keystore/index.js.map +1 -0
- package/dist/packlets/crypto-utils/keystore/keyStore.js +431 -118
- package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
- package/dist/packlets/crypto-utils/keystore/model.js +22 -1
- package/dist/packlets/crypto-utils/keystore/model.js.map +1 -0
- package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js +21 -0
- package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
- package/dist/packlets/crypto-utils/model.js +10 -0
- package/dist/packlets/crypto-utils/model.js.map +1 -0
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js +163 -1
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
- package/dist/packlets/csv/csvFileHelpers.js.map +1 -0
- package/dist/packlets/csv/csvHelpers.js.map +1 -0
- package/dist/packlets/csv/index.browser.js.map +1 -0
- package/dist/packlets/csv/index.js.map +1 -0
- package/dist/packlets/experimental/extendedArray.js.map +1 -0
- package/dist/packlets/experimental/formatter.js.map +1 -0
- package/dist/packlets/experimental/index.js.map +1 -0
- package/dist/packlets/experimental/rangeOf.js.map +1 -0
- package/dist/packlets/hash/index.browser.js.map +1 -0
- package/dist/packlets/hash/index.js.map +1 -0
- package/dist/packlets/hash/index.node.js.map +1 -0
- package/dist/packlets/hash/md5Normalizer.browser.js.map +1 -0
- package/dist/packlets/hash/md5Normalizer.js.map +1 -0
- package/dist/packlets/mustache/index.js.map +1 -0
- package/dist/packlets/mustache/interfaces.js.map +1 -0
- package/dist/packlets/mustache/mustacheTemplate.js.map +1 -0
- package/dist/packlets/record-jar/index.browser.js.map +1 -0
- package/dist/packlets/record-jar/index.js.map +1 -0
- package/dist/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
- package/dist/packlets/record-jar/recordJarHelpers.js.map +1 -0
- package/dist/packlets/yaml/converters.js.map +1 -0
- package/dist/packlets/yaml/index.js +1 -0
- package/dist/packlets/yaml/index.js.map +1 -0
- package/dist/packlets/yaml/serializers.js +48 -0
- package/dist/packlets/yaml/serializers.js.map +1 -0
- package/dist/packlets/zip-file-tree/index.js.map +1 -0
- package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js +2 -2
- package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
- package/dist/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
- package/dist/ts-extras.d.ts +1499 -41
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/index.browser.d.ts +2 -1
- package/lib/index.browser.d.ts.map +1 -0
- package/lib/index.browser.js +3 -1
- package/lib/index.browser.js.map +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js.map +1 -0
- package/lib/packlets/ai-assist/apiClient.d.ts +140 -1
- package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -0
- package/lib/packlets/ai-assist/apiClient.js +810 -66
- package/lib/packlets/ai-assist/apiClient.js.map +1 -0
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +89 -0
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -0
- package/lib/packlets/ai-assist/chatRequestBuilders.js +189 -0
- package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -0
- package/lib/packlets/ai-assist/converters.d.ts.map +1 -0
- package/lib/packlets/ai-assist/converters.js +2 -1
- package/lib/packlets/ai-assist/converters.js.map +1 -0
- package/lib/packlets/ai-assist/endpoint.d.ts +28 -0
- package/lib/packlets/ai-assist/endpoint.d.ts.map +1 -0
- package/lib/packlets/ai-assist/endpoint.js +82 -0
- package/lib/packlets/ai-assist/endpoint.js.map +1 -0
- package/lib/packlets/ai-assist/index.d.ts +4 -3
- package/lib/packlets/ai-assist/index.d.ts.map +1 -0
- package/lib/packlets/ai-assist/index.js +12 -1
- package/lib/packlets/ai-assist/index.js.map +1 -0
- package/lib/packlets/ai-assist/model.d.ts +341 -3
- package/lib/packlets/ai-assist/model.d.ts.map +1 -0
- package/lib/packlets/ai-assist/model.js +21 -3
- package/lib/packlets/ai-assist/model.js.map +1 -0
- package/lib/packlets/ai-assist/registry.d.ts +34 -1
- package/lib/packlets/ai-assist/registry.d.ts.map +1 -0
- package/lib/packlets/ai-assist/registry.js +140 -11
- package/lib/packlets/ai-assist/registry.js.map +1 -0
- package/lib/packlets/ai-assist/sseParser.d.ts +45 -0
- package/lib/packlets/ai-assist/sseParser.d.ts.map +1 -0
- package/lib/packlets/ai-assist/sseParser.js +127 -0
- package/lib/packlets/ai-assist/sseParser.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +18 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +195 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +79 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.js +81 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +19 -0
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js +163 -0
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +18 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +153 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +19 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +167 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +34 -0
- package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/proxy.js +160 -0
- package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
- package/lib/packlets/ai-assist/streamingClient.d.ts +33 -0
- package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingClient.js +99 -0
- package/lib/packlets/ai-assist/streamingClient.js.map +1 -0
- package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -0
- package/lib/packlets/ai-assist/toolFormats.js.map +1 -0
- package/lib/packlets/conversion/converters.d.ts +8 -1
- package/lib/packlets/conversion/converters.d.ts.map +1 -0
- package/lib/packlets/conversion/converters.js +35 -2
- package/lib/packlets/conversion/converters.js.map +1 -0
- package/lib/packlets/conversion/index.d.ts.map +1 -0
- package/lib/packlets/conversion/index.js.map +1 -0
- package/lib/packlets/crypto-utils/constants.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/constants.js.map +1 -0
- package/lib/packlets/crypto-utils/converters.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/converters.js.map +1 -0
- package/lib/packlets/crypto-utils/directEncryptionProvider.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
- package/lib/packlets/crypto-utils/encryptedFile.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/encryptedFile.js.map +1 -0
- package/lib/packlets/crypto-utils/index.browser.d.ts +1 -0
- package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/index.browser.js +4 -1
- package/lib/packlets/crypto-utils/index.browser.js.map +1 -0
- package/lib/packlets/crypto-utils/index.d.ts +1 -0
- package/lib/packlets/crypto-utils/index.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/index.js +4 -1
- package/lib/packlets/crypto-utils/index.js.map +1 -0
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts +50 -0
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js +66 -0
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
- package/lib/packlets/crypto-utils/keystore/converters.d.ts +68 -6
- package/lib/packlets/crypto-utils/keystore/converters.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keystore/converters.js +100 -8
- package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -0
- package/lib/packlets/crypto-utils/keystore/index.d.ts +1 -0
- package/lib/packlets/crypto-utils/keystore/index.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keystore/index.js +1 -0
- package/lib/packlets/crypto-utils/keystore/index.js.map +1 -0
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +125 -12
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keystore/keyStore.js +431 -118
- package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
- package/lib/packlets/crypto-utils/keystore/model.d.ts +248 -17
- package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keystore/model.js +24 -2
- package/lib/packlets/crypto-utils/keystore/model.js.map +1 -0
- package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts +50 -0
- package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js +22 -0
- package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
- package/lib/packlets/crypto-utils/model.d.ts +145 -0
- package/lib/packlets/crypto-utils/model.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/model.js +11 -1
- package/lib/packlets/crypto-utils/model.js.map +1 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +51 -1
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js +162 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
- package/lib/packlets/csv/csvFileHelpers.d.ts.map +1 -0
- package/lib/packlets/csv/csvFileHelpers.js.map +1 -0
- package/lib/packlets/csv/csvHelpers.d.ts.map +1 -0
- package/lib/packlets/csv/csvHelpers.js.map +1 -0
- package/lib/packlets/csv/index.browser.d.ts.map +1 -0
- package/lib/packlets/csv/index.browser.js.map +1 -0
- package/lib/packlets/csv/index.d.ts.map +1 -0
- package/lib/packlets/csv/index.js.map +1 -0
- package/lib/packlets/experimental/extendedArray.d.ts.map +1 -0
- package/lib/packlets/experimental/extendedArray.js.map +1 -0
- package/lib/packlets/experimental/formatter.d.ts.map +1 -0
- package/lib/packlets/experimental/formatter.js.map +1 -0
- package/lib/packlets/experimental/index.d.ts.map +1 -0
- package/lib/packlets/experimental/index.js.map +1 -0
- package/lib/packlets/experimental/rangeOf.d.ts.map +1 -0
- package/lib/packlets/experimental/rangeOf.js.map +1 -0
- package/lib/packlets/hash/index.browser.d.ts.map +1 -0
- package/lib/packlets/hash/index.browser.js.map +1 -0
- package/lib/packlets/hash/index.d.ts.map +1 -0
- package/lib/packlets/hash/index.js.map +1 -0
- package/lib/packlets/hash/index.node.d.ts.map +1 -0
- package/lib/packlets/hash/index.node.js.map +1 -0
- package/lib/packlets/hash/md5Normalizer.browser.d.ts.map +1 -0
- package/lib/packlets/hash/md5Normalizer.browser.js.map +1 -0
- package/lib/packlets/hash/md5Normalizer.d.ts.map +1 -0
- package/lib/packlets/hash/md5Normalizer.js.map +1 -0
- package/lib/packlets/mustache/index.d.ts.map +1 -0
- package/lib/packlets/mustache/index.js.map +1 -0
- package/lib/packlets/mustache/interfaces.d.ts.map +1 -0
- package/lib/packlets/mustache/interfaces.js.map +1 -0
- package/lib/packlets/mustache/mustacheTemplate.d.ts.map +1 -0
- package/lib/packlets/mustache/mustacheTemplate.js.map +1 -0
- package/lib/packlets/record-jar/index.browser.d.ts.map +1 -0
- package/lib/packlets/record-jar/index.browser.js.map +1 -0
- package/lib/packlets/record-jar/index.d.ts.map +1 -0
- package/lib/packlets/record-jar/index.js.map +1 -0
- package/lib/packlets/record-jar/recordJarFileHelpers.d.ts.map +1 -0
- package/lib/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
- package/lib/packlets/record-jar/recordJarHelpers.d.ts.map +1 -0
- package/lib/packlets/record-jar/recordJarHelpers.js.map +1 -0
- package/lib/packlets/yaml/converters.d.ts.map +1 -0
- package/lib/packlets/yaml/converters.js.map +1 -0
- package/lib/packlets/yaml/index.d.ts +1 -0
- package/lib/packlets/yaml/index.d.ts.map +1 -0
- package/lib/packlets/yaml/index.js +1 -0
- package/lib/packlets/yaml/index.js.map +1 -0
- package/lib/packlets/yaml/serializers.d.ts +45 -0
- package/lib/packlets/yaml/serializers.d.ts.map +1 -0
- package/lib/packlets/yaml/serializers.js +84 -0
- package/lib/packlets/yaml/serializers.js.map +1 -0
- package/lib/packlets/zip-file-tree/index.d.ts.map +1 -0
- package/lib/packlets/zip-file-tree/index.js.map +1 -0
- package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts +2 -2
- package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts.map +1 -0
- package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js +2 -2
- package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
- package/lib/packlets/zip-file-tree/zipFileTreeWriter.d.ts.map +1 -0
- package/lib/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
- package/package.json +24 -23
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
// SOFTWARE.
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
22
|
exports.callProviderCompletion = callProviderCompletion;
|
|
23
|
+
exports.callProviderImageGeneration = callProviderImageGeneration;
|
|
24
|
+
exports.callProviderListModels = callProviderListModels;
|
|
25
|
+
exports.callProxiedListModels = callProxiedListModels;
|
|
23
26
|
exports.callProxiedCompletion = callProxiedCompletion;
|
|
27
|
+
exports.callProxiedImageGeneration = callProxiedImageGeneration;
|
|
24
28
|
/**
|
|
25
29
|
* Chat completion client for AI assist with support for multiple provider APIs.
|
|
26
30
|
*
|
|
@@ -36,39 +40,75 @@ exports.callProxiedCompletion = callProxiedCompletion;
|
|
|
36
40
|
const ts_json_base_1 = require("@fgv/ts-json-base");
|
|
37
41
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
38
42
|
const model_1 = require("./model");
|
|
43
|
+
const chatRequestBuilders_1 = require("./chatRequestBuilders");
|
|
44
|
+
const endpoint_1 = require("./endpoint");
|
|
45
|
+
const registry_1 = require("./registry");
|
|
39
46
|
const toolFormats_1 = require("./toolFormats");
|
|
40
47
|
// ============================================================================
|
|
41
48
|
// Shared helpers
|
|
42
49
|
// ============================================================================
|
|
43
50
|
/**
|
|
44
|
-
*
|
|
51
|
+
* Makes an HTTP request and returns the parsed JSON, or a failure.
|
|
45
52
|
* @internal
|
|
46
53
|
*/
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
async function fetchJson(url, headers, body, logger, signal) {
|
|
55
|
+
/* c8 ignore next 1 - optional logger */
|
|
56
|
+
logger === null || logger === void 0 ? void 0 : logger.detail(`AI API request: POST ${url}`);
|
|
57
|
+
let response;
|
|
58
|
+
try {
|
|
59
|
+
response = await fetch(url, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, headers),
|
|
62
|
+
body: JSON.stringify(body),
|
|
63
|
+
signal
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
68
|
+
/* c8 ignore next 1 - optional logger */
|
|
69
|
+
logger === null || logger === void 0 ? void 0 : logger.error(`AI API request failed: ${detail}`);
|
|
70
|
+
return (0, ts_utils_1.fail)(`AI API request failed: ${detail}`);
|
|
71
|
+
}
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const errorText = await response.text().catch(() => 'unknown error');
|
|
74
|
+
/* c8 ignore next 1 - optional logger */
|
|
75
|
+
logger === null || logger === void 0 ? void 0 : logger.error(`AI API returned ${response.status}: ${errorText}`);
|
|
76
|
+
return (0, ts_utils_1.fail)(`AI API returned ${response.status}: ${errorText}`);
|
|
56
77
|
}
|
|
57
|
-
|
|
78
|
+
/* c8 ignore next 1 - optional logger */
|
|
79
|
+
logger === null || logger === void 0 ? void 0 : logger.detail(`AI API response: ${response.status}`);
|
|
80
|
+
let json;
|
|
81
|
+
try {
|
|
82
|
+
json = await response.json();
|
|
83
|
+
}
|
|
84
|
+
catch (_a) {
|
|
85
|
+
/* c8 ignore next 1 - optional logger */
|
|
86
|
+
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned invalid JSON response');
|
|
87
|
+
return (0, ts_utils_1.fail)('AI API returned invalid JSON response');
|
|
88
|
+
}
|
|
89
|
+
if (!(0, ts_json_base_1.isJsonObject)(json)) {
|
|
90
|
+
/* c8 ignore next 1 - optional logger */
|
|
91
|
+
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned non-object JSON response');
|
|
92
|
+
return (0, ts_utils_1.fail)('AI API returned non-object JSON response');
|
|
93
|
+
}
|
|
94
|
+
return (0, ts_utils_1.succeed)(json);
|
|
58
95
|
}
|
|
59
96
|
/**
|
|
60
|
-
* Makes
|
|
97
|
+
* Makes a multipart/form-data POST request and returns the parsed JSON, or a
|
|
98
|
+
* failure. The Content-Type header (with boundary) is set automatically by
|
|
99
|
+
* `fetch` from the `FormData` body — callers must NOT pass it explicitly.
|
|
61
100
|
* @internal
|
|
62
101
|
*/
|
|
63
|
-
async function
|
|
102
|
+
async function fetchMultipart(url, headers, body, logger, signal) {
|
|
64
103
|
/* c8 ignore next 1 - optional logger */
|
|
65
|
-
logger === null || logger === void 0 ? void 0 : logger.detail(`AI API request: POST ${url}`);
|
|
104
|
+
logger === null || logger === void 0 ? void 0 : logger.detail(`AI API request: POST ${url} (multipart)`);
|
|
66
105
|
let response;
|
|
67
106
|
try {
|
|
68
107
|
response = await fetch(url, {
|
|
69
108
|
method: 'POST',
|
|
70
|
-
headers
|
|
71
|
-
body
|
|
109
|
+
headers,
|
|
110
|
+
body,
|
|
111
|
+
signal
|
|
72
112
|
});
|
|
73
113
|
}
|
|
74
114
|
catch (err) {
|
|
@@ -101,6 +141,95 @@ async function fetchJson(url, headers, body, logger) {
|
|
|
101
141
|
}
|
|
102
142
|
return (0, ts_utils_1.succeed)(json);
|
|
103
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Decodes a base64-encoded image attachment into a `Blob` suitable for use as
|
|
146
|
+
* a multipart file field. On Node hands the `Buffer` straight to `Blob`
|
|
147
|
+
* (Buffer extends Uint8Array) to skip an intermediate copy; falls back to
|
|
148
|
+
* `atob` in browsers. Inputs come from `FileReader` or prior provider
|
|
149
|
+
* responses, which are trusted to be valid. Note that Node's
|
|
150
|
+
* `Buffer.from(..., 'base64')` silently strips invalid characters rather
|
|
151
|
+
* than throwing, so failures are only observable in the browser path.
|
|
152
|
+
* @internal
|
|
153
|
+
*/
|
|
154
|
+
function attachmentToBlob(attachment) {
|
|
155
|
+
if (typeof Buffer !== 'undefined') {
|
|
156
|
+
return (0, ts_utils_1.succeed)(new Blob([Buffer.from(attachment.base64, 'base64')], { type: attachment.mimeType }));
|
|
157
|
+
}
|
|
158
|
+
/* c8 ignore start - Browser-only fallback cannot be tested in Node.js environment */
|
|
159
|
+
try {
|
|
160
|
+
const binary = atob(attachment.base64);
|
|
161
|
+
const bytes = new Uint8Array(binary.length);
|
|
162
|
+
for (let i = 0; i < binary.length; i++) {
|
|
163
|
+
bytes[i] = binary.charCodeAt(i);
|
|
164
|
+
}
|
|
165
|
+
return (0, ts_utils_1.succeed)(new Blob([bytes], { type: attachment.mimeType }));
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
169
|
+
return (0, ts_utils_1.fail)(`Invalid base64: ${message}`);
|
|
170
|
+
}
|
|
171
|
+
/* c8 ignore stop */
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Maps a MIME type to a sensible file extension for multipart filenames.
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
function extensionForMimeType(mimeType) {
|
|
178
|
+
switch (mimeType) {
|
|
179
|
+
case 'image/png':
|
|
180
|
+
return 'png';
|
|
181
|
+
case 'image/jpeg':
|
|
182
|
+
case 'image/jpg':
|
|
183
|
+
return 'jpg';
|
|
184
|
+
case 'image/webp':
|
|
185
|
+
return 'webp';
|
|
186
|
+
case 'image/gif':
|
|
187
|
+
return 'gif';
|
|
188
|
+
default:
|
|
189
|
+
return 'bin';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Makes an HTTP GET request and returns the parsed JSON, or a failure.
|
|
194
|
+
* @internal
|
|
195
|
+
*/
|
|
196
|
+
async function fetchGetJson(url, headers, logger, signal) {
|
|
197
|
+
/* c8 ignore next 1 - optional logger */
|
|
198
|
+
logger === null || logger === void 0 ? void 0 : logger.detail(`AI API request: GET ${url}`);
|
|
199
|
+
let response;
|
|
200
|
+
try {
|
|
201
|
+
response = await fetch(url, { method: 'GET', headers, signal });
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
205
|
+
/* c8 ignore next 1 - optional logger */
|
|
206
|
+
logger === null || logger === void 0 ? void 0 : logger.error(`AI API request failed: ${detail}`);
|
|
207
|
+
return (0, ts_utils_1.fail)(`AI API request failed: ${detail}`);
|
|
208
|
+
}
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
const errorText = await response.text().catch(() => 'unknown error');
|
|
211
|
+
/* c8 ignore next 1 - optional logger */
|
|
212
|
+
logger === null || logger === void 0 ? void 0 : logger.error(`AI API returned ${response.status}: ${errorText}`);
|
|
213
|
+
return (0, ts_utils_1.fail)(`AI API returned ${response.status}: ${errorText}`);
|
|
214
|
+
}
|
|
215
|
+
/* c8 ignore next 1 - optional logger */
|
|
216
|
+
logger === null || logger === void 0 ? void 0 : logger.detail(`AI API response: ${response.status}`);
|
|
217
|
+
let json;
|
|
218
|
+
try {
|
|
219
|
+
json = await response.json();
|
|
220
|
+
}
|
|
221
|
+
catch (_a) {
|
|
222
|
+
/* c8 ignore next 1 - optional logger */
|
|
223
|
+
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned invalid JSON response');
|
|
224
|
+
return (0, ts_utils_1.fail)('AI API returned invalid JSON response');
|
|
225
|
+
}
|
|
226
|
+
if (!(0, ts_json_base_1.isJsonObject)(json)) {
|
|
227
|
+
/* c8 ignore next 1 - optional logger */
|
|
228
|
+
logger === null || logger === void 0 ? void 0 : logger.error('AI API returned non-object JSON response');
|
|
229
|
+
return (0, ts_utils_1.fail)('AI API returned non-object JSON response');
|
|
230
|
+
}
|
|
231
|
+
return (0, ts_utils_1.succeed)(json);
|
|
232
|
+
}
|
|
104
233
|
const openAiMessage = ts_utils_1.Validators.object({
|
|
105
234
|
content: ts_utils_1.Validators.string
|
|
106
235
|
});
|
|
@@ -153,16 +282,16 @@ const geminiResponse = ts_utils_1.Validators.object({
|
|
|
153
282
|
* Works for xAI Grok, OpenAI, Groq, and Mistral.
|
|
154
283
|
* @internal
|
|
155
284
|
*/
|
|
156
|
-
async function callOpenAiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger) {
|
|
285
|
+
async function callOpenAiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, signal) {
|
|
157
286
|
const url = `${config.baseUrl}/chat/completions`;
|
|
158
|
-
const messages = buildMessages(prompt,
|
|
287
|
+
const messages = (0, chatRequestBuilders_1.buildMessages)(prompt.system, (0, chatRequestBuilders_1.buildOpenAiChatUserContent)(prompt), {
|
|
288
|
+
tail: additionalMessages
|
|
289
|
+
});
|
|
159
290
|
const body = { model: config.model, messages, temperature };
|
|
160
|
-
const headers =
|
|
161
|
-
Authorization: `Bearer ${config.apiKey}`
|
|
162
|
-
};
|
|
291
|
+
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
163
292
|
/* c8 ignore next 1 - optional logger */
|
|
164
293
|
logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI completion: model=${config.model}`);
|
|
165
|
-
const jsonResult = await fetchJson(url, headers, body, logger);
|
|
294
|
+
const jsonResult = await fetchJson(url, headers, body, logger, signal);
|
|
166
295
|
if (jsonResult.isFailure()) {
|
|
167
296
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
168
297
|
}
|
|
@@ -201,21 +330,21 @@ function extractResponsesApiText(output) {
|
|
|
201
330
|
* Used when tools are configured for an openai-format provider.
|
|
202
331
|
* @internal
|
|
203
332
|
*/
|
|
204
|
-
async function callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature = 0.7, logger) {
|
|
333
|
+
async function callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature = 0.7, logger, signal) {
|
|
205
334
|
const url = `${config.baseUrl}/responses`;
|
|
206
|
-
const input = buildMessages(prompt,
|
|
335
|
+
const input = (0, chatRequestBuilders_1.buildMessages)(prompt.system, (0, chatRequestBuilders_1.buildOpenAiResponsesUserContent)(prompt), {
|
|
336
|
+
tail: additionalMessages
|
|
337
|
+
});
|
|
207
338
|
const body = {
|
|
208
339
|
model: config.model,
|
|
209
340
|
input,
|
|
210
341
|
tools: (0, toolFormats_1.toResponsesApiTools)(tools),
|
|
211
342
|
temperature
|
|
212
343
|
};
|
|
213
|
-
const headers =
|
|
214
|
-
Authorization: `Bearer ${config.apiKey}`
|
|
215
|
-
};
|
|
344
|
+
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
216
345
|
/* c8 ignore next 1 - optional logger */
|
|
217
346
|
logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI Responses API: model=${config.model}, tools=${tools.map((t) => t.type).join(',')}`);
|
|
218
|
-
const jsonResult = await fetchJson(url, headers, body, logger);
|
|
347
|
+
const jsonResult = await fetchJson(url, headers, body, logger, signal);
|
|
219
348
|
if (jsonResult.isFailure()) {
|
|
220
349
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
221
350
|
}
|
|
@@ -260,18 +389,10 @@ function extractAnthropicText(content) {
|
|
|
260
389
|
* mixed content block responses.
|
|
261
390
|
* @internal
|
|
262
391
|
*/
|
|
263
|
-
async function callAnthropicCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools) {
|
|
392
|
+
async function callAnthropicCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal) {
|
|
264
393
|
const url = `${config.baseUrl}/messages`;
|
|
265
394
|
// Anthropic uses system as a top-level field, not in messages
|
|
266
|
-
const messages =
|
|
267
|
-
if (additionalMessages) {
|
|
268
|
-
for (const msg of additionalMessages) {
|
|
269
|
-
// Anthropic doesn't have a system role in messages
|
|
270
|
-
if (msg.role !== 'system') {
|
|
271
|
-
messages.push({ role: msg.role, content: msg.content });
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
395
|
+
const messages = (0, chatRequestBuilders_1.buildAnthropicMessages)(prompt, { tail: additionalMessages });
|
|
275
396
|
const body = {
|
|
276
397
|
model: config.model,
|
|
277
398
|
system: prompt.system,
|
|
@@ -293,7 +414,7 @@ async function callAnthropicCompletion(config, prompt, additionalMessages, tempe
|
|
|
293
414
|
'anthropic-version': '2023-06-01',
|
|
294
415
|
'anthropic-dangerous-direct-browser-access': 'true'
|
|
295
416
|
};
|
|
296
|
-
const jsonResult = await fetchJson(url, headers, body, logger);
|
|
417
|
+
const jsonResult = await fetchJson(url, headers, body, logger, signal);
|
|
297
418
|
if (jsonResult.isFailure()) {
|
|
298
419
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
299
420
|
}
|
|
@@ -328,22 +449,10 @@ async function callAnthropicCompletion(config, prompt, additionalMessages, tempe
|
|
|
328
449
|
* When tools are configured, includes Google Search grounding.
|
|
329
450
|
* @internal
|
|
330
451
|
*/
|
|
331
|
-
async function callGeminiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools) {
|
|
452
|
+
async function callGeminiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools, signal) {
|
|
332
453
|
const url = `${config.baseUrl}/models/${config.model}:generateContent`;
|
|
333
454
|
// Gemini uses 'contents' with 'parts', and 'model' role instead of 'assistant'
|
|
334
|
-
const contents =
|
|
335
|
-
{ role: 'user', parts: [{ text: prompt.user }] }
|
|
336
|
-
];
|
|
337
|
-
if (additionalMessages) {
|
|
338
|
-
for (const msg of additionalMessages) {
|
|
339
|
-
if (msg.role !== 'system') {
|
|
340
|
-
contents.push({
|
|
341
|
-
role: msg.role === 'assistant' ? 'model' : msg.role,
|
|
342
|
-
parts: [{ text: msg.content }]
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
455
|
+
const contents = (0, chatRequestBuilders_1.buildGeminiContents)(prompt, { tail: additionalMessages });
|
|
347
456
|
const body = {
|
|
348
457
|
systemInstruction: { parts: [{ text: prompt.system }] },
|
|
349
458
|
contents,
|
|
@@ -361,7 +470,7 @@ async function callGeminiCompletion(config, prompt, additionalMessages, temperat
|
|
|
361
470
|
const headers = {
|
|
362
471
|
'x-goog-api-key': config.apiKey
|
|
363
472
|
};
|
|
364
|
-
const jsonResult = await fetchJson(url, headers, body, logger);
|
|
473
|
+
const jsonResult = await fetchJson(url, headers, body, logger, signal);
|
|
365
474
|
if (jsonResult.isFailure()) {
|
|
366
475
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
367
476
|
}
|
|
@@ -397,16 +506,24 @@ async function callGeminiCompletion(config, prompt, additionalMessages, temperat
|
|
|
397
506
|
* @public
|
|
398
507
|
*/
|
|
399
508
|
async function callProviderCompletion(params) {
|
|
400
|
-
const { descriptor, apiKey, prompt, additionalMessages, temperature = 0.7, modelOverride, logger, tools } = params;
|
|
401
|
-
|
|
402
|
-
|
|
509
|
+
const { descriptor, apiKey, prompt, additionalMessages, temperature = 0.7, modelOverride, logger, tools, signal, endpoint } = params;
|
|
510
|
+
const baseUrlResult = (0, endpoint_1.resolveEffectiveBaseUrl)(descriptor, endpoint);
|
|
511
|
+
if (baseUrlResult.isFailure()) {
|
|
512
|
+
return (0, ts_utils_1.fail)(baseUrlResult.message);
|
|
513
|
+
}
|
|
514
|
+
if (prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {
|
|
515
|
+
return (0, ts_utils_1.fail)(`provider "${descriptor.id}" does not accept image input`);
|
|
403
516
|
}
|
|
404
517
|
const hasTools = tools !== undefined && tools.length > 0;
|
|
405
518
|
const modelContext = hasTools ? 'tools' : undefined;
|
|
519
|
+
const model = (0, model_1.resolveModel)(modelOverride !== null && modelOverride !== void 0 ? modelOverride : descriptor.defaultModel, modelContext);
|
|
520
|
+
if (model.length === 0) {
|
|
521
|
+
return (0, ts_utils_1.fail)(`provider "${descriptor.id}": no model resolved; pass modelOverride or set descriptor.defaultModel`);
|
|
522
|
+
}
|
|
406
523
|
const config = {
|
|
407
|
-
baseUrl:
|
|
524
|
+
baseUrl: baseUrlResult.value,
|
|
408
525
|
apiKey,
|
|
409
|
-
model
|
|
526
|
+
model
|
|
410
527
|
};
|
|
411
528
|
/* c8 ignore next 8 - optional logger diagnostic output */
|
|
412
529
|
if (logger) {
|
|
@@ -418,13 +535,13 @@ async function callProviderCompletion(params) {
|
|
|
418
535
|
switch (descriptor.apiFormat) {
|
|
419
536
|
case 'openai':
|
|
420
537
|
if (hasTools) {
|
|
421
|
-
return callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature, logger);
|
|
538
|
+
return callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature, logger, signal);
|
|
422
539
|
}
|
|
423
|
-
return callOpenAiCompletion(config, prompt, additionalMessages, temperature, logger);
|
|
540
|
+
return callOpenAiCompletion(config, prompt, additionalMessages, temperature, logger, signal);
|
|
424
541
|
case 'anthropic':
|
|
425
|
-
return callAnthropicCompletion(config, prompt, additionalMessages, temperature, logger, tools);
|
|
542
|
+
return callAnthropicCompletion(config, prompt, additionalMessages, temperature, logger, tools, signal);
|
|
426
543
|
case 'gemini':
|
|
427
|
-
return callGeminiCompletion(config, prompt, additionalMessages, temperature, logger, tools);
|
|
544
|
+
return callGeminiCompletion(config, prompt, additionalMessages, temperature, logger, tools, signal);
|
|
428
545
|
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
429
546
|
default: {
|
|
430
547
|
const _exhaustive = descriptor.apiFormat;
|
|
@@ -432,6 +549,579 @@ async function callProviderCompletion(params) {
|
|
|
432
549
|
}
|
|
433
550
|
}
|
|
434
551
|
}
|
|
552
|
+
const openAiImageItem = ts_utils_1.Validators.object({
|
|
553
|
+
b64_json: ts_utils_1.Validators.string,
|
|
554
|
+
revised_prompt: ts_utils_1.Validators.string.optional()
|
|
555
|
+
});
|
|
556
|
+
const openAiImageResponse = ts_utils_1.Validators.object({
|
|
557
|
+
data: ts_utils_1.Validators.arrayOf(openAiImageItem).withConstraint((arr) => arr.length > 0)
|
|
558
|
+
});
|
|
559
|
+
const imagenPrediction = ts_utils_1.Validators.object({
|
|
560
|
+
bytesBase64Encoded: ts_utils_1.Validators.string,
|
|
561
|
+
mimeType: ts_utils_1.Validators.string.optional()
|
|
562
|
+
});
|
|
563
|
+
const imagenResponse = ts_utils_1.Validators.object({
|
|
564
|
+
predictions: ts_utils_1.Validators.arrayOf(imagenPrediction).withConstraint((arr) => arr.length > 0)
|
|
565
|
+
});
|
|
566
|
+
const geminiImageInlineData = ts_utils_1.Validators.object({
|
|
567
|
+
mimeType: ts_utils_1.Validators.string,
|
|
568
|
+
data: ts_utils_1.Validators.string
|
|
569
|
+
});
|
|
570
|
+
const geminiImageOutPart = ts_utils_1.Validators.object({
|
|
571
|
+
text: ts_utils_1.Validators.string.optional(),
|
|
572
|
+
inlineData: geminiImageInlineData.optional()
|
|
573
|
+
});
|
|
574
|
+
const geminiImageOutContent = ts_utils_1.Validators.object({
|
|
575
|
+
parts: ts_utils_1.Validators.arrayOf(geminiImageOutPart).withConstraint((arr) => arr.length > 0)
|
|
576
|
+
});
|
|
577
|
+
const geminiImageOutCandidate = ts_utils_1.Validators.object({
|
|
578
|
+
content: geminiImageOutContent,
|
|
579
|
+
finishReason: ts_utils_1.Validators.string.optional()
|
|
580
|
+
});
|
|
581
|
+
const geminiImageOutResponse = ts_utils_1.Validators.object({
|
|
582
|
+
candidates: ts_utils_1.Validators.arrayOf(geminiImageOutCandidate).withConstraint((arr) => arr.length > 0)
|
|
583
|
+
});
|
|
584
|
+
// ---- Proxied image generation response ----
|
|
585
|
+
const proxiedGeneratedImage = ts_utils_1.Validators.object({
|
|
586
|
+
mimeType: ts_utils_1.Validators.string,
|
|
587
|
+
base64: ts_utils_1.Validators.string,
|
|
588
|
+
revisedPrompt: ts_utils_1.Validators.string.optional()
|
|
589
|
+
});
|
|
590
|
+
const proxiedImageGenerationResponse = ts_utils_1.Validators.object({
|
|
591
|
+
images: ts_utils_1.Validators.arrayOf(proxiedGeneratedImage).withConstraint((arr) => arr.length > 0)
|
|
592
|
+
});
|
|
593
|
+
const proxiedListModelsEntry = ts_utils_1.Validators.object({
|
|
594
|
+
id: ts_utils_1.Validators.string,
|
|
595
|
+
capabilities: ts_utils_1.Validators.arrayOf(ts_utils_1.Validators.enumeratedValue(['chat', 'tools', 'vision', 'image-generation'])),
|
|
596
|
+
displayName: ts_utils_1.Validators.string.optional()
|
|
597
|
+
});
|
|
598
|
+
const proxiedListModelsResponse = ts_utils_1.Validators.object({
|
|
599
|
+
models: ts_utils_1.Validators.arrayOf(proxiedListModelsEntry)
|
|
600
|
+
});
|
|
601
|
+
// ============================================================================
|
|
602
|
+
// Image generation — adapters
|
|
603
|
+
// ============================================================================
|
|
604
|
+
/**
|
|
605
|
+
* Calls the OpenAI Images API. Used for both `openai-images` and `xai-images`
|
|
606
|
+
* formats — the request shape is the same; the only difference is whether the
|
|
607
|
+
* `size` field is honored (OpenAI: yes, xAI: ignored at the provider).
|
|
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 : [];
|
|
620
|
+
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
621
|
+
const n = (_c = opts.count) !== null && _c !== void 0 ? _c : 1;
|
|
622
|
+
const fetched = refs.length > 0
|
|
623
|
+
? await callOpenAiImagesEdits(config, request, headers, n, refs, logger, signal)
|
|
624
|
+
: await callOpenAiImagesGenerations(config, request, headers, n, logger, signal);
|
|
625
|
+
return fetched.onSuccess((json) => openAiImageResponse
|
|
626
|
+
.validate(json)
|
|
627
|
+
.withErrorFormat((msg) => `OpenAI images API response: ${msg}`)
|
|
628
|
+
.onSuccess((response) => (0, ts_utils_1.succeed)({
|
|
629
|
+
images: response.data.map((item) => (Object.assign({ mimeType: defaultMimeType, base64: item.b64_json }, (item.revised_prompt !== undefined ? { revisedPrompt: item.revised_prompt } : {}))))
|
|
630
|
+
})));
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Builds and posts the JSON `/images/generations` request (no refs).
|
|
634
|
+
* @internal
|
|
635
|
+
*/
|
|
636
|
+
function callOpenAiImagesGenerations(config, request, headers, n, logger, signal) {
|
|
637
|
+
var _a;
|
|
638
|
+
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
639
|
+
const body = {
|
|
640
|
+
model: config.model,
|
|
641
|
+
prompt: request.prompt,
|
|
642
|
+
n,
|
|
643
|
+
response_format: 'b64_json'
|
|
644
|
+
};
|
|
645
|
+
if (opts.size !== undefined) {
|
|
646
|
+
body.size = opts.size;
|
|
647
|
+
}
|
|
648
|
+
if (opts.quality !== undefined) {
|
|
649
|
+
body.quality = opts.quality;
|
|
650
|
+
}
|
|
651
|
+
if (opts.seed !== undefined) {
|
|
652
|
+
body.seed = opts.seed;
|
|
653
|
+
}
|
|
654
|
+
/* c8 ignore next 1 - optional logger */
|
|
655
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`Image generation: model=${config.model}, n=${n}`);
|
|
656
|
+
return fetchJson(`${config.baseUrl}/images/generations`, headers, body, logger, signal);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Builds and posts the multipart `/images/edits` request (with refs).
|
|
660
|
+
* @internal
|
|
661
|
+
*/
|
|
662
|
+
async function callOpenAiImagesEdits(config, request, headers, n, refs, logger, signal) {
|
|
663
|
+
var _a;
|
|
664
|
+
const blobsResult = (0, ts_utils_1.mapResults)(refs.map((ref, i) => attachmentToBlob(ref).withErrorFormat((msg) => `reference image ${i}: ${msg}`)));
|
|
665
|
+
/* c8 ignore next 3 - decode failure unreachable via Node's Buffer.from (silently strips invalid input) */
|
|
666
|
+
if (blobsResult.isFailure()) {
|
|
667
|
+
return (0, ts_utils_1.fail)(blobsResult.message);
|
|
668
|
+
}
|
|
669
|
+
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
670
|
+
const form = new FormData();
|
|
671
|
+
form.append('model', config.model);
|
|
672
|
+
form.append('prompt', request.prompt);
|
|
673
|
+
form.append('n', String(n));
|
|
674
|
+
form.append('response_format', 'b64_json');
|
|
675
|
+
if (opts.size !== undefined) {
|
|
676
|
+
form.append('size', opts.size);
|
|
677
|
+
}
|
|
678
|
+
if (opts.quality !== undefined) {
|
|
679
|
+
form.append('quality', opts.quality);
|
|
680
|
+
}
|
|
681
|
+
if (opts.seed !== undefined) {
|
|
682
|
+
form.append('seed', String(opts.seed));
|
|
683
|
+
}
|
|
684
|
+
blobsResult.value.forEach((blob, i) => {
|
|
685
|
+
form.append('image[]', blob, `ref-${i}.${extensionForMimeType(refs[i].mimeType)}`);
|
|
686
|
+
});
|
|
687
|
+
/* 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}`);
|
|
689
|
+
return fetchMultipart(`${config.baseUrl}/images/edits`, headers, form, logger, signal);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Calls Gemini's chat-style `:generateContent` endpoint for image output
|
|
693
|
+
* (Gemini 2.5 Flash Image / "Nano Banana"). Accepts reference images, which
|
|
694
|
+
* are passed as `inlineData` parts alongside the text prompt.
|
|
695
|
+
*
|
|
696
|
+
* @internal
|
|
697
|
+
*/
|
|
698
|
+
async function callGeminiImageOutGeneration(config, request, logger, signal) {
|
|
699
|
+
var _a;
|
|
700
|
+
const url = `${config.baseUrl}/models/${config.model}:generateContent`;
|
|
701
|
+
const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
|
|
702
|
+
const parts = [{ text: request.prompt }];
|
|
703
|
+
for (const ref of refs) {
|
|
704
|
+
parts.push({ inlineData: { mimeType: ref.mimeType, data: ref.base64 } });
|
|
705
|
+
}
|
|
706
|
+
const body = {
|
|
707
|
+
contents: [{ role: 'user', parts }]
|
|
708
|
+
};
|
|
709
|
+
const headers = {
|
|
710
|
+
'x-goog-api-key': config.apiKey
|
|
711
|
+
};
|
|
712
|
+
/* c8 ignore next 1 - optional logger */
|
|
713
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`Gemini image-out: model=${config.model}, refs=${refs.length}`);
|
|
714
|
+
return (await fetchJson(url, headers, body, logger, signal)).onSuccess((json) => geminiImageOutResponse
|
|
715
|
+
.validate(json)
|
|
716
|
+
.withErrorFormat((msg) => `Gemini image API response: ${msg}`)
|
|
717
|
+
.onSuccess((response) => {
|
|
718
|
+
const images = [];
|
|
719
|
+
for (const candidate of response.candidates) {
|
|
720
|
+
for (const part of candidate.content.parts) {
|
|
721
|
+
if (part.inlineData) {
|
|
722
|
+
images.push({
|
|
723
|
+
mimeType: part.inlineData.mimeType,
|
|
724
|
+
base64: part.inlineData.data
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (images.length === 0) {
|
|
730
|
+
return (0, ts_utils_1.fail)('Gemini image API response: no image parts in response');
|
|
731
|
+
}
|
|
732
|
+
return (0, ts_utils_1.succeed)({ images });
|
|
733
|
+
}));
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Calls the Gemini Imagen `:predict` endpoint.
|
|
737
|
+
* @internal
|
|
738
|
+
*/
|
|
739
|
+
async function callImagenGeneration(config, request, logger, signal) {
|
|
740
|
+
var _a, _b, _c, _d;
|
|
741
|
+
const url = `${config.baseUrl}/models/${config.model}:predict`;
|
|
742
|
+
const opts = (_a = request.options) !== null && _a !== void 0 ? _a : {};
|
|
743
|
+
const parameters = {
|
|
744
|
+
sampleCount: (_b = opts.count) !== null && _b !== void 0 ? _b : 1
|
|
745
|
+
};
|
|
746
|
+
if (((_c = opts.imagen) === null || _c === void 0 ? void 0 : _c.aspectRatio) !== undefined) {
|
|
747
|
+
parameters.aspectRatio = opts.imagen.aspectRatio;
|
|
748
|
+
}
|
|
749
|
+
if (((_d = opts.imagen) === null || _d === void 0 ? void 0 : _d.negativePrompt) !== undefined) {
|
|
750
|
+
parameters.negativePrompt = opts.imagen.negativePrompt;
|
|
751
|
+
}
|
|
752
|
+
if (opts.seed !== undefined) {
|
|
753
|
+
parameters.seed = opts.seed;
|
|
754
|
+
}
|
|
755
|
+
const body = {
|
|
756
|
+
instances: [{ prompt: request.prompt }],
|
|
757
|
+
parameters
|
|
758
|
+
};
|
|
759
|
+
const headers = {
|
|
760
|
+
'x-goog-api-key': config.apiKey
|
|
761
|
+
};
|
|
762
|
+
/* c8 ignore next 1 - optional logger */
|
|
763
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`Imagen generation: model=${config.model}, n=${parameters.sampleCount}`);
|
|
764
|
+
const jsonResult = await fetchJson(url, headers, body, logger, signal);
|
|
765
|
+
if (jsonResult.isFailure()) {
|
|
766
|
+
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
767
|
+
}
|
|
768
|
+
return imagenResponse
|
|
769
|
+
.validate(jsonResult.value)
|
|
770
|
+
.withErrorFormat((msg) => `Imagen API response: ${msg}`)
|
|
771
|
+
.onSuccess((response) => {
|
|
772
|
+
const images = response.predictions.map((p) => {
|
|
773
|
+
var _a;
|
|
774
|
+
return ({
|
|
775
|
+
mimeType: (_a = p.mimeType) !== null && _a !== void 0 ? _a : 'image/png',
|
|
776
|
+
base64: p.bytesBase64Encoded
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
return (0, ts_utils_1.succeed)({ images });
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
// ============================================================================
|
|
783
|
+
// Image generation — dispatcher
|
|
784
|
+
// ============================================================================
|
|
785
|
+
/**
|
|
786
|
+
* Calls the appropriate image-generation API for a given provider.
|
|
787
|
+
*
|
|
788
|
+
* Resolves a {@link IAiImageModelCapability} from
|
|
789
|
+
* {@link IAiProviderDescriptor.imageGeneration} for the requested model and
|
|
790
|
+
* routes by its `format`:
|
|
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
|
+
*
|
|
800
|
+
* @param params - Request parameters including descriptor, API key, and prompt
|
|
801
|
+
* @returns The generated images, or a failure
|
|
802
|
+
* @public
|
|
803
|
+
*/
|
|
804
|
+
async function callProviderImageGeneration(params) {
|
|
805
|
+
var _a, _b;
|
|
806
|
+
const { descriptor, apiKey, params: request, modelOverride, logger, signal, endpoint } = params;
|
|
807
|
+
if (!(0, registry_1.supportsImageGeneration)(descriptor)) {
|
|
808
|
+
return (0, ts_utils_1.fail)(`provider "${descriptor.id}" does not support image generation`);
|
|
809
|
+
}
|
|
810
|
+
const baseUrlResult = (0, endpoint_1.resolveEffectiveBaseUrl)(descriptor, endpoint);
|
|
811
|
+
if (baseUrlResult.isFailure()) {
|
|
812
|
+
return (0, ts_utils_1.fail)(baseUrlResult.message);
|
|
813
|
+
}
|
|
814
|
+
const model = (0, model_1.resolveModel)(modelOverride !== null && modelOverride !== void 0 ? modelOverride : descriptor.defaultModel, 'image');
|
|
815
|
+
if (model.length === 0) {
|
|
816
|
+
return (0, ts_utils_1.fail)(`provider "${descriptor.id}": no image model resolved; ` +
|
|
817
|
+
`pass modelOverride or set descriptor.defaultModel ` +
|
|
818
|
+
`(a plain string, or an object with an "image" entry)`);
|
|
819
|
+
}
|
|
820
|
+
const capability = (0, registry_1.resolveImageCapability)(descriptor, model);
|
|
821
|
+
if (capability === undefined) {
|
|
822
|
+
return (0, ts_utils_1.fail)(`provider "${descriptor.id}" does not support image generation for model "${model}"`);
|
|
823
|
+
}
|
|
824
|
+
if (((_b = (_a = request.referenceImages) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 && !capability.acceptsImageReferenceInput) {
|
|
825
|
+
return (0, ts_utils_1.fail)(`model "${model}" does not support reference images`);
|
|
826
|
+
}
|
|
827
|
+
const config = {
|
|
828
|
+
baseUrl: baseUrlResult.value,
|
|
829
|
+
apiKey,
|
|
830
|
+
model
|
|
831
|
+
};
|
|
832
|
+
/* c8 ignore next 6 - optional logger diagnostic output */
|
|
833
|
+
if (logger) {
|
|
834
|
+
logger.info(`AI image generation: provider=${descriptor.id}, format=${capability.format}, ` +
|
|
835
|
+
`model=${config.model}`);
|
|
836
|
+
}
|
|
837
|
+
switch (capability.format) {
|
|
838
|
+
case 'openai-images':
|
|
839
|
+
return callOpenAiImageGeneration(config, request, 'image/png', logger, signal);
|
|
840
|
+
case 'xai-images':
|
|
841
|
+
return callOpenAiImageGeneration(config, request, 'image/jpeg', logger, signal);
|
|
842
|
+
case 'gemini-imagen':
|
|
843
|
+
return callImagenGeneration(config, request, logger, signal);
|
|
844
|
+
case 'gemini-image-out':
|
|
845
|
+
return callGeminiImageOutGeneration(config, request, logger, signal);
|
|
846
|
+
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
847
|
+
default: {
|
|
848
|
+
const _exhaustive = capability.format;
|
|
849
|
+
return (0, ts_utils_1.fail)(`unsupported image API format: ${String(_exhaustive)}`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
const openAiListEntry = ts_utils_1.Validators.object({
|
|
854
|
+
id: ts_utils_1.Validators.string
|
|
855
|
+
});
|
|
856
|
+
const openAiListResponse = ts_utils_1.Validators.object({
|
|
857
|
+
data: ts_utils_1.Validators.arrayOf(openAiListEntry)
|
|
858
|
+
});
|
|
859
|
+
const anthropicListEntry = ts_utils_1.Validators.object({
|
|
860
|
+
id: ts_utils_1.Validators.string,
|
|
861
|
+
display_name: ts_utils_1.Validators.string.optional()
|
|
862
|
+
});
|
|
863
|
+
const anthropicListResponse = ts_utils_1.Validators.object({
|
|
864
|
+
data: ts_utils_1.Validators.arrayOf(anthropicListEntry)
|
|
865
|
+
});
|
|
866
|
+
const geminiListEntry = ts_utils_1.Validators.object({
|
|
867
|
+
name: ts_utils_1.Validators.string,
|
|
868
|
+
displayName: ts_utils_1.Validators.string.optional(),
|
|
869
|
+
supportedGenerationMethods: ts_utils_1.Validators.arrayOf(ts_utils_1.Validators.string).optional()
|
|
870
|
+
});
|
|
871
|
+
const geminiListResponse = ts_utils_1.Validators.object({
|
|
872
|
+
models: ts_utils_1.Validators.arrayOf(geminiListEntry)
|
|
873
|
+
});
|
|
874
|
+
// ============================================================================
|
|
875
|
+
// List models — capability resolution
|
|
876
|
+
// ============================================================================
|
|
877
|
+
/**
|
|
878
|
+
* Translates Gemini's `supportedGenerationMethods` strings into our abstract
|
|
879
|
+
* capability vocabulary. Methods without a mapping are ignored.
|
|
880
|
+
* @internal
|
|
881
|
+
*/
|
|
882
|
+
function geminiMethodsToCapabilities(methods) {
|
|
883
|
+
const out = [];
|
|
884
|
+
for (const m of methods) {
|
|
885
|
+
if (m === 'generateContent') {
|
|
886
|
+
out.push('chat');
|
|
887
|
+
}
|
|
888
|
+
else if (m === 'predict') {
|
|
889
|
+
out.push('image-generation');
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return out;
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Strips the `models/` prefix Gemini includes on listed model names.
|
|
896
|
+
* @internal
|
|
897
|
+
*/
|
|
898
|
+
function geminiBareId(name) {
|
|
899
|
+
return name.startsWith('models/') ? name.substring('models/'.length) : name;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Applies a capability config to a model id. Walks per-provider rules then
|
|
903
|
+
* global rules; unions all matching rules' capabilities. Returns the union
|
|
904
|
+
* and the first matching `displayName` (if any).
|
|
905
|
+
* @internal
|
|
906
|
+
*/
|
|
907
|
+
function applyCapabilityConfig(config, providerId, modelId) {
|
|
908
|
+
var _a, _b, _c;
|
|
909
|
+
const caps = new Set();
|
|
910
|
+
let displayName;
|
|
911
|
+
const rulesets = [
|
|
912
|
+
(_b = (_a = config.perProvider) === null || _a === void 0 ? void 0 : _a[providerId]) !== null && _b !== void 0 ? _b : [],
|
|
913
|
+
(_c = config.global) !== null && _c !== void 0 ? _c : []
|
|
914
|
+
];
|
|
915
|
+
for (const rules of rulesets) {
|
|
916
|
+
for (const rule of rules) {
|
|
917
|
+
rule.idPattern.lastIndex = 0;
|
|
918
|
+
if (rule.idPattern.test(modelId)) {
|
|
919
|
+
for (const cap of rule.capabilities) {
|
|
920
|
+
caps.add(cap);
|
|
921
|
+
}
|
|
922
|
+
if (displayName === undefined && rule.displayName !== undefined) {
|
|
923
|
+
displayName = typeof rule.displayName === 'function' ? rule.displayName(modelId) : rule.displayName;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return { capabilities: Array.from(caps), displayName };
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Combines provider-native capability info (when supplied) and config-derived
|
|
932
|
+
* capability info into a final {@link IAiModelInfo}.
|
|
933
|
+
* @internal
|
|
934
|
+
*/
|
|
935
|
+
function buildModelInfo(providerId, id, nativeCapabilities, nativeDisplayName, config) {
|
|
936
|
+
const fromConfig = applyCapabilityConfig(config, providerId, id);
|
|
937
|
+
const all = new Set([...nativeCapabilities, ...fromConfig.capabilities]);
|
|
938
|
+
return Object.assign({ id, capabilities: all }, (nativeDisplayName !== undefined
|
|
939
|
+
? { displayName: nativeDisplayName }
|
|
940
|
+
: fromConfig.displayName !== undefined
|
|
941
|
+
? { displayName: fromConfig.displayName }
|
|
942
|
+
: {}));
|
|
943
|
+
}
|
|
944
|
+
// ============================================================================
|
|
945
|
+
// List models — adapters
|
|
946
|
+
// ============================================================================
|
|
947
|
+
/**
|
|
948
|
+
* Calls the OpenAI-style `GET /models` endpoint. Used by openai, xai-grok,
|
|
949
|
+
* groq, and mistral. Provider supplies no capability info — capabilities are
|
|
950
|
+
* derived entirely from the config.
|
|
951
|
+
* @internal
|
|
952
|
+
*/
|
|
953
|
+
async function callOpenAiListModels(config, providerId, capabilityConfig, logger, signal) {
|
|
954
|
+
const url = `${config.baseUrl}/models`;
|
|
955
|
+
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
956
|
+
/* c8 ignore next 1 - optional logger */
|
|
957
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`List models: provider=${providerId}, format=openai`);
|
|
958
|
+
const jsonResult = await fetchGetJson(url, headers, logger, signal);
|
|
959
|
+
if (jsonResult.isFailure()) {
|
|
960
|
+
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
961
|
+
}
|
|
962
|
+
return openAiListResponse
|
|
963
|
+
.validate(jsonResult.value)
|
|
964
|
+
.withErrorFormat((msg) => `OpenAI models API response: ${msg}`)
|
|
965
|
+
.onSuccess((response) => {
|
|
966
|
+
const models = response.data.map((entry) => buildModelInfo(providerId, entry.id, [], undefined, capabilityConfig));
|
|
967
|
+
return (0, ts_utils_1.succeed)(models);
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Calls the Anthropic `GET /models` endpoint. Provider supplies a
|
|
972
|
+
* `display_name` but no native capability info.
|
|
973
|
+
* @internal
|
|
974
|
+
*/
|
|
975
|
+
async function callAnthropicListModels(config, providerId, capabilityConfig, logger, signal) {
|
|
976
|
+
const url = `${config.baseUrl}/models`;
|
|
977
|
+
const headers = {
|
|
978
|
+
'x-api-key': config.apiKey,
|
|
979
|
+
'anthropic-version': '2023-06-01',
|
|
980
|
+
'anthropic-dangerous-direct-browser-access': 'true'
|
|
981
|
+
};
|
|
982
|
+
/* c8 ignore next 1 - optional logger */
|
|
983
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`List models: provider=${providerId}, format=anthropic`);
|
|
984
|
+
const jsonResult = await fetchGetJson(url, headers, logger, signal);
|
|
985
|
+
if (jsonResult.isFailure()) {
|
|
986
|
+
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
987
|
+
}
|
|
988
|
+
return anthropicListResponse
|
|
989
|
+
.validate(jsonResult.value)
|
|
990
|
+
.withErrorFormat((msg) => `Anthropic models API response: ${msg}`)
|
|
991
|
+
.onSuccess((response) => {
|
|
992
|
+
const models = response.data.map((entry) => buildModelInfo(providerId, entry.id, [], entry.display_name, capabilityConfig));
|
|
993
|
+
return (0, ts_utils_1.succeed)(models);
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Calls the Gemini `GET /models` endpoint. Provider supplies both a
|
|
998
|
+
* `displayName` and `supportedGenerationMethods` — translated to native
|
|
999
|
+
* capabilities and unioned with config-derived capabilities.
|
|
1000
|
+
* @internal
|
|
1001
|
+
*/
|
|
1002
|
+
async function callGeminiListModels(config, providerId, capabilityConfig, logger, signal) {
|
|
1003
|
+
const url = `${config.baseUrl}/models`;
|
|
1004
|
+
const headers = {
|
|
1005
|
+
'x-goog-api-key': config.apiKey
|
|
1006
|
+
};
|
|
1007
|
+
/* c8 ignore next 1 - optional logger */
|
|
1008
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`List models: provider=${providerId}, format=gemini`);
|
|
1009
|
+
const jsonResult = await fetchGetJson(url, headers, logger, signal);
|
|
1010
|
+
if (jsonResult.isFailure()) {
|
|
1011
|
+
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
1012
|
+
}
|
|
1013
|
+
return geminiListResponse
|
|
1014
|
+
.validate(jsonResult.value)
|
|
1015
|
+
.withErrorFormat((msg) => `Gemini models API response: ${msg}`)
|
|
1016
|
+
.onSuccess((response) => {
|
|
1017
|
+
const models = response.models.map((entry) => {
|
|
1018
|
+
const id = geminiBareId(entry.name);
|
|
1019
|
+
const native = entry.supportedGenerationMethods
|
|
1020
|
+
? geminiMethodsToCapabilities(entry.supportedGenerationMethods)
|
|
1021
|
+
: [];
|
|
1022
|
+
return buildModelInfo(providerId, id, native, entry.displayName, capabilityConfig);
|
|
1023
|
+
});
|
|
1024
|
+
return (0, ts_utils_1.succeed)(models);
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
// ============================================================================
|
|
1028
|
+
// List models — dispatcher
|
|
1029
|
+
// ============================================================================
|
|
1030
|
+
/**
|
|
1031
|
+
* Lists models available from a provider, with capabilities resolved from
|
|
1032
|
+
* native provider info (where supplied) and a configurable rule set.
|
|
1033
|
+
*
|
|
1034
|
+
* Routes based on `descriptor.apiFormat` — listing reuses the existing
|
|
1035
|
+
* format dispatch and does not require a separate descriptor field.
|
|
1036
|
+
*
|
|
1037
|
+
* @param params - Request parameters including descriptor, API key, and optional capability filter
|
|
1038
|
+
* @returns The resolved model list, or a failure
|
|
1039
|
+
* @public
|
|
1040
|
+
*/
|
|
1041
|
+
async function callProviderListModels(params) {
|
|
1042
|
+
const { descriptor, apiKey, capability, capabilityConfig, logger, signal, endpoint } = params;
|
|
1043
|
+
const baseUrlResult = (0, endpoint_1.resolveEffectiveBaseUrl)(descriptor, endpoint);
|
|
1044
|
+
if (baseUrlResult.isFailure()) {
|
|
1045
|
+
return (0, ts_utils_1.fail)(baseUrlResult.message);
|
|
1046
|
+
}
|
|
1047
|
+
const config = {
|
|
1048
|
+
baseUrl: baseUrlResult.value,
|
|
1049
|
+
apiKey,
|
|
1050
|
+
model: '' // unused by listing
|
|
1051
|
+
};
|
|
1052
|
+
const effectiveConfig = capabilityConfig !== null && capabilityConfig !== void 0 ? capabilityConfig : registry_1.DEFAULT_MODEL_CAPABILITY_CONFIG;
|
|
1053
|
+
let listResult;
|
|
1054
|
+
switch (descriptor.apiFormat) {
|
|
1055
|
+
case 'openai':
|
|
1056
|
+
listResult = await callOpenAiListModels(config, descriptor.id, effectiveConfig, logger, signal);
|
|
1057
|
+
break;
|
|
1058
|
+
case 'anthropic':
|
|
1059
|
+
listResult = await callAnthropicListModels(config, descriptor.id, effectiveConfig, logger, signal);
|
|
1060
|
+
break;
|
|
1061
|
+
case 'gemini':
|
|
1062
|
+
listResult = await callGeminiListModels(config, descriptor.id, effectiveConfig, logger, signal);
|
|
1063
|
+
break;
|
|
1064
|
+
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
1065
|
+
default: {
|
|
1066
|
+
const _exhaustive = descriptor.apiFormat;
|
|
1067
|
+
return (0, ts_utils_1.fail)(`unsupported API format: ${String(_exhaustive)}`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
if (listResult.isFailure()) {
|
|
1071
|
+
return listResult;
|
|
1072
|
+
}
|
|
1073
|
+
if (capability === undefined) {
|
|
1074
|
+
return listResult;
|
|
1075
|
+
}
|
|
1076
|
+
return (0, ts_utils_1.succeed)(listResult.value.filter((m) => m.capabilities.has(capability)));
|
|
1077
|
+
}
|
|
1078
|
+
// ============================================================================
|
|
1079
|
+
// Proxied list models
|
|
1080
|
+
// ============================================================================
|
|
1081
|
+
/**
|
|
1082
|
+
* Calls the model-listing endpoint on a proxy server.
|
|
1083
|
+
*
|
|
1084
|
+
* @remarks
|
|
1085
|
+
* Proxy contract:
|
|
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
|
+
*
|
|
1095
|
+
* @public
|
|
1096
|
+
*/
|
|
1097
|
+
async function callProxiedListModels(proxyUrl, params) {
|
|
1098
|
+
const { descriptor, apiKey, capability, logger, signal } = params;
|
|
1099
|
+
const body = {
|
|
1100
|
+
providerId: descriptor.id,
|
|
1101
|
+
apiKey
|
|
1102
|
+
};
|
|
1103
|
+
if (capability !== undefined) {
|
|
1104
|
+
body.capability = capability;
|
|
1105
|
+
}
|
|
1106
|
+
/* c8 ignore next 1 - optional logger */
|
|
1107
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`AI list-models proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
|
|
1108
|
+
const url = `${proxyUrl}/api/ai/list-models`;
|
|
1109
|
+
const jsonResult = await fetchJson(url, {}, body, logger, signal);
|
|
1110
|
+
if (jsonResult.isFailure()) {
|
|
1111
|
+
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
1112
|
+
}
|
|
1113
|
+
const response = jsonResult.value;
|
|
1114
|
+
if (typeof response.error === 'string') {
|
|
1115
|
+
return (0, ts_utils_1.fail)(`proxy: ${response.error}`);
|
|
1116
|
+
}
|
|
1117
|
+
return proxiedListModelsResponse
|
|
1118
|
+
.validate(response)
|
|
1119
|
+
.withErrorFormat((msg) => `proxy returned invalid response: ${msg}`)
|
|
1120
|
+
.onSuccess((parsed) => {
|
|
1121
|
+
const models = parsed.models.map((m) => (Object.assign({ id: m.id, capabilities: new Set(m.capabilities) }, (m.displayName !== undefined ? { displayName: m.displayName } : {}))));
|
|
1122
|
+
return (0, ts_utils_1.succeed)(models);
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
435
1125
|
// ============================================================================
|
|
436
1126
|
// Proxied completion (routes through a backend server)
|
|
437
1127
|
// ============================================================================
|
|
@@ -449,11 +1139,15 @@ async function callProviderCompletion(params) {
|
|
|
449
1139
|
* @public
|
|
450
1140
|
*/
|
|
451
1141
|
async function callProxiedCompletion(proxyUrl, params) {
|
|
452
|
-
const { descriptor, apiKey, prompt, additionalMessages, temperature, modelOverride, logger, tools } = params;
|
|
1142
|
+
const { descriptor, apiKey, prompt, additionalMessages, temperature, modelOverride, logger, tools, signal } = params;
|
|
1143
|
+
const promptBody = { system: prompt.system, user: prompt.user };
|
|
1144
|
+
if (prompt.attachments.length > 0) {
|
|
1145
|
+
promptBody.attachments = prompt.attachments;
|
|
1146
|
+
}
|
|
453
1147
|
const body = {
|
|
454
1148
|
providerId: descriptor.id,
|
|
455
1149
|
apiKey,
|
|
456
|
-
prompt:
|
|
1150
|
+
prompt: promptBody,
|
|
457
1151
|
temperature: temperature !== null && temperature !== void 0 ? temperature : 0.7
|
|
458
1152
|
};
|
|
459
1153
|
if (additionalMessages && additionalMessages.length > 0) {
|
|
@@ -468,7 +1162,7 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
468
1162
|
/* c8 ignore next 1 - optional logger */
|
|
469
1163
|
logger === null || logger === void 0 ? void 0 : logger.info(`AI proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
|
|
470
1164
|
const url = `${proxyUrl}/api/ai/completion`;
|
|
471
|
-
const jsonResult = await fetchJson(url, {}, body, logger);
|
|
1165
|
+
const jsonResult = await fetchJson(url, {}, body, logger, signal);
|
|
472
1166
|
if (jsonResult.isFailure()) {
|
|
473
1167
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
474
1168
|
}
|
|
@@ -485,4 +1179,54 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
485
1179
|
truncated: response.truncated === true
|
|
486
1180
|
});
|
|
487
1181
|
}
|
|
1182
|
+
// ============================================================================
|
|
1183
|
+
// Proxied image generation
|
|
1184
|
+
// ============================================================================
|
|
1185
|
+
/**
|
|
1186
|
+
* Calls the image-generation endpoint on a proxy server instead of calling
|
|
1187
|
+
* the provider API directly from the browser.
|
|
1188
|
+
*
|
|
1189
|
+
* @remarks
|
|
1190
|
+
* The proxy contract:
|
|
1191
|
+
* - Endpoint: `POST ${proxyUrl}/api/ai/image-generation`
|
|
1192
|
+
* - Request body: `{providerId, apiKey, params, modelOverride?}`
|
|
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
|
+
*
|
|
1202
|
+
* @param proxyUrl - Base URL of the proxy server (e.g. `http://localhost:3001`)
|
|
1203
|
+
* @param params - Same parameters as {@link callProviderImageGeneration}
|
|
1204
|
+
* @returns The generated images, or a failure
|
|
1205
|
+
* @public
|
|
1206
|
+
*/
|
|
1207
|
+
async function callProxiedImageGeneration(proxyUrl, params) {
|
|
1208
|
+
const { descriptor, apiKey, params: request, modelOverride, logger, signal } = params;
|
|
1209
|
+
const body = {
|
|
1210
|
+
providerId: descriptor.id,
|
|
1211
|
+
apiKey,
|
|
1212
|
+
params: request
|
|
1213
|
+
};
|
|
1214
|
+
if (modelOverride !== undefined) {
|
|
1215
|
+
body.modelOverride = modelOverride;
|
|
1216
|
+
}
|
|
1217
|
+
/* c8 ignore next 1 - optional logger */
|
|
1218
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`AI image proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
|
|
1219
|
+
const url = `${proxyUrl}/api/ai/image-generation`;
|
|
1220
|
+
const jsonResult = await fetchJson(url, {}, body, logger, signal);
|
|
1221
|
+
if (jsonResult.isFailure()) {
|
|
1222
|
+
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
1223
|
+
}
|
|
1224
|
+
const response = jsonResult.value;
|
|
1225
|
+
if (typeof response.error === 'string') {
|
|
1226
|
+
return (0, ts_utils_1.fail)(`proxy: ${response.error}`);
|
|
1227
|
+
}
|
|
1228
|
+
return proxiedImageGenerationResponse
|
|
1229
|
+
.validate(response)
|
|
1230
|
+
.withErrorFormat((msg) => `proxy returned invalid response: ${msg}`);
|
|
1231
|
+
}
|
|
488
1232
|
//# sourceMappingURL=apiClient.js.map
|