@juspay/neurolink 12.10.0 → 12.11.0

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.
@@ -18,14 +18,19 @@ export declare enum AIProviderName {
18
18
  NVIDIA_NIM = "nvidia-nim",
19
19
  LM_STUDIO = "lm-studio",
20
20
  LLAMACPP = "llamacpp",
21
+ BASETEN = "baseten",
21
22
  CEREBRAS = "cerebras",
22
23
  CLOUDFLARE = "cloudflare",
23
24
  FIREWORKS = "fireworks",
25
+ GMICLOUD = "gmicloud",
24
26
  GROQ = "groq",
27
+ INCEPTION_LABS = "inception-labs",
28
+ IO_INTELLIGENCE = "io-intelligence",
25
29
  MISTRAL = "mistral",
26
30
  PERPLEXITY = "perplexity",
27
31
  SAMBANOVA = "sambanova",
28
32
  TOGETHER_AI = "together-ai",
33
+ UPSTAGE = "upstage",
29
34
  XAI = "xai",
30
35
  COHERE = "cohere",
31
36
  REPLICATE = "replicate",
@@ -809,6 +814,24 @@ export declare enum ReplicateModels {
809
814
  * authentication failures during ongoing operations
810
815
  */
811
816
  export declare const TOKEN_EXPIRY_BUFFER_MS: number;
817
+ export declare enum BasetenModels {
818
+ OPENAI_GPT_OSS_120B = "openai/gpt-oss-120b",
819
+ ZAI_ORG_GLM_4_7 = "zai-org/GLM-4.7",
820
+ MOONSHOTAI_KIMI_K2_6 = "moonshotai/Kimi-K2.6",
821
+ DEEPSEEK_AI_DEEPSEEK_V4_PRO = "deepseek-ai/DeepSeek-V4-Pro",
822
+ NVIDIA_NVIDIA_NEMOTRON_3_ULTRA_550B_A55B = "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B",
823
+ ZAI_ORG_GLM_5_2 = "zai-org/GLM-5.2",
824
+ MOONSHOTAI_KIMI_K2_7_CODE = "moonshotai/Kimi-K2.7-Code",
825
+ DEEPSEEK_AI_DEEPSEEK_V4_FLASH_0731 = "deepseek-ai/DeepSeek-V4-Flash-0731",
826
+ THINKINGMACHINES_INKLING = "thinkingmachines/inkling",
827
+ ZAI_ORG_GLM_5_2_FAST = "zai-org/GLM-5.2-Fast",
828
+ MOONSHOTAI_KIMI_K3 = "moonshotai/Kimi-K3",
829
+ THINKINGMACHINES_INKLING_SMALL = "thinkingmachines/inkling-small",
830
+ DEEPSEEK_AI_DEEPSEEK_V4_PRO_0813 = "deepseek-ai/DeepSeek-V4-Pro-0813",
831
+ ZAI_ORG_GLM_5_3_FLASH = "zai-org/GLM-5.3-Flash",
832
+ ZAI_ORG_GLM_5_3 = "zai-org/GLM-5.3",
833
+ ZAI_ORG_GLM_5_3_FAST = "zai-org/GLM-5.3-Fast"
834
+ }
812
835
  export declare enum CerebrasModels {
813
836
  GPT_OSS_120B = "gpt-oss-120b",
814
837
  GEMMA_4_31B = "gemma-4-31b"
@@ -838,6 +861,9 @@ export declare enum FireworksModels {
838
861
  LLAMA_V3P2_11B_VISION_INSTRUCT = "accounts/fireworks/models/llama-v3p2-11b-vision-instruct",
839
862
  PHI_3_VISION_128K_INSTRUCT = "accounts/fireworks/models/phi-3-vision-128k-instruct"
840
863
  }
864
+ export declare enum GmicloudModels {
865
+ MINIMAXAI_MINIMAX_M3 = "MiniMaxAI/MiniMax-M3"
866
+ }
841
867
  export declare enum GroqModels {
842
868
  GPT_OSS_120B = "openai/gpt-oss-120b",
843
869
  GPT_OSS_20B = "openai/gpt-oss-20b",
@@ -854,6 +880,45 @@ export declare enum GroqModels {
854
880
  LLAMA_3_2_90B_VISION_PREVIEW = "llama-3.2-90b-vision-preview",
855
881
  LLAMA_3_2_11B_VISION_PREVIEW = "llama-3.2-11b-vision-preview"
856
882
  }
883
+ export declare enum InceptionLabsModels {
884
+ MERCURY_2 = "mercury-2"
885
+ }
886
+ export declare enum IoIntelligenceModels {
887
+ ZAI_ORG_GLM_5_3_FLASH = "zai-org/GLM-5.3-Flash",
888
+ ZAI_ORG_GLM_5_3 = "zai-org/GLM-5.3",
889
+ QWEN_QWEN3_8_27B = "Qwen/Qwen3.8-27B",
890
+ DEEPSEEK_AI_DEEPSEEK_V4_FLASH_0731 = "deepseek-ai/DeepSeek-V4-Flash-0731",
891
+ MOONSHOTAI_KIMI_K3 = "moonshotai/Kimi-K3",
892
+ XIAOMIMIMO_MIMO_V2_5 = "XiaomiMiMo/MiMo-V2.5",
893
+ ZAI_ORG_GLM_5_2 = "zai-org/GLM-5.2",
894
+ MOONSHOTAI_KIMI_K2_7_CODE = "moonshotai/Kimi-K2.7-Code",
895
+ QWEN_QWEN3_6_35B_A3B = "Qwen/Qwen3.6-35B-A3B",
896
+ QWEN_QWEN3_6_27B = "Qwen/Qwen3.6-27B",
897
+ MINIMAXAI_MINIMAX_M2_7 = "MiniMaxAI/MiniMax-M2.7",
898
+ DEEPSEEK_AI_DEEPSEEK_V4_FLASH = "deepseek-ai/DeepSeek-V4-Flash",
899
+ DEEPSEEK_AI_DEEPSEEK_V4_PRO = "deepseek-ai/DeepSeek-V4-Pro",
900
+ MOONSHOTAI_KIMI_K2_6 = "moonshotai/Kimi-K2.6",
901
+ ZAI_ORG_GLM_5_1 = "zai-org/GLM-5.1",
902
+ MINIMAXAI_MINIMAX_M2_5 = "MiniMaxAI/MiniMax-M2.5",
903
+ MOONSHOTAI_KIMI_K2_5 = "moonshotai/Kimi-K2.5",
904
+ ZAI_ORG_GLM_5 = "zai-org/GLM-5",
905
+ DEEPSEEK_AI_DEEPSEEK_V3_2 = "deepseek-ai/DeepSeek-V3.2",
906
+ MOONSHOTAI_KIMI_K2_THINKING = "moonshotai/Kimi-K2-Thinking",
907
+ ZAI_ORG_GLM_4_5_AIR = "zai-org/GLM-4.5-Air",
908
+ GOOGLE_GEMMA_4_26B_A4B_IT = "google/gemma-4-26b-a4b-it",
909
+ ZAI_ORG_GLM_4_7_FLASH = "zai-org/GLM-4.7-Flash",
910
+ ZAI_ORG_GLM_4_7 = "zai-org/GLM-4.7",
911
+ MOONSHOTAI_KIMI_K2_INSTRUCT_0905 = "moonshotai/Kimi-K2-Instruct-0905",
912
+ OPENAI_GPT_OSS_120B = "openai/gpt-oss-120b",
913
+ DEEPSEEK_AI_DEEPSEEK_R1_0528 = "deepseek-ai/DeepSeek-R1-0528",
914
+ ZAI_ORG_GLM_4_6 = "zai-org/GLM-4.6",
915
+ QWEN_QWEN3_NEXT_80B_A3B_INSTRUCT = "Qwen/Qwen3-Next-80B-A3B-Instruct",
916
+ INTEL_QWEN3_CODER_480B_A35B_INSTRUCT_INT4_MIXED_AR = "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar",
917
+ META_LLAMA_LLAMA_4_MAVERICK_17B_128E_INSTRUCT_FP8 = "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
918
+ MISTRALAI_MISTRAL_NEMO_INSTRUCT_2407 = "mistralai/Mistral-Nemo-Instruct-2407",
919
+ OPENAI_GPT_OSS_20B = "openai/gpt-oss-20b",
920
+ META_LLAMA_LLAMA_3_3_70B_INSTRUCT = "meta-llama/Llama-3.3-70B-Instruct"
921
+ }
857
922
  export declare enum MistralModels {
858
923
  MISTRAL_LARGE_LATEST = "mistral-large-latest",
859
924
  MISTRAL_LARGE_2512 = "mistral-large-2512",
@@ -918,6 +983,18 @@ export declare enum TogetherAIModels {
918
983
  GEMMA_2_27B_IT = "google/gemma-2-27b-it",
919
984
  WIZARDLM_2_8X22B = "microsoft/WizardLM-2-8x22B"
920
985
  }
986
+ export declare enum UpstageModels {
987
+ SOLAR_PRO4 = "solar-pro4",
988
+ SOLAR_PRO4_260806 = "solar-pro4-260806",
989
+ SOLAR_PRO3 = "solar-pro3",
990
+ SOLAR_PRO3_260323 = "solar-pro3-260323",
991
+ SOLAR_PRO2 = "solar-pro2",
992
+ SOLAR_PRO2_251215 = "solar-pro2-251215",
993
+ SOLAR_MINI = "solar-mini",
994
+ SOLAR_MINI_250422 = "solar-mini-250422",
995
+ SYN_PRO = "syn-pro",
996
+ SYN_PRO_251021 = "syn-pro-251021"
997
+ }
921
998
  export declare enum XaiModels {
922
999
  GROK_4_6 = "grok-4.6",
923
1000
  GROK_4_5 = "grok-4.5",
@@ -23,14 +23,19 @@ export var AIProviderName;
23
23
  AIProviderName["LM_STUDIO"] = "lm-studio";
24
24
  AIProviderName["LLAMACPP"] = "llamacpp";
25
25
  // ── BEGIN GENERATED(provider-members): provider catalog (pnpm run codegen:catalog) ──
26
+ AIProviderName["BASETEN"] = "baseten";
26
27
  AIProviderName["CEREBRAS"] = "cerebras";
27
28
  AIProviderName["CLOUDFLARE"] = "cloudflare";
28
29
  AIProviderName["FIREWORKS"] = "fireworks";
30
+ AIProviderName["GMICLOUD"] = "gmicloud";
29
31
  AIProviderName["GROQ"] = "groq";
32
+ AIProviderName["INCEPTION_LABS"] = "inception-labs";
33
+ AIProviderName["IO_INTELLIGENCE"] = "io-intelligence";
30
34
  AIProviderName["MISTRAL"] = "mistral";
31
35
  AIProviderName["PERPLEXITY"] = "perplexity";
32
36
  AIProviderName["SAMBANOVA"] = "sambanova";
33
37
  AIProviderName["TOGETHER_AI"] = "together-ai";
38
+ AIProviderName["UPSTAGE"] = "upstage";
34
39
  AIProviderName["XAI"] = "xai";
35
40
  // ── END GENERATED(provider-members) ──
36
41
  AIProviderName["COHERE"] = "cohere";
@@ -1057,6 +1062,25 @@ export var ReplicateModels;
1057
1062
  */
1058
1063
  export const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
1059
1064
  // ── BEGIN GENERATED(models-enums): provider catalog (pnpm run codegen:catalog) ──
1065
+ export var BasetenModels;
1066
+ (function (BasetenModels) {
1067
+ BasetenModels["OPENAI_GPT_OSS_120B"] = "openai/gpt-oss-120b";
1068
+ BasetenModels["ZAI_ORG_GLM_4_7"] = "zai-org/GLM-4.7";
1069
+ BasetenModels["MOONSHOTAI_KIMI_K2_6"] = "moonshotai/Kimi-K2.6";
1070
+ BasetenModels["DEEPSEEK_AI_DEEPSEEK_V4_PRO"] = "deepseek-ai/DeepSeek-V4-Pro";
1071
+ BasetenModels["NVIDIA_NVIDIA_NEMOTRON_3_ULTRA_550B_A55B"] = "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B";
1072
+ BasetenModels["ZAI_ORG_GLM_5_2"] = "zai-org/GLM-5.2";
1073
+ BasetenModels["MOONSHOTAI_KIMI_K2_7_CODE"] = "moonshotai/Kimi-K2.7-Code";
1074
+ BasetenModels["DEEPSEEK_AI_DEEPSEEK_V4_FLASH_0731"] = "deepseek-ai/DeepSeek-V4-Flash-0731";
1075
+ BasetenModels["THINKINGMACHINES_INKLING"] = "thinkingmachines/inkling";
1076
+ BasetenModels["ZAI_ORG_GLM_5_2_FAST"] = "zai-org/GLM-5.2-Fast";
1077
+ BasetenModels["MOONSHOTAI_KIMI_K3"] = "moonshotai/Kimi-K3";
1078
+ BasetenModels["THINKINGMACHINES_INKLING_SMALL"] = "thinkingmachines/inkling-small";
1079
+ BasetenModels["DEEPSEEK_AI_DEEPSEEK_V4_PRO_0813"] = "deepseek-ai/DeepSeek-V4-Pro-0813";
1080
+ BasetenModels["ZAI_ORG_GLM_5_3_FLASH"] = "zai-org/GLM-5.3-Flash";
1081
+ BasetenModels["ZAI_ORG_GLM_5_3"] = "zai-org/GLM-5.3";
1082
+ BasetenModels["ZAI_ORG_GLM_5_3_FAST"] = "zai-org/GLM-5.3-Fast";
1083
+ })(BasetenModels || (BasetenModels = {}));
1060
1084
  export var CerebrasModels;
1061
1085
  (function (CerebrasModels) {
1062
1086
  CerebrasModels["GPT_OSS_120B"] = "gpt-oss-120b";
@@ -1089,6 +1113,10 @@ export var FireworksModels;
1089
1113
  FireworksModels["LLAMA_V3P2_11B_VISION_INSTRUCT"] = "accounts/fireworks/models/llama-v3p2-11b-vision-instruct";
1090
1114
  FireworksModels["PHI_3_VISION_128K_INSTRUCT"] = "accounts/fireworks/models/phi-3-vision-128k-instruct";
1091
1115
  })(FireworksModels || (FireworksModels = {}));
1116
+ export var GmicloudModels;
1117
+ (function (GmicloudModels) {
1118
+ GmicloudModels["MINIMAXAI_MINIMAX_M3"] = "MiniMaxAI/MiniMax-M3";
1119
+ })(GmicloudModels || (GmicloudModels = {}));
1092
1120
  export var GroqModels;
1093
1121
  (function (GroqModels) {
1094
1122
  GroqModels["GPT_OSS_120B"] = "openai/gpt-oss-120b";
@@ -1106,6 +1134,47 @@ export var GroqModels;
1106
1134
  GroqModels["LLAMA_3_2_90B_VISION_PREVIEW"] = "llama-3.2-90b-vision-preview";
1107
1135
  GroqModels["LLAMA_3_2_11B_VISION_PREVIEW"] = "llama-3.2-11b-vision-preview";
1108
1136
  })(GroqModels || (GroqModels = {}));
1137
+ export var InceptionLabsModels;
1138
+ (function (InceptionLabsModels) {
1139
+ InceptionLabsModels["MERCURY_2"] = "mercury-2";
1140
+ })(InceptionLabsModels || (InceptionLabsModels = {}));
1141
+ export var IoIntelligenceModels;
1142
+ (function (IoIntelligenceModels) {
1143
+ IoIntelligenceModels["ZAI_ORG_GLM_5_3_FLASH"] = "zai-org/GLM-5.3-Flash";
1144
+ IoIntelligenceModels["ZAI_ORG_GLM_5_3"] = "zai-org/GLM-5.3";
1145
+ IoIntelligenceModels["QWEN_QWEN3_8_27B"] = "Qwen/Qwen3.8-27B";
1146
+ IoIntelligenceModels["DEEPSEEK_AI_DEEPSEEK_V4_FLASH_0731"] = "deepseek-ai/DeepSeek-V4-Flash-0731";
1147
+ IoIntelligenceModels["MOONSHOTAI_KIMI_K3"] = "moonshotai/Kimi-K3";
1148
+ IoIntelligenceModels["XIAOMIMIMO_MIMO_V2_5"] = "XiaomiMiMo/MiMo-V2.5";
1149
+ IoIntelligenceModels["ZAI_ORG_GLM_5_2"] = "zai-org/GLM-5.2";
1150
+ IoIntelligenceModels["MOONSHOTAI_KIMI_K2_7_CODE"] = "moonshotai/Kimi-K2.7-Code";
1151
+ IoIntelligenceModels["QWEN_QWEN3_6_35B_A3B"] = "Qwen/Qwen3.6-35B-A3B";
1152
+ IoIntelligenceModels["QWEN_QWEN3_6_27B"] = "Qwen/Qwen3.6-27B";
1153
+ IoIntelligenceModels["MINIMAXAI_MINIMAX_M2_7"] = "MiniMaxAI/MiniMax-M2.7";
1154
+ IoIntelligenceModels["DEEPSEEK_AI_DEEPSEEK_V4_FLASH"] = "deepseek-ai/DeepSeek-V4-Flash";
1155
+ IoIntelligenceModels["DEEPSEEK_AI_DEEPSEEK_V4_PRO"] = "deepseek-ai/DeepSeek-V4-Pro";
1156
+ IoIntelligenceModels["MOONSHOTAI_KIMI_K2_6"] = "moonshotai/Kimi-K2.6";
1157
+ IoIntelligenceModels["ZAI_ORG_GLM_5_1"] = "zai-org/GLM-5.1";
1158
+ IoIntelligenceModels["MINIMAXAI_MINIMAX_M2_5"] = "MiniMaxAI/MiniMax-M2.5";
1159
+ IoIntelligenceModels["MOONSHOTAI_KIMI_K2_5"] = "moonshotai/Kimi-K2.5";
1160
+ IoIntelligenceModels["ZAI_ORG_GLM_5"] = "zai-org/GLM-5";
1161
+ IoIntelligenceModels["DEEPSEEK_AI_DEEPSEEK_V3_2"] = "deepseek-ai/DeepSeek-V3.2";
1162
+ IoIntelligenceModels["MOONSHOTAI_KIMI_K2_THINKING"] = "moonshotai/Kimi-K2-Thinking";
1163
+ IoIntelligenceModels["ZAI_ORG_GLM_4_5_AIR"] = "zai-org/GLM-4.5-Air";
1164
+ IoIntelligenceModels["GOOGLE_GEMMA_4_26B_A4B_IT"] = "google/gemma-4-26b-a4b-it";
1165
+ IoIntelligenceModels["ZAI_ORG_GLM_4_7_FLASH"] = "zai-org/GLM-4.7-Flash";
1166
+ IoIntelligenceModels["ZAI_ORG_GLM_4_7"] = "zai-org/GLM-4.7";
1167
+ IoIntelligenceModels["MOONSHOTAI_KIMI_K2_INSTRUCT_0905"] = "moonshotai/Kimi-K2-Instruct-0905";
1168
+ IoIntelligenceModels["OPENAI_GPT_OSS_120B"] = "openai/gpt-oss-120b";
1169
+ IoIntelligenceModels["DEEPSEEK_AI_DEEPSEEK_R1_0528"] = "deepseek-ai/DeepSeek-R1-0528";
1170
+ IoIntelligenceModels["ZAI_ORG_GLM_4_6"] = "zai-org/GLM-4.6";
1171
+ IoIntelligenceModels["QWEN_QWEN3_NEXT_80B_A3B_INSTRUCT"] = "Qwen/Qwen3-Next-80B-A3B-Instruct";
1172
+ IoIntelligenceModels["INTEL_QWEN3_CODER_480B_A35B_INSTRUCT_INT4_MIXED_AR"] = "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar";
1173
+ IoIntelligenceModels["META_LLAMA_LLAMA_4_MAVERICK_17B_128E_INSTRUCT_FP8"] = "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8";
1174
+ IoIntelligenceModels["MISTRALAI_MISTRAL_NEMO_INSTRUCT_2407"] = "mistralai/Mistral-Nemo-Instruct-2407";
1175
+ IoIntelligenceModels["OPENAI_GPT_OSS_20B"] = "openai/gpt-oss-20b";
1176
+ IoIntelligenceModels["META_LLAMA_LLAMA_3_3_70B_INSTRUCT"] = "meta-llama/Llama-3.3-70B-Instruct";
1177
+ })(IoIntelligenceModels || (IoIntelligenceModels = {}));
1109
1178
  export var MistralModels;
1110
1179
  (function (MistralModels) {
1111
1180
  MistralModels["MISTRAL_LARGE_LATEST"] = "mistral-large-latest";
@@ -1174,6 +1243,19 @@ export var TogetherAIModels;
1174
1243
  TogetherAIModels["GEMMA_2_27B_IT"] = "google/gemma-2-27b-it";
1175
1244
  TogetherAIModels["WIZARDLM_2_8X22B"] = "microsoft/WizardLM-2-8x22B";
1176
1245
  })(TogetherAIModels || (TogetherAIModels = {}));
1246
+ export var UpstageModels;
1247
+ (function (UpstageModels) {
1248
+ UpstageModels["SOLAR_PRO4"] = "solar-pro4";
1249
+ UpstageModels["SOLAR_PRO4_260806"] = "solar-pro4-260806";
1250
+ UpstageModels["SOLAR_PRO3"] = "solar-pro3";
1251
+ UpstageModels["SOLAR_PRO3_260323"] = "solar-pro3-260323";
1252
+ UpstageModels["SOLAR_PRO2"] = "solar-pro2";
1253
+ UpstageModels["SOLAR_PRO2_251215"] = "solar-pro2-251215";
1254
+ UpstageModels["SOLAR_MINI"] = "solar-mini";
1255
+ UpstageModels["SOLAR_MINI_250422"] = "solar-mini-250422";
1256
+ UpstageModels["SYN_PRO"] = "syn-pro";
1257
+ UpstageModels["SYN_PRO_251021"] = "syn-pro-251021";
1258
+ })(UpstageModels || (UpstageModels = {}));
1177
1259
  export var XaiModels;
1178
1260
  (function (XaiModels) {
1179
1261
  XaiModels["GROK_4_6"] = "grok-4.6";
@@ -39,9 +39,6 @@ export declare function resolveTurnBudget(options: TextGenerationOptions, turnSt
39
39
  wrapupLeadMs: number;
40
40
  turnDeadline: number | undefined;
41
41
  };
42
- /**
43
- * GenerationHandler class - Handles text generation operations for AI providers
44
- */
45
42
  export declare class GenerationHandler {
46
43
  private readonly providerName;
47
44
  private readonly modelName;
@@ -127,6 +124,21 @@ export declare class GenerationHandler {
127
124
  /**
128
125
  * Format the enhanced result
129
126
  */
127
+ /**
128
+ * One re-ask with `toolChoice: "none"` when a tool loop ends on a
129
+ * `tool-calls` finish that carries neither a tool call nor any text.
130
+ *
131
+ * io.net's Llama endpoint does exactly this on the step after a tool result
132
+ * when the caller asked for JSON: the model's JSON-shaped answer trips the
133
+ * vendor's tool-call parser, which drops it and reports
134
+ * `finish_reason: tool_calls` with `content: null` and no `tool_calls`.
135
+ * The AI-SDK loop has nothing to execute and stops, so the caller gets an
136
+ * empty turn although the tool ran. Replaying that request with
137
+ * `tool_choice: "none"` (or no tool list) returns the answer — verified on
138
+ * the wire, 4/4 — so the recovery is one bounded extra step that keeps the
139
+ * executed tool steps and usage in the returned result.
140
+ */
141
+ private recoverEmptyToolCallsFinish;
130
142
  formatEnhancedResult(generateResult: Awaited<ReturnType<typeof generateText>>, tools: Record<string, Tool>, toolsUsed: string[], toolExecutions: ToolExecutionRecord[], options: TextGenerationOptions): EnhancedGenerateResult;
131
143
  /**
132
144
  * Analyze AI response structure and log detailed debugging information
@@ -157,6 +157,53 @@ function buildWrapupStepResult(prepared, stepMessages) {
157
157
  /**
158
158
  * GenerationHandler class - Handles text generation operations for AI providers
159
159
  */
160
+ /**
161
+ * Append a schema-derived JSON instruction to the hoisted system prompt for
162
+ * the structured-output fallback retry. Phrased for tool loops too: the model
163
+ * may still call tools first; only the final answer must be the object.
164
+ */
165
+ function appendJsonInstruction(system, schema) {
166
+ const jsonSchema = JSON.stringify(convertZodToJsonSchema(schema));
167
+ const instruction = "When you give your final answer, respond with only a single JSON object that conforms to the following JSON Schema. " +
168
+ "No prose before or after it, and no markdown code fence. " +
169
+ `JSON Schema: ${jsonSchema}`;
170
+ const existing = system ?? [];
171
+ const last = existing[existing.length - 1];
172
+ // Merge into the caller's own system message when there is one, the way
173
+ // messageBuilder folds STRUCTURED_OUTPUT_INSTRUCTIONS into a single system
174
+ // turn — several self-hosted OpenAI-compatible stacks only honour one
175
+ // leading system message.
176
+ if (last !== undefined && typeof last.content === "string") {
177
+ return [
178
+ ...existing.slice(0, -1),
179
+ { ...last, content: `${last.content}\n\n${instruction}` },
180
+ ];
181
+ }
182
+ return [...existing, { role: "system", content: instruction }];
183
+ }
184
+ /** Sum two AI-SDK usage records leaf by leaf (the v6 shape nests token
185
+ * details); undefined counts are treated as absent, not zero. */
186
+ function addUsage(a, b) {
187
+ const sum = (x, y) => {
188
+ if (typeof x === "number" || typeof y === "number") {
189
+ return (typeof x === "number" ? x : 0) + (typeof y === "number" ? y : 0);
190
+ }
191
+ if (x && y && typeof x === "object" && typeof y === "object") {
192
+ const left = x;
193
+ const right = y;
194
+ const out = {};
195
+ for (const key of new Set([
196
+ ...Object.keys(left),
197
+ ...Object.keys(right),
198
+ ])) {
199
+ out[key] = sum(left[key], right[key]);
200
+ }
201
+ return out;
202
+ }
203
+ return x ?? y;
204
+ };
205
+ return sum(a, b);
206
+ }
160
207
  export class GenerationHandler {
161
208
  providerName;
162
209
  modelName;
@@ -192,7 +239,7 @@ export class GenerationHandler {
192
239
  * @private
193
240
  */
194
241
  async callGenerateText(model, messages, tools, options, callConfig) {
195
- const { shouldUseTools, includeStructuredOutput, turnStartMs } = callConfig;
242
+ const { shouldUseTools, includeStructuredOutput, turnStartMs, promptJsonInstruction, } = callConfig;
196
243
  // Check if this is a Google provider (for provider-specific options)
197
244
  const isGoogleProvider = this.providerName === "google-ai" || this.providerName === "vertex";
198
245
  // Check if this is an Anthropic provider (includes Vertex+Claude)
@@ -259,7 +306,17 @@ export class GenerationHandler {
259
306
  // Hoist system-role messages into generateText's top-level `system` option
260
307
  // rather than passing them inside `messages` (deprecated by the AI SDK,
261
308
  // rejected in v7). See extractSystemMessages for the rationale. (#1024)
262
- const { system, messages: nonSystemMessages } = extractSystemMessages(messages);
309
+ const { system: hoistedSystem, messages: nonSystemMessages } = extractSystemMessages(messages);
310
+ // Structured-output fallback: spell the schema out in the prompt. The
311
+ // native `response_format` attempt already failed to yield an object, and
312
+ // a vendor that ignores `response_format` outright (GMI Cloud's MiniMax
313
+ // endpoint answers a strict json_schema request in markdown) would answer
314
+ // a silent retry the same way. With the schema in the system prompt the
315
+ // same text-coercion path in formatEnhancedResult recovers a schema-valid
316
+ // object — verified live on that endpoint, 3/3.
317
+ const system = promptJsonInstruction && options.schema
318
+ ? appendJsonInstruction(hoistedSystem, options.schema)
319
+ : hoistedSystem;
263
320
  // Per-step context budget guard: the tool loop appends assistant turns and
264
321
  // tool results on every step — growth the pre-call budget check never
265
322
  // sees. Estimate each step's projected request and deterministically
@@ -509,12 +566,19 @@ export class GenerationHandler {
509
566
  });
510
567
  }
511
568
  // Set token usage and completion attributes on span
512
- this.setUsageSpanAttributes(span, result);
513
- if (result.finishReason) {
514
- span.setAttribute("gen_ai.response.finish_reason", result.finishReason);
569
+ // Span attributes come from the RECOVERED result: a successful
570
+ // toolChoice:"none" re-ask changes the finish reason and adds usage.
571
+ const recovered = await this.recoverEmptyToolCallsFinish(model, messages, tools, options, {
572
+ shouldUseTools,
573
+ includeStructuredOutput: true,
574
+ turnStartMs: genStartTime,
575
+ }, { result, span });
576
+ this.setUsageSpanAttributes(span, recovered);
577
+ if (recovered.finishReason) {
578
+ span.setAttribute("gen_ai.response.finish_reason", recovered.finishReason);
515
579
  }
516
580
  span.setStatus({ code: SpanStatusCode.OK });
517
- return result;
581
+ return recovered;
518
582
  }
519
583
  catch (error) {
520
584
  // Fall back to text-mode (no experimental_output) when structured
@@ -567,11 +631,13 @@ export class GenerationHandler {
567
631
  // includeStructuredOutput intentionally omitted
568
632
  includeStructuredOutput: false,
569
633
  turnStartMs: genStartTime,
634
+ promptJsonInstruction: true,
570
635
  }), span, "generateText(fallback)");
571
636
  // NLK-GAP-007: Record recovery event after successful fallback
572
637
  span.addEvent("retry.recovered", {
573
638
  "retry.attempts": 2,
574
639
  "retry.strategy": "structured_output_disabled",
640
+ "retry.prompt_json_instruction": true,
575
641
  });
576
642
  span.setAttribute("retry.count", 1);
577
643
  logger.info("[GenerationHandler] generateText returned (fallback)", {
@@ -582,12 +648,20 @@ export class GenerationHandler {
582
648
  toolCallsTotal: result.toolCalls?.length || 0,
583
649
  responseChars: result.text?.length || 0,
584
650
  });
585
- this.setUsageSpanAttributes(span, result);
586
- if (result.finishReason) {
587
- span.setAttribute("gen_ai.response.finish_reason", result.finishReason);
651
+ // Span attributes come from the RECOVERED result: a successful
652
+ // toolChoice:"none" re-ask changes the finish reason and adds usage.
653
+ const recovered = await this.recoverEmptyToolCallsFinish(model, messages, tools, options, {
654
+ shouldUseTools,
655
+ includeStructuredOutput: false,
656
+ turnStartMs: genStartTime,
657
+ promptJsonInstruction: true,
658
+ }, { result, span });
659
+ this.setUsageSpanAttributes(span, recovered);
660
+ if (recovered.finishReason) {
661
+ span.setAttribute("gen_ai.response.finish_reason", recovered.finishReason);
588
662
  }
589
663
  span.setStatus({ code: SpanStatusCode.OK });
590
- return result;
664
+ return recovered;
591
665
  }
592
666
  // Retry once without `temperature` when the model deprecated it. The
593
667
  // newest Anthropic models (e.g. claude-opus-4-8 with tools + advanced
@@ -619,12 +693,19 @@ export class GenerationHandler {
619
693
  "retry.strategy": "temperature_omitted",
620
694
  });
621
695
  span.setAttribute("retry.count", 1);
622
- this.setUsageSpanAttributes(span, result);
623
- if (result.finishReason) {
624
- span.setAttribute("gen_ai.response.finish_reason", result.finishReason);
696
+ // Span attributes come from the RECOVERED result: a successful
697
+ // toolChoice:"none" re-ask changes the finish reason and adds usage.
698
+ const recovered = await this.recoverEmptyToolCallsFinish(model, messages, tools, { ...options, temperature: undefined }, {
699
+ shouldUseTools,
700
+ includeStructuredOutput: true,
701
+ turnStartMs: genStartTime,
702
+ }, { result, span });
703
+ this.setUsageSpanAttributes(span, recovered);
704
+ if (recovered.finishReason) {
705
+ span.setAttribute("gen_ai.response.finish_reason", recovered.finishReason);
625
706
  }
626
707
  span.setStatus({ code: SpanStatusCode.OK });
627
- return result;
708
+ return recovered;
628
709
  }
629
710
  span.setStatus({
630
711
  code: SpanStatusCode.ERROR,
@@ -774,6 +855,83 @@ export class GenerationHandler {
774
855
  /**
775
856
  * Format the enhanced result
776
857
  */
858
+ /**
859
+ * One re-ask with `toolChoice: "none"` when a tool loop ends on a
860
+ * `tool-calls` finish that carries neither a tool call nor any text.
861
+ *
862
+ * io.net's Llama endpoint does exactly this on the step after a tool result
863
+ * when the caller asked for JSON: the model's JSON-shaped answer trips the
864
+ * vendor's tool-call parser, which drops it and reports
865
+ * `finish_reason: tool_calls` with `content: null` and no `tool_calls`.
866
+ * The AI-SDK loop has nothing to execute and stops, so the caller gets an
867
+ * empty turn although the tool ran. Replaying that request with
868
+ * `tool_choice: "none"` (or no tool list) returns the answer — verified on
869
+ * the wire, 4/4 — so the recovery is one bounded extra step that keeps the
870
+ * executed tool steps and usage in the returned result.
871
+ */
872
+ async recoverEmptyToolCallsFinish(model, messages, tools, options, callConfig, attempt) {
873
+ const { result, span } = attempt;
874
+ // The re-ask is one more loop step, so it must fit the caller's step
875
+ // budget: when the loop already spent every step, the honest answer is
876
+ // the step-cap stop the caller configured, not an extra request.
877
+ const maxSteps = options.maxSteps ?? DEFAULT_MAX_STEPS;
878
+ const emptyToolCallsFinish = callConfig.shouldUseTools &&
879
+ !callConfig.isToolReask &&
880
+ Object.keys(tools).length > 0 &&
881
+ result.finishReason === "tool-calls" &&
882
+ result.toolCalls.length === 0 &&
883
+ (result.text ?? "").trim().length === 0 &&
884
+ result.steps.length < maxSteps;
885
+ if (!emptyToolCallsFinish) {
886
+ return result;
887
+ }
888
+ logger.warn("[GenerationHandler] tool loop ended on a tool-calls finish with no tool call and no text — re-asking once with toolChoice: none", {
889
+ provider: this.providerName,
890
+ model: this.modelName,
891
+ stepsSoFar: result.steps.length,
892
+ });
893
+ span.setAttribute("neurolink.has_fallback", true);
894
+ span.addEvent("retry.initial_failure", {
895
+ "retry.attempt": 1,
896
+ "retry.reason": "empty_tool_calls_finish",
897
+ });
898
+ let reask;
899
+ try {
900
+ reask = await withProviderRetry(() => this.callGenerateText(model, [...messages, ...result.response.messages], tools, { ...options, toolChoice: "none", maxSteps: 1 }, { ...callConfig, isToolReask: true }), span, "generateText(tool-choice-none)");
901
+ }
902
+ catch (reaskError) {
903
+ // The re-ask is a bonus request on top of a call that already
904
+ // succeeded. If it fails for any reason, hand back the original result
905
+ // — the executed tool steps are still in it — rather than turning a
906
+ // degraded turn into a thrown one.
907
+ logger.warn("[GenerationHandler] toolChoice: none re-ask failed; returning the original result", {
908
+ provider: this.providerName,
909
+ model: this.modelName,
910
+ error: reaskError instanceof Error
911
+ ? reaskError.message
912
+ : String(reaskError),
913
+ });
914
+ span.addEvent("retry.failed", {
915
+ "retry.attempts": 2,
916
+ "retry.strategy": "tool_choice_none_reask",
917
+ });
918
+ return result;
919
+ }
920
+ span.addEvent("retry.recovered", {
921
+ "retry.attempts": 2,
922
+ "retry.strategy": "tool_choice_none_reask",
923
+ });
924
+ span.setAttribute("retry.count", 1);
925
+ // Keep the executed tool steps in front of the re-ask so
926
+ // extractToolInformation still reports them and usage stays a true
927
+ // cross-step total. `steps` and `totalUsage` are constructor-assigned
928
+ // fields on the AI-SDK result; every other accessor derives from the
929
+ // final step, which is now the re-ask's answer.
930
+ const merged = reask;
931
+ merged.steps = [...result.steps, ...reask.steps];
932
+ merged.totalUsage = addUsage(result.totalUsage, reask.totalUsage);
933
+ return reask;
934
+ }
777
935
  formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions, options) {
778
936
  // Structured output check — schema alone is sufficient to activate
779
937
  const useStructuredOutput = !!options.schema ||