@fgv/ts-extras 5.1.0-44 → 5.1.0-45

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.
Files changed (75) hide show
  1. package/dist/packlets/ai-assist/apiClient.js +38 -14
  2. package/dist/packlets/ai-assist/apiClient.js.map +1 -1
  3. package/dist/packlets/ai-assist/index.js +1 -1
  4. package/dist/packlets/ai-assist/index.js.map +1 -1
  5. package/dist/packlets/ai-assist/jsonResponse.js +16 -5
  6. package/dist/packlets/ai-assist/jsonResponse.js.map +1 -1
  7. package/dist/packlets/ai-assist/model.js +94 -0
  8. package/dist/packlets/ai-assist/model.js.map +1 -1
  9. package/dist/packlets/ai-assist/registry.js +18 -3
  10. package/dist/packlets/ai-assist/registry.js.map +1 -1
  11. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +17 -6
  12. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  13. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +5 -5
  14. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
  15. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  16. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +4 -1
  17. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  18. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +6 -1
  19. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  20. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +5 -1
  21. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  22. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +6 -1
  23. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  24. package/dist/packlets/ai-assist/streamingClient.js +6 -6
  25. package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
  26. package/dist/ts-extras.d.ts +143 -1
  27. package/lib/packlets/ai-assist/apiClient.d.ts +9 -0
  28. package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
  29. package/lib/packlets/ai-assist/apiClient.js +37 -13
  30. package/lib/packlets/ai-assist/apiClient.js.map +1 -1
  31. package/lib/packlets/ai-assist/index.d.ts +1 -1
  32. package/lib/packlets/ai-assist/index.d.ts.map +1 -1
  33. package/lib/packlets/ai-assist/index.js +6 -1
  34. package/lib/packlets/ai-assist/index.js.map +1 -1
  35. package/lib/packlets/ai-assist/jsonResponse.d.ts +7 -0
  36. package/lib/packlets/ai-assist/jsonResponse.d.ts.map +1 -1
  37. package/lib/packlets/ai-assist/jsonResponse.js +16 -5
  38. package/lib/packlets/ai-assist/jsonResponse.js.map +1 -1
  39. package/lib/packlets/ai-assist/model.d.ts +101 -1
  40. package/lib/packlets/ai-assist/model.d.ts.map +1 -1
  41. package/lib/packlets/ai-assist/model.js +98 -1
  42. package/lib/packlets/ai-assist/model.js.map +1 -1
  43. package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
  44. package/lib/packlets/ai-assist/registry.js +18 -3
  45. package/lib/packlets/ai-assist/registry.js.map +1 -1
  46. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +1 -1
  47. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
  48. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +17 -6
  49. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  50. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +9 -0
  51. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -1
  52. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +4 -4
  53. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
  54. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +9 -0
  55. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
  56. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  57. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +1 -1
  58. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
  59. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +4 -1
  60. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
  61. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +1 -1
  62. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
  63. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +6 -1
  64. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  65. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +1 -1
  66. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
  67. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +5 -1
  68. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  69. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
  70. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +6 -1
  71. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  72. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
  73. package/lib/packlets/ai-assist/streamingClient.js +5 -5
  74. package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
  75. package/package.json +7 -7
@@ -28,7 +28,7 @@
28
28
  */
29
29
  import { isJsonObject } from '@fgv/ts-json-base';
30
30
  import { fail, mapResults, succeed, Validators } from '@fgv/ts-utils';
31
- import { allModelCapabilities, isResponsesOnlyModel, resolveProviderModel } from './model';
31
+ import { allModelCapabilities, DEFAULT_ANTHROPIC_MAX_TOKENS, isAdaptiveThinkingModel, isResponsesOnlyModel, resolveProviderModel, usesMaxCompletionTokensField } from './model';
32
32
  import { anthropicEffortToBudgetTokens, checkTemperatureConflict, mergeThinkingConfig, providerDiscriminatorForId } from './thinkingOptionsResolver';
33
33
  import { buildAnthropicMessages, buildGeminiContents, buildMessages, buildOpenAiChatUserContent, buildOpenAiResponsesUserContent, normalizeOutboundMessages, splitChatRequest } from './chatRequestBuilders';
