@fgv/ts-extras 5.1.0-55 → 5.1.0-56
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 +95 -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 +70 -5
- package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
- package/dist/packlets/ai-assist/completionClient.js +122 -45
- package/dist/packlets/ai-assist/completionClient.js.map +1 -1
- package/dist/packlets/ai-assist/imageGenerationClient.js +1 -1
- 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/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 +124 -0
- package/dist/packlets/ai-assist/streamUsageCapability.js.map +1 -0
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +24 -5
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +64 -33
- 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 +34 -8
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +22 -14
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/dist/packlets/ai-assist/streamingClient.js +3 -2
- package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
- package/dist/packlets/ai-assist/thinkingOptionsResolver.js +18 -3
- 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 +547 -30
- 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 +34 -4
- package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -1
- package/lib/packlets/ai-assist/chatRequestBuilders.js +73 -5
- package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
- package/lib/packlets/ai-assist/completionClient.d.ts +6 -0
- package/lib/packlets/ai-assist/completionClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/completionClient.js +121 -44
- package/lib/packlets/ai-assist/completionClient.js.map +1 -1
- package/lib/packlets/ai-assist/imageGenerationClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/imageGenerationClient.js +1 -1
- 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/model.d.ts +59 -288
- 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 +119 -0
- package/lib/packlets/ai-assist/streamUsageCapability.d.ts.map +1 -0
- package/lib/packlets/ai-assist/streamUsageCapability.js +130 -0
- package/lib/packlets/ai-assist/streamUsageCapability.js.map +1 -0
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +23 -4
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +21 -12
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +64 -34
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +21 -0
- 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 +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +33 -7
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +21 -13
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.js +3 -2
- package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
- package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -1
- package/lib/packlets/ai-assist/thinkingOptionsResolver.js +18 -3
- 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 +328 -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,6 +21,15 @@
|
|
|
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");
|
|
26
35
|
const thinkingOptionsResolver_1 = require("./thinkingOptionsResolver");
|
|
@@ -30,6 +39,8 @@ const http_1 = require("./http");
|
|
|
30
39
|
const toolFormats_1 = require("./toolFormats");
|
|
31
40
|
const structuredOutput_1 = require("./structuredOutput");
|
|
32
41
|
const registry_1 = require("./registry");
|
|
42
|
+
const streamUsageCapability_1 = require("./streamUsageCapability");
|
|
43
|
+
const usageNormalization_1 = require("./usageNormalization");
|
|
33
44
|
const openAiMessage = ts_utils_1.Validators.object({
|
|
34
45
|
content: ts_utils_1.Validators.string
|
|
35
46
|
});
|
|
@@ -55,15 +66,22 @@ const responsesApiResponse = ts_utils_1.Validators.object({
|
|
|
55
66
|
status: ts_utils_1.Validators.string
|
|
56
67
|
});
|
|
57
68
|
const geminiPart = ts_utils_1.Validators.object({
|
|
58
|
-
text: ts_utils_1.Validators.string
|
|
69
|
+
text: ts_utils_1.Validators.string.optional()
|
|
59
70
|
});
|
|
60
71
|
const geminiContent = ts_utils_1.Validators.object({
|
|
61
|
-
parts:
|
|
72
|
+
// No non-empty constraint: `parts: []` is a shape Gemini actually returns, and rejecting it
|
|
73
|
+
// here reports a schema violation for what is really "the model produced no output".
|
|
74
|
+
parts: ts_utils_1.Validators.arrayOf(geminiPart).optional()
|
|
62
75
|
});
|
|
63
76
|
const geminiCandidate = ts_utils_1.Validators.object({
|
|
64
|
-
content: geminiContent,
|
|
65
|
-
finishReason: ts_utils_1.Validators.string
|
|
77
|
+
content: geminiContent.optional(),
|
|
78
|
+
finishReason: ts_utils_1.Validators.string.optional(),
|
|
79
|
+
finishMessage: ts_utils_1.Validators.string.optional()
|
|
66
80
|
});
|
|
81
|
+
// Terminal reasons that mean "the model finished normally", as distinct from a refusal. Kept in
|
|
82
|
+
// step with the identical set in `imageGenerationClient.ts`; both exist so an empty response can
|
|
83
|
+
// be reported as the decline it is rather than as an empty string or a parse error.
|
|
84
|
+
const benignGeminiFinishReasons = new Set(['STOP', 'MAX_TOKENS']);
|
|
67
85
|
const geminiResponse = ts_utils_1.Validators.object({
|
|
68
86
|
candidates: ts_utils_1.Validators.arrayOf(geminiCandidate).withConstraint((arr) => arr.length > 0)
|
|
69
87
|
});
|
|
@@ -75,36 +93,52 @@ const geminiResponse = ts_utils_1.Validators.object({
|
|
|
75
93
|
* Works for xAI Grok, OpenAI, Groq, and Mistral.
|
|
76
94
|
* @internal
|
|
77
95
|
*/
|
|
78
|
-
async function callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking, maxTokens, useMaxCompletionTokensField = false, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT) {
|
|
96
|
+
async function callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking, maxTokens, useMaxCompletionTokensField = false, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT, reportsUsage = false, cache, cacheKeyHeader) {
|
|
79
97
|
var _a;
|
|
80
98
|
const url = `${config.baseUrl}/chat/completions`;
|
|
81
|
-
const
|
|
99
|
+
const systemContentResult = (0, chatRequestBuilders_1.buildOpenAiChatSystemContent)(prompt.system, cache);
|
|
100
|
+
if (systemContentResult.isFailure()) {
|
|
101
|
+
return (0, ts_utils_1.fail)(systemContentResult.message);
|
|
102
|
+
}
|
|
103
|
+
const messages = (0, chatRequestBuilders_1.buildMessages)(systemContentResult.value, (0, chatRequestBuilders_1.buildOpenAiChatUserContent)(prompt), {
|
|
82
104
|
head
|
|
83
105
|
});
|
|
84
106
|
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
107
|
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 } : {}))
|
|
108
|
+
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
|
|
109
|
+
? // eslint-disable-next-line @typescript-eslint/naming-convention -- wire field name
|
|
110
|
+
{ prompt_cache_key: cache.cacheKey }
|
|
111
|
+
: {}));
|
|
87
112
|
if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
|
|
88
113
|
Object.assign(body, resolvedThinking.otherParams);
|
|
89
114
|
}
|
|
90
115
|
Object.assign(body, structured.wire);
|
|
91
116
|
const headers = (0, endpoint_1.bearerAuthHeader)(config.apiKey);
|
|
117
|
+
// Sticky-routing key, when the provider carries it as a header rather than a body field.
|
|
118
|
+
// xAI's prompt cache is per-server, so an identical prefix can still miss if the request is
|
|
119
|
+
// routed to a different box; `x-grok-conv-id` pins it.
|
|
120
|
+
if ((cache === null || cache === void 0 ? void 0 : cache.cacheKey) !== undefined && cacheKeyHeader !== undefined) {
|
|
121
|
+
headers[cacheKeyHeader] = cache.cacheKey;
|
|
122
|
+
}
|
|
92
123
|
/* c8 ignore next 1 - optional logger */
|
|
93
124
|
logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI completion: model=${config.model}`);
|
|
94
125
|
const jsonResult = await (0, http_1.fetchJson)(url, headers, body, logger, signal);
|
|
95
126
|
if (jsonResult.isFailure()) {
|
|
96
127
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
97
128
|
}
|
|
129
|
+
// Only descriptors with confirmed cache-relevant usage reporting are normalized — the
|
|
130
|
+
// adapter is shared by Groq/Mistral/Ollama/openai-compat too, and an ordinary usage block
|
|
131
|
+
// from one of those carries no cache information. See supportsCacheUsageReporting.
|
|
132
|
+
const rawUsage = jsonResult.value.usage;
|
|
133
|
+
const usage = reportsUsage
|
|
134
|
+
? (0, usageNormalization_1.normalizeOpenAiChatUsage)((0, ts_json_base_1.isJsonObject)(rawUsage) ? rawUsage : undefined)
|
|
135
|
+
: undefined;
|
|
98
136
|
return openAiResponse
|
|
99
137
|
.validate(jsonResult.value)
|
|
100
138
|
.withErrorFormat((msg) => `OpenAI API response: ${msg}`)
|
|
101
139
|
.onSuccess((response) => {
|
|
102
140
|
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
|
-
});
|
|
141
|
+
return (0, ts_utils_1.succeed)(Object.assign({ content: choice.message.content, truncated: choice.finish_reason === 'length', structuredOutput: structured.enforcement }, (usage !== undefined ? { usage } : {})));
|
|
108
142
|
});
|
|
109
143
|
}
|
|
110
144
|
// ============================================================================
|
|
@@ -131,14 +165,21 @@ function extractResponsesApiText(output) {
|
|
|
131
165
|
* Used when tools are configured for an openai-format provider.
|
|
132
166
|
* @internal
|
|
133
167
|
*/
|
|
134
|
-
async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, temperature, logger, signal, resolvedThinking, maxTokens, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT) {
|
|
168
|
+
async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, temperature, logger, signal, resolvedThinking, maxTokens, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT, reportsUsage = false, cache) {
|
|
135
169
|
var _a;
|
|
136
170
|
const url = `${config.baseUrl}/responses`;
|
|
137
|
-
const
|
|
171
|
+
const systemContentResult = (0, chatRequestBuilders_1.buildOpenAiResponsesSystemContent)(prompt.system, cache);
|
|
172
|
+
if (systemContentResult.isFailure()) {
|
|
173
|
+
return (0, ts_utils_1.fail)(systemContentResult.message);
|
|
174
|
+
}
|
|
175
|
+
const input = (0, chatRequestBuilders_1.buildMessages)(systemContentResult.value, (0, chatRequestBuilders_1.buildOpenAiResponsesUserContent)(prompt), {
|
|
138
176
|
head
|
|
139
177
|
});
|
|
140
178
|
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 } } : {}))
|
|
179
|
+
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
|
|
180
|
+
? // eslint-disable-next-line @typescript-eslint/naming-convention -- wire field name
|
|
181
|
+
{ prompt_cache_key: cache.cacheKey }
|
|
182
|
+
: {}));
|
|
142
183
|
// Shared by OpenAI and xAI — both route through the Responses API with the same field name.
|
|
143
184
|
if (maxTokens !== undefined) {
|
|
144
185
|
body.max_output_tokens = maxTokens;
|
|
@@ -154,15 +195,16 @@ async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, t
|
|
|
154
195
|
if (jsonResult.isFailure()) {
|
|
155
196
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
156
197
|
}
|
|
198
|
+
// See the identical gate in callOpenAiCompletion above — this route is shared the same way.
|
|
199
|
+
const rawResponsesUsage = jsonResult.value.usage;
|
|
200
|
+
const responsesUsage = reportsUsage
|
|
201
|
+
? (0, usageNormalization_1.normalizeOpenAiResponsesUsage)((0, ts_json_base_1.isJsonObject)(rawResponsesUsage) ? rawResponsesUsage : undefined)
|
|
202
|
+
: undefined;
|
|
157
203
|
return responsesApiResponse
|
|
158
204
|
.validate(jsonResult.value)
|
|
159
205
|
.withErrorFormat((msg) => `Responses API response: ${msg}`)
|
|
160
206
|
.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
|
-
}));
|
|
207
|
+
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
208
|
});
|
|
167
209
|
}
|
|
168
210
|
// ============================================================================
|
|
@@ -228,10 +270,14 @@ function extractAnthropicStructuredOutput(content) {
|
|
|
228
270
|
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
271
|
}
|
|
230
272
|
/** 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) {
|
|
273
|
+
async function callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, useAdaptiveThinking = false, maxTokens, structured = structuredOutput_1.NO_STRUCTURED_OUTPUT, cache) {
|
|
232
274
|
const url = `${config.baseUrl}/messages`;
|
|
233
275
|
const messages = (0, chatRequestBuilders_1.buildAnthropicMessages)(prompt, { head });
|
|
234
|
-
const
|
|
276
|
+
const systemResult = (0, chatRequestBuilders_1.buildAnthropicSystem)(prompt.system, cache);
|
|
277
|
+
if (systemResult.isFailure()) {
|
|
278
|
+
return (0, ts_utils_1.fail)(systemResult.message);
|
|
279
|
+
}
|
|
280
|
+
const body = Object.assign({ model: config.model, system: systemResult.value, messages,
|
|
235
281
|
// Anthropic's Messages API requires max_tokens on every request — see
|
|
236
282
|
// AiAssist.DEFAULT_ANTHROPIC_MAX_TOKENS for why only this provider defaults it.
|
|
237
283
|
max_tokens: maxTokens !== null && maxTokens !== void 0 ? maxTokens : model_1.DEFAULT_ANTHROPIC_MAX_TOKENS }, (temperature !== undefined ? { temperature } : {}));
|
|
@@ -282,6 +328,8 @@ async function callAnthropicCompletion(config, prompt, head, temperature, logger
|
|
|
282
328
|
}
|
|
283
329
|
const rawContent = jsonResult.value.content;
|
|
284
330
|
const stopReason = jsonResult.value.stop_reason;
|
|
331
|
+
const rawAnthropicUsage = jsonResult.value.usage;
|
|
332
|
+
const anthropicUsage = (0, usageNormalization_1.normalizeAnthropicUsage)((0, ts_json_base_1.isJsonObject)(rawAnthropicUsage) ? rawAnthropicUsage : undefined);
|
|
285
333
|
if (!Array.isArray(rawContent)) {
|
|
286
334
|
return (0, ts_utils_1.fail)('Anthropic API response: content is not an array');
|
|
287
335
|
}
|
|
@@ -291,11 +339,7 @@ async function callAnthropicCompletion(config, prompt, head, temperature, logger
|
|
|
291
339
|
const extracted = structured.enforcement === 'tool-forced'
|
|
292
340
|
? extractAnthropicStructuredOutput(rawContent)
|
|
293
341
|
: 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
|
-
}));
|
|
342
|
+
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
343
|
}
|
|
300
344
|
// ============================================================================
|
|
301
345
|
// Google Gemini adapter
|
|
@@ -343,22 +387,35 @@ async function callGeminiCompletion(config, prompt, head, temperature, logger, t
|
|
|
343
387
|
if (jsonResult.isFailure()) {
|
|
344
388
|
return (0, ts_utils_1.fail)(jsonResult.message);
|
|
345
389
|
}
|
|
390
|
+
const rawGeminiUsage = jsonResult.value.usageMetadata;
|
|
391
|
+
const geminiUsage = (0, usageNormalization_1.normalizeGeminiUsage)((0, ts_json_base_1.isJsonObject)(rawGeminiUsage) ? rawGeminiUsage : undefined);
|
|
346
392
|
return geminiResponse
|
|
347
393
|
.validate(jsonResult.value)
|
|
348
394
|
.withErrorFormat((msg) => `Gemini API response: ${msg}`)
|
|
349
395
|
.onSuccess((response) => {
|
|
396
|
+
var _a, _b, _c;
|
|
350
397
|
const candidate = response.candidates[0];
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
398
|
+
// ALL parts, not `parts[0]`. Gemini may split one reply across several
|
|
399
|
+
// text parts, and reading only the first silently discards the rest —
|
|
400
|
+
// yielding a truncated document that often still parses, which is the
|
|
401
|
+
// worst way to be wrong. The streaming adapter has always concatenated
|
|
402
|
+
// (`fullText += part.text`); this path did not, so the same response gave
|
|
403
|
+
// different text depending on which one you called.
|
|
404
|
+
//
|
|
405
|
+
// `part.text ?? ''` rather than `part.text`: a reasoning or tool part contributes no text,
|
|
406
|
+
// and joining `undefined` would put the literal string "undefined" into the reply.
|
|
407
|
+
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 : '';
|
|
408
|
+
if (content.length === 0) {
|
|
409
|
+
// Nothing came back. If the candidate says why, say so — an empty string handed to a
|
|
410
|
+
// caller is indistinguishable from a model that legitimately replied with nothing, and
|
|
411
|
+
// sends them looking in the wrong place.
|
|
412
|
+
const reason = candidate.finishReason;
|
|
413
|
+
if (reason !== undefined && !benignGeminiFinishReasons.has(reason)) {
|
|
414
|
+
const suffix = candidate.finishMessage ? ` — ${candidate.finishMessage}` : '';
|
|
415
|
+
return (0, ts_utils_1.fail)(`Gemini completion declined: ${reason}${suffix}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return (0, ts_utils_1.succeed)(Object.assign({ content, truncated: candidate.finishReason === 'MAX_TOKENS', structuredOutput: structured.enforcement }, (geminiUsage !== undefined ? { usage: geminiUsage } : {})));
|
|
362
419
|
});
|
|
363
420
|
}
|
|
364
421
|
// ============================================================================
|
|
@@ -371,7 +428,7 @@ async function callGeminiCompletion(config, prompt, head, temperature, logger, t
|
|
|
371
428
|
* @public
|
|
372
429
|
*/
|
|
373
430
|
async function callProviderCompletion(params) {
|
|
374
|
-
const { descriptor, apiKey, system, messages, temperature, modelOverride, tier, logger, tools, signal, endpoint, thinking, maxTokens, structuredOutput } = params;
|
|
431
|
+
const { descriptor, apiKey, system, messages, temperature, modelOverride, tier, logger, tools, signal, endpoint, thinking, maxTokens, structuredOutput, cache } = params;
|
|
375
432
|
const splitResult = (0, chatRequestBuilders_1.splitChatRequest)(system, messages);
|
|
376
433
|
if (splitResult.isFailure()) {
|
|
377
434
|
return (0, ts_utils_1.fail)(splitResult.message);
|
|
@@ -448,15 +505,30 @@ async function callProviderCompletion(params) {
|
|
|
448
505
|
`tools=${toolTypes}, supported=${supported}`);
|
|
449
506
|
}
|
|
450
507
|
switch (descriptor.apiFormat) {
|
|
451
|
-
case 'openai':
|
|
508
|
+
case 'openai': {
|
|
452
509
|
// Responses-API-only models (e.g. gpt-5.5-pro) 400 on /chat/completions, so they route
|
|
453
510
|
// to the Responses path even with no tools requested — same path the tools case uses.
|
|
511
|
+
// `cache` carries two independently-supported things, and they are gated separately.
|
|
512
|
+
// `systemBreakpoints` restructures `content` and needs the server to tolerate an
|
|
513
|
+
// unrecognized field inside it (OpenAI only). `cacheKey` is an opaque routing string that
|
|
514
|
+
// makes repeated requests with a shared prefix land on the same cache-holding server —
|
|
515
|
+
// which xAI needs precisely because it does per-server byte-for-byte prefix matching.
|
|
516
|
+
// Gating them together withheld the routing key from the provider that depends on it most.
|
|
517
|
+
// Every descriptor supporting neither (Groq, Mistral, Ollama, openai-compat) still gets the
|
|
518
|
+
// exact request body it would have gotten had the caller passed no `cache` at all.
|
|
519
|
+
const routing = (0, streamUsageCapability_1.supportsPromptCacheRouting)(descriptor);
|
|
520
|
+
const breakpoints = (0, streamUsageCapability_1.supportsPromptCacheBreakpoints)(descriptor) ? cache === null || cache === void 0 ? void 0 : cache.systemBreakpoints : undefined;
|
|
521
|
+
const routedKey = routing !== undefined ? cache === null || cache === void 0 ? void 0 : cache.cacheKey : undefined;
|
|
522
|
+
const gatedCache = breakpoints !== undefined || routedKey !== undefined
|
|
523
|
+
? { systemBreakpoints: breakpoints, cacheKey: routedKey }
|
|
524
|
+
: undefined;
|
|
454
525
|
if (usesResponsesApi) {
|
|
455
|
-
return callOpenAiResponsesCompletion(config, prompt, tools, head, temperature, logger, signal, resolvedThinking, maxTokens, resolvedStructured);
|
|
526
|
+
return callOpenAiResponsesCompletion(config, prompt, tools, head, temperature, logger, signal, resolvedThinking, maxTokens, resolvedStructured, (0, streamUsageCapability_1.supportsCacheUsageReporting)(descriptor), gatedCache);
|
|
456
527
|
}
|
|
457
|
-
return callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking, maxTokens, (0, model_1.usesMaxCompletionTokensField)(descriptor), resolvedStructured);
|
|
528
|
+
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);
|
|
529
|
+
}
|
|
458
530
|
case 'anthropic':
|
|
459
|
-
return callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, (0, model_1.isAdaptiveThinkingModel)(descriptor, config.model), maxTokens, resolvedStructured);
|
|
531
|
+
return callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, (0, model_1.isAdaptiveThinkingModel)(descriptor, config.model), maxTokens, resolvedStructured, cache);
|
|
460
532
|
case 'gemini':
|
|
461
533
|
return callGeminiCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, maxTokens, resolvedStructured);
|
|
462
534
|
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
@@ -480,7 +552,7 @@ async function callProviderCompletion(params) {
|
|
|
480
552
|
* @public
|
|
481
553
|
*/
|
|
482
554
|
async function callProxiedCompletion(proxyUrl, params) {
|
|
483
|
-
const { descriptor, apiKey, system, messages, temperature, modelOverride, logger, tools, signal, thinking, maxTokens, structuredOutput } = params;
|
|
555
|
+
const { descriptor, apiKey, system, messages, temperature, modelOverride, logger, tools, signal, thinking, maxTokens, structuredOutput, cache } = params;
|
|
484
556
|
const splitResult = (0, chatRequestBuilders_1.splitChatRequest)(system, messages);
|
|
485
557
|
if (splitResult.isFailure()) {
|
|
486
558
|
return (0, ts_utils_1.fail)(splitResult.message);
|
|
@@ -527,6 +599,11 @@ async function callProxiedCompletion(proxyUrl, params) {
|
|
|
527
599
|
? { onUnsupported: structuredOutput.onUnsupported }
|
|
528
600
|
: {}));
|
|
529
601
|
}
|
|
602
|
+
// `IAiCacheRequest` is plain numbers and a string — JSON-serializable as-is, unlike
|
|
603
|
+
// `structuredOutput`'s schema above, so it needs no wire projection before forwarding.
|
|
604
|
+
if (cache !== undefined) {
|
|
605
|
+
body.cache = cache;
|
|
606
|
+
}
|
|
530
607
|
/* c8 ignore next 1 - optional logger */
|
|
531
608
|
logger === null || logger === void 0 ? void 0 : logger.info(`AI proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
|
|
532
609
|
const url = `${proxyUrl}/api/ai/completion`;
|