@nexus-cortex/core 4.124.59 → 4.124.61
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/.env.defaults +26 -0
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +2 -0
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/middleware/HelperModelMiddleware.d.ts.map +1 -1
- package/dist/middleware/HelperModelMiddleware.js +4 -1
- package/dist/middleware/HelperModelMiddleware.js.map +1 -1
- package/dist/orchestrator/APIClient.d.ts.map +1 -1
- package/dist/orchestrator/APIClient.js +159 -117
- package/dist/orchestrator/APIClient.js.map +1 -1
- package/dist/orchestrator/CortexOrchestrator.d.ts +1 -0
- package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
- package/dist/orchestrator/CortexOrchestrator.js +23 -1
- package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
- package/dist/orchestrator/wallRetryLadder.d.ts +29 -0
- package/dist/orchestrator/wallRetryLadder.d.ts.map +1 -0
- package/dist/orchestrator/wallRetryLadder.js +29 -0
- package/dist/orchestrator/wallRetryLadder.js.map +1 -0
- package/package.json +3 -3
|
@@ -2122,118 +2122,16 @@ export class APIClient {
|
|
|
2122
2122
|
// Capture this context before entering generator
|
|
2123
2123
|
const self = this;
|
|
2124
2124
|
// Create async generator that both yields chunks AND accumulates final message
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
if (captureStreamUsage && chunk?.usage)
|
|
2136
|
-
streamUsage = chunk.usage;
|
|
2137
|
-
const delta = chunk.choices?.[0]?.delta;
|
|
2138
|
-
// Debug: Log first few chunks to see what fields are present
|
|
2139
|
-
if ((process.env.DEBUG === 'true' || process.env.DEBUG_THINKING === 'true') && chunkCount <= 3) {
|
|
2140
|
-
console.log(`[DEBUG APIClient] Chunk ${chunkCount} delta keys:`, delta ? Object.keys(delta) : 'null');
|
|
2141
|
-
if (delta?.reasoning_content) {
|
|
2142
|
-
console.log(`[DEBUG APIClient] Found reasoning_content in chunk ${chunkCount}`);
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
// Extract reasoning content (for models with native interleaved thinking)
|
|
2146
|
-
// XAI: chunk.choices[0].delta.reasoning_content
|
|
2147
|
-
// DeepSeek: chunk.choices[0].delta.reasoning_content
|
|
2148
|
-
// GPT-5: chunk.choices[0].delta.reasoning_content (when reasoning_effort != 'none')
|
|
2149
|
-
if (delta?.reasoning_content) {
|
|
2150
|
-
fullReasoningContent += delta.reasoning_content;
|
|
2151
|
-
yield {
|
|
2152
|
-
type: 'content_block_delta',
|
|
2153
|
-
delta: delta.reasoning_content,
|
|
2154
|
-
data: { ...chunk, reasoning: true }
|
|
2155
|
-
};
|
|
2156
|
-
}
|
|
2157
|
-
// Extract regular text content
|
|
2158
|
-
if (delta?.content) {
|
|
2159
|
-
fullContent += delta.content;
|
|
2160
|
-
yield {
|
|
2161
|
-
type: 'text_delta',
|
|
2162
|
-
delta: delta.content,
|
|
2163
|
-
data: chunk
|
|
2164
|
-
};
|
|
2165
|
-
}
|
|
2166
|
-
// Phase 2.8: Handle streaming tool_calls
|
|
2167
|
-
if (delta?.tool_calls && Array.isArray(delta.tool_calls)) {
|
|
2168
|
-
for (const toolCallDelta of delta.tool_calls) {
|
|
2169
|
-
const index = toolCallDelta.index;
|
|
2170
|
-
// Initialize tool call accumulator if needed
|
|
2171
|
-
if (!accumulatedToolCalls.has(index)) {
|
|
2172
|
-
accumulatedToolCalls.set(index, { arguments: '' });
|
|
2173
|
-
}
|
|
2174
|
-
const accumulated = accumulatedToolCalls.get(index);
|
|
2175
|
-
// Accumulate tool call properties
|
|
2176
|
-
if (toolCallDelta.id) {
|
|
2177
|
-
accumulated.id = toolCallDelta.id;
|
|
2178
|
-
}
|
|
2179
|
-
if (toolCallDelta.function?.name) {
|
|
2180
|
-
accumulated.name = toolCallDelta.function.name;
|
|
2181
|
-
}
|
|
2182
|
-
if (toolCallDelta.function?.arguments) {
|
|
2183
|
-
accumulated.arguments += toolCallDelta.function.arguments;
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
// Check if finish_reason indicates tool_calls completion (or length-truncated tool call)
|
|
2188
|
-
const finishReason = chunk.choices?.[0]?.finish_reason;
|
|
2189
|
-
if (finishReason)
|
|
2190
|
-
lastFinishReason = String(finishReason);
|
|
2191
|
-
if ((finishReason === 'tool_calls' || finishReason === 'length') && accumulatedToolCalls.size > 0) {
|
|
2192
|
-
// Emit tool_use_complete events for each accumulated tool call
|
|
2193
|
-
for (const [_, toolCall] of accumulatedToolCalls) {
|
|
2194
|
-
if (toolCall.id && toolCall.name) {
|
|
2195
|
-
try {
|
|
2196
|
-
let input;
|
|
2197
|
-
try {
|
|
2198
|
-
input = JSON.parse(toolCall.arguments);
|
|
2199
|
-
}
|
|
2200
|
-
catch {
|
|
2201
|
-
input = self.repairTruncatedJSON(toolCall.arguments);
|
|
2202
|
-
}
|
|
2203
|
-
const decodedInput = self.decodeHtmlEntitiesInObject(input);
|
|
2204
|
-
yield {
|
|
2205
|
-
type: 'tool_use_complete',
|
|
2206
|
-
toolUse: {
|
|
2207
|
-
id: toolCall.id,
|
|
2208
|
-
name: toolCall.name,
|
|
2209
|
-
input: decodedInput
|
|
2210
|
-
},
|
|
2211
|
-
data: chunk
|
|
2212
|
-
};
|
|
2213
|
-
}
|
|
2214
|
-
catch (error) {
|
|
2215
|
-
console.error(`[APIClient] Failed to parse tool arguments (${toolCall.arguments.length} chars): ${toolCall.arguments.slice(0, 200)}...`);
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
// Stream complete - log summary
|
|
2222
|
-
if (process.env.DEBUG === 'true' || process.env.DEBUG_THINKING === 'true') {
|
|
2223
|
-
console.log(`[DEBUG APIClient] Stream complete - total chunks: ${chunkCount}, reasoning content length: ${fullReasoningContent.length}, content length: ${fullContent.length}`);
|
|
2224
|
-
}
|
|
2225
|
-
// Stream complete - resolve final message in OpenAI Chat Completions format
|
|
2226
|
-
// Include accumulated tool_calls in the final message
|
|
2227
|
-
const finalToolCalls = Array.from(accumulatedToolCalls.values())
|
|
2228
|
-
.filter(tc => tc.id && tc.name)
|
|
2229
|
-
.map(tc => ({
|
|
2230
|
-
id: tc.id,
|
|
2231
|
-
type: 'function',
|
|
2232
|
-
function: {
|
|
2233
|
-
name: tc.name,
|
|
2234
|
-
arguments: tc.arguments
|
|
2235
|
-
}
|
|
2236
|
-
}));
|
|
2125
|
+
// R238 (2026-10-06): `finalMessage` used to resolve ONLY at the natural end of the stream — a consumer that broke out of `chunks`
|
|
2126
|
+
// (abort, tail-loop guard, ESC) or a stream that threw left the promise pending forever. Any early exit now settles it with the
|
|
2127
|
+
// PARTIAL message (finish_reason 'aborted', content/reasoning/tool_calls accumulated so far, x_partial: true, an estimated output-token
|
|
2128
|
+
// count for accounting since the provider's usage chunk never arrives). The natural-end path is byte-identical.
|
|
2129
|
+
let streamCompletedNaturally = false;
|
|
2130
|
+
let streamError = undefined;
|
|
2131
|
+
const resolvePartial = () => {
|
|
2132
|
+
const partialToolCalls = Array.from(accumulatedToolCalls.values())
|
|
2133
|
+
.filter((tc) => tc.id && tc.name)
|
|
2134
|
+
.map((tc) => ({ id: tc.id, type: 'function', function: { name: tc.name, arguments: tc.arguments } }));
|
|
2237
2135
|
finalMessageResolve({
|
|
2238
2136
|
id: firstChunk?.id || 'chatcmpl-' + Date.now(),
|
|
2239
2137
|
object: 'chat.completion',
|
|
@@ -2245,14 +2143,158 @@ export class APIClient {
|
|
|
2245
2143
|
role: 'assistant',
|
|
2246
2144
|
content: fullContent || null,
|
|
2247
2145
|
...(fullReasoningContent && { reasoning_content: fullReasoningContent }),
|
|
2248
|
-
...(
|
|
2146
|
+
...(partialToolCalls.length > 0 && { tool_calls: partialToolCalls })
|
|
2249
2147
|
},
|
|
2250
|
-
|
|
2251
|
-
// ('length' = output cap hit) instead of a synthetic 'stop'.
|
|
2252
|
-
finish_reason: finalToolCalls.length > 0 ? 'tool_calls' : (lastFinishReason ?? 'stop')
|
|
2148
|
+
finish_reason: 'aborted'
|
|
2253
2149
|
}],
|
|
2254
|
-
usage: streamFinalUsage(streamUsage)
|
|
2150
|
+
usage: streamFinalUsage(streamUsage),
|
|
2151
|
+
x_partial: true,
|
|
2152
|
+
x_estimated_output_tokens: Math.round((fullContent.length + fullReasoningContent.length) / 4),
|
|
2153
|
+
...(streamError !== undefined ? { x_error: String(streamError?.message ?? streamError).slice(0, 300) } : {}),
|
|
2255
2154
|
});
|
|
2155
|
+
};
|
|
2156
|
+
const chunks = async function* () {
|
|
2157
|
+
try {
|
|
2158
|
+
if (process.env.DEBUG === 'true' || process.env.DEBUG_THINKING === 'true') {
|
|
2159
|
+
console.log(`[DEBUG APIClient] OpenAI request:`, JSON.stringify(openaiRequest, null, 2));
|
|
2160
|
+
}
|
|
2161
|
+
const stream = await client.chat.completions.create(openaiRequest);
|
|
2162
|
+
let chunkCount = 0;
|
|
2163
|
+
for await (const chunk of stream) {
|
|
2164
|
+
if (!firstChunk)
|
|
2165
|
+
firstChunk = chunk;
|
|
2166
|
+
chunkCount++;
|
|
2167
|
+
if (captureStreamUsage && chunk?.usage)
|
|
2168
|
+
streamUsage = chunk.usage;
|
|
2169
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
2170
|
+
// Debug: Log first few chunks to see what fields are present
|
|
2171
|
+
if ((process.env.DEBUG === 'true' || process.env.DEBUG_THINKING === 'true') && chunkCount <= 3) {
|
|
2172
|
+
console.log(`[DEBUG APIClient] Chunk ${chunkCount} delta keys:`, delta ? Object.keys(delta) : 'null');
|
|
2173
|
+
if (delta?.reasoning_content) {
|
|
2174
|
+
console.log(`[DEBUG APIClient] Found reasoning_content in chunk ${chunkCount}`);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
// Extract reasoning content (for models with native interleaved thinking)
|
|
2178
|
+
// XAI: chunk.choices[0].delta.reasoning_content
|
|
2179
|
+
// DeepSeek: chunk.choices[0].delta.reasoning_content
|
|
2180
|
+
// GPT-5: chunk.choices[0].delta.reasoning_content (when reasoning_effort != 'none')
|
|
2181
|
+
if (delta?.reasoning_content) {
|
|
2182
|
+
fullReasoningContent += delta.reasoning_content;
|
|
2183
|
+
yield {
|
|
2184
|
+
type: 'content_block_delta',
|
|
2185
|
+
delta: delta.reasoning_content,
|
|
2186
|
+
data: { ...chunk, reasoning: true }
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
// Extract regular text content
|
|
2190
|
+
if (delta?.content) {
|
|
2191
|
+
fullContent += delta.content;
|
|
2192
|
+
yield {
|
|
2193
|
+
type: 'text_delta',
|
|
2194
|
+
delta: delta.content,
|
|
2195
|
+
data: chunk
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
// Phase 2.8: Handle streaming tool_calls
|
|
2199
|
+
if (delta?.tool_calls && Array.isArray(delta.tool_calls)) {
|
|
2200
|
+
for (const toolCallDelta of delta.tool_calls) {
|
|
2201
|
+
const index = toolCallDelta.index;
|
|
2202
|
+
// Initialize tool call accumulator if needed
|
|
2203
|
+
if (!accumulatedToolCalls.has(index)) {
|
|
2204
|
+
accumulatedToolCalls.set(index, { arguments: '' });
|
|
2205
|
+
}
|
|
2206
|
+
const accumulated = accumulatedToolCalls.get(index);
|
|
2207
|
+
// Accumulate tool call properties
|
|
2208
|
+
if (toolCallDelta.id) {
|
|
2209
|
+
accumulated.id = toolCallDelta.id;
|
|
2210
|
+
}
|
|
2211
|
+
if (toolCallDelta.function?.name) {
|
|
2212
|
+
accumulated.name = toolCallDelta.function.name;
|
|
2213
|
+
}
|
|
2214
|
+
if (toolCallDelta.function?.arguments) {
|
|
2215
|
+
accumulated.arguments += toolCallDelta.function.arguments;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
// Check if finish_reason indicates tool_calls completion (or length-truncated tool call)
|
|
2220
|
+
const finishReason = chunk.choices?.[0]?.finish_reason;
|
|
2221
|
+
if (finishReason)
|
|
2222
|
+
lastFinishReason = String(finishReason);
|
|
2223
|
+
if ((finishReason === 'tool_calls' || finishReason === 'length') && accumulatedToolCalls.size > 0) {
|
|
2224
|
+
// Emit tool_use_complete events for each accumulated tool call
|
|
2225
|
+
for (const [_, toolCall] of accumulatedToolCalls) {
|
|
2226
|
+
if (toolCall.id && toolCall.name) {
|
|
2227
|
+
try {
|
|
2228
|
+
let input;
|
|
2229
|
+
try {
|
|
2230
|
+
input = JSON.parse(toolCall.arguments);
|
|
2231
|
+
}
|
|
2232
|
+
catch {
|
|
2233
|
+
input = self.repairTruncatedJSON(toolCall.arguments);
|
|
2234
|
+
}
|
|
2235
|
+
const decodedInput = self.decodeHtmlEntitiesInObject(input);
|
|
2236
|
+
yield {
|
|
2237
|
+
type: 'tool_use_complete',
|
|
2238
|
+
toolUse: {
|
|
2239
|
+
id: toolCall.id,
|
|
2240
|
+
name: toolCall.name,
|
|
2241
|
+
input: decodedInput
|
|
2242
|
+
},
|
|
2243
|
+
data: chunk
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
catch (error) {
|
|
2247
|
+
console.error(`[APIClient] Failed to parse tool arguments (${toolCall.arguments.length} chars): ${toolCall.arguments.slice(0, 200)}...`);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
// Stream complete - log summary
|
|
2254
|
+
if (process.env.DEBUG === 'true' || process.env.DEBUG_THINKING === 'true') {
|
|
2255
|
+
console.log(`[DEBUG APIClient] Stream complete - total chunks: ${chunkCount}, reasoning content length: ${fullReasoningContent.length}, content length: ${fullContent.length}`);
|
|
2256
|
+
}
|
|
2257
|
+
// Stream complete - resolve final message in OpenAI Chat Completions format
|
|
2258
|
+
// Include accumulated tool_calls in the final message
|
|
2259
|
+
const finalToolCalls = Array.from(accumulatedToolCalls.values())
|
|
2260
|
+
.filter(tc => tc.id && tc.name)
|
|
2261
|
+
.map(tc => ({
|
|
2262
|
+
id: tc.id,
|
|
2263
|
+
type: 'function',
|
|
2264
|
+
function: {
|
|
2265
|
+
name: tc.name,
|
|
2266
|
+
arguments: tc.arguments
|
|
2267
|
+
}
|
|
2268
|
+
}));
|
|
2269
|
+
finalMessageResolve({
|
|
2270
|
+
id: firstChunk?.id || 'chatcmpl-' + Date.now(),
|
|
2271
|
+
object: 'chat.completion',
|
|
2272
|
+
created: firstChunk?.created || Math.floor(Date.now() / 1000),
|
|
2273
|
+
model: firstChunk?.model || request.modelId,
|
|
2274
|
+
choices: [{
|
|
2275
|
+
index: 0,
|
|
2276
|
+
message: {
|
|
2277
|
+
role: 'assistant',
|
|
2278
|
+
content: fullContent || null,
|
|
2279
|
+
...(fullReasoningContent && { reasoning_content: fullReasoningContent }),
|
|
2280
|
+
...(finalToolCalls.length > 0 && { tool_calls: finalToolCalls })
|
|
2281
|
+
},
|
|
2282
|
+
// R153: tool calls keep 'tool_calls' (downstream contract); otherwise report what the provider said
|
|
2283
|
+
// ('length' = output cap hit) instead of a synthetic 'stop'.
|
|
2284
|
+
finish_reason: finalToolCalls.length > 0 ? 'tool_calls' : (lastFinishReason ?? 'stop')
|
|
2285
|
+
}],
|
|
2286
|
+
usage: streamFinalUsage(streamUsage)
|
|
2287
|
+
});
|
|
2288
|
+
streamCompletedNaturally = true;
|
|
2289
|
+
}
|
|
2290
|
+
catch (err) {
|
|
2291
|
+
streamError = err;
|
|
2292
|
+
throw err; // the chunk consumer still sees the error, as before
|
|
2293
|
+
}
|
|
2294
|
+
finally {
|
|
2295
|
+
if (!streamCompletedNaturally)
|
|
2296
|
+
resolvePartial(); // R238: abort / early return / throw → settle with the partial message
|
|
2297
|
+
}
|
|
2256
2298
|
}();
|
|
2257
2299
|
return {
|
|
2258
2300
|
chunks,
|