34
34
  import { bearerAuthHeader, resolveEffectiveBaseUrl } from './endpoint';
@@ -221,14 +221,15 @@ const geminiResponse = Validators.object({
221
221
  * Works for xAI Grok, OpenAI, Groq, and Mistral.
222
222
  * @internal
223
223
  */
224
- async function callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking) {
224
+ async function callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking, maxTokens, useMaxCompletionTokensField = false) {
225
225
  var _a;
226
226
  const url = `${config.baseUrl}/chat/completions`;
227
227
  const messages = buildMessages(prompt.system, buildOpenAiChatUserContent(prompt), {
228
228
  head
229
229
  });
230
230
  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;
231
- const body = Object.assign(Object.assign({ model: config.model, messages }, (temperature !== undefined ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning_effort: effort } : {}));
231
+ const maxTokensField = useMaxCompletionTokensField ? 'max_completion_tokens' : 'max_tokens';
232
+ 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 } : {}));
232
233
  if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
233
234
  Object.assign(body, resolvedThinking.otherParams);
234
235
  }
@@ -274,7 +275,7 @@ function extractResponsesApiText(output) {
274
275
  * Used when tools are configured for an openai-format provider.
275
276
  * @internal
276
277
  */
277
- async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, temperature, logger, signal, resolvedThinking) {
278
+ async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, temperature, logger, signal, resolvedThinking, maxTokens) {
278
279
  var _a;
279
280
  const url = `${config.baseUrl}/responses`;
280
281
  const input = buildMessages(prompt.system, buildOpenAiResponsesUserContent(prompt), {
@@ -282,6 +283,10 @@ async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, t
282
283
  });
283
284
  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;
284
285
  const body = Object.assign(Object.assign(Object.assign({ model: config.model, input }, (tools.length > 0 ? { tools: toResponsesApiTools(tools) } : {})), (temperature !== undefined ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning: { effort } } : {}));
286
+ // Shared by OpenAI and xAI — both route through the Responses API with the same field name.
287
+ if (maxTokens !== undefined) {
288
+ body.max_output_tokens = maxTokens;
289
+ }
285
290
  if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
286
291
  Object.assign(body, resolvedThinking.otherParams);
287
292
  }
@@ -328,13 +333,24 @@ function extractAnthropicText(content) {
328
333
  return succeed(textParts.join(''));
329
334
  }
330
335
  /** Calls the Anthropic Messages API with optional tool support. @internal */
