@fgv/ts-extras 5.1.0-3 → 5.1.0-31
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 +4 -2
- package/dist/index.browser.js.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/packlets/ai-assist/apiClient.js +958 -131
- package/dist/packlets/ai-assist/apiClient.js.map +1 -0
- package/dist/packlets/ai-assist/chatRequestBuilders.js +186 -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/imageOptionsResolver.js +212 -0
- package/dist/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
- package/dist/packlets/ai-assist/index.js +7 -3
- package/dist/packlets/ai-assist/index.js.map +1 -0
- package/dist/packlets/ai-assist/jsonCompletion.js +95 -0
- package/dist/packlets/ai-assist/jsonCompletion.js.map +1 -0
- package/dist/packlets/ai-assist/jsonResponse.js +149 -0
- package/dist/packlets/ai-assist/jsonResponse.js.map +1 -0
- package/dist/packlets/ai-assist/model.js +21 -4
- package/dist/packlets/ai-assist/model.js.map +1 -0
- package/dist/packlets/ai-assist/registry.js +235 -10
- package/dist/packlets/ai-assist/registry.js.map +1 -0
- package/dist/packlets/ai-assist/sseParser.js +123 -0
- package/dist/packlets/ai-assist/sseParser.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +197 -0
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/common.js +79 -0
- package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js +172 -0
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +165 -0
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +179 -0
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js +163 -0
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
- package/dist/packlets/ai-assist/streamingClient.js +116 -0
- package/dist/packlets/ai-assist/streamingClient.js.map +1 -0
- package/dist/packlets/ai-assist/thinkingOptionsResolver.js +265 -0
- package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
- package/dist/packlets/ai-assist/toolFormats.js.map +1 -0
- package/dist/packlets/conversion/converters.js +35 -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 +24 -4
- 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/hpkeProvider.js +333 -0
- package/dist/packlets/crypto-utils/hpkeProvider.js.map +1 -0
- package/dist/packlets/crypto-utils/index.browser.js +7 -0
- package/dist/packlets/crypto-utils/index.browser.js.map +1 -0
- package/dist/packlets/crypto-utils/index.js +6 -0
- package/dist/packlets/crypto-utils/index.js.map +1 -0
- package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js +71 -0
- package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
- package/dist/packlets/crypto-utils/keystore/converters.js +103 -11
- 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 +618 -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 +32 -0
- package/dist/packlets/crypto-utils/model.js.map +1 -0
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js +270 -1
- package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
- package/dist/packlets/crypto-utils/spkiHelpers.js +130 -0
- package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -0
- package/dist/packlets/csv/csvFileHelpers.js +0 -14
- package/dist/packlets/csv/csvFileHelpers.js.map +1 -0
- package/dist/packlets/csv/csvHelpers.js +14 -0
- package/dist/packlets/csv/csvHelpers.js.map +1 -0
- package/dist/packlets/csv/index.browser.js +1 -3
- 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 +42 -4
- package/dist/packlets/mustache/mustacheTemplate.js.map +1 -0
- package/dist/packlets/record-jar/index.browser.js +1 -3
- 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 +0 -18
- package/dist/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
- package/dist/packlets/record-jar/recordJarHelpers.js +18 -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 +2869 -154
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/index.browser.d.ts +4 -2
- package/lib/index.browser.d.ts.map +1 -0
- package/lib/index.browser.js +8 -3
- 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 +99 -16
- package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -0
- package/lib/packlets/ai-assist/apiClient.js +961 -130
- 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 +195 -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/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 +7 -3
- package/lib/packlets/ai-assist/index.d.ts.map +1 -0
- package/lib/packlets/ai-assist/index.js +21 -1
- package/lib/packlets/ai-assist/index.js.map +1 -0
- package/lib/packlets/ai-assist/jsonCompletion.d.ts +93 -0
- package/lib/packlets/ai-assist/jsonCompletion.d.ts.map +1 -0
- package/lib/packlets/ai-assist/jsonCompletion.js +99 -0
- package/lib/packlets/ai-assist/jsonCompletion.js.map +1 -0
- package/lib/packlets/ai-assist/jsonResponse.d.ts +91 -0
- package/lib/packlets/ai-assist/jsonResponse.d.ts.map +1 -0
- package/lib/packlets/ai-assist/jsonResponse.js +154 -0
- package/lib/packlets/ai-assist/jsonResponse.js.map +1 -0
- package/lib/packlets/ai-assist/model.d.ts +720 -7
- package/lib/packlets/ai-assist/model.d.ts.map +1 -0
- package/lib/packlets/ai-assist/model.js +22 -4
- 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 +238 -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 +128 -0
- package/lib/packlets/ai-assist/sseParser.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +19 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +200 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +83 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.js +83 -0
- package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +20 -0
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js +175 -0
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +19 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +168 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +20 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +182 -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 +166 -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 +121 -0
- package/lib/packlets/ai-assist/streamingClient.js.map +1 -0
- 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/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 +36 -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 +12 -1
- package/lib/packlets/crypto-utils/converters.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/converters.js +25 -5
- 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/hpkeProvider.d.ts +142 -0
- package/lib/packlets/crypto-utils/hpkeProvider.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/hpkeProvider.js +337 -0
- package/lib/packlets/crypto-utils/hpkeProvider.js.map +1 -0
- package/lib/packlets/crypto-utils/index.browser.d.ts +3 -0
- package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/index.browser.js +14 -1
- package/lib/packlets/crypto-utils/index.browser.js.map +1 -0
- package/lib/packlets/crypto-utils/index.d.ts +3 -0
- package/lib/packlets/crypto-utils/index.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/index.js +13 -1
- package/lib/packlets/crypto-utils/index.js.map +1 -0
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts +54 -0
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js +74 -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 +101 -9
- 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 +198 -13
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/keystore/keyStore.js +624 -124
- package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
- package/lib/packlets/crypto-utils/keystore/model.d.ts +268 -19
- 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 +338 -10
- package/lib/packlets/crypto-utils/model.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/model.js +33 -1
- package/lib/packlets/crypto-utils/model.js.map +1 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +110 -2
- package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js +269 -0
- package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
- package/lib/packlets/crypto-utils/spkiHelpers.d.ts +53 -0
- package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -0
- package/lib/packlets/crypto-utils/spkiHelpers.js +136 -0
- package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -0
- package/lib/packlets/csv/csvFileHelpers.d.ts +0 -10
- package/lib/packlets/csv/csvFileHelpers.d.ts.map +1 -0
- package/lib/packlets/csv/csvFileHelpers.js +0 -15
- package/lib/packlets/csv/csvFileHelpers.js.map +1 -0
- package/lib/packlets/csv/csvHelpers.d.ts +10 -0
- package/lib/packlets/csv/csvHelpers.d.ts.map +1 -0
- package/lib/packlets/csv/csvHelpers.js +15 -0
- package/lib/packlets/csv/csvHelpers.js.map +1 -0
- package/lib/packlets/csv/index.browser.d.ts +0 -1
- package/lib/packlets/csv/index.browser.d.ts.map +1 -0
- package/lib/packlets/csv/index.browser.js +1 -5
- 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 +1 -1
- 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 +34 -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 +2 -0
- package/lib/packlets/mustache/mustacheTemplate.d.ts.map +1 -0
- package/lib/packlets/mustache/mustacheTemplate.js +42 -4
- package/lib/packlets/mustache/mustacheTemplate.js.map +1 -0
- package/lib/packlets/record-jar/index.browser.d.ts +0 -1
- package/lib/packlets/record-jar/index.browser.d.ts.map +1 -0
- package/lib/packlets/record-jar/index.browser.js +1 -5
- 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 +0 -11
- package/lib/packlets/record-jar/recordJarFileHelpers.d.ts.map +1 -0
- package/lib/packlets/record-jar/recordJarFileHelpers.js +0 -19
- package/lib/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
- package/lib/packlets/record-jar/recordJarHelpers.d.ts +11 -0
- package/lib/packlets/record-jar/recordJarHelpers.d.ts.map +1 -0
- package/lib/packlets/record-jar/recordJarHelpers.js +19 -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 +16 -15
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/endpoint.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;AAEZ;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAU,OAAO,EAAE,MAAM,eAAe,CAAC;AAItD;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAiC,EACjC,QAAiB;IAEjB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,aAAa,UAAU,CAAC,EAAE,kCAAkC,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1D,OAAO,IAAI,CAAC,aAAa,UAAU,CAAC,EAAE,6CAA6C,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,IAAI,CAAC,aAAa,UAAU,CAAC,EAAE,gCAAgC,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC,aAAa,UAAU,CAAC,EAAE,2CAA2C,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvG,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC,aAAa,UAAU,CAAC,EAAE,yDAAyD,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7D,OAAO,IAAI,CACT,aAAa,UAAU,CAAC,EAAE,4EAA4E,CACvG,CAAC;IACJ,CAAC;IACD,uEAAuE;IACvE,wEAAwE;IACxE,sCAAsC;IACtC,OAAO,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Helper for resolving a request's effective base URL from a provider\n * descriptor and an optional caller-supplied endpoint override.\n *\n * @packageDocumentation\n */\n\nimport { fail, Result, succeed } from '@fgv/ts-utils';\n\nimport { type IAiProviderDescriptor } from './model';\n\n/**\n * Builds an OpenAI-style `Authorization: Bearer ${key}` header, or an empty\n * record when the key is empty. Self-hosted/local OpenAI-compatible servers\n * (Ollama, LM Studio, llama.cpp) often reject `Authorization: Bearer ` with\n * an empty key, so we omit the header entirely in that case.\n *\n * @internal\n */\nexport function bearerAuthHeader(apiKey: string): Record<string, string> {\n return apiKey.length > 0 ? { Authorization: `Bearer ${apiKey}` } : {};\n}\n\n/**\n * Resolves the effective base URL for a request, validating the optional\n * `endpoint` override when present. Returns the URL with any trailing slash\n * stripped so per-route suffix concatenation (e.g. `/chat/completions`)\n * produces the same shape regardless of whether the caller supplied an\n * override or the descriptor's default is used.\n *\n * @internal\n */\nexport function resolveEffectiveBaseUrl(\n descriptor: IAiProviderDescriptor,\n endpoint?: string\n): Result<string> {\n if (endpoint === undefined) {\n if (!descriptor.baseUrl) {\n return fail(`provider \"${descriptor.id}\" has no API endpoint configured`);\n }\n return succeed(descriptor.baseUrl.replace(/\\/+$/, ''));\n }\n if (typeof endpoint !== 'string' || endpoint.length === 0) {\n return fail(`provider \"${descriptor.id}\": endpoint must be a non-empty http(s) URL`);\n }\n let parsed: URL;\n try {\n parsed = new URL(endpoint);\n } catch {\n return fail(`provider \"${descriptor.id}\": endpoint is not a valid URL`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return fail(`provider \"${descriptor.id}\": endpoint must use http or https (got ${parsed.protocol})`);\n }\n if (parsed.search.length > 0 || parsed.hash.length > 0) {\n return fail(`provider \"${descriptor.id}\": endpoint must not include a query string or fragment`);\n }\n if (parsed.username.length > 0 || parsed.password.length > 0) {\n return fail(\n `provider \"${descriptor.id}\": endpoint must not include userinfo; pass credentials via apiKey instead`\n );\n }\n // Reconstruct from origin + pathname so the returned URL is normalized\n // (no userinfo, no query, no fragment) and the suffix concat in callers\n // produces a well-formed request URL.\n return succeed(`${parsed.origin}${parsed.pathname}`.replace(/\\/+$/, ''));\n}\n"]}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// Copyright (c) 2026 Erik Fortune
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
// SOFTWARE.
|
|
20
|
+
import { fail, succeed } from '@fgv/ts-utils';
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Provider lineage helpers
|
|
23
|
+
// ============================================================================
|
|
24
|
+
/** Maps AiImageApiFormat values to the corresponding IModelFamilyConfig provider discriminator. */
|
|
25
|
+
function providerLineageForFormat(format) {
|
|
26
|
+
switch (format) {
|
|
27
|
+
case 'openai-images':
|
|
28
|
+
return 'openai';
|
|
29
|
+
case 'xai-images':
|
|
30
|
+
case 'xai-images-edits':
|
|
31
|
+
return 'xai';
|
|
32
|
+
case 'gemini-imagen':
|
|
33
|
+
case 'gemini-image-out':
|
|
34
|
+
return 'google';
|
|
35
|
+
default:
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Type guards
|
|
41
|
+
// ============================================================================
|
|
42
|
+
function isDallEModelOptions(block) {
|
|
43
|
+
return block.provider === 'openai' && block.family === 'dall-e';
|
|
44
|
+
}
|
|
45
|
+
function isGptImageModelOptions(block) {
|
|
46
|
+
return block.provider === 'openai' && block.family === 'gpt-image';
|
|
47
|
+
}
|
|
48
|
+
function isGrokImagineModelOptions(block) {
|
|
49
|
+
return block.provider === 'xai' && block.family === 'grok-imagine';
|
|
50
|
+
}
|
|
51
|
+
function isImagen4ModelOptions(block) {
|
|
52
|
+
return block.provider === 'google' && block.family === 'imagen-4';
|
|
53
|
+
}
|
|
54
|
+
function isGeminiFlashImageModelOptions(block) {
|
|
55
|
+
return block.provider === 'google' && block.family === 'gemini-flash-image';
|
|
56
|
+
}
|
|
57
|
+
function isOtherModelOptions(block) {
|
|
58
|
+
return block.provider === 'other';
|
|
59
|
+
}
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Merge logic
|
|
62
|
+
// ============================================================================
|
|
63
|
+
/**
|
|
64
|
+
* Resolves the merged image options for a given model and capability.
|
|
65
|
+
*
|
|
66
|
+
* @remarks
|
|
67
|
+
* **Merge precedence (later wins):**
|
|
68
|
+
* 1. Generic top-level options (size, count, quality, seed)
|
|
69
|
+
* 2. Family-generic blocks (models field omitted, provider matches)
|
|
70
|
+
* 3. Model-specific blocks (models array includes the resolved model name)
|
|
71
|
+
* 4. Other blocks (provider: 'other', models array includes model name)
|
|
72
|
+
*
|
|
73
|
+
* Provider-mismatch blocks are silently skipped.
|
|
74
|
+
*
|
|
75
|
+
* Within each tier, declaration order — later declaration wins.
|
|
76
|
+
*
|
|
77
|
+
* @param modelId - The resolved model string
|
|
78
|
+
* @param capability - The resolved IAiImageModelCapability for this model
|
|
79
|
+
* @param options - Caller-supplied options
|
|
80
|
+
* @returns The merged wire parameters
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
export function resolveImageOptions(modelId, capability, options) {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
const opts = options !== null && options !== void 0 ? options : {};
|
|
86
|
+
const lineage = providerLineageForFormat(capability.format);
|
|
87
|
+
// Start from generic top-level
|
|
88
|
+
let resolved = Object.assign(Object.assign(Object.assign({ n: (_a = opts.count) !== null && _a !== void 0 ? _a : 1 }, (opts.size !== undefined ? { size: opts.size } : {})), (opts.quality !== undefined ? { quality: opts.quality } : {})), (opts.seed !== undefined ? { seed: opts.seed } : {}));
|
|
89
|
+
const modelBlocks = (_b = opts.models) !== null && _b !== void 0 ? _b : [];
|
|
90
|
+
// Tier 2: family-generic blocks (models field omitted, provider matches lineage)
|
|
91
|
+
for (const block of modelBlocks) {
|
|
92
|
+
if (block.provider !== lineage && block.provider !== 'other')
|
|
93
|
+
continue;
|
|
94
|
+
if (block.provider === 'other')
|
|
95
|
+
continue; // other blocks handled in tier 4
|
|
96
|
+
if (!isApplicableBlock(block, modelId))
|
|
97
|
+
continue;
|
|
98
|
+
if (!isFamilyGenericBlock(block))
|
|
99
|
+
continue;
|
|
100
|
+
resolved = applyBlock(resolved, block);
|
|
101
|
+
}
|
|
102
|
+
// Tier 3: model-specific blocks (models array includes this model)
|
|
103
|
+
for (const block of modelBlocks) {
|
|
104
|
+
if (block.provider !== lineage && block.provider !== 'other')
|
|
105
|
+
continue;
|
|
106
|
+
if (block.provider === 'other')
|
|
107
|
+
continue;
|
|
108
|
+
if (!isApplicableBlock(block, modelId))
|
|
109
|
+
continue;
|
|
110
|
+
if (isFamilyGenericBlock(block))
|
|
111
|
+
continue;
|
|
112
|
+
resolved = applyBlock(resolved, block);
|
|
113
|
+
}
|
|
114
|
+
// Tier 4: Other blocks (same precedence as model-specific)
|
|
115
|
+
for (const block of modelBlocks) {
|
|
116
|
+
if (!isOtherModelOptions(block))
|
|
117
|
+
continue;
|
|
118
|
+
if (!block.models.includes(modelId))
|
|
119
|
+
continue;
|
|
120
|
+
resolved = applyOtherBlock(resolved, block);
|
|
121
|
+
}
|
|
122
|
+
return resolved;
|
|
123
|
+
}
|
|
124
|
+
function isApplicableBlock(block, modelId) {
|
|
125
|
+
/* c8 ignore next 3 - defensive coding: other blocks are filtered before isApplicableBlock is called */
|
|
126
|
+
if (isOtherModelOptions(block)) {
|
|
127
|
+
return block.models.includes(modelId);
|
|
128
|
+
}
|
|
129
|
+
// Has models array? Must include this modelId.
|
|
130
|
+
const named = block;
|
|
131
|
+
if (named.models !== undefined && named.models.length > 0) {
|
|
132
|
+
return named.models.includes(modelId);
|
|
133
|
+
}
|
|
134
|
+
// No models array = family-generic = applies to all in this family
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
function isFamilyGenericBlock(block) {
|
|
138
|
+
/* c8 ignore next 1 - defensive coding: other blocks are filtered before isFamilyGenericBlock is called */
|
|
139
|
+
if (isOtherModelOptions(block))
|
|
140
|
+
return false;
|
|
141
|
+
const named = block;
|
|
142
|
+
return named.models === undefined || named.models.length === 0;
|
|
143
|
+
}
|
|
144
|
+
function applyBlock(resolved, block) {
|
|
145
|
+
if (isDallEModelOptions(block)) {
|
|
146
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({}, resolved), (block.config.size !== undefined ? { size: block.config.size } : {})), (block.config.quality !== undefined ? { quality: block.config.quality } : {})), (block.config.style !== undefined ? { style: block.config.style } : {}));
|
|
147
|
+
}
|
|
148
|
+
if (isGptImageModelOptions(block)) {
|
|
149
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, resolved), (block.config.size !== undefined ? { size: block.config.size } : {})), (block.config.quality !== undefined ? { quality: block.config.quality } : {})), (block.config.outputFormat !== undefined ? { outputFormat: block.config.outputFormat } : {})), (block.config.outputCompression !== undefined
|
|
150
|
+
? { outputCompression: block.config.outputCompression }
|
|
151
|
+
: {})), (block.config.background !== undefined ? { background: block.config.background } : {})), (block.config.moderation !== undefined ? { moderation: block.config.moderation } : {}));
|
|
152
|
+
}
|
|
153
|
+
if (isGrokImagineModelOptions(block)) {
|
|
154
|
+
return Object.assign(Object.assign(Object.assign({}, resolved), (block.config.aspectRatio !== undefined ? { aspectRatio: block.config.aspectRatio } : {})), (block.config.resolution !== undefined ? { resolution: block.config.resolution } : {}));
|
|
155
|
+
}
|
|
156
|
+
if (isImagen4ModelOptions(block)) {
|
|
157
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, resolved), (block.config.aspectRatio !== undefined ? { imagenAspectRatio: block.config.aspectRatio } : {})), (block.config.imageSize !== undefined ? { imageSize: block.config.imageSize } : {})), (block.config.addWatermark !== undefined ? { addWatermark: block.config.addWatermark } : {})), (block.config.enhancePrompt !== undefined ? { enhancePrompt: block.config.enhancePrompt } : {})), (block.config.outputMimeType !== undefined
|
|
158
|
+
? { imagenOutputMimeType: block.config.outputMimeType }
|
|
159
|
+
: {})), (block.config.outputCompressionQuality !== undefined
|
|
160
|
+
? { imagenOutputCompressionQuality: block.config.outputCompressionQuality }
|
|
161
|
+
: {})), (block.config.personGeneration !== undefined
|
|
162
|
+
? { personGeneration: block.config.personGeneration }
|
|
163
|
+
: {}));
|
|
164
|
+
}
|
|
165
|
+
if (isGeminiFlashImageModelOptions(block)) {
|
|
166
|
+
return Object.assign(Object.assign({}, resolved), (block.config.aspectRatio !== undefined ? { geminiAspectRatio: block.config.aspectRatio } : {}));
|
|
167
|
+
}
|
|
168
|
+
/* c8 ignore next 2 - defensive coding: exhaustive union, all family types handled above */
|
|
169
|
+
return resolved;
|
|
170
|
+
}
|
|
171
|
+
function applyOtherBlock(resolved, block) {
|
|
172
|
+
var _a;
|
|
173
|
+
return Object.assign(Object.assign({}, resolved), { otherParams: Object.assign(Object.assign({}, ((_a = resolved.otherParams) !== null && _a !== void 0 ? _a : {})), block.config) });
|
|
174
|
+
}
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// Runtime validation
|
|
177
|
+
// ============================================================================
|
|
178
|
+
/**
|
|
179
|
+
* Validates the resolved options against per-model registry constraints.
|
|
180
|
+
*
|
|
181
|
+
* @remarks
|
|
182
|
+
* Fails fast on the first violation. Error format:
|
|
183
|
+
* `model "${model}": ${field} "${value}" is not accepted; accepted values: ${JSON.stringify(accepted)}`
|
|
184
|
+
*
|
|
185
|
+
* @param modelId - The resolved model string
|
|
186
|
+
* @param capability - The resolved capability entry from the registry
|
|
187
|
+
* @param resolved - The merged options from resolveImageOptions
|
|
188
|
+
* @returns The same resolved options on success, or a failure with a contextual message
|
|
189
|
+
* @public
|
|
190
|
+
*/
|
|
191
|
+
export function validateResolvedOptions(modelId, capability, resolved) {
|
|
192
|
+
// Validate count
|
|
193
|
+
if (capability.maxCount !== undefined && resolved.n > capability.maxCount) {
|
|
194
|
+
return fail(`model "${modelId}": count ${resolved.n} exceeds maximum of ${capability.maxCount}`);
|
|
195
|
+
}
|
|
196
|
+
// Validate size
|
|
197
|
+
if (capability.acceptedSizes !== undefined && resolved.size !== undefined) {
|
|
198
|
+
if (!capability.acceptedSizes.includes(resolved.size)) {
|
|
199
|
+
return fail(`model "${modelId}": size "${resolved.size}" is not accepted; accepted values: ${JSON.stringify(capability.acceptedSizes)}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Validate quality
|
|
203
|
+
if (capability.supportsQualityParam &&
|
|
204
|
+
capability.acceptedQualities !== undefined &&
|
|
205
|
+
resolved.quality !== undefined) {
|
|
206
|
+
if (!capability.acceptedQualities.includes(resolved.quality)) {
|
|
207
|
+
return fail(`model "${modelId}": quality "${resolved.quality}" is not accepted; accepted values: ${JSON.stringify(capability.acceptedQualities)}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return succeed(resolved);
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=imageOptionsResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imageOptionsResolver.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/imageOptionsResolver.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Merge logic and runtime validation for image generation options.\n * @packageDocumentation\n */\n\nimport { type JsonObject } from '@fgv/ts-json-base';\nimport { fail, Result, succeed } from '@fgv/ts-utils';\n\nimport type {\n IAiImageModelCapability,\n IAiImageGenerationOptions,\n IModelFamilyConfig,\n INamedModelFamilyConfig,\n IDallEModelOptions,\n IGptImageModelOptions,\n IGrokImagineModelOptions,\n IImagen4ModelOptions,\n IGeminiFlashImageModelOptions,\n IOtherModelOptions\n} from './model';\n\n// ============================================================================\n// Provider lineage helpers\n// ============================================================================\n\n/** Maps AiImageApiFormat values to the corresponding IModelFamilyConfig provider discriminator. */\nfunction providerLineageForFormat(format: string): 'openai' | 'xai' | 'google' | 'other' | undefined {\n switch (format) {\n case 'openai-images':\n return 'openai';\n case 'xai-images':\n case 'xai-images-edits':\n return 'xai';\n case 'gemini-imagen':\n case 'gemini-image-out':\n return 'google';\n default:\n return undefined;\n }\n}\n\n// ============================================================================\n// Type guards\n// ============================================================================\n\nfunction isDallEModelOptions(block: IModelFamilyConfig): block is IDallEModelOptions {\n return block.provider === 'openai' && block.family === 'dall-e';\n}\n\nfunction isGptImageModelOptions(block: IModelFamilyConfig): block is IGptImageModelOptions {\n return block.provider === 'openai' && block.family === 'gpt-image';\n}\n\nfunction isGrokImagineModelOptions(block: IModelFamilyConfig): block is IGrokImagineModelOptions {\n return block.provider === 'xai' && block.family === 'grok-imagine';\n}\n\nfunction isImagen4ModelOptions(block: IModelFamilyConfig): block is IImagen4ModelOptions {\n return block.provider === 'google' && block.family === 'imagen-4';\n}\n\nfunction isGeminiFlashImageModelOptions(block: IModelFamilyConfig): block is IGeminiFlashImageModelOptions {\n return block.provider === 'google' && block.family === 'gemini-flash-image';\n}\n\nfunction isOtherModelOptions(block: IModelFamilyConfig): block is IOtherModelOptions {\n return block.provider === 'other';\n}\n\n// ============================================================================\n// Resolved wire shape\n// ============================================================================\n\n/**\n * The resolved, merged wire parameters for an image generation request.\n * Built from the layered options and ready for provider-specific encoding.\n * @public\n */\nexport interface IResolvedImageOptions {\n /** Number of images to generate. */\n readonly n: number;\n /** Image size (OpenAI-style pixel strings). */\n readonly size?: string;\n /** Quality tier. */\n readonly quality?: string;\n /** Seed for reproducibility. */\n readonly seed?: number;\n // DallE-specific\n readonly style?: string;\n // GptImage-specific\n readonly outputFormat?: string;\n readonly outputCompression?: number;\n readonly background?: string;\n readonly moderation?: string;\n // xAI-specific\n readonly aspectRatio?: string;\n readonly resolution?: string;\n // Imagen-specific\n readonly imagenAspectRatio?: string;\n readonly imageSize?: string;\n readonly addWatermark?: boolean;\n readonly enhancePrompt?: boolean;\n readonly imagenOutputMimeType?: string;\n readonly imagenOutputCompressionQuality?: number;\n readonly personGeneration?: string;\n // Gemini Flash-specific\n readonly geminiAspectRatio?: string;\n // Other-block passthroughs (merged at model-specific tier)\n readonly otherParams?: JsonObject;\n}\n\n// ============================================================================\n// Merge logic\n// ============================================================================\n\n/**\n * Resolves the merged image options for a given model and capability.\n *\n * @remarks\n * **Merge precedence (later wins):**\n * 1. Generic top-level options (size, count, quality, seed)\n * 2. Family-generic blocks (models field omitted, provider matches)\n * 3. Model-specific blocks (models array includes the resolved model name)\n * 4. Other blocks (provider: 'other', models array includes model name)\n *\n * Provider-mismatch blocks are silently skipped.\n *\n * Within each tier, declaration order — later declaration wins.\n *\n * @param modelId - The resolved model string\n * @param capability - The resolved IAiImageModelCapability for this model\n * @param options - Caller-supplied options\n * @returns The merged wire parameters\n * @public\n */\nexport function resolveImageOptions(\n modelId: string,\n capability: IAiImageModelCapability,\n options: IAiImageGenerationOptions | undefined\n): IResolvedImageOptions {\n const opts = options ?? {};\n const lineage = providerLineageForFormat(capability.format);\n\n // Start from generic top-level\n let resolved: IResolvedImageOptions = {\n n: opts.count ?? 1,\n ...(opts.size !== undefined ? { size: opts.size } : {}),\n ...(opts.quality !== undefined ? { quality: opts.quality } : {}),\n ...(opts.seed !== undefined ? { seed: opts.seed } : {})\n };\n\n const modelBlocks = opts.models ?? [];\n\n // Tier 2: family-generic blocks (models field omitted, provider matches lineage)\n for (const block of modelBlocks) {\n if (block.provider !== lineage && block.provider !== 'other') continue;\n if (block.provider === 'other') continue; // other blocks handled in tier 4\n if (!isApplicableBlock(block, modelId)) continue;\n if (!isFamilyGenericBlock(block)) continue;\n resolved = applyBlock(resolved, block);\n }\n\n // Tier 3: model-specific blocks (models array includes this model)\n for (const block of modelBlocks) {\n if (block.provider !== lineage && block.provider !== 'other') continue;\n if (block.provider === 'other') continue;\n if (!isApplicableBlock(block, modelId)) continue;\n if (isFamilyGenericBlock(block)) continue;\n resolved = applyBlock(resolved, block);\n }\n\n // Tier 4: Other blocks (same precedence as model-specific)\n for (const block of modelBlocks) {\n if (!isOtherModelOptions(block)) continue;\n if (!block.models.includes(modelId)) continue;\n resolved = applyOtherBlock(resolved, block);\n }\n\n return resolved;\n}\n\nfunction isApplicableBlock(block: IModelFamilyConfig, modelId: string): boolean {\n /* c8 ignore next 3 - defensive coding: other blocks are filtered before isApplicableBlock is called */\n if (isOtherModelOptions(block)) {\n return block.models.includes(modelId);\n }\n // Has models array? Must include this modelId.\n const named = block as INamedModelFamilyConfig;\n if (named.models !== undefined && named.models.length > 0) {\n return named.models.includes(modelId);\n }\n // No models array = family-generic = applies to all in this family\n return true;\n}\n\nfunction isFamilyGenericBlock(block: IModelFamilyConfig): boolean {\n /* c8 ignore next 1 - defensive coding: other blocks are filtered before isFamilyGenericBlock is called */\n if (isOtherModelOptions(block)) return false;\n const named = block as INamedModelFamilyConfig;\n return named.models === undefined || named.models.length === 0;\n}\n\nfunction applyBlock(resolved: IResolvedImageOptions, block: IModelFamilyConfig): IResolvedImageOptions {\n if (isDallEModelOptions(block)) {\n return {\n ...resolved,\n ...(block.config.size !== undefined ? { size: block.config.size } : {}),\n ...(block.config.quality !== undefined ? { quality: block.config.quality } : {}),\n ...(block.config.style !== undefined ? { style: block.config.style } : {})\n };\n }\n if (isGptImageModelOptions(block)) {\n return {\n ...resolved,\n ...(block.config.size !== undefined ? { size: block.config.size } : {}),\n ...(block.config.quality !== undefined ? { quality: block.config.quality } : {}),\n ...(block.config.outputFormat !== undefined ? { outputFormat: block.config.outputFormat } : {}),\n ...(block.config.outputCompression !== undefined\n ? { outputCompression: block.config.outputCompression }\n : {}),\n ...(block.config.background !== undefined ? { background: block.config.background } : {}),\n ...(block.config.moderation !== undefined ? { moderation: block.config.moderation } : {})\n };\n }\n if (isGrokImagineModelOptions(block)) {\n return {\n ...resolved,\n ...(block.config.aspectRatio !== undefined ? { aspectRatio: block.config.aspectRatio } : {}),\n ...(block.config.resolution !== undefined ? { resolution: block.config.resolution } : {})\n };\n }\n if (isImagen4ModelOptions(block)) {\n return {\n ...resolved,\n ...(block.config.aspectRatio !== undefined ? { imagenAspectRatio: block.config.aspectRatio } : {}),\n ...(block.config.imageSize !== undefined ? { imageSize: block.config.imageSize } : {}),\n ...(block.config.addWatermark !== undefined ? { addWatermark: block.config.addWatermark } : {}),\n ...(block.config.enhancePrompt !== undefined ? { enhancePrompt: block.config.enhancePrompt } : {}),\n ...(block.config.outputMimeType !== undefined\n ? { imagenOutputMimeType: block.config.outputMimeType }\n : {}),\n ...(block.config.outputCompressionQuality !== undefined\n ? { imagenOutputCompressionQuality: block.config.outputCompressionQuality }\n : {}),\n ...(block.config.personGeneration !== undefined\n ? { personGeneration: block.config.personGeneration }\n : {})\n };\n }\n if (isGeminiFlashImageModelOptions(block)) {\n return {\n ...resolved,\n ...(block.config.aspectRatio !== undefined ? { geminiAspectRatio: block.config.aspectRatio } : {})\n };\n }\n /* c8 ignore next 2 - defensive coding: exhaustive union, all family types handled above */\n return resolved;\n}\n\nfunction applyOtherBlock(resolved: IResolvedImageOptions, block: IOtherModelOptions): IResolvedImageOptions {\n return {\n ...resolved,\n otherParams: { ...(resolved.otherParams ?? {}), ...block.config }\n };\n}\n\n// ============================================================================\n// Runtime validation\n// ============================================================================\n\n/**\n * Validates the resolved options against per-model registry constraints.\n *\n * @remarks\n * Fails fast on the first violation. Error format:\n * `model \"${model}\": ${field} \"${value}\" is not accepted; accepted values: ${JSON.stringify(accepted)}`\n *\n * @param modelId - The resolved model string\n * @param capability - The resolved capability entry from the registry\n * @param resolved - The merged options from resolveImageOptions\n * @returns The same resolved options on success, or a failure with a contextual message\n * @public\n */\nexport function validateResolvedOptions(\n modelId: string,\n capability: IAiImageModelCapability,\n resolved: IResolvedImageOptions\n): Result<IResolvedImageOptions> {\n // Validate count\n if (capability.maxCount !== undefined && resolved.n > capability.maxCount) {\n return fail(`model \"${modelId}\": count ${resolved.n} exceeds maximum of ${capability.maxCount}`);\n }\n\n // Validate size\n if (capability.acceptedSizes !== undefined && resolved.size !== undefined) {\n if (!capability.acceptedSizes.includes(resolved.size)) {\n return fail(\n `model \"${modelId}\": size \"${resolved.size}\" is not accepted; accepted values: ${JSON.stringify(\n capability.acceptedSizes\n )}`\n );\n }\n }\n\n // Validate quality\n if (\n capability.supportsQualityParam &&\n capability.acceptedQualities !== undefined &&\n resolved.quality !== undefined\n ) {\n if (!capability.acceptedQualities.includes(resolved.quality)) {\n return fail(\n `model \"${modelId}\": quality \"${resolved.quality}\" is not accepted; accepted values: ${JSON.stringify(\n capability.acceptedQualities\n )}`\n );\n }\n }\n\n return succeed(resolved);\n}\n"]}
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
* AI assist packlet - provider registry, prompt class, settings, and API client.
|
|
3
3
|
* @packageDocumentation
|
|
4
4
|
*/
|
|
5
|
-
export { AiPrompt, DEFAULT_AI_ASSIST, allModelSpecKeys, MODEL_SPEC_BASE_KEY, resolveModel } from './model';
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
5
|
+
export { AiPrompt, DEFAULT_AI_ASSIST, allModelSpecKeys, MODEL_SPEC_BASE_KEY, resolveModel, toDataUrl } from './model';
|
|
6
|
+
export { resolveImageOptions, validateResolvedOptions } from './imageOptionsResolver';
|
|
7
|
+
export { allProviderIds, getProviderDescriptors, getProviderDescriptor, resolveImageCapability, supportsImageGeneration, DEFAULT_MODEL_CAPABILITY_CONFIG } from './registry';
|
|
8
|
+
export { callProviderCompletion, callProxiedCompletion, callProviderImageGeneration, callProxiedImageGeneration, callProviderListModels, callProxiedListModels } from './apiClient';
|
|
9
|
+
export { callProviderCompletionStream, callProxiedCompletionStream } from './streamingClient';
|
|
8
10
|
export { aiProviderId, aiServerToolType, aiWebSearchToolConfig, aiServerToolConfig, aiToolEnablement, aiAssistProviderConfig, aiAssistSettings, modelSpecKey, modelSpec } from './converters';
|
|
9
11
|
export { resolveEffectiveTools } from './toolFormats';
|
|
12
|
+
export { extractJsonText, fencedStringifiedJson } from './jsonResponse';
|
|
13
|
+
export { generateJsonCompletion, SMART_JSON_PROMPT_HINT } from './jsonCompletion';
|
|
10
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,QAAQ,EAeR,iBAAiB,EA2CjB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,SAAS,EAiBV,MAAM,SAAS,CAAC;AAEjB,OAAO,EAEL,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,+BAA+B,EAChC,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,EAC1B,sBAAsB,EACtB,qBAAqB,EAItB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAE5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EACL,eAAe,EACf,qBAAqB,EAItB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EAIvB,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * AI assist packlet - provider registry, prompt class, settings, and API client.\n * @packageDocumentation\n */\n\nexport {\n AiPrompt,\n type AiModelCapability,\n type AiProviderId,\n type AiServerToolType,\n type AiServerToolConfig,\n type IAiWebSearchToolConfig,\n type IAiToolEnablement,\n type IAiCompletionResponse,\n type IChatMessage,\n type AiApiFormat,\n type AiImageApiFormat,\n type IAiImageModelCapability,\n type IAiProviderDescriptor,\n type IAiAssistProviderConfig,\n type IAiAssistSettings,\n DEFAULT_AI_ASSIST,\n type IAiAssistKeyStore,\n type IAiImageAttachment,\n type IAiImageData,\n type AiImageSize,\n type AiImageQuality,\n type DallE2Size,\n type DallE3Size,\n type GptImageSize,\n type DallE3Quality,\n type GptImageQuality,\n type DallEModelNames,\n type GptImageModelNames,\n type GrokImagineModelNames,\n type Imagen4ModelNames,\n type GeminiFlashImageModelNames,\n type IDallEImageGenerationConfig,\n type IGptImageGenerationConfig,\n type IGrokImagineImageGenerationConfig,\n type IImagen4GenerationConfig,\n type IGeminiFlashImageGenerationConfig,\n type IDallEModelOptions,\n type IGptImageModelOptions,\n type IGrokImagineModelOptions,\n type IImagen4ModelOptions,\n type IGeminiFlashImageModelOptions,\n type IOtherModelOptions,\n type IModelFamilyConfig,\n type IAiImageGenerationOptions,\n type IAiImageGenerationParams,\n type IAiGeneratedImage,\n type IAiImageGenerationResponse,\n type IAiModelCapabilityRule,\n type IAiModelCapabilityConfig,\n type IAiModelInfo,\n type IAiStreamEvent,\n type IAiStreamTextDelta,\n type IAiStreamToolEvent,\n type IAiStreamDone,\n type IAiStreamError,\n type ModelSpec,\n type ModelSpecKey,\n type IModelSpecMap,\n allModelSpecKeys,\n MODEL_SPEC_BASE_KEY,\n resolveModel,\n toDataUrl,\n type AiThinkingMode,\n type IThinkingConfig,\n type IThinkingProviderConfig,\n type IAnthropicThinkingOptions,\n type IOpenAiThinkingOptions,\n type IGeminiThinkingOptions,\n type IXAiThinkingOptions,\n type IOtherThinkingOptions,\n type IAnthropicThinkingConfig,\n type IOpenAiThinkingConfig,\n type IGeminiThinkingConfig,\n type IXAiThinkingConfig,\n type AnthropicThinkingModelNames,\n type OpenAiThinkingModelNames,\n type GeminiThinkingModelNames,\n type XAiThinkingModelNames\n} from './model';\n\nexport {\n type IResolvedImageOptions,\n resolveImageOptions,\n validateResolvedOptions\n} from './imageOptionsResolver';\n\nexport {\n allProviderIds,\n getProviderDescriptors,\n getProviderDescriptor,\n resolveImageCapability,\n supportsImageGeneration,\n DEFAULT_MODEL_CAPABILITY_CONFIG\n} from './registry';\n\nexport {\n callProviderCompletion,\n callProxiedCompletion,\n callProviderImageGeneration,\n callProxiedImageGeneration,\n callProviderListModels,\n callProxiedListModels,\n type IProviderCompletionParams,\n type IProviderImageGenerationParams,\n type IProviderListModelsParams\n} from './apiClient';\n\nexport {\n callProviderCompletionStream,\n callProxiedCompletionStream,\n type IProviderCompletionStreamParams\n} from './streamingClient';\n\nexport {\n aiProviderId,\n aiServerToolType,\n aiWebSearchToolConfig,\n aiServerToolConfig,\n aiToolEnablement,\n aiAssistProviderConfig,\n aiAssistSettings,\n modelSpecKey,\n modelSpec\n} from './converters';\n\nexport { resolveEffectiveTools } from './toolFormats';\n\nexport {\n extractJsonText,\n fencedStringifiedJson,\n type IFencedStringifiedJsonExtractorOptions,\n type IFencedStringifiedJsonOptions,\n type JsonTextExtractor\n} from './jsonResponse';\n\nexport {\n generateJsonCompletion,\n SMART_JSON_PROMPT_HINT,\n type IGenerateJsonCompletionParams,\n type IGenerateJsonCompletionResult,\n type JsonPromptHint\n} from './jsonCompletion';\n"]}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Copyright (c) 2026 Erik Fortune
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
// SOFTWARE.
|
|
20
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
21
|
+
var t = {};
|
|
22
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
23
|
+
t[p] = s[p];
|
|
24
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
25
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
26
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
27
|
+
t[p[i]] = s[p[i]];
|
|
28
|
+
}
|
|
29
|
+
return t;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* `generateJsonCompletion<T>` — request a JSON-shaped completion from any
|
|
33
|
+
* provider supported by AiAssist and validate it against a caller-supplied
|
|
34
|
+
* `Converter<T>` or `Validator<T>`. Wraps fence/preamble tolerance + parsing +
|
|
35
|
+
* validation into a single call so consumers don't reinvent the pipeline.
|
|
36
|
+
*
|
|
37
|
+
* @packageDocumentation
|
|
38
|
+
*/
|
|
39
|
+
import { fail, succeed } from '@fgv/ts-utils';
|
|
40
|
+
import { callProviderCompletion } from './apiClient';
|
|
41
|
+
import { fencedStringifiedJson } from './jsonResponse';
|
|
42
|
+
import { AiPrompt } from './model';
|
|
43
|
+
/**
|
|
44
|
+
* Default system-prompt suffix appended when {@link AiAssist.IGenerateJsonCompletionParams.promptHint}
|
|
45
|
+
* is `'smart'` (the default). Designed to discourage code fences and prose in
|
|
46
|
+
* the model's response while still tolerating them via the read-side extractor.
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
export const SMART_JSON_PROMPT_HINT = 'Respond with raw JSON only — no Markdown code fences, no explanatory prose, ' +
|
|
50
|
+
'no preamble or trailing commentary. The response must parse with JSON.parse.';
|
|
51
|
+
function applyPromptHint(prompt, hint) {
|
|
52
|
+
if (hint === 'none') {
|
|
53
|
+
return prompt;
|
|
54
|
+
}
|
|
55
|
+
const suffix = hint === 'smart' ? SMART_JSON_PROMPT_HINT : hint;
|
|
56
|
+
const system = prompt.system.length > 0 ? `${prompt.system}\n\n${suffix}` : suffix;
|
|
57
|
+
return new AiPrompt(prompt.user, system, prompt.attachments);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Calls {@link AiAssist.callProviderCompletion}, then runs the response text
|
|
61
|
+
* through a tolerant JSON converter (default:
|
|
62
|
+
* {@link AiAssist.fencedStringifiedJson}) and the caller's
|
|
63
|
+
* `converter`/`validator`. Returns the validated value plus the raw text and
|
|
64
|
+
* underlying completion response for diagnostics.
|
|
65
|
+
*
|
|
66
|
+
* @remarks
|
|
67
|
+
* The default smart prompt hint asks the model to emit raw JSON. The read-side
|
|
68
|
+
* extractor still tolerates fences and prose, so models that ignore the hint
|
|
69
|
+
* are still handled.
|
|
70
|
+
*
|
|
71
|
+
* Either `converter` or `jsonConverter` must be provided; passing both lets
|
|
72
|
+
* `jsonConverter` win.
|
|
73
|
+
*
|
|
74
|
+
* @param params - Provider parameters plus JSON validation options.
|
|
75
|
+
* @returns The validated value, the raw text, and the underlying response.
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
export async function generateJsonCompletion(params) {
|
|
79
|
+
const { converter, jsonConverter, promptHint = 'smart', prompt } = params, rest = __rest(params, ["converter", "jsonConverter", "promptHint", "prompt"]);
|
|
80
|
+
if (jsonConverter === undefined && converter === undefined) {
|
|
81
|
+
return fail('generateJsonCompletion: either converter or jsonConverter must be provided.');
|
|
82
|
+
}
|
|
83
|
+
const pipeline = jsonConverter !== null && jsonConverter !== void 0 ? jsonConverter : fencedStringifiedJson({ inner: converter });
|
|
84
|
+
const augmentedPrompt = applyPromptHint(prompt, promptHint);
|
|
85
|
+
const response = await callProviderCompletion(Object.assign(Object.assign({}, rest), { prompt: augmentedPrompt }));
|
|
86
|
+
if (response.isFailure()) {
|
|
87
|
+
return fail(response.message);
|
|
88
|
+
}
|
|
89
|
+
const completion = response.value;
|
|
90
|
+
return pipeline
|
|
91
|
+
.convert(completion.content)
|
|
92
|
+
.withErrorFormat((msg) => `generateJsonCompletion: ${msg}`)
|
|
93
|
+
.onSuccess((value) => succeed({ value, raw: completion.content, response: completion }));
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=jsonCompletion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonCompletion.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/jsonCompletion.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;;;;;;;;;;;;AAEZ;;;;;;;GAOG;AAEH,OAAO,EAAkB,IAAI,EAAU,OAAO,EAAkB,MAAM,eAAe,CAAC;AAEtF,OAAO,EAAE,sBAAsB,EAAkC,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAA8B,MAAM,SAAS,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GACjC,8EAA8E;IAC9E,8EAA8E,CAAC;AA6DjF,SAAS,eAAe,CAAC,MAAgB,EAAE,IAAoB;IAC7D,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACnF,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAwC;IAExC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,GAAG,OAAO,EAAE,MAAM,KAAc,MAAM,EAAf,IAAI,UAAK,MAAM,EAA5E,sDAAmE,CAAS,CAAC;IAEnF,IAAI,aAAa,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC3D,OAAO,IAAI,CAAC,6EAA6E,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,QAAQ,GAAiB,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,qBAAqB,CAAI,EAAE,KAAK,EAAE,SAAU,EAAE,CAAC,CAAC;IAChG,MAAM,eAAe,GAAG,eAAe,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAE5D,MAAM,QAAQ,GAAG,MAAM,sBAAsB,iCAAM,IAAI,KAAE,MAAM,EAAE,eAAe,IAAG,CAAC;IACpF,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IACD,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC;IAElC,OAAO,QAAQ;SACZ,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;SAC3B,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,2BAA2B,GAAG,EAAE,CAAC;SAC1D,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;AAC7F,CAAC","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * `generateJsonCompletion<T>` — request a JSON-shaped completion from any\n * provider supported by AiAssist and validate it against a caller-supplied\n * `Converter<T>` or `Validator<T>`. Wraps fence/preamble tolerance + parsing +\n * validation into a single call so consumers don't reinvent the pipeline.\n *\n * @packageDocumentation\n */\n\nimport { type Converter, fail, Result, succeed, type Validator } from '@fgv/ts-utils';\n\nimport { callProviderCompletion, type IProviderCompletionParams } from './apiClient';\nimport { fencedStringifiedJson } from './jsonResponse';\nimport { AiPrompt, type IAiCompletionResponse } from './model';\n\n/**\n * Default system-prompt suffix appended when {@link AiAssist.IGenerateJsonCompletionParams.promptHint}\n * is `'smart'` (the default). Designed to discourage code fences and prose in\n * the model's response while still tolerating them via the read-side extractor.\n * @public\n */\nexport const SMART_JSON_PROMPT_HINT: string =\n 'Respond with raw JSON only — no Markdown code fences, no explanatory prose, ' +\n 'no preamble or trailing commentary. The response must parse with JSON.parse.';\n\n/**\n * Controls the optional system-prompt augmentation applied by\n * {@link AiAssist.generateJsonCompletion}.\n *\n * - `'smart'` (default): append {@link AiAssist.SMART_JSON_PROMPT_HINT}.\n * - `'none'`: do not modify the prompt.\n * - A string: append the supplied text verbatim.\n *\n * @remarks\n * The `string & {}` branch is the standard TypeScript trick that prevents\n * the literal members from being widened away — callers still get\n * autocomplete for `'smart'` and `'none'` while accepting any string.\n *\n * @public\n */\nexport type JsonPromptHint = 'smart' | 'none' | (string & {});\n\n/**\n * Parameters for {@link AiAssist.generateJsonCompletion}. Extends\n * {@link AiAssist.IProviderCompletionParams} with JSON-validation knobs.\n * @public\n */\nexport interface IGenerateJsonCompletionParams<T> extends IProviderCompletionParams {\n /**\n * Caller-supplied `Converter<T>` or `Validator<T>` applied to the parsed\n * JSON value. Wrapped internally in {@link AiAssist.fencedStringifiedJson}\n * unless {@link AiAssist.IGenerateJsonCompletionParams.jsonConverter} is\n * provided.\n */\n readonly converter?: Converter<T> | Validator<T>;\n\n /**\n * Full string-to-`T` pipeline override. When supplied, takes precedence over\n * {@link AiAssist.IGenerateJsonCompletionParams.converter} and lets the\n * caller plug in a custom extractor or skip the default fence tolerance\n * entirely.\n */\n readonly jsonConverter?: Converter<T>;\n\n /**\n * Controls the optional system-prompt augmentation. Defaults to `'smart'`.\n * Pass `'none'` to disable, or a string to append custom guidance.\n */\n readonly promptHint?: JsonPromptHint;\n}\n\n/**\n * Successful result of {@link AiAssist.generateJsonCompletion}.\n * @public\n */\nexport interface IGenerateJsonCompletionResult<T> {\n /** The validated JSON value. */\n readonly value: T;\n /** The raw response text returned by the provider. */\n readonly raw: string;\n /** The full underlying completion response. */\n readonly response: IAiCompletionResponse;\n}\n\nfunction applyPromptHint(prompt: AiPrompt, hint: JsonPromptHint): AiPrompt {\n if (hint === 'none') {\n return prompt;\n }\n const suffix = hint === 'smart' ? SMART_JSON_PROMPT_HINT : hint;\n const system = prompt.system.length > 0 ? `${prompt.system}\\n\\n${suffix}` : suffix;\n return new AiPrompt(prompt.user, system, prompt.attachments);\n}\n\n/**\n * Calls {@link AiAssist.callProviderCompletion}, then runs the response text\n * through a tolerant JSON converter (default:\n * {@link AiAssist.fencedStringifiedJson}) and the caller's\n * `converter`/`validator`. Returns the validated value plus the raw text and\n * underlying completion response for diagnostics.\n *\n * @remarks\n * The default smart prompt hint asks the model to emit raw JSON. The read-side\n * extractor still tolerates fences and prose, so models that ignore the hint\n * are still handled.\n *\n * Either `converter` or `jsonConverter` must be provided; passing both lets\n * `jsonConverter` win.\n *\n * @param params - Provider parameters plus JSON validation options.\n * @returns The validated value, the raw text, and the underlying response.\n * @public\n */\nexport async function generateJsonCompletion<T>(\n params: IGenerateJsonCompletionParams<T>\n): Promise<Result<IGenerateJsonCompletionResult<T>>> {\n const { converter, jsonConverter, promptHint = 'smart', prompt, ...rest } = params;\n\n if (jsonConverter === undefined && converter === undefined) {\n return fail('generateJsonCompletion: either converter or jsonConverter must be provided.');\n }\n\n const pipeline: Converter<T> = jsonConverter ?? fencedStringifiedJson<T>({ inner: converter! });\n const augmentedPrompt = applyPromptHint(prompt, promptHint);\n\n const response = await callProviderCompletion({ ...rest, prompt: augmentedPrompt });\n if (response.isFailure()) {\n return fail(response.message);\n }\n const completion = response.value;\n\n return pipeline\n .convert(completion.content)\n .withErrorFormat((msg) => `generateJsonCompletion: ${msg}`)\n .onSuccess((value) => succeed({ value, raw: completion.content, response: completion }));\n}\n"]}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Copyright (c) 2026 Erik Fortune
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
// SOFTWARE.
|
|
20
|
+
/**
|
|
21
|
+
* JSON-tolerant extraction and converters for LLM responses.
|
|
22
|
+
*
|
|
23
|
+
* Models commonly wrap JSON output in Markdown code fences, add a
|
|
24
|
+
* "Sure, here's the JSON:" preamble, or trail off with prose after the
|
|
25
|
+
* closing brace. These helpers normalize that quirk on the read side so every
|
|
26
|
+
* AiAssist consumer can reach a validated `T` from raw model text without
|
|
27
|
+
* reimplementing the same fence-stripping logic.
|
|
28
|
+
*
|
|
29
|
+
* Scope: strip wrappers (fences, prose, BOM, whitespace). Out of scope: repair
|
|
30
|
+
* malformed JSON (missing commas, unquoted keys, smart quotes, etc.).
|
|
31
|
+
*
|
|
32
|
+
* @packageDocumentation
|
|
33
|
+
*/
|
|
34
|
+
import { Conversion, fail, succeed } from '@fgv/ts-utils';
|
|
35
|
+
import { Converters as JsonBaseConverters } from '@fgv/ts-json-base';
|
|
36
|
+
const FENCED_BLOCK = /```[A-Za-z0-9_-]*\s*\r?\n([\s\S]*?)\r?\n?```/;
|
|
37
|
+
const BOM = /^\uFEFF/;
|
|
38
|
+
// Full RFC 8259 grammar so the extractor only succeeds when the entire
|
|
39
|
+
// candidate parses as a JSON primitive (instead of just starting like one).
|
|
40
|
+
const JSON_NUMBER = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
41
|
+
// eslint-disable-next-line no-control-regex
|
|
42
|
+
const JSON_STRING = /^"(?:[^"\\\u0000-\u001F]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"$/;
|
|
43
|
+
const JSON_KEYWORD = /^(?:true|false|null)$/;
|
|
44
|
+
function stripBom(text) {
|
|
45
|
+
return text.replace(BOM, '');
|
|
46
|
+
}
|
|
47
|
+
function findBalancedJsonSubstring(text) {
|
|
48
|
+
// Walk the text once tracking string state. The first '{' or '[' that is
|
|
49
|
+
// *outside* a quoted string is the candidate start; from there, count
|
|
50
|
+
// matching close characters while ignoring delimiters that appear inside
|
|
51
|
+
// strings.
|
|
52
|
+
let inString = false;
|
|
53
|
+
let escape = false;
|
|
54
|
+
let start = -1;
|
|
55
|
+
let open = '';
|
|
56
|
+
let close = '';
|
|
57
|
+
let depth = 0;
|
|
58
|
+
for (let i = 0; i < text.length; i++) {
|
|
59
|
+
const ch = text.charAt(i);
|
|
60
|
+
if (inString) {
|
|
61
|
+
if (escape) {
|
|
62
|
+
escape = false;
|
|
63
|
+
}
|
|
64
|
+
else if (ch === '\\') {
|
|
65
|
+
escape = true;
|
|
66
|
+
}
|
|
67
|
+
else if (ch === '"') {
|
|
68
|
+
inString = false;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (ch === '"') {
|
|
73
|
+
inString = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (start < 0) {
|
|
77
|
+
if (ch === '{' || ch === '[') {
|
|
78
|
+
start = i;
|
|
79
|
+
open = ch;
|
|
80
|
+
close = ch === '{' ? '}' : ']';
|
|
81
|
+
depth = 1;
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (ch === open) {
|
|
86
|
+
depth++;
|
|
87
|
+
}
|
|
88
|
+
else if (ch === close) {
|
|
89
|
+
depth--;
|
|
90
|
+
if (depth === 0) {
|
|
91
|
+
return text.slice(start, i + 1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Default {@link AiAssist.JsonTextExtractor | extractor} for LLM responses. Tolerates:
|
|
99
|
+
*
|
|
100
|
+
* - Leading/trailing whitespace and a leading byte-order mark.
|
|
101
|
+
* - Markdown code fences (with or without a language tag).
|
|
102
|
+
* - Conversational preamble before the first `{` or `[`.
|
|
103
|
+
* - Trailing prose after the matched closing `}` or `]`.
|
|
104
|
+
*
|
|
105
|
+
* Out of scope: repairing malformed JSON, handling smart quotes, etc.
|
|
106
|
+
*
|
|
107
|
+
* @param text - Raw model output.
|
|
108
|
+
* @returns A `Result<string>` containing the JSON-shaped substring, or a
|
|
109
|
+
* `Failure` if no JSON-shaped substring was found.
|
|
110
|
+
* @public
|
|
111
|
+
*/
|
|
112
|
+
export const extractJsonText = (text) => {
|
|
113
|
+
if (typeof text !== 'string') {
|
|
114
|
+
return fail('extractJsonText: input must be a string.');
|
|
115
|
+
}
|
|
116
|
+
const stripped = stripBom(text).trim();
|
|
117
|
+
if (stripped.length === 0) {
|
|
118
|
+
return fail('extractJsonText: input is empty.');
|
|
119
|
+
}
|
|
120
|
+
const fenced = FENCED_BLOCK.exec(stripped);
|
|
121
|
+
const candidate = fenced ? fenced[1].trim() : stripped;
|
|
122
|
+
if (candidate.length === 0) {
|
|
123
|
+
return fail('extractJsonText: no JSON content found.');
|
|
124
|
+
}
|
|
125
|
+
// Whole-candidate primitive check runs before the brace scan so that a
|
|
126
|
+
// valid JSON string containing braces (e.g. `"text with { }"`) is returned
|
|
127
|
+
// intact instead of being mangled into the first balanced `{ }` match.
|
|
128
|
+
if (JSON_KEYWORD.test(candidate) || JSON_NUMBER.test(candidate) || JSON_STRING.test(candidate)) {
|
|
129
|
+
return succeed(candidate);
|
|
130
|
+
}
|
|
131
|
+
const balanced = findBalancedJsonSubstring(candidate);
|
|
132
|
+
if (balanced !== undefined) {
|
|
133
|
+
return succeed(balanced);
|
|
134
|
+
}
|
|
135
|
+
return fail('extractJsonText: no JSON-shaped substring found.');
|
|
136
|
+
};
|
|
137
|
+
export function fencedStringifiedJson(options) {
|
|
138
|
+
var _a;
|
|
139
|
+
const extractor = (_a = options === null || options === void 0 ? void 0 : options.extractor) !== null && _a !== void 0 ? _a : extractJsonText;
|
|
140
|
+
const inner = options === null || options === void 0 ? void 0 : options.inner;
|
|
141
|
+
const parser = inner !== undefined ? JsonBaseConverters.stringifiedJson(inner) : JsonBaseConverters.stringifiedJson();
|
|
142
|
+
return new Conversion.BaseConverter((from) => {
|
|
143
|
+
if (typeof from !== 'string') {
|
|
144
|
+
return fail('fencedStringifiedJson: input must be a string.');
|
|
145
|
+
}
|
|
146
|
+
return extractor(from).onSuccess((extracted) => parser.convert(extracted));
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=jsonResponse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonResponse.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/jsonResponse.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * JSON-tolerant extraction and converters for LLM responses.\n *\n * Models commonly wrap JSON output in Markdown code fences, add a\n * \"Sure, here's the JSON:\" preamble, or trail off with prose after the\n * closing brace. These helpers normalize that quirk on the read side so every\n * AiAssist consumer can reach a validated `T` from raw model text without\n * reimplementing the same fence-stripping logic.\n *\n * Scope: strip wrappers (fences, prose, BOM, whitespace). Out of scope: repair\n * malformed JSON (missing commas, unquoted keys, smart quotes, etc.).\n *\n * @packageDocumentation\n */\n\nimport { Conversion, type Converter, fail, Result, succeed, type Validator } from '@fgv/ts-utils';\nimport { Converters as JsonBaseConverters, type JsonValue } from '@fgv/ts-json-base';\n\n/**\n * A function that pulls a JSON-shaped substring out of arbitrary model text.\n * Implementations strip whatever wrappers the model added (fences, preamble,\n * trailing prose) and return the JSON-shaped substring ready for `JSON.parse`.\n * @public\n */\nexport type JsonTextExtractor = (text: string) => Result<string>;\n\nconst FENCED_BLOCK: RegExp = /```[A-Za-z0-9_-]*\\s*\\r?\\n([\\s\\S]*?)\\r?\\n?```/;\nconst BOM: RegExp = /^\\uFEFF/;\n// Full RFC 8259 grammar so the extractor only succeeds when the entire\n// candidate parses as a JSON primitive (instead of just starting like one).\nconst JSON_NUMBER: RegExp = /^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$/;\n// eslint-disable-next-line no-control-regex\nconst JSON_STRING: RegExp = /^\"(?:[^\"\\\\\\u0000-\\u001F]|\\\\(?:[\"\\\\/bfnrt]|u[0-9a-fA-F]{4}))*\"$/;\nconst JSON_KEYWORD: RegExp = /^(?:true|false|null)$/;\n\nfunction stripBom(text: string): string {\n return text.replace(BOM, '');\n}\n\nfunction findBalancedJsonSubstring(text: string): string | undefined {\n // Walk the text once tracking string state. The first '{' or '[' that is\n // *outside* a quoted string is the candidate start; from there, count\n // matching close characters while ignoring delimiters that appear inside\n // strings.\n let inString = false;\n let escape = false;\n let start = -1;\n let open = '';\n let close = '';\n let depth = 0;\n for (let i = 0; i < text.length; i++) {\n const ch = text.charAt(i);\n if (inString) {\n if (escape) {\n escape = false;\n } else if (ch === '\\\\') {\n escape = true;\n } else if (ch === '\"') {\n inString = false;\n }\n continue;\n }\n if (ch === '\"') {\n inString = true;\n continue;\n }\n if (start < 0) {\n if (ch === '{' || ch === '[') {\n start = i;\n open = ch;\n close = ch === '{' ? '}' : ']';\n depth = 1;\n }\n continue;\n }\n if (ch === open) {\n depth++;\n } else if (ch === close) {\n depth--;\n if (depth === 0) {\n return text.slice(start, i + 1);\n }\n }\n }\n return undefined;\n}\n\n/**\n * Default {@link AiAssist.JsonTextExtractor | extractor} for LLM responses. Tolerates:\n *\n * - Leading/trailing whitespace and a leading byte-order mark.\n * - Markdown code fences (with or without a language tag).\n * - Conversational preamble before the first `{` or `[`.\n * - Trailing prose after the matched closing `}` or `]`.\n *\n * Out of scope: repairing malformed JSON, handling smart quotes, etc.\n *\n * @param text - Raw model output.\n * @returns A `Result<string>` containing the JSON-shaped substring, or a\n * `Failure` if no JSON-shaped substring was found.\n * @public\n */\nexport const extractJsonText: JsonTextExtractor = (text: string): Result<string> => {\n if (typeof text !== 'string') {\n return fail('extractJsonText: input must be a string.');\n }\n const stripped = stripBom(text).trim();\n if (stripped.length === 0) {\n return fail('extractJsonText: input is empty.');\n }\n\n const fenced = FENCED_BLOCK.exec(stripped);\n const candidate = fenced ? fenced[1].trim() : stripped;\n\n if (candidate.length === 0) {\n return fail('extractJsonText: no JSON content found.');\n }\n\n // Whole-candidate primitive check runs before the brace scan so that a\n // valid JSON string containing braces (e.g. `\"text with { }\"`) is returned\n // intact instead of being mangled into the first balanced `{ }` match.\n if (JSON_KEYWORD.test(candidate) || JSON_NUMBER.test(candidate) || JSON_STRING.test(candidate)) {\n return succeed(candidate);\n }\n\n const balanced = findBalancedJsonSubstring(candidate);\n if (balanced !== undefined) {\n return succeed(balanced);\n }\n\n return fail('extractJsonText: no JSON-shaped substring found.');\n};\n\n/**\n * Options shared by every {@link AiAssist.fencedStringifiedJson} call.\n * @public\n */\nexport interface IFencedStringifiedJsonExtractorOptions {\n /**\n * Optional pre-parse extractor. Defaults to {@link AiAssist.extractJsonText}.\n * Provide a custom extractor to handle response shapes the default does not\n * understand.\n */\n readonly extractor?: JsonTextExtractor;\n}\n\n/**\n * Options for the validating overload of {@link AiAssist.fencedStringifiedJson}.\n * `inner` is required so the typed `Converter<T>` return value can never lie\n * about the runtime shape.\n * @public\n */\nexport interface IFencedStringifiedJsonOptions<T> extends IFencedStringifiedJsonExtractorOptions {\n /** Inner converter or validator applied to the parsed JSON value. */\n readonly inner: Converter<T> | Validator<T>;\n}\n\n/**\n * Creates a `Converter` that accepts raw LLM response text, runs it through a\n * tolerant extractor (default: {@link AiAssist.extractJsonText}), parses the\n * extracted substring as JSON, and applies an optional inner converter or\n * validator.\n *\n * @example\n * ```ts\n * const converter = fencedStringifiedJson({ inner: myShapeConverter });\n * const result = converter.convert(llmText); // Result<MyShape>\n * ```\n *\n * @param options - Optional extractor; omit to keep the default. Without an\n * `inner` step, the converter resolves to the parsed `JsonValue`.\n * @returns A `Converter<JsonValue>`.\n * @public\n */\nexport function fencedStringifiedJson(options?: IFencedStringifiedJsonExtractorOptions): Converter<JsonValue>;\n/**\n * Creates a `Converter` that accepts raw LLM response text, runs it through a\n * tolerant extractor (default: {@link AiAssist.extractJsonText}), parses the\n * extracted substring as JSON, and applies the supplied inner converter or\n * validator.\n *\n * @param options - Required `inner` converter/validator and optional extractor.\n * @returns A `Converter<T>`.\n * @public\n */\nexport function fencedStringifiedJson<T>(options: IFencedStringifiedJsonOptions<T>): Converter<T>;\nexport function fencedStringifiedJson<T>(\n options?: IFencedStringifiedJsonExtractorOptions | IFencedStringifiedJsonOptions<T>\n): Converter<T | JsonValue> {\n const extractor: JsonTextExtractor = options?.extractor ?? extractJsonText;\n const inner = (options as IFencedStringifiedJsonOptions<T> | undefined)?.inner;\n const parser: Converter<T | JsonValue> =\n inner !== undefined ? JsonBaseConverters.stringifiedJson<T>(inner) : JsonBaseConverters.stringifiedJson();\n\n return new Conversion.BaseConverter<T | JsonValue>((from: unknown): Result<T | JsonValue> => {\n if (typeof from !== 'string') {\n return fail('fencedStringifiedJson: input must be a string.');\n }\n return extractor(from).onSuccess((extracted) => parser.convert(extracted));\n });\n}\n"]}
|