@fgv/ts-extras 5.1.0-55 → 5.1.0-57
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/CAPABILITIES.md +96 -0
- package/dist/packlets/ai-assist/cacheRequest.js +118 -0
- package/dist/packlets/ai-assist/cacheRequest.js.map +1 -0
- package/dist/packlets/ai-assist/chatRequestBuilders.js +72 -7
- package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
- package/dist/packlets/ai-assist/completionClient.js +195 -58
- package/dist/packlets/ai-assist/completionClient.js.map +1 -1
- package/dist/packlets/ai-assist/converters.js +54 -0
- package/dist/packlets/ai-assist/converters.js.map +1 -1
- package/dist/packlets/ai-assist/embeddingClient.js +16 -1
- package/dist/packlets/ai-assist/embeddingClient.js.map +1 -1
- package/dist/packlets/ai-assist/imageGenerationClient.js +17 -2
- package/dist/packlets/ai-assist/imageGenerationClient.js.map +1 -1
- package/dist/packlets/ai-assist/index.js +3 -1
- package/dist/packlets/ai-assist/index.js.map +1 -1
- package/dist/packlets/ai-assist/listModelsClient.js +19 -1
- package/dist/packlets/ai-assist/listModelsClient.js.map +1 -1
- package/dist/packlets/ai-assist/model.js.map +1 -1
- package/dist/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.js +200 -0
- package/dist/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.js.map +1 -0
- package/dist/packlets/ai-assist/promptCacheObservabilityHarness.js +137 -0
- package/dist/packlets/ai-assist/promptCacheObservabilityHarness.js.map +1 -0
- package/dist/packlets/ai-assist/promptCacheRoutingAbHarness.js +156 -0
- package/dist/packlets/ai-assist/promptCacheRoutingAbHarness.js.map +1 -0
- package/dist/packlets/ai-assist/promptCacheUsageMath.js +124 -0
- package/dist/packlets/ai-assist/promptCacheUsageMath.js.map +1 -0
- package/dist/packlets/ai-assist/registry.js +9 -11
- package/dist/packlets/ai-assist/registry.js.map +1 -1
- package/dist/packlets/ai-assist/streamUsageCapability.js +134 -0
- package/dist/packlets/ai-assist/streamUsageCapability.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +32 -9
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +81 -35
- package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/common.js +25 -1
- package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js +12 -5
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +45 -11
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +33 -22
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js +62 -4
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
- package/dist/packlets/ai-assist/streamingClient.js +20 -5
- package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
- package/dist/packlets/ai-assist/thinkingOptionsResolver.js +23 -5
- package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
- package/dist/packlets/ai-assist/toolFormats.js +80 -0
- package/dist/packlets/ai-assist/toolFormats.js.map +1 -1
- package/dist/packlets/ai-assist/toolTypes.js +21 -0
- package/dist/packlets/ai-assist/toolTypes.js.map +1 -0
- package/dist/packlets/ai-assist/usageNormalization.js +178 -0
- package/dist/packlets/ai-assist/usageNormalization.js.map +1 -0
- package/dist/packlets/ai-assist/usageTypes.js +21 -0
- package/dist/packlets/ai-assist/usageTypes.js.map +1 -0
- package/dist/packlets/mustache/index.js.map +1 -1
- package/dist/packlets/mustache/interfaces.js.map +1 -1
- package/dist/packlets/mustache/mustacheTemplate.js +91 -0
- package/dist/packlets/mustache/mustacheTemplate.js.map +1 -1
- package/dist/ts-extras.d.ts +764 -48
- package/lib/packlets/ai-assist/cacheRequest.d.ts +100 -0
- package/lib/packlets/ai-assist/cacheRequest.d.ts.map +1 -0
- package/lib/packlets/ai-assist/cacheRequest.js +123 -0
- package/lib/packlets/ai-assist/cacheRequest.js.map +1 -0
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +36 -6
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
- package/lib/packlets/ai-assist/chatRequestBuilders.js +75 -7
- package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
- package/lib/packlets/ai-assist/completionClient.d.ts +19 -0
- package/lib/packlets/ai-assist/completionClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/completionClient.js +194 -57
- package/lib/packlets/ai-assist/completionClient.js.map +1 -1
- package/lib/packlets/ai-assist/converters.d.ts +46 -0
- package/lib/packlets/ai-assist/converters.d.ts.map +1 -1
- package/lib/packlets/ai-assist/converters.js +55 -1
- package/lib/packlets/ai-assist/converters.js.map +1 -1
- package/lib/packlets/ai-assist/embeddingClient.d.ts +9 -0
- package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/embeddingClient.js +16 -1
- package/lib/packlets/ai-assist/embeddingClient.js.map +1 -1
- package/lib/packlets/ai-assist/imageGenerationClient.d.ts +9 -0
- package/lib/packlets/ai-assist/imageGenerationClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/imageGenerationClient.js +17 -2
- package/lib/packlets/ai-assist/imageGenerationClient.js.map +1 -1
- package/lib/packlets/ai-assist/index.d.ts +7 -2
- package/lib/packlets/ai-assist/index.d.ts.map +1 -1
- package/lib/packlets/ai-assist/index.js +11 -1
- package/lib/packlets/ai-assist/index.js.map +1 -1
- package/lib/packlets/ai-assist/listModelsClient.d.ts +12 -0
- package/lib/packlets/ai-assist/listModelsClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/listModelsClient.js +19 -1
- package/lib/packlets/ai-assist/listModelsClient.js.map +1 -1
- package/lib/packlets/ai-assist/model.d.ts +81 -295
- package/lib/packlets/ai-assist/model.d.ts.map +1 -1
- package/lib/packlets/ai-assist/model.js.map +1 -1
- package/lib/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.d.ts +124 -0
- package/lib/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.d.ts.map +1 -0
- package/lib/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.js +208 -0
- package/lib/packlets/ai-assist/promptCacheAnthropicBreakpointHarness.js.map +1 -0
- package/lib/packlets/ai-assist/promptCacheObservabilityHarness.d.ts +77 -0
- package/lib/packlets/ai-assist/promptCacheObservabilityHarness.d.ts.map +1 -0
- package/lib/packlets/ai-assist/promptCacheObservabilityHarness.js +143 -0
- package/lib/packlets/ai-assist/promptCacheObservabilityHarness.js.map +1 -0
- package/lib/packlets/ai-assist/promptCacheRoutingAbHarness.d.ts +84 -0
- package/lib/packlets/ai-assist/promptCacheRoutingAbHarness.d.ts.map +1 -0
- package/lib/packlets/ai-assist/promptCacheRoutingAbHarness.js +163 -0
- package/lib/packlets/ai-assist/promptCacheRoutingAbHarness.js.map +1 -0
- package/lib/packlets/ai-assist/promptCacheUsageMath.d.ts +105 -0
- package/lib/packlets/ai-assist/promptCacheUsageMath.d.ts.map +1 -0
- package/lib/packlets/ai-assist/promptCacheUsageMath.js +130 -0
- package/lib/packlets/ai-assist/promptCacheUsageMath.js.map +1 -0
- package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
- package/lib/packlets/ai-assist/registry.js +9 -11
- package/lib/packlets/ai-assist/registry.js.map +1 -1
- package/lib/packlets/ai-assist/streamUsageCapability.d.ts +129 -0
- package/lib/packlets/ai-assist/streamUsageCapability.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamUsageCapability.js +140 -0
- package/lib/packlets/ai-assist/streamUsageCapability.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +2 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +29 -6
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +58 -14
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +81 -36
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +38 -3
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.js +25 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js +12 -5
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +2 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +42 -8
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +2 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +30 -19
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +22 -0
- package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/proxy.js +62 -4
- package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.d.ts +5 -0
- package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.js +20 -5
- package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
- package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +11 -3
- package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -1
- package/lib/packlets/ai-assist/thinkingOptionsResolver.js +23 -5
- package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
- package/lib/packlets/ai-assist/toolFormats.d.ts +55 -1
- package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -1
- package/lib/packlets/ai-assist/toolFormats.js +82 -0
- package/lib/packlets/ai-assist/toolFormats.js.map +1 -1
- package/lib/packlets/ai-assist/toolTypes.d.ts +386 -0
- package/lib/packlets/ai-assist/toolTypes.d.ts.map +1 -0
- package/lib/packlets/ai-assist/toolTypes.js +22 -0
- package/lib/packlets/ai-assist/toolTypes.js.map +1 -0
- package/lib/packlets/ai-assist/usageNormalization.d.ts +58 -0
- package/lib/packlets/ai-assist/usageNormalization.d.ts.map +1 -0
- package/lib/packlets/ai-assist/usageNormalization.js +184 -0
- package/lib/packlets/ai-assist/usageNormalization.js.map +1 -0
- package/lib/packlets/ai-assist/usageTypes.d.ts +84 -0
- package/lib/packlets/ai-assist/usageTypes.d.ts.map +1 -0
- package/lib/packlets/ai-assist/usageTypes.js +22 -0
- package/lib/packlets/ai-assist/usageTypes.js.map +1 -0
- package/lib/packlets/mustache/index.d.ts +1 -1
- package/lib/packlets/mustache/index.d.ts.map +1 -1
- package/lib/packlets/mustache/index.js.map +1 -1
- package/lib/packlets/mustache/interfaces.d.ts +30 -0
- package/lib/packlets/mustache/interfaces.d.ts.map +1 -1
- package/lib/packlets/mustache/interfaces.js.map +1 -1
- package/lib/packlets/mustache/mustacheTemplate.d.ts +56 -1
- package/lib/packlets/mustache/mustacheTemplate.d.ts.map +1 -1
- package/lib/packlets/mustache/mustacheTemplate.js +91 -0
- package/lib/packlets/mustache/mustacheTemplate.js.map +1 -1
- package/package.json +16 -16
- package/perf/promptCacheAnthropicBreakpoint.js +135 -0
- package/perf/promptCacheObservability.js +107 -0
- package/perf/promptCacheRoutingAb.js +105 -0
|
@@ -21,8 +21,18 @@
|
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
22
|
exports.callProviderCompletion = callProviderCompletion;
|
|
23
23
|
exports.callProxiedCompletion = callProxiedCompletion;
|
|
24
|
+
/**
|
|
25
|
+
* Chat completion clients for AI assist. One provider dispatcher over
|
|
26
|
+
* `IAiProviderDescriptor.apiFormat`, the four adapters it routes to (OpenAI Chat
|
|
27
|
+
* Completions, OpenAI/xAI Responses, Anthropic, Google Gemini), the response
|
|
28
|
+
* validators those adapters use, and the proxied variant of the same modality.
|
|
29
|
+
*
|
|
30
|
+
* @packageDocumentation
|
|
31
|
+
*/
|
|
32
|
+
const ts_json_base_1 = require("@fgv/ts-json-base");
|
|
24
33
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
25
34
|
const model_1 = require("./model");
|
|
35
|
+
const converters_1 = require("./converters");
|
|
26
36
|
const thinkingOptionsResolver_1 = require("./thinkingOptionsResolver");
|
|
27
37
|
const chatRequestBuilders_1 = require("./chatRequestBuilders");
|
|
28
38
|
const endpoint_1 = require("./endpoint");
|
|
@@ -30,6 +40,8 @@ const http_1 = require("./http");
|
|
|
30
40
|
const toolFormats_1 = require("./toolFormats");
|
|
31
41
|
const structuredOutput_1 = require("./structuredOutput");
|
|
32
42
|
const registry_1 = require("./registry");
|
|
43
|
+
const streamUsageCapability_1 = require("./streamUsageCapability");
|
|
44
|
+
const usageNormalization_1 = require("./usageNormalization");
|
|
33
45
|
const openAiMessage = ts_utils_1.Validators.object({
|
|
34
46
|
content: ts_utils_1.Validators.string
|
|
35
47
|
});
|
|
@@ -55,15 +67,22 @@ const responsesApiResponse = ts_utils_1.Validators.object({
|
|
|
55
67
|
status: ts_utils_1.Validators.string
|
|
56
68
|
});
|
|
57
69
|
const geminiPart = ts_utils_1.Validators.object({
|
|
58
|
-
text: ts_utils_1.Validators.string
|
|
70
|
+
text: ts_utils_1.Validators.string.optional()
|
|
59
71
|
});
|
|
60
72
|
const geminiContent = ts_utils_1.Validators.object({
|
|
61
|
-
parts:
|
|
73
|
+
// No non-empty constraint: `parts: []` is a shape Gemini actually returns, and rejecting it
|
|
74
|
+
// here reports a schema violation for what is really "the model produced no output".
|
|
75
|
+
parts: ts_utils_1.Validators.arrayOf(geminiPart).optional()
|
|
62
76
|
});
|
|
63
77
|
const geminiCandidate = ts_utils_1.Validators.object({
|
|
64
|
-
content: geminiContent,
|
|
65
|
-
finishReason: ts_utils_1.Validators.string
|
|
78
|
+
content: geminiContent.optional(),
|
|
79
|
+
finishReason: ts_utils_1.Validators.string.optional(),
|
|
80
|
+
finishMessage: ts_utils_1.Validators.string.optional()
|
|
66
81
|
});
|
|
82
|
+
// Terminal reasons that mean "the model finished normally", as distinct from a refusal. Kept in
|
|
83
|
+
// step with the identical set in `imageGenerationClient.ts`; both exist so an empty response can
|
|
84
|
+
// be reported as the decline it is rather than as an empty string or a parse error.
|
|
85
|
+
const benignGeminiFinishReasons = new Set(['STOP', 'MAX_TOKENS']);
|
|
67
86
|
const geminiResponse = ts_utils_1.Validators.object({
|
|
68
87
|
candidates: ts_utils_1.Validators.arrayOf(geminiCandidate).withConstraint((arr) => arr.length > 0)
|
|
69
88
|
});
|
|
@@ -75,36 +94,52 @@ const geminiResponse = ts_utils_1.Validators.object({
|
|
|
75
94
|
* Works for xAI Grok, OpenAI, Groq, and Mistral.
|
|
76
95
|
* @internal
|
|
77
96
|
*/
|
|
78
|
-
async function callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking, maxTokens, useMaxCompletionTokensField = false, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT) {
|
|
97
|
+
async function callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking, maxTokens, useMaxCompletionTokensField = false, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT, reportsUsage = false, cache, cacheKeyHeader) {
|
|
79
98
|
var _a;
|
|
80
99
|
const url = `${config.baseUrl}/chat/completions`;
|
|
81
|
-
const
|
|
100
|
+
const systemContentResult = (0, chatRequestBuilders_1.buildOpenAiChatSystemContent)(prompt.system, cache);
|
|
101
|
+
if (systemContentResult.isFailure()) {
|
|
102
|
+
return (0, ts_utils_1.fail)(systemContentResult.message);
|
|
103
|
+
}
|
|
104
|
+
const messages = (0, chatRequestBuilders_1.buildMessages)(systemContentResult.value, (0, chatRequestBuilders_1.buildOpenAiChatUserContent)(prompt), {
|
|
82
105
|
head
|
|
83
106
|
});
|
|
84
107
|
const effort = (_a = resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.openAiEffort) !== null && _a !== void 0 ? _a : resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.xaiEffort;
|
|
85
108
|
const maxTokensField = useMaxCompletionTokensField ? 'max_completion_tokens' : 'max_tokens';
|
|
86
|
-
const body = Object.assign(Object.assign(Object.assign({ model: config.model, messages }, (temperature !== undefined ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning_effort: effort } : {})), (maxTokens !== undefined ? { [maxTokensField]: maxTokens } : {}))
|
|
109
|
+
const body = Object.assign(Object.assign(Object.assign(Object.assign({ model: config.model, messages }, (temperature !== undefined ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning_effort: effort } : {})), (maxTokens !== undefined ? { [maxTokensField]: maxTokens } : {})), ((cache === null || cache === void 0 ? void 0 : cache.cacheKey) !== undefined && cacheKeyHeader === undefined
|
|
110
|
+
? // eslint-disable-next-line @typescript-eslint/naming-convention -- wire field name
|
|
111
|
+
{ prompt_cache_key: cache.cacheKey }
|
|
112
|
+
: {}));
|
|
87
113
|
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
88
114
|
Object.assign(body, resolvedThinking.otherParams);
|
|
89
115
|
}
|
|
90
116
|
Object.assign(body, structured.wire);
|
|
91
117
|
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
118
|
+
// Sticky-routing key, when the provider carries it as a header rather than a body field.
|
|
119
|
+
// xAI's prompt cache is per-server, so an identical prefix can still miss if the request is
|
|
120
|
+
// routed to a different box; `x-grok-conv-id` pins it.
|
|
121
|
+
if ((cache === null || cache === void 0 ? void 0 : cache.cacheKey) !== undefined && cacheKeyHeader !== undefined) {
|
|
122
|
+
headers[cacheKeyHeader] = cache.cacheKey;
|
|
123
|
+
}
|
|
92
124
|
/* c8 ignore next 1 - optional logger */
|
|
93
125
|
logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI completion: model=${config.model}`);
|
|
94
126
|
const jsonResult = await (0, http_1.fetchJson)(url, headers, body, logger, signal);
|
|
95
127
|
if (jsonResult.isFailure()) {
|
|
96
128
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
97
129
|
}
|
|
130
|
+
// Only descriptors with confirmed cache-relevant usage reporting are normalized — the
|
|
131
|
+
// adapter is shared by Groq/Mistral/Ollama/openai-compat too, and an ordinary usage block
|
|
132
|
+
// from one of those carries no cache information. See supportsCacheUsageReporting.
|
|
133
|
+
const rawUsage = jsonResult.value.usage;
|
|
134
|
+
const usage = reportsUsage
|
|
135
|
+
? (0, usageNormalization_1.normalizeOpenAiChatUsage)((0, ts_json_base_1.isJsonObject)(rawUsage) ? rawUsage : undefined)
|
|
136
|
+
: undefined;
|
|
98
137
|
return openAiResponse
|
|
99
138
|
.validate(jsonResult.value)
|
|
100
139
|
.withErrorFormat((msg) => `OpenAI API response: ${msg}`)
|
|
101
140
|
.onSuccess((response) => {
|
|
102
141
|
const choice = response.choices[0];
|
|
103
|
-
return (0, ts_utils_1.succeed)({
|
|
104
|
-
content: choice.message.content,
|
|
105
|
-
truncated: choice.finish_reason === 'length',
|
|
106
|
-
structuredOutput: structured.enforcement
|
|
107
|
-
});
|
|
142
|
+
return (0, ts_utils_1.succeed)(Object.assign({ content: choice.message.content, truncated: choice.finish_reason === 'length', structuredOutput: structured.enforcement }, (usage !== undefined ? { usage } : {})));
|
|
108
143
|
});
|
|
109
144
|
}
|
|
110
145
|
// ============================================================================
|
|
@@ -131,14 +166,21 @@ function extractResponsesApiText(output) {
|
|
|
131
166
|
* Used when tools are configured for an openai-format provider.
|
|
132
167
|
* @internal
|
|
133
168
|
*/
|
|
134
|
-
async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, temperature, logger, signal, resolvedThinking, maxTokens, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT) {
|
|
169
|
+
async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, temperature, logger, signal, resolvedThinking, maxTokens, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT, reportsUsage = false, cache) {
|
|
135
170
|
var _a;
|
|
136
171
|
const url = `${config.baseUrl}/responses`;
|
|
137
|
-
const
|
|
172
|
+
const systemContentResult = (0, chatRequestBuilders_1.buildOpenAiResponsesSystemContent)(prompt.system, cache);
|
|
173
|
+
if (systemContentResult.isFailure()) {
|
|
174
|
+
return (0, ts_utils_1.fail)(systemContentResult.message);
|
|
175
|
+
}
|
|
176
|
+
const input = (0, chatRequestBuilders_1.buildMessages)(systemContentResult.value, (0, chatRequestBuilders_1.buildOpenAiResponsesUserContent)(prompt), {
|
|
138
177
|
head
|
|
139
178
|
});
|
|
140
179
|
const effort = (_a = resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.openAiEffort) !== null && _a !== void 0 ? _a : resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.xaiEffort;
|
|
141
|
-
const body = Object.assign(Object.assign(Object.assign({ model: config.model, input }, (tools.length > 0 ? { tools: (0, toolFormats_1.toResponsesApiTools)(tools) } : {})), (temperature !== undefined ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning: { effort } } : {}))
|
|
180
|
+
const body = Object.assign(Object.assign(Object.assign(Object.assign({ model: config.model, input }, (tools.length > 0 ? { tools: (0, toolFormats_1.toResponsesApiTools)(tools) } : {})), (temperature !== undefined ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning: { effort } } : {})), ((cache === null || cache === void 0 ? void 0 : cache.cacheKey) !== undefined
|
|
181
|
+
? // eslint-disable-next-line @typescript-eslint/naming-convention -- wire field name
|
|
182
|
+
{ prompt_cache_key: cache.cacheKey }
|
|
183
|
+
: {}));
|
|
142
184
|
// Shared by OpenAI and xAI — both route through the Responses API with the same field name.
|
|
143
185
|
if (maxTokens !== undefined) {
|
|
144
186
|
body.max_output_tokens = maxTokens;
|
|
@@ -154,15 +196,16 @@ async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, t
|
|
|
154
196
|
if (jsonResult.isFailure()) {
|
|
155
197
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
156
198
|
}
|
|
199
|
+
// See the identical gate in callOpenAiCompletion above — this route is shared the same way.
|
|
200
|
+
const rawResponsesUsage = jsonResult.value.usage;
|
|
201
|
+
const responsesUsage = reportsUsage
|
|
202
|
+
? (0, usageNormalization_1.normalizeOpenAiResponsesUsage)((0, ts_json_base_1.isJsonObject)(rawResponsesUsage) ? rawResponsesUsage : undefined)
|
|
203
|
+
: undefined;
|
|
157
204
|
return responsesApiResponse
|
|
158
205
|
.validate(jsonResult.value)
|
|
159
206
|
.withErrorFormat((msg) => `Responses API response: ${msg}`)
|
|
160
207
|
.onSuccess((response) => {
|
|
161
|
-
return extractResponsesApiText(response.output).onSuccess((text) => (0, ts_utils_1.succeed)({
|
|
162
|
-
content: text,
|
|
163
|
-
truncated: response.status === 'incomplete',
|
|
164
|
-
structuredOutput: structured.enforcement
|
|
165
|
-
}));
|
|
208
|
+
return extractResponsesApiText(response.output).onSuccess((text) => (0, ts_utils_1.succeed)(Object.assign({ content: text, truncated: response.status === 'incomplete', structuredOutput: structured.enforcement }, (responsesUsage !== undefined ? { usage: responsesUsage } : {}))));
|
|
166
209
|
});
|
|
167
210
|
}
|
|
168
211
|
// ============================================================================
|
|
@@ -228,10 +271,14 @@ function extractAnthropicStructuredOutput(content) {
|
|
|
228
271
|
return (0, ts_utils_1.fail)(`Anthropic API response: structured output was forced but no '${structuredOutput_1.ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME}' tool_use block was returned`);
|
|
229
272
|
}
|
|
230
273
|
/** Calls the Anthropic Messages API with optional tool support. @internal */
|
|
231
|
-
async function callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, useAdaptiveThinking = false, maxTokens, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT) {
|
|
274
|
+
async function callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, useAdaptiveThinking = false, maxTokens, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT, cache) {
|
|
232
275
|
const url = `${config.baseUrl}/messages`;
|
|
233
276
|
const messages = (0, chatRequestBuilders_1.buildAnthropicMessages)(prompt, { head });
|
|
234
|
-
const
|
|
277
|
+
const systemResult = (0, chatRequestBuilders_1.buildAnthropicSystem)(prompt.system, cache);
|
|
278
|
+
if (systemResult.isFailure()) {
|
|
279
|
+
return (0, ts_utils_1.fail)(systemResult.message);
|
|
280
|
+
}
|
|
281
|
+
const body = Object.assign({ model: config.model, system: systemResult.value, messages,
|
|
235
282
|
// Anthropic's Messages API requires max_tokens on every request — see
|
|
236
283
|
// AiAssist.DEFAULT_ANTHROPIC_MAX_TOKENS for why only this provider defaults it.
|
|
237
284
|
max_tokens: maxTokens !== null && maxTokens !== void 0 ? maxTokens : model_1.DEFAULT_ANTHROPIC_MAX_TOKENS }, (temperature !== undefined ? { temperature } : {}));
|
|
@@ -282,6 +329,8 @@ async function callAnthropicCompletion(config, prompt, head, temperature, logger
|
|
|
282
329
|
}
|
|
283
330
|
const rawContent = jsonResult.value.content;
|
|
284
331
|
const stopReason = jsonResult.value.stop_reason;
|
|
332
|
+
const rawAnthropicUsage = jsonResult.value.usage;
|
|
333
|
+
const anthropicUsage = (0, usageNormalization_1.normalizeAnthropicUsage)((0, ts_json_base_1.isJsonObject)(rawAnthropicUsage) ? rawAnthropicUsage : undefined);
|
|
285
334
|
if (!Array.isArray(rawContent)) {
|
|
286
335
|
return (0, ts_utils_1.fail)('Anthropic API response: content is not an array');
|
|
287
336
|
}
|
|
@@ -291,11 +340,7 @@ async function callAnthropicCompletion(config, prompt, head, temperature, logger
|
|
|
291
340
|
const extracted = structured.enforcement === 'tool-forced'
|
|
292
341
|
? extractAnthropicStructuredOutput(rawContent)
|
|
293
342
|
: extractAnthropicText(rawContent);
|
|
294
|
-
return extracted.onSuccess((text) => (0, ts_utils_1.succeed)({
|
|
295
|
-
content: text,
|
|
296
|
-
truncated: stopReason === 'max_tokens',
|
|
297
|
-
structuredOutput: structured.enforcement
|
|
298
|
-
}));
|
|
343
|
+
return extracted.onSuccess((text) => (0, ts_utils_1.succeed)(Object.assign({ content: text, truncated: stopReason === 'max_tokens', structuredOutput: structured.enforcement }, (anthropicUsage !== undefined ? { usage: anthropicUsage } : {}))));
|
|
299
344
|
}
|
|
300
345
|
// ============================================================================
|
|
301
346
|
// Google Gemini adapter
|
|
@@ -343,22 +388,35 @@ async function callGeminiCompletion(config, prompt, head, temperature, logger, t
|
|
|
343
388
|
if (jsonResult.isFailure()) {
|
|
344
389
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
345
390
|
}
|
|
391
|
+
const rawGeminiUsage = jsonResult.value.usageMetadata;
|
|
392
|
+
const geminiUsage = (0, usageNormalization_1.normalizeGeminiUsage)((0, ts_json_base_1.isJsonObject)(rawGeminiUsage) ? rawGeminiUsage : undefined);
|
|
346
393
|
return geminiResponse
|
|
347
394
|
.validate(jsonResult.value)
|
|
348
395
|
.withErrorFormat((msg) => `Gemini API response: ${msg}`)
|
|
349
396
|
.onSuccess((response) => {
|
|
397
|
+
var _a, _b, _c;
|
|
350
398
|
const candidate = response.candidates[0];
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
399
|
+
// ALL parts, not `parts[0]`. Gemini may split one reply across several
|
|
400
|
+
// text parts, and reading only the first silently discards the rest —
|
|
401
|
+
// yielding a truncated document that often still parses, which is the
|
|
402
|
+
// worst way to be wrong. The streaming adapter has always concatenated
|
|
403
|
+
// (`fullText += part.text`); this path did not, so the same response gave
|
|
404
|
+
// different text depending on which one you called.
|
|
405
|
+
//
|
|
406
|
+
// `part.text ?? ''` rather than `part.text`: a reasoning or tool part contributes no text,
|
|
407
|
+
// and joining `undefined` would put the literal string "undefined" into the reply.
|
|
408
|
+
const content = (_c = (_b = (_a = candidate.content) === null || _a === void 0 ? void 0 : _a.parts) === null || _b === void 0 ? void 0 : _b.map((part) => { var _a; return (_a = part.text) !== null && _a !== void 0 ? _a : ''; }).join('')) !== null && _c !== void 0 ? _c : '';
|
|
409
|
+
if (content.length === 0) {
|
|
410
|
+
// Nothing came back. If the candidate says why, say so — an empty string handed to a
|
|
411
|
+
// caller is indistinguishable from a model that legitimately replied with nothing, and
|
|
412
|
+
// sends them looking in the wrong place.
|
|
413
|
+
const reason = candidate.finishReason;
|
|
414
|
+
if (reason !== undefined && !benignGeminiFinishReasons.has(reason)) {
|
|
415
|
+
const suffix = candidate.finishMessage ? ` — ${candidate.finishMessage}` : '';
|
|
416
|
+
return (0, ts_utils_1.fail)(`Gemini completion declined: ${reason}${suffix}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return (0, ts_utils_1.succeed)(Object.assign({ content, truncated: candidate.finishReason === 'MAX_TOKENS', structuredOutput: structured.enforcement }, (geminiUsage !== undefined ? { usage: geminiUsage } : {})));
|
|
362
420
|
});
|
|
363
421
|
}
|
|
364
422
|
// ============================================================================
|
|
@@ -371,7 +429,7 @@ async function callGeminiCompletion(config, prompt, head, temperature, logger, t
|
|
|
371
429
|
* @public
|
|
372
430
|
*/
|
|
373
431
|
async function callProviderCompletion(params) {
|
|
374
|
-
const { descriptor, apiKey, system, messages, temperature, modelOverride, tier, logger, tools, signal, endpoint, thinking, maxTokens, structuredOutput } = params;
|
|
432
|
+
const { descriptor, apiKey, system, messages, temperature, modelOverride, tier, logger, tools, signal, endpoint, thinking, maxTokens, structuredOutput, cache } = params;
|
|
375
433
|
const splitResult = (0, chatRequestBuilders_1.splitChatRequest)(system, messages);
|
|
376
434
|
if (splitResult.isFailure()) {
|
|
377
435
|
return (0, ts_utils_1.fail)(splitResult.message);
|
|
@@ -448,15 +506,30 @@ async function callProviderCompletion(params) {
|
|
|
448
506
|
`tools=${toolTypes}, supported=${supported}`);
|
|
449
507
|
}
|
|
450
508
|
switch (descriptor.apiFormat) {
|
|
451
|
-
case 'openai':
|
|
509
|
+
case 'openai': {
|
|
452
510
|
// Responses-API-only models (e.g. gpt-5.5-pro) 400 on /chat/completions, so they route
|
|
453
511
|
// to the Responses path even with no tools requested — same path the tools case uses.
|
|
512
|
+
// `cache` carries two independently-supported things, and they are gated separately.
|
|
513
|
+
// `systemBreakpoints` restructures `content` and needs the server to tolerate an
|
|
514
|
+
// unrecognized field inside it (OpenAI only). `cacheKey` is an opaque routing string that
|
|
515
|
+
// makes repeated requests with a shared prefix land on the same cache-holding server —
|
|
516
|
+
// which xAI needs precisely because it does per-server byte-for-byte prefix matching.
|
|
517
|
+
// Gating them together withheld the routing key from the provider that depends on it most.
|
|
518
|
+
// Every descriptor supporting neither (Groq, Mistral, Ollama, openai-compat) still gets the
|
|
519
|
+
// exact request body it would have gotten had the caller passed no `cache` at all.
|
|
520
|
+
const routing = (0, streamUsageCapability_1.supportsPromptCacheRouting)(descriptor);
|
|
521
|
+
const breakpoints = (0, streamUsageCapability_1.supportsPromptCacheBreakpoints)(descriptor) ? cache === null || cache === void 0 ? void 0 : cache.systemBreakpoints : undefined;
|
|
522
|
+
const routedKey = routing !== undefined ? cache === null || cache === void 0 ? void 0 : cache.cacheKey : undefined;
|
|
523
|
+
const gatedCache = breakpoints !== undefined || routedKey !== undefined
|
|
524
|
+
? { systemBreakpoints: breakpoints, cacheKey: routedKey }
|
|
525
|
+
: undefined;
|
|
454
526
|
if (usesResponsesApi) {
|
|
455
|
-
return callOpenAiResponsesCompletion(config, prompt, tools, head, temperature, logger, signal, resolvedThinking, maxTokens, resolvedStructured);
|
|
527
|
+
return callOpenAiResponsesCompletion(config, prompt, tools, head, temperature, logger, signal, resolvedThinking, maxTokens, resolvedStructured, (0, streamUsageCapability_1.supportsCacheUsageReporting)(descriptor), gatedCache);
|
|
456
528
|
}
|
|
457
|
-
return callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking, maxTokens, (0, model_1.usesMaxCompletionTokensField)(descriptor), resolvedStructured);
|
|
529
|
+
return callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking, maxTokens, (0, model_1.usesMaxCompletionTokensField)(descriptor), resolvedStructured, (0, streamUsageCapability_1.supportsCacheUsageReporting)(descriptor), gatedCache, routing === null || routing === void 0 ? void 0 : routing.chatCompletionsHeader);
|
|
530
|
+
}
|
|
458
531
|
case 'anthropic':
|
|
459
|
-
return callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, (0, model_1.isAdaptiveThinkingModel)(descriptor, config.model), maxTokens, resolvedStructured);
|
|
532
|
+
return callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, (0, model_1.isAdaptiveThinkingModel)(descriptor, config.model), maxTokens, resolvedStructured, cache);
|
|
460
533
|
case 'gemini':
|
|
461
534
|
return callGeminiCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, maxTokens, resolvedStructured);
|
|
462
535
|
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
@@ -475,12 +548,25 @@ async function callProviderCompletion(params) {
|
|
|
475
548
|
* CORS, and API key forwarding. The request body serializes the unified
|
|
476
549
|
* {@link AiAssist.IChatRequest} shape (`system?` + `messages`). Enforces the same
|
|
477
550
|
* non-empty / trailing-user-turn and image-input invariants as the direct path.
|
|
551
|
+
*
|
|
552
|
+
* @remarks
|
|
553
|
+
* Two parameters are handled before the body is composed, rather than forwarded.
|
|
554
|
+
* `tier` is resolved here and sent as a concrete `modelOverride`, because both
|
|
555
|
+
* halves of the resolution live on the descriptor the caller already holds — so a
|
|
556
|
+
* proxy needs no `tier` vocabulary, where a `tier` body field it did not understand
|
|
557
|
+
* would be ignored and silently serve a frontier request from the base model.
|
|
558
|
+
* `endpoint` is **refused**: it names where the prompt must go, the proxy is what
|
|
559
|
+
* makes that call, and the field has never been sent to one — so honoring it is
|
|
560
|
+
* unverifiable and ignoring it would reach the provider's default upstream instead
|
|
561
|
+
* of the host the caller pinned. Use `callProviderCompletion`, or point the
|
|
562
|
+
* proxy itself at the intended upstream.
|
|
563
|
+
*
|
|
478
564
|
* @param proxyUrl - Base URL of the proxy server
|
|
479
565
|
* @param params - Same parameters as {@link callProviderCompletion}
|
|
480
566
|
* @public
|
|
481
567
|
*/
|
|
482
568
|
async function callProxiedCompletion(proxyUrl, params) {
|
|
483
|
-
const { descriptor, apiKey, system, messages, temperature, modelOverride, logger, tools, signal, thinking, maxTokens, structuredOutput } = params;
|
|
569
|
+
const { descriptor, apiKey, system, messages, temperature, modelOverride, logger, tools, signal, thinking, maxTokens, structuredOutput, cache, tier, endpoint } = params;
|
|
484
570
|
const splitResult = (0, chatRequestBuilders_1.splitChatRequest)(system, messages);
|
|
485
571
|
if (splitResult.isFailure()) {
|
|
486
572
|
return (0, ts_utils_1.fail)(splitResult.message);
|
|
@@ -488,6 +574,48 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
488
574
|
if (splitResult.value.prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {
|
|
489
575
|
return (0, ts_utils_1.fail)(`provider "${descriptor.id}" does not accept image input`);
|
|
490
576
|
}
|
|
577
|
+
// `endpoint` names the upstream the request must reach. Unlike `tier` it cannot be
|
|
578
|
+
// resolved on this side, because the proxy is the one making that call — it would
|
|
579
|
+
// have to be forwarded and honored. No proxy honors it: the field has never been
|
|
580
|
+
// sent, so every deployed proxy would ignore it and fall back to the provider's
|
|
581
|
+
// public API.
|
|
582
|
+
//
|
|
583
|
+
// Silently is the problem. A caller names an endpoint to pin *where the prompt
|
|
584
|
+
// goes* — a self-hosted model, a LAN deployment, a residency boundary — so the
|
|
585
|
+
// failure is not a degraded answer, it is the content going somewhere they
|
|
586
|
+
// explicitly said it must not, discoverable only by watching the proxy's traffic.
|
|
587
|
+
// That is the same shape as the structured-output silent-drop this function
|
|
588
|
+
// already refuses to allow, with a worse consequence.
|
|
589
|
+
//
|
|
590
|
+
// So refuse rather than pretend. Honoring `endpoint` over a proxy needs a wire
|
|
591
|
+
// field AND a report-back the caller can verify, the way `structuredOutput` is
|
|
592
|
+
// acknowledged below; until that protocol exists, the direct path is where this
|
|
593
|
+
// parameter works.
|
|
594
|
+
if (endpoint !== undefined) {
|
|
595
|
+
return (0, ts_utils_1.fail)(`callProxiedCompletion: endpoint is not supported on the proxied path — a proxy ` +
|
|
596
|
+
`cannot confirm it honored it, and silently reaching the provider's default ` +
|
|
597
|
+
`upstream would send the request somewhere the caller excluded. Use ` +
|
|
598
|
+
`callProviderCompletion, or route the proxy itself at the intended upstream.`);
|
|
599
|
+
}
|
|
600
|
+
// The quality tier is resolved HERE rather than forwarded, because it can be:
|
|
601
|
+
// the tier walk and the alias map both live on the descriptor, which is
|
|
602
|
+
// client-side, so this side already holds everything the resolution needs.
|
|
603
|
+
// Sending the concrete model through the existing `modelOverride` field means a
|
|
604
|
+
// proxy needs no new vocabulary and an already-deployed one honors the tier
|
|
605
|
+
// without being updated — where a `tier` body field it did not understand would
|
|
606
|
+
// be ignored, silently serving a frontier request from the base model.
|
|
607
|
+
//
|
|
608
|
+
// Only when a tier was actually asked for. With no tier, `modelOverride` passes
|
|
609
|
+
// through exactly as before, including absent — which is what lets a proxy apply
|
|
610
|
+
// its own default.
|
|
611
|
+
let effectiveModelOverride = modelOverride;
|
|
612
|
+
if (tier !== undefined) {
|
|
613
|
+
const tierResult = (0, model_1.resolveProviderModel)(descriptor, modelOverride, tier);
|
|
614
|
+
if (tierResult.isFailure()) {
|
|
615
|
+
return (0, ts_utils_1.fail)(tierResult.message);
|
|
616
|
+
}
|
|
617
|
+
effectiveModelOverride = tierResult.value;
|
|
618
|
+
}
|
|
491
619
|
const body = {
|
|
492
620
|
providerId: descriptor.id,
|
|
493
621
|
apiKey,
|
|
@@ -501,8 +629,8 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
501
629
|
if (system !== undefined) {
|
|
502
630
|
body.system = system;
|
|
503
631
|
}
|
|
504
|
-
if (
|
|
505
|
-
body.modelOverride =
|
|
632
|
+
if (effectiveModelOverride !== undefined) {
|
|
633
|
+
body.modelOverride = effectiveModelOverride;
|
|
506
634
|
}
|
|
507
635
|
if (tools && tools.length > 0) {
|
|
508
636
|
body.tools = tools;
|
|
@@ -521,12 +649,19 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
521
649
|
// `JsonSchema.fromJson(raw)` before calling `callProviderCompletion`.
|
|
522
650
|
body.structuredOutput =
|
|
523
651
|
structuredOutput.mode === 'schema'
|
|
524
|
-
? Object.assign({ mode: 'schema', schema: structuredOutput.schema.toJson() }, (structuredOutput.onUnsupported !== undefined
|
|
652
|
+
? Object.assign(Object.assign({ mode: 'schema', schema: structuredOutput.schema.toJson() }, (structuredOutput.onUnsupported !== undefined
|
|
525
653
|
? { onUnsupported: structuredOutput.onUnsupported }
|
|
654
|
+
: {})), (structuredOutput.adaptOptionalToNullable !== undefined
|
|
655
|
+
? { adaptOptionalToNullable: structuredOutput.adaptOptionalToNullable }
|
|
526
656
|
: {})) : Object.assign({ mode: 'json-object' }, (structuredOutput.onUnsupported !== undefined
|
|
527
657
|
? { onUnsupported: structuredOutput.onUnsupported }
|
|
528
658
|
: {}));
|
|
529
659
|
}
|
|
660
|
+
// `IAiCacheRequest` is plain numbers and a string — JSON-serializable as-is, unlike
|
|
661
|
+
// `structuredOutput`'s schema above, so it needs no wire projection before forwarding.
|
|
662
|
+
if (cache !== undefined) {
|
|
663
|
+
body.cache = cache;
|
|
664
|
+
}
|
|
530
665
|
/* c8 ignore next 1 - optional logger */
|
|
531
666
|
logger === null || logger === void 0 ? void 0 : logger.info(`AI proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
|
|
532
667
|
const url = `${proxyUrl}/api/ai/completion`;
|
|
@@ -546,22 +681,24 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
546
681
|
// rather than a response claiming an enforcement nobody verified — a proxy
|
|
547
682
|
// predating this feature drops the constraint silently, which is the exact
|
|
548
683
|
// failure this surface exists to remove.
|
|
684
|
+
// A proxy relaying `callProviderCompletion`'s result carries an already-normalized
|
|
685
|
+
// IAiCompletionUsage, not a provider wire shape — so this validates that shape
|
|
686
|
+
// rather than reaching for the per-provider normalizers. A proxy that reports
|
|
687
|
+
// nothing, or reports something malformed, yields `undefined`: the documented
|
|
688
|
+
// "no normalized usage exposed", never a partially-trusted object and never a
|
|
689
|
+
// fabricated zero.
|
|
690
|
+
const usage = converters_1.aiCompletionUsage.convert(response.usage).orDefault();
|
|
691
|
+
// Built once: the two returns differ only in the enforcement they report, and
|
|
692
|
+
// duplicating the shape is how a field added to one and not the other drifts.
|
|
693
|
+
const base = Object.assign({ content: response.content, truncated: response.truncated === true }, (usage !== undefined ? { usage } : {}));
|
|
549
694
|
if (structuredOutput === undefined) {
|
|
550
|
-
return (0, ts_utils_1.succeed)({
|
|
551
|
-
content: response.content,
|
|
552
|
-
truncated: response.truncated === true,
|
|
553
|
-
structuredOutput: 'none'
|
|
554
|
-
});
|
|
695
|
+
return (0, ts_utils_1.succeed)(Object.assign(Object.assign({}, base), { structuredOutput: 'none' }));
|
|
555
696
|
}
|
|
556
697
|
if (!(0, structuredOutput_1.isStructuredOutputEnforcement)(response.structuredOutput)) {
|
|
557
698
|
return (0, ts_utils_1.fail)(`proxy did not report which structured-output constraint it applied ` +
|
|
558
699
|
`(got ${JSON.stringify(response.structuredOutput)}); it may predate the feature and have ` +
|
|
559
700
|
`dropped the request silently`);
|
|
560
701
|
}
|
|
561
|
-
return (0, ts_utils_1.succeed)({
|
|
562
|
-
content: response.content,
|
|
563
|
-
truncated: response.truncated === true,
|
|
564
|
-
structuredOutput: response.structuredOutput
|
|
565
|
-
});
|
|
702
|
+
return (0, ts_utils_1.succeed)(Object.assign(Object.assign({}, base), { structuredOutput: response.structuredOutput }));
|
|
566
703
|
}
|
|
567
704
|
//# sourceMappingURL=completionClient.js.map
|