331
- async function callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking) {
336
+ async function callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, useAdaptiveThinking = false, maxTokens) {
332
337
  const url = `${config.baseUrl}/messages`;
333
338
  const messages = buildAnthropicMessages(prompt, { head });
334
- const body = Object.assign({ model: config.model, system: prompt.system, messages, max_tokens: 4096 }, (temperature !== undefined ? { temperature } : {}));
339
+ const body = Object.assign({ model: config.model, system: prompt.system, messages,
340
+ // Anthropic's Messages API requires max_tokens on every request — see
341
+ // AiAssist.DEFAULT_ANTHROPIC_MAX_TOKENS for why only this provider defaults it.
342
+ max_tokens: maxTokens !== null && maxTokens !== void 0 ? maxTokens : DEFAULT_ANTHROPIC_MAX_TOKENS }, (temperature !== undefined ? { temperature } : {}));
335
343
  const effort = resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.anthropicEffort;
336
344
  if (effort !== undefined) {
337
- body.thinking = { type: 'enabled', budget_tokens: anthropicEffortToBudgetTokens(effort) };
345
+ if (useAdaptiveThinking) {
346
+ // Claude 5 family: adaptive thinking — no budget_tokens; effort moves to the
347
+ // top-level output_config block. See AiAssist.isAdaptiveThinkingModel.
348
+ body.thinking = { type: 'adaptive' };
349
+ body.output_config = { effort };
350
+ }
351
+ else {
352
+ body.thinking = { type: 'enabled', budget_tokens: anthropicEffortToBudgetTokens(effort) };
353
+ }
338
354
  }
339
355
  if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
340
356
  Object.assign(body, resolvedThinking.otherParams);
@@ -378,7 +394,7 @@ async function callAnthropicCompletion(config, prompt, head, temperature, logger
378
394
  * When tools are configured, includes Google Search grounding.
379
395
  * @internal
380
396
  */
381
- async function callGeminiCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking) {
397
+ async function callGeminiCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, maxTokens) {
382
398
  const url = `${config.baseUrl}/models/${config.model}:generateContent`;
383
399
  const contents = buildGeminiContents(prompt, { head });
384
400
  // Temperature is sent only when explicitly provided; otherwise Gemini's default applies.
@@ -386,6 +402,9 @@ async function callGeminiCompletion(config, prompt, head, temperature, logger, t
386
402
  if (temperature !== undefined) {
387
403
  generationConfig.temperature = temperature;
388
404
  }
405
+ if (maxTokens !== undefined) {
406
+ generationConfig.maxOutputTokens = maxTokens;
407
+ }
389
408
  if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.geminiThinkingBudget) !== undefined) {
390
409
  generationConfig.thinkingConfig = { thinkingBudget: resolvedThinking.geminiThinkingBudget };
391
410
  }
@@ -434,7 +453,7 @@ async function callGeminiCompletion(config, prompt, head, temperature, logger, t
434
453
  * @public
435
454
  */
436
455
  export async function callProviderCompletion(params) {
437
- const { descriptor, apiKey, system, messages, temperature, modelOverride, tier, logger, tools, signal, endpoint, thinking } = params;
456
+ const { descriptor, apiKey, system, messages, temperature, modelOverride, tier, logger, tools, signal, endpoint, thinking, maxTokens } = params;
438
457
  const splitResult = splitChatRequest(system, messages);
439
458
  if (splitResult.isFailure()) {
440
459
  return fail(splitResult.message);
@@ -489,13 +508,13 @@ export async function callProviderCompletion(params) {
489
508
  // Responses-API-only models (e.g. gpt-5.5-pro) 400 on /chat/completions, so they route
490
509
  // to the Responses path even with no tools requested — same path the tools case uses.
491
510
  if (hasTools || isResponsesOnlyModel(descriptor, config.model)) {
492
- return callOpenAiResponsesCompletion(config, prompt, tools, head, temperature, logger, signal, resolvedThinking);
511
+ return callOpenAiResponsesCompletion(config, prompt, tools, head, temperature, logger, signal, resolvedThinking, maxTokens);
493
512
  }
494
- return callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking);
513
+ return callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking, maxTokens, usesMaxCompletionTokensField(descriptor));
495
514
  case 'anthropic':
496
- return callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking);
515
+ return callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, isAdaptiveThinkingModel(descriptor, config.model), maxTokens);
497
516
  case 'gemini':
498
- return callGeminiCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking);
517
+ return callGeminiCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking, maxTokens);
499
518
  /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
500
519
  default: {
501
520
  const _exhaustive = descriptor.apiFormat;
@@ -1120,7 +1139,7 @@ export async function callProxiedListModels(proxyUrl, params) {
1120
1139
  * @public
1121
1140
  */
1122
1141
  export async function callProxiedCompletion(proxyUrl, params) {
1123
- const { descriptor, apiKey, system, messages, temperature, modelOverride, logger, tools, signal, thinking } = params;
1142
+ const { descriptor, apiKey, system, messages, temperature, modelOverride, logger, tools, signal, thinking, maxTokens } = params;
1124
1143
  const splitResult = splitChatRequest(system, messages);
1125
1144
  if (splitResult.isFailure()) {
1126
1145
  return fail(splitResult.message);
@@ -1150,6 +1169,11 @@ export async function callProxiedCompletion(proxyUrl, params) {
1150
1169
  if (thinking !== undefined) {
1151
1170
  body.thinking = thinking;
1152
1171
  }
1172
+ // Forwarded only when explicitly provided; the proxy is responsible for mapping it to the
1173
+ // correct upstream provider field (see AiAssist.usesMaxCompletionTokensField).
1174
+ if (maxTokens !== undefined) {
1175
+ body.maxTokens = maxTokens;
1176
+ }
1153
1177
  /* c8 ignore next 1 - optional logger */
1154
1178
  logger === null || logger === void 0 ? void 0 : logger.info(`AI proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
1155
1179
  const url = `${proxyUrl}/api/ai/completion`;