@nexus-cortex/core 4.124.44 → 4.124.46

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 (41) hide show
  1. package/.env.defaults +144 -0
  2. package/dist/adapters/GatewayTranslationLayer.d.ts.map +1 -1
  3. package/dist/adapters/GatewayTranslationLayer.js +27 -1
  4. package/dist/adapters/GatewayTranslationLayer.js.map +1 -1
  5. package/dist/adapters/ServerSideToolDetection.d.ts.map +1 -1
  6. package/dist/adapters/ServerSideToolDetection.js +5 -1
  7. package/dist/adapters/ServerSideToolDetection.js.map +1 -1
  8. package/dist/config/effectiveConfig.d.ts.map +1 -1
  9. package/dist/config/effectiveConfig.js +16 -0
  10. package/dist/config/effectiveConfig.js.map +1 -1
  11. package/dist/orchestrator/APIClient.d.ts.map +1 -1
  12. package/dist/orchestrator/APIClient.js +120 -25
  13. package/dist/orchestrator/APIClient.js.map +1 -1
  14. package/dist/orchestrator/CortexOrchestrator.d.ts +22 -0
  15. package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
  16. package/dist/orchestrator/CortexOrchestrator.js +210 -83
  17. package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
  18. package/dist/orchestrator/appendOnlyHistory.d.ts +39 -0
  19. package/dist/orchestrator/appendOnlyHistory.d.ts.map +1 -0
  20. package/dist/orchestrator/appendOnlyHistory.js +79 -0
  21. package/dist/orchestrator/appendOnlyHistory.js.map +1 -0
  22. package/dist/orchestrator/transportCacheFixes.d.ts +22 -0
  23. package/dist/orchestrator/transportCacheFixes.d.ts.map +1 -0
  24. package/dist/orchestrator/transportCacheFixes.js +114 -0
  25. package/dist/orchestrator/transportCacheFixes.js.map +1 -0
  26. package/dist/orchestrator/transportEffortFixes.d.ts +62 -0
  27. package/dist/orchestrator/transportEffortFixes.d.ts.map +1 -0
  28. package/dist/orchestrator/transportEffortFixes.js +114 -0
  29. package/dist/orchestrator/transportEffortFixes.js.map +1 -0
  30. package/dist/orchestrator/wallGuard.d.ts +3 -1
  31. package/dist/orchestrator/wallGuard.d.ts.map +1 -1
  32. package/dist/orchestrator/wallGuard.js +15 -2
  33. package/dist/orchestrator/wallGuard.js.map +1 -1
  34. package/dist/tools/ClientSideToolFilter.d.ts +9 -2
  35. package/dist/tools/ClientSideToolFilter.d.ts.map +1 -1
  36. package/dist/tools/ClientSideToolFilter.js +26 -3
  37. package/dist/tools/ClientSideToolFilter.js.map +1 -1
  38. package/dist/training/DecisionStore.d.ts +1 -1
  39. package/dist/training/DecisionStore.d.ts.map +1 -1
  40. package/dist/training/DecisionStore.js.map +1 -1
  41. package/package.json +3 -3
@@ -20,6 +20,8 @@ import { v4 as uuidv4 } from 'uuid';
20
20
  import { assertCompletionBody } from './completionBodyGuard.js';
21
21
  import { parseHFCompletion, normalizeToolCallArguments } from '../models/hfSpace/normalize.js';
22
22
  import { isResponsesInlineRemindersEnabled, inlineRemindersForResponses, isResponsesStopReasonEnabled, isAnthropicHistoryCacheEnabled, applyAnthropicHistoryCache, countAnthropicBreakpoints, } from './transportFixes.js';
23
+ import { isGeminiSystemFixEnabled, isOpenAICacheKeyEnabled, isStreamUsageEnabled, applyAnthropicCacheLevers, streamFinalUsage, } from './transportCacheFixes.js';
24
+ import { isAnthropicEffortEnabled, isGeminiToolsThinkingEnabled, anthropicOutputEffort, anthropicMaxEffortBudget, } from './transportEffortFixes.js';
23
25
  import { anthropicCredentialService, CredentialError } from '../config/AnthropicCredentialService.js';
24
26
  /**
25
27
  * Anthropic families on the adaptive-thinking-only request surface — sending
@@ -359,6 +361,10 @@ export class APIClient {
359
361
  if (enableCaching && isAnthropicHistoryCacheEnabled()) {
360
362
  anthropicRequest.messages = applyAnthropicHistoryCache(anthropicRequest.messages, countAnthropicBreakpoints({ system: anthropicRequest.system, tools: anthropicRequest.tools })).messages;
361
363
  }
364
+ // R225 (DARK): CORTEX_ANTHROPIC_AUTO_CACHE top-level automatic cache_control (free slot only) +
365
+ // CORTEX_ANTHROPIC_CACHE_TTL=1h on every marker. Anthropic only; no-op with both flags off.
366
+ if (enableCaching)
367
+ applyAnthropicCacheLevers(anthropicRequest);
362
368
  // Phase 2.8: Enable extended thinking for Claude 4+ models with reasoning support
363
369
  // Requires beta header: anthropic-beta: interleaved-thinking-2025-05-14
364
370
  // Skip if disableThinking option is set (used for continuation requests)
@@ -377,6 +383,11 @@ export class APIClient {
377
383
  process.env.DEBUG_THINKING === 'true'
378
384
  ? { type: 'adaptive', display: 'summarized' }
379
385
  : { type: 'adaptive' };
386
+ // DARK (CORTEX_ANTHROPIC_EFFORT): the action/request effort reaches adaptive families as output_config.effort.
387
+ const outputEffort = isAnthropicEffortEnabled() ? anthropicOutputEffort(reasoningEffort) : undefined;
388
+ if (outputEffort) {
389
+ anthropicRequest.output_config = { ...(anthropicRequest.output_config || {}), effort: outputEffort };
390
+ }
380
391
  }
381
392
  else {
382
393
  const budgetMap = {
@@ -385,7 +396,11 @@ export class APIClient {
385
396
  medium: 10000,
386
397
  high: 50000,
387
398
  };
388
- const budgetTokens = reasoningEffort ? (budgetMap[reasoningEffort] || 10000) : 10000;
399
+ // DARK (CORTEX_ANTHROPIC_EFFORT): 'max' gets a large budget clamped below max_tokens (was the 10000 fallback).
400
+ const maxBudget = isAnthropicEffortEnabled() && reasoningEffort === 'max'
401
+ ? anthropicMaxEffortBudget(anthropicRequest.max_tokens)
402
+ : undefined;
403
+ const budgetTokens = maxBudget ?? (reasoningEffort ? (budgetMap[reasoningEffort] || 10000) : 10000);
389
404
  anthropicRequest.thinking = {
390
405
  type: 'enabled',
391
406
  budget_tokens: budgetTokens
@@ -742,6 +757,8 @@ export class APIClient {
742
757
  // deepseek runs (reasoning + tools, no forced choice) are fine — so drop reasoning ONLY when a
743
758
  // tool_choice is actually forced. OpenAI's R19b case (tools alone) stays as-is.
744
759
  const hasForcedToolChoice = !!request.toolChoice;
760
+ // R19b STAYS (live probe 2026-10-03): gpt-5.4-nano AND gpt-5.6-luna still 400 "Function tools with reasoning_effort are not
761
+ // supported … in /v1/chat/completions. To use function tools, use /v1/responses" — CORTEX_OPENAI_TOOLS_REASONING is Responses-only.
745
762
  const dropReasoningForToolCompat = (willAttachTools && isChatCompletionsRoute && modelConfig.provider === 'openai') ||
746
763
  (hasForcedToolChoice && isChatCompletionsRoute && modelConfig.provider === 'deepseek');
747
764
  // Enable reasoning for OpenAI models that support it (GPT-5 family, o-series)
@@ -784,6 +801,16 @@ export class APIClient {
784
801
  chatRequest.tools = request.tools;
785
802
  this.applyToolChoice(chatRequest, request, 'chat/completions'); // §13-B1 forced tool_choice
786
803
  }
804
+ // R225 (DARK, CORTEX_OPENAI_CACHE_KEY): OpenAI routes same-key requests to the same prompt cache.
805
+ // OpenAI provider only (other chat/completions providers may reject the unknown field).
806
+ if (isOpenAICacheKeyEnabled() && modelConfig.provider === 'openai' && request.conversationId) {
807
+ chatRequest.prompt_cache_key = request.conversationId;
808
+ }
809
+ // R224a (DARK, CORTEX_STREAM_USAGE): ask for the final usage chunk on streaming requests (otherwise the
810
+ // reassembled message reports zeros). Body-level, after the prefix — cache-neutral.
811
+ if (opts.stream && isStreamUsageEnabled()) {
812
+ chatRequest.stream_options = { include_usage: true };
813
+ }
787
814
  this.logChatCompletionsPayload(chatRequest, request.modelId, opts.stream);
788
815
  return { client, chatRequest };
789
816
  }
@@ -1054,6 +1081,10 @@ export class APIClient {
1054
1081
  // opt-in ZDR mode: "DESIGN NOTE — stateless ZDR mode" in
1055
1082
  // ResponsesAPIAdapter.ts (adapters/).
1056
1083
  }
1084
+ // R225 (DARK, CORTEX_OPENAI_CACHE_KEY): the same session-scoped cache routing key for OpenAI Responses.
1085
+ if (isOpenAICacheKeyEnabled() && modelConfig.provider?.toLowerCase() === 'openai' && request.conversationId) {
1086
+ responsesRequest.prompt_cache_key = request.conversationId;
1087
+ }
1057
1088
  // Set system context as instructions (enables provider-side caching).
1058
1089
  // R63: request.systemMessage (the R28 static system prompt) was previously
1059
1090
  // dropped on this path — only <system-reminder> text mined from user
@@ -1165,6 +1196,12 @@ export class APIClient {
1165
1196
  if (request.tools && request.tools.length > 0) {
1166
1197
  return this.sendGenerateContentHTTP(request, modelConfig, apiKey);
1167
1198
  }
1199
+ // R229 (DARK, CORTEX_GEMINI_SYSTEM_FIX): the @google/generative-ai SDK (v0.2.1, v1 endpoint) has no
1200
+ // systemInstruction support, so a request carrying a system prompt goes through the v1beta REST builder,
1201
+ // which sends it as system_instruction.
1202
+ if (isGeminiSystemFixEnabled() && request.systemMessage) {
1203
+ return this.sendGenerateContentHTTP(request, modelConfig, apiKey);
1204
+ }
1168
1205
  // For non-tool requests, use SDK as before
1169
1206
  if (!this.googleClient) {
1170
1207
  this.googleClient = new GoogleGenerativeAI(apiKey);
@@ -1212,6 +1249,11 @@ export class APIClient {
1212
1249
  contents: request.messages,
1213
1250
  generation_config: generationConfig // Use the nested object directly
1214
1251
  };
1252
+ // R229 (DARK, CORTEX_GEMINI_SYSTEM_FIX): deliver the system prompt (the streaming HTTP builder always did;
1253
+ // this non-streaming builder never did). Same snake_case shape as streamGenerateContentHTTP.
1254
+ if (isGeminiSystemFixEnabled() && request.systemMessage) {
1255
+ requestBody.system_instruction = { parts: [{ text: request.systemMessage }] };
1256
+ }
1215
1257
  // Add tools if present
1216
1258
  if (request.tools && request.tools.length > 0) {
1217
1259
  // REST API uses snake_case "function_declarations", not camelCase "functionDeclarations"
@@ -1353,6 +1395,9 @@ export class APIClient {
1353
1395
  if (enableCaching && isAnthropicHistoryCacheEnabled()) {
1354
1396
  anthropicRequest.messages = applyAnthropicHistoryCache(anthropicRequest.messages, countAnthropicBreakpoints({ system: anthropicRequest.system, tools: anthropicRequest.tools })).messages;
1355
1397
  }
1398
+ // R225 (DARK): automatic cache_control + 1h TTL — mirrors sendAnthropicMessagesAPI.
1399
+ if (enableCaching)
1400
+ applyAnthropicCacheLevers(anthropicRequest);
1356
1401
  // Phase 2.8: Enable extended thinking for Claude 4+ models with reasoning support
1357
1402
  // Requires beta header: anthropic-beta: interleaved-thinking-2025-05-14
1358
1403
  // Skip if disableThinking option is set (used for continuation requests)
@@ -1370,6 +1415,11 @@ export class APIClient {
1370
1415
  process.env.DEBUG_THINKING === 'true'
1371
1416
  ? { type: 'adaptive', display: 'summarized' }
1372
1417
  : { type: 'adaptive' };
1418
+ // DARK (CORTEX_ANTHROPIC_EFFORT): parity with the non-streaming path.
1419
+ const outputEffort = isAnthropicEffortEnabled() ? anthropicOutputEffort(reasoningEffort) : undefined;
1420
+ if (outputEffort) {
1421
+ anthropicRequest.output_config = { ...(anthropicRequest.output_config || {}), effort: outputEffort };
1422
+ }
1373
1423
  if (process.env.DEBUG === 'true' || process.env.DEBUG_THINKING === 'true') {
1374
1424
  console.log(`[DEBUG APIClient] Adaptive thinking for ${modelConfig.id}${process.env.DEBUG_THINKING === 'true' ? ' (display: summarized)' : ''}`);
1375
1425
  }
@@ -1381,7 +1431,11 @@ export class APIClient {
1381
1431
  medium: 10000,
1382
1432
  high: 50000,
1383
1433
  };
1384
- const budgetTokens = reasoningEffort ? (budgetMap[reasoningEffort] || 10000) : 10000;
1434
+ // DARK (CORTEX_ANTHROPIC_EFFORT): parity with the non-streaming path ('max' → clamped large budget).
1435
+ const maxBudget = isAnthropicEffortEnabled() && reasoningEffort === 'max'
1436
+ ? anthropicMaxEffortBudget(anthropicRequest.max_tokens)
1437
+ : undefined;
1438
+ const budgetTokens = maxBudget ?? (reasoningEffort ? (budgetMap[reasoningEffort] || 10000) : 10000);
1385
1439
  anthropicRequest.thinking = {
1386
1440
  type: 'enabled',
1387
1441
  budget_tokens: budgetTokens
@@ -1981,6 +2035,9 @@ export class APIClient {
1981
2035
  // R153 (2026-09-16): keep the provider's LAST non-null finish_reason so a `length` cutoff survives reassembly
1982
2036
  // (the empty-response classifier keys `truncated` on it; the old hardcode reported every text turn as `stop`).
1983
2037
  let lastFinishReason = null;
2038
+ // R224a (DARK, CORTEX_STREAM_USAGE): the provider's final usage chunk (choices: []) when requested.
2039
+ const captureStreamUsage = isStreamUsageEnabled();
2040
+ let streamUsage = null;
1984
2041
  // Capture this context before entering generator
1985
2042
  const self = this;
1986
2043
  // Create async generator that both yields chunks AND accumulates final message
@@ -1994,7 +2051,9 @@ export class APIClient {
1994
2051
  if (!firstChunk)
1995
2052
  firstChunk = chunk;
1996
2053
  chunkCount++;
1997
- const delta = chunk.choices[0]?.delta;
2054
+ if (captureStreamUsage && chunk?.usage)
2055
+ streamUsage = chunk.usage;
2056
+ const delta = chunk.choices?.[0]?.delta;
1998
2057
  // Debug: Log first few chunks to see what fields are present
1999
2058
  if ((process.env.DEBUG === 'true' || process.env.DEBUG_THINKING === 'true') && chunkCount <= 3) {
2000
2059
  console.log(`[DEBUG APIClient] Chunk ${chunkCount} delta keys:`, delta ? Object.keys(delta) : 'null');
@@ -2045,7 +2104,7 @@ export class APIClient {
2045
2104
  }
2046
2105
  }
2047
2106
  // Check if finish_reason indicates tool_calls completion (or length-truncated tool call)
2048
- const finishReason = chunk.choices[0]?.finish_reason;
2107
+ const finishReason = chunk.choices?.[0]?.finish_reason;
2049
2108
  if (finishReason)
2050
2109
  lastFinishReason = String(finishReason);
2051
2110
  if ((finishReason === 'tool_calls' || finishReason === 'length') && accumulatedToolCalls.size > 0) {
@@ -2111,11 +2170,7 @@ export class APIClient {
2111
2170
  // ('length' = output cap hit) instead of a synthetic 'stop'.
2112
2171
  finish_reason: finalToolCalls.length > 0 ? 'tool_calls' : (lastFinishReason ?? 'stop')
2113
2172
  }],
2114
- usage: {
2115
- prompt_tokens: 0,
2116
- completion_tokens: 0,
2117
- total_tokens: 0
2118
- }
2173
+ usage: streamFinalUsage(streamUsage)
2119
2174
  });
2120
2175
  }();
2121
2176
  return {
@@ -2215,6 +2270,10 @@ export class APIClient {
2215
2270
  responsesRequest.prompt_cache_key = request.conversationId;
2216
2271
  }
2217
2272
  }
2273
+ // R225 (DARK, CORTEX_OPENAI_CACHE_KEY): mirrors sendResponsesAPI.
2274
+ if (isOpenAICacheKeyEnabled() && modelConfig.provider?.toLowerCase() === 'openai' && request.conversationId) {
2275
+ responsesRequest.prompt_cache_key = request.conversationId;
2276
+ }
2218
2277
  // Set system context as instructions (enables provider-side caching).
2219
2278
  // R63: merge the R28 static system prompt (previously dropped on this
2220
2279
  // path) ahead of reminders mined from user messages; XAI gets it as a
@@ -2475,6 +2534,11 @@ export class APIClient {
2475
2534
  if (request.tools && request.tools.length > 0) {
2476
2535
  return this.streamGenerateContentHTTP(request, modelConfig, apiKey);
2477
2536
  }
2537
+ // R229 (DARK, CORTEX_GEMINI_SYSTEM_FIX): the old SDK cannot send a system prompt — use the v1beta REST
2538
+ // streaming builder (already sends system_instruction) whenever one is present.
2539
+ if (isGeminiSystemFixEnabled() && request.systemMessage) {
2540
+ return this.streamGenerateContentHTTP(request, modelConfig, apiKey);
2541
+ }
2478
2542
  // For non-tool requests, use SDK streaming
2479
2543
  if (!this.googleClient) {
2480
2544
  this.googleClient = new GoogleGenerativeAI(apiKey);
@@ -2819,12 +2883,22 @@ export class APIClient {
2819
2883
  Object.assign(config, generationConfig);
2820
2884
  }
2821
2885
  // CRITICAL: thinkingConfig and tools CANNOT coexist
2886
+ // (DARK CORTEX_GEMINI_TOOLS_THINKING: a Gemini 2.0-era observation — on, thinking is sent WITH tools.)
2822
2887
  const hasTools = request.tools && request.tools.length > 0;
2888
+ const geminiToolsThinking = isGeminiToolsThinkingEnabled();
2823
2889
  if (_modelConfig.reasoning?.supported && _modelConfig.reasoning?.pattern === 'interleaved' && !disableThinking && !hasTools) {
2824
- config.thinkingConfig = {
2825
- includeThoughts: true,
2826
- thinkingBudget: 10000
2827
- };
2890
+ config.thinkingConfig = geminiToolsThinking && config.thinkingConfig
2891
+ // effort-mapped thinkingLevel/thinkingBudget from the gateway; keep the thought summaries this path always asked for
2892
+ ? { includeThoughts: true, ...config.thinkingConfig }
2893
+ : {
2894
+ includeThoughts: true,
2895
+ thinkingBudget: 10000
2896
+ };
2897
+ }
2898
+ else if (_modelConfig.reasoning?.supported && _modelConfig.reasoning?.pattern === 'interleaved' && !disableThinking && hasTools && geminiToolsThinking) {
2899
+ // Non-streaming: no includeThoughts — this response is converted by the message-level adapter, which would turn
2900
+ // thought-summary parts into visible text. Effort-mapped config from the gateway, else the no-tools default budget.
2901
+ config.thinkingConfig = config.thinkingConfig ?? { thinkingBudget: 10000 };
2828
2902
  }
2829
2903
  // Add tools to config if present
2830
2904
  if (hasTools && request.tools) {
@@ -2843,7 +2917,14 @@ export class APIClient {
2843
2917
  config
2844
2918
  };
2845
2919
  if (request.systemMessage) {
2846
- sdkRequest.systemInstruction = request.systemMessage;
2920
+ if (isGeminiSystemFixEnabled()) {
2921
+ // R229 (DARK): @google/genai reads the system prompt ONLY from config.systemInstruction
2922
+ // (GenerateContentParameters = {model, contents, config}); a top-level field is dropped.
2923
+ config.systemInstruction = request.systemMessage;
2924
+ }
2925
+ else {
2926
+ sdkRequest.systemInstruction = request.systemMessage;
2927
+ }
2847
2928
  }
2848
2929
  if (process.env.DEBUG === 'true') {
2849
2930
  console.log(`[DEBUG APIClient SDK] Non-streaming request for model: ${modelId}`);
@@ -2904,16 +2985,24 @@ export class APIClient {
2904
2985
  // Following Claude/Grok pattern: Skip if disableThinking flag is set (used for continuation requests)
2905
2986
  // CRITICAL: thinkingConfig and tools CANNOT coexist — Google API returns empty responses when both present
2906
2987
  const hasTools = request.tools && request.tools.length > 0;
2907
- if (_modelConfig.reasoning?.supported && _modelConfig.reasoning?.pattern === 'interleaved' && !disableThinking && !hasTools) {
2908
- config.thinkingConfig = {
2909
- includeThoughts: true, // Enable thought summaries in response
2910
- // Gemini 2.5 Flash supports thinkingBudget (0-24576)
2911
- // -1 = dynamic (model decides), or set explicit budget like Claude's 10000
2912
- // Set to 10000 tokens to encourage verbose reasoning like Claude
2913
- thinkingBudget: 10000
2914
- };
2988
+ // DARK (CORTEX_GEMINI_TOOLS_THINKING): on, thinking is sent WITH tools (effort-mapped by the gateway).
2989
+ const geminiToolsThinking = isGeminiToolsThinkingEnabled();
2990
+ if (_modelConfig.reasoning?.supported && _modelConfig.reasoning?.pattern === 'interleaved' && !disableThinking && (!hasTools || geminiToolsThinking)) {
2991
+ if (geminiToolsThinking && config.thinkingConfig) {
2992
+ // effort-mapped thinkingLevel/thinkingBudget from the gateway (+ thought summaries; the stream parser filters part.thought)
2993
+ config.thinkingConfig = { includeThoughts: true, ...config.thinkingConfig };
2994
+ }
2995
+ else {
2996
+ config.thinkingConfig = {
2997
+ includeThoughts: true, // Enable thought summaries in response
2998
+ // Gemini 2.5 Flash supports thinkingBudget (0-24576)
2999
+ // -1 = dynamic (model decides), or set explicit budget like Claude's 10000
3000
+ // Set to 10000 tokens to encourage verbose reasoning like Claude
3001
+ thinkingBudget: 10000
3002
+ };
3003
+ }
2915
3004
  if (process.env.DEBUG === 'true') {
2916
- console.log(`[DEBUG APIClient SDK] Extended thinking enabled for ${modelId} with budget: 10000`);
3005
+ console.log(`[DEBUG APIClient SDK] Extended thinking enabled for ${modelId}: ${JSON.stringify(config.thinkingConfig)}`);
2917
3006
  }
2918
3007
  }
2919
3008
  else if (hasTools && process.env.DEBUG === 'true') {
@@ -2940,9 +3029,15 @@ export class APIClient {
2940
3029
  contents: request.messages,
2941
3030
  config // Config contains both generation params and tools
2942
3031
  };
2943
- // Add system instruction if present (top level, not in config)
3032
+ // Add system instruction if present. Legacy: top level (the SDK DROPS it — GenerateContentParameters =
3033
+ // {model, contents, config}). R229 (DARK, CORTEX_GEMINI_SYSTEM_FIX): config.systemInstruction.
2944
3034
  if (request.systemMessage) {
2945
- sdkRequest.systemInstruction = request.systemMessage;
3035
+ if (isGeminiSystemFixEnabled()) {
3036
+ config.systemInstruction = request.systemMessage;
3037
+ }
3038
+ else {
3039
+ sdkRequest.systemInstruction = request.systemMessage;
3040
+ }
2946
3041
  }
2947
3042
  if (process.env.DEBUG === 'true') {
2948
3043
  console.log(`[DEBUG APIClient SDK] Using model: ${modelId}`);