@cognipeer/agent-sdk 0.5.4 → 0.6.1

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/dist/index.cjs CHANGED
@@ -1978,7 +1978,9 @@ function createAgentCoreNode(opts) {
1978
1978
  const cancellationToken = state.ctx?.__cancellationToken;
1979
1979
  const abortSignal = state.ctx?.__abortSignal;
1980
1980
  const responseFormat = runtime.responseFormat;
1981
- const extractText = (chunk) => {
1981
+ const reasoningForCall = state.ctx?.__reasoning;
1982
+ const reasoningInvokeOpts = reasoningForCall ? { reasoning: reasoningForCall } : void 0;
1983
+ const extractText2 = (chunk) => {
1982
1984
  if (chunk == null) return "";
1983
1985
  if (typeof chunk === "string") return chunk;
1984
1986
  if (typeof chunk?.content === "string") return chunk.content;
@@ -1996,7 +1998,7 @@ function createAgentCoreNode(opts) {
1996
1998
  if (streamingEnabled && typeof modelWithTools.stream === "function") {
1997
1999
  let streamedText = "";
1998
2000
  let streamedMessage;
1999
- const streamOptions = { signal: abortSignal, cancellationToken, ...responseFormat };
2001
+ const streamOptions = { signal: abortSignal, cancellationToken, ...responseFormat, ...reasoningInvokeOpts };
2000
2002
  for await (const chunk of modelWithTools.stream(normalizedMessages, streamOptions)) {
2001
2003
  if (cancellationToken && cancellationToken.isCancellationRequested || abortSignal?.aborted) {
2002
2004
  break;
@@ -2004,7 +2006,7 @@ function createAgentCoreNode(opts) {
2004
2006
  if (chunk && typeof chunk === "object" && chunk.role) {
2005
2007
  streamedMessage = chunk;
2006
2008
  }
2007
- const text = extractText(chunk);
2009
+ const text = extractText2(chunk);
2008
2010
  if (text) {
2009
2011
  streamedText += text;
2010
2012
  onStream?.({ text });
@@ -2020,7 +2022,7 @@ function createAgentCoreNode(opts) {
2020
2022
  response = { role: "assistant", content: streamedText };
2021
2023
  }
2022
2024
  } else {
2023
- response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken, ...responseFormat });
2025
+ response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken, ...responseFormat, ...reasoningInvokeOpts });
2024
2026
  }
2025
2027
  } catch (err) {
2026
2028
  const durationMs2 = Date.now() - start;
@@ -2136,18 +2138,13 @@ var BASE_DEFAULTS = {
2136
2138
  requireJsonOutputContract: true
2137
2139
  },
2138
2140
  toolResponses: {
2139
- maxToolResponseChars: 3600,
2140
- maxToolResponseTokens: 1200,
2141
- smallResponseChars: 0,
2142
- smallResponsePolicy: "keep_full",
2143
- defaultPolicy: "keep_structured",
2144
- largeResponsePolicy: "summarize_archive",
2141
+ maxToolResponseChars: 12e3,
2142
+ maxToolResponseTokens: 3e3,
2143
+ defaultPolicy: "summarize_archive",
2145
2144
  toolResponseRetentionByTool: {},
2146
2145
  criticalTools: [...DEFAULT_CRITICAL_TOOLS],
2147
2146
  schemaValidation: "strict",
2148
- retryOnSchemaError: true,
2149
- fallbackPolicy: "keep_full",
2150
- keepRecentFullCount: 0
2147
+ retryOnSchemaError: true
2151
2148
  }
2152
2149
  };
2153
2150
  function buildProfile(overrides) {
@@ -2186,7 +2183,7 @@ var DEFAULT_PROFILE_CONFIGS = {
2186
2183
  },
2187
2184
  memory: { readPolicy: "recent_only" },
2188
2185
  delegation: { mode: "off", maxDelegationDepth: 1, maxChildCalls: 2, maxParallelChild: 1, childContextPolicy: "minimal" },
2189
- toolResponses: { maxToolResponseChars: 2400, maxToolResponseTokens: 800 }
2186
+ toolResponses: { maxToolResponseChars: 8e3, maxToolResponseTokens: 2e3 }
2190
2187
  }),
2191
2188
  balanced: buildProfile({}),
2192
2189
  deep: buildProfile({
@@ -2203,7 +2200,7 @@ var DEFAULT_PROFILE_CONFIGS = {
2203
2200
  },
2204
2201
  memory: { scope: "workspace" },
2205
2202
  delegation: { maxDelegationDepth: 3, maxChildCalls: 6 },
2206
- toolResponses: { maxToolResponseChars: 5400, maxToolResponseTokens: 1800 }
2203
+ toolResponses: { maxToolResponseChars: 16e3, maxToolResponseTokens: 4e3 }
2207
2204
  }),
2208
2205
  research: buildProfile({
2209
2206
  limits: { maxToolCalls: 20, maxParallelTools: 4, maxContextTokens: 56e3 },
@@ -2219,7 +2216,7 @@ var DEFAULT_PROFILE_CONFIGS = {
2219
2216
  },
2220
2217
  memory: { scope: "workspace" },
2221
2218
  delegation: { mode: "automatic", maxDelegationDepth: 4, maxChildCalls: 8, maxParallelChild: 3, childContextPolicy: "full" },
2222
- toolResponses: { maxToolResponseChars: 7200, maxToolResponseTokens: 2200 }
2219
+ toolResponses: { maxToolResponseChars: 24e3, maxToolResponseTokens: 6e3 }
2223
2220
  })
2224
2221
  };
2225
2222
  function mergeLimits(base, override) {
@@ -2426,63 +2423,65 @@ function summarizeObject(value) {
2426
2423
  return safeStringify(value);
2427
2424
  }
2428
2425
  function classifyToolResponse(toolName, output, config) {
2429
- const serialized = safeStringify(output);
2430
- const tokenCount = countApproxTokens(serialized);
2431
2426
  const criticalTools = new Set(config.toolResponses.criticalTools);
2432
2427
  if (criticalTools.has(toolName)) return "critical";
2433
- if (serialized === "ok" || serialized === "true" || serialized === "null") {
2434
- return "redundant";
2435
- }
2428
+ const serialized = safeStringify(output);
2429
+ const tokenCount = countApproxTokens(serialized);
2436
2430
  if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
2437
2431
  return "verbose";
2438
2432
  }
2439
- const smallChars = config.toolResponses.smallResponseChars;
2440
- if (smallChars > 0 && serialized.length <= smallChars) {
2441
- return "small";
2442
- }
2443
2433
  return "informative";
2444
2434
  }
2445
- function resolveToolResponsePolicy(toolName, output, config) {
2435
+ function applyToolResponseHardCap(toolName, output, executionId, config) {
2446
2436
  const serialized = safeStringify(output);
2447
2437
  const tokenCount = countApproxTokens(serialized);
2448
2438
  const classification = classifyToolResponse(toolName, output, config);
2449
- const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2450
- let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
2451
- if (byTool) ; else if (classification === "critical") {
2452
- retentionPolicy = config.toolResponses.fallbackPolicy;
2453
- } else if (classification === "verbose") {
2454
- retentionPolicy = config.toolResponses.largeResponsePolicy;
2455
- } else if (classification === "small") {
2456
- retentionPolicy = config.toolResponses.smallResponsePolicy;
2457
- } else if (classification === "redundant") {
2458
- retentionPolicy = "drop";
2459
- }
2460
- const summary = summarizeObject(output);
2461
- if (retentionPolicy === "keep_full") {
2462
- return { classification, retentionPolicy, content: serialized, rawOutput: output, summary, tokenCount };
2463
- }
2464
- if (retentionPolicy === "keep_structured") {
2465
- return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
2466
- }
2467
- if (retentionPolicy === "summarize_archive") {
2468
- return {
2469
- classification,
2470
- retentionPolicy,
2471
- content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
2472
- rawOutput: output,
2473
- summary,
2474
- tokenCount
2475
- };
2439
+ if (classification !== "verbose") {
2440
+ return { classification, content: serialized, rawOutput: output, tokenCount, truncated: false };
2476
2441
  }
2442
+ const cap = config.toolResponses.maxToolResponseChars;
2443
+ const head = serialized.slice(0, cap);
2444
+ const note = `
2445
+ ... [TRUNCATED ${serialized.length - cap} chars by hard cap. Use get_tool_response with executionId="${executionId}" to fetch the full payload.]`;
2477
2446
  return {
2478
2447
  classification,
2479
- retentionPolicy,
2480
- content: `DROPPED_TOOL_RESPONSE: ${summary}`,
2448
+ content: head + note,
2481
2449
  rawOutput: output,
2482
- summary,
2483
- tokenCount
2450
+ tokenCount,
2451
+ truncated: true
2484
2452
  };
2485
2453
  }
2454
+ function resolveSummarizationRetention(toolName, config) {
2455
+ const criticalTools = new Set(config.toolResponses.criticalTools);
2456
+ if (criticalTools.has(toolName)) return "keep_full";
2457
+ const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2458
+ return byTool || config.toolResponses.defaultPolicy;
2459
+ }
2460
+ function renderRetainedToolMessage(args) {
2461
+ const { policy, rawOutput, toolName, toolCallId, executionId, prebuiltSummary } = args;
2462
+ if (policy === "keep_full") {
2463
+ return typeof rawOutput === "string" ? rawOutput : safeStringify(rawOutput);
2464
+ }
2465
+ const summary = prebuiltSummary || summarizeObject(rawOutput);
2466
+ const refs = [
2467
+ `toolName=${toolName}`,
2468
+ toolCallId ? `toolCallId=${toolCallId}` : null,
2469
+ executionId ? `executionId=${executionId}` : null
2470
+ ].filter(Boolean).join("; ");
2471
+ const refId = executionId || toolCallId || "";
2472
+ if (policy === "keep_structured") {
2473
+ return `STRUCTURED_TOOL_RESPONSE [${refs}]
2474
+ Preview: ${summary}
2475
+ Use get_tool_response with executionId "${refId}" to fetch the full payload when a specific field is needed.`;
2476
+ }
2477
+ if (policy === "summarize_archive") {
2478
+ return `ARCHIVED_TOOL_RESPONSE [${refs}]
2479
+ Summary: ${summary}
2480
+ Use get_tool_response with executionId "${refId}" to fetch the full payload.`;
2481
+ }
2482
+ return `DROPPED_TOOL_RESPONSE [${refs}]
2483
+ Use get_tool_response with executionId "${refId}" only if you must recover the original payload.`;
2484
+ }
2486
2485
  function validateToolArgs(tool, args) {
2487
2486
  const schema = tool.schema;
2488
2487
  if (!schema || typeof schema.parse !== "function") {
@@ -2761,52 +2760,25 @@ function createToolsNode(initialTools, opts) {
2761
2760
  state.ctx.__structuredOutputParsed = output.data;
2762
2761
  state.ctx.__finalizedDueToStructuredOutput = true;
2763
2762
  }
2764
- const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
2763
+ const responsePolicy = applyToolResponseHardCap(toolName, output, executionId, resolved);
2765
2764
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2766
- const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
2767
2765
  toolHistory.push({
2768
2766
  executionId,
2769
2767
  toolName,
2770
2768
  args,
2771
- output: storedOutput,
2769
+ output,
2772
2770
  rawOutput: output,
2773
2771
  timestamp,
2774
2772
  tool_call_id: tc.id,
2775
- summarized: responsePolicy.retentionPolicy !== "keep_full",
2773
+ summarized: false,
2776
2774
  originalTokenCount: responsePolicy.tokenCount,
2777
2775
  classification: responsePolicy.classification,
2778
- retentionPolicy: responsePolicy.retentionPolicy,
2779
- summary: responsePolicy.summary,
2780
- archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
2776
+ retentionPolicy: "keep_full",
2777
+ summary: void 0,
2778
+ archiveId: void 0,
2781
2779
  status: "success"
2782
2780
  });
2783
- if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
2784
- toolHistoryArchived.push({
2785
- executionId,
2786
- toolName,
2787
- args,
2788
- output,
2789
- rawOutput: output,
2790
- timestamp,
2791
- tool_call_id: tc.id,
2792
- summarized: true,
2793
- originalTokenCount: responsePolicy.tokenCount,
2794
- classification: responsePolicy.classification,
2795
- retentionPolicy: responsePolicy.retentionPolicy,
2796
- summary: responsePolicy.summary,
2797
- archiveId: executionId,
2798
- status: "success"
2799
- });
2800
- }
2801
- let toolMessageContent = responsePolicy.content;
2802
- if (responsePolicy.retentionPolicy === "summarize_archive") {
2803
- toolMessageContent = `ARCHIVED_TOOL_RESPONSE [executionId=${executionId}]: ${responsePolicy.summary}
2804
- Only use get_tool_response with executionId "${executionId}" if this summary is missing a specific field, id, snippet, or exact payload you still need.`;
2805
- } else if (responsePolicy.retentionPolicy === "drop") {
2806
- toolMessageContent = `DROPPED_TOOL_RESPONSE [executionId=${executionId}]: ${responsePolicy.summary}
2807
- Only use get_tool_response with executionId "${executionId}" if this summary is missing a specific field, id, snippet, or exact payload you still need.`;
2808
- }
2809
- appended.push({ role: "tool", content: toolMessageContent, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2781
+ appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2810
2782
  onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
2811
2783
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
2812
2784
  if (needsApproval && approvalEntry) {
@@ -2850,8 +2822,9 @@ Only use get_tool_response with executionId "${executionId}" if this summary is
2850
2822
  messageList,
2851
2823
  debug: {
2852
2824
  classification: responsePolicy.classification,
2853
- retentionPolicy: responsePolicy.retentionPolicy,
2854
- originalTokenCount: responsePolicy.tokenCount
2825
+ retentionPolicy: "keep_full",
2826
+ originalTokenCount: responsePolicy.tokenCount,
2827
+ truncated: responsePolicy.truncated
2855
2828
  }
2856
2829
  });
2857
2830
  toolCount += 1;
@@ -2968,6 +2941,246 @@ function createToolLimitFinalizeNode(_opts) {
2968
2941
  return { messages: [...state.messages, notice], ctx };
2969
2942
  };
2970
2943
  }
2944
+ var REFLECTION_SYSTEM_NAME = "agent_reflection";
2945
+ var REFLECTION_ASK_NAME = "agent_reflection_ask";
2946
+ var DEFAULT_REFLECT_PROMPT = `Reflect briefly \u2014 plain text, no JSON, no tool calls.
2947
+ Structure your note (1\u20132 sentences each):
2948
+ 1) What the latest tool results actually tell us (facts, surprises).
2949
+ 2) What is still missing or uncertain before we can finish the user's task.
2950
+ 3) The single next step you plan to take, and why that step over alternatives.
2951
+ Be concrete. Do not repeat the task description. Do not apologize. Max ${"${maxChars}"} characters total.`;
2952
+ function trimToChars(text, maxChars) {
2953
+ if (text.length <= maxChars) return text;
2954
+ return text.slice(0, maxChars - 1).trimEnd() + "\u2026";
2955
+ }
2956
+ function countToolCallsInLastTurn(state) {
2957
+ const ids = [];
2958
+ const names = [];
2959
+ for (let i = state.messages.length - 1; i >= 0; i--) {
2960
+ const m = state.messages[i];
2961
+ if (m?.role === "tool") {
2962
+ if (m.tool_call_id) ids.push(m.tool_call_id);
2963
+ if (m.name) names.push(m.name);
2964
+ } else if (m?.role === "assistant") {
2965
+ break;
2966
+ }
2967
+ }
2968
+ return { ids, names };
2969
+ }
2970
+ function stripReflectionAskMessages(messages) {
2971
+ return messages.filter((m) => !(m?.role === "user" && m?.name === REFLECTION_ASK_NAME));
2972
+ }
2973
+ function shouldRunReflection(cadence, state, ranToolsThisTurn) {
2974
+ if (cadence === "off") return false;
2975
+ if (cadence === "every_turn") return true;
2976
+ if (cadence === "after_tool") return ranToolsThisTurn;
2977
+ if (cadence === "on_branch") {
2978
+ const reflections = state.reflections || [];
2979
+ const lastReflectionTurn = reflections[reflections.length - 1]?.turn ?? -1;
2980
+ const currentTurnId = state.toolCallCount ?? 0;
2981
+ return currentTurnId - lastReflectionTurn >= 2;
2982
+ }
2983
+ return false;
2984
+ }
2985
+ function createReflectionNode(opts, resolved) {
2986
+ return async function reflectNode(state, trigger) {
2987
+ if (!resolved.enabled) return {};
2988
+ const runtime = state.agent || {
2989
+ model: opts.model,
2990
+ name: opts.name,
2991
+ version: opts.version
2992
+ };
2993
+ const model = runtime.model;
2994
+ if (!model?.invoke && !model?.stream) return {};
2995
+ const onEvent = state.ctx?.__onEvent;
2996
+ const traceSession = state.ctx?.__traceSession;
2997
+ const maxChars = resolved.maxChars;
2998
+ const promptBody = (resolved.promptTemplate || DEFAULT_REFLECT_PROMPT).replace(/\$\{maxChars\}/g, String(maxChars));
2999
+ const baseMessages = stripReflectionAskMessages([...state.messages]);
3000
+ const askMessage = {
3001
+ role: "user",
3002
+ name: REFLECTION_ASK_NAME,
3003
+ content: promptBody
3004
+ };
3005
+ const messagesForCall = resolved.mode === "piggyback" ? [...baseMessages, askMessage] : buildSeparateCallMessages(baseMessages, promptBody);
3006
+ const start = Date.now();
3007
+ let response;
3008
+ try {
3009
+ response = await model.invoke(messagesForCall, {
3010
+ // tool_choice:"none" so the model won't try to call tools during reflection
3011
+ tool_choice: "none",
3012
+ toolChoice: "none",
3013
+ // Override max_tokens for the reflection call. BaseChatModel passes this through
3014
+ // to the adapter which forwards into the provider body.
3015
+ max_tokens: resolved.maxTokens,
3016
+ maxTokens: resolved.maxTokens
3017
+ });
3018
+ } catch (err) {
3019
+ recordTraceEvent(traceSession, {
3020
+ type: "reflection",
3021
+ label: "Reflection error",
3022
+ actor: { scope: "agent", name: opts.name || "agent", role: "reflection" },
3023
+ status: "error",
3024
+ error: { message: err?.message || String(err) }
3025
+ });
3026
+ return {};
3027
+ }
3028
+ const durationMs = Date.now() - start;
3029
+ const rawText = extractText(response);
3030
+ const text = trimToChars(String(rawText ?? "").trim(), maxChars);
3031
+ if (!text) return {};
3032
+ const rawUsage = response?.usage_metadata || response?.response_metadata?.usage || response?.usage || response?.additional_kwargs?.usage;
3033
+ const usage = normalizeUsage(rawUsage);
3034
+ const { ids, names } = countToolCallsInLastTurn(state);
3035
+ const turn = state.toolCallCount ?? 0;
3036
+ const record = {
3037
+ id: safeRandomId(),
3038
+ turn,
3039
+ text,
3040
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3041
+ durationMs,
3042
+ anchorMessageIndex: state.messages.length,
3043
+ trigger,
3044
+ usage: usage ? {
3045
+ inputTokens: usage.prompt_tokens,
3046
+ outputTokens: usage.completion_tokens,
3047
+ cachedInputTokens: usage.prompt_tokens_details?.cached_tokens,
3048
+ totalTokens: usage.total_tokens,
3049
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens
3050
+ } : void 0,
3051
+ toolCallIds: ids.length > 0 ? ids : void 0
3052
+ };
3053
+ recordTraceEvent(traceSession, {
3054
+ type: "reflection",
3055
+ label: `Reflection turn ${turn}`,
3056
+ actor: { scope: "agent", name: opts.name || "agent", role: "reflection" },
3057
+ status: "success",
3058
+ durationMs,
3059
+ inputTokens: record.usage?.inputTokens,
3060
+ outputTokens: record.usage?.outputTokens,
3061
+ cachedInputTokens: record.usage?.cachedInputTokens,
3062
+ totalTokens: record.usage?.totalTokens,
3063
+ debug: { text, toolNames: names }
3064
+ });
3065
+ if (resolved.emitEvents) {
3066
+ try {
3067
+ onEvent?.({
3068
+ type: "reflection",
3069
+ id: record.id,
3070
+ turn,
3071
+ text,
3072
+ trigger,
3073
+ durationMs,
3074
+ usage: record.usage,
3075
+ toolCallIds: record.toolCallIds
3076
+ });
3077
+ } catch (e) {
3078
+ }
3079
+ }
3080
+ const nextMessages = stripReflectionAskMessages([...state.messages]);
3081
+ const reflections = [...state.reflections || [], record];
3082
+ const kept = reflections.slice(-Math.max(1, resolved.keepLast));
3083
+ const prunedMessages = nextMessages.filter(
3084
+ (m) => !(m?.role === "system" && m?.name === REFLECTION_SYSTEM_NAME)
3085
+ );
3086
+ for (const r of kept) {
3087
+ prunedMessages.push({
3088
+ role: "system",
3089
+ name: REFLECTION_SYSTEM_NAME,
3090
+ content: `<agent_reflection turn=${r.turn} at=${r.createdAt}>
3091
+ ${r.text}
3092
+ </agent_reflection>`,
3093
+ metadata: { reflectionId: r.id, reflectionTurn: r.turn, reflectionTrigger: r.trigger }
3094
+ });
3095
+ }
3096
+ return { messages: prunedMessages, reflections };
3097
+ };
3098
+ }
3099
+ function extractText(chunk) {
3100
+ if (chunk == null) return "";
3101
+ if (typeof chunk === "string") return chunk;
3102
+ if (typeof chunk?.content === "string") return chunk.content;
3103
+ if (Array.isArray(chunk?.content)) {
3104
+ return chunk.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
3105
+ }
3106
+ if (typeof chunk?.text === "string") return chunk.text;
3107
+ return "";
3108
+ }
3109
+ function buildSeparateCallMessages(base, promptBody) {
3110
+ const systemMessages = base.filter((m) => m?.role === "system");
3111
+ const tail = base.slice(-8);
3112
+ return [
3113
+ ...systemMessages,
3114
+ ...tail,
3115
+ { role: "user", name: REFLECTION_ASK_NAME, content: promptBody }
3116
+ ];
3117
+ }
3118
+ function safeRandomId() {
3119
+ try {
3120
+ return crypto.randomUUID();
3121
+ } catch {
3122
+ return `r_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;
3123
+ }
3124
+ }
3125
+
3126
+ // src/smart/reasoning.ts
3127
+ var LEVEL_DEFAULTS = {
3128
+ low: {
3129
+ native: { effort: "low" },
3130
+ reflection: { maxTokens: 200, maxChars: 600, cadence: "on_branch" }
3131
+ },
3132
+ medium: {
3133
+ native: { effort: "medium" },
3134
+ reflection: { maxTokens: 350, maxChars: 1200, cadence: "after_tool" }
3135
+ },
3136
+ high: {
3137
+ native: { effort: "high" },
3138
+ reflection: { maxTokens: 600, maxChars: 2e3, cadence: "after_tool" }
3139
+ }
3140
+ };
3141
+ function resolveReasoning(input) {
3142
+ if (!input || input.enabled === false) return void 0;
3143
+ const enabled = input.enabled ?? (input.native !== void 0 || input.reflection !== void 0 || input.level !== void 0);
3144
+ if (!enabled) return void 0;
3145
+ const level = input.level ?? "medium";
3146
+ const presets = LEVEL_DEFAULTS[level];
3147
+ let native;
3148
+ if (input.native === false) {
3149
+ native = void 0;
3150
+ } else {
3151
+ native = { ...presets.native, ...input.native ?? {} };
3152
+ }
3153
+ const reflection = resolveReflection(input.reflection, presets.reflection);
3154
+ return { enabled: true, level, native, reflection };
3155
+ }
3156
+ function resolveReflection(input, preset) {
3157
+ if (input === false) {
3158
+ return {
3159
+ enabled: false,
3160
+ cadence: "off",
3161
+ mode: "piggyback",
3162
+ maxTokens: preset.maxTokens,
3163
+ maxChars: preset.maxChars,
3164
+ keepLast: 3,
3165
+ summarize: false,
3166
+ emitEvents: true
3167
+ };
3168
+ }
3169
+ const cfg = input ?? {};
3170
+ const enabled = cfg.enabled ?? true;
3171
+ const cadence = cfg.cadence ?? preset.cadence;
3172
+ return {
3173
+ enabled: enabled && cadence !== "off",
3174
+ cadence: enabled ? cadence : "off",
3175
+ mode: cfg.mode ?? "piggyback",
3176
+ maxTokens: cfg.maxTokens ?? preset.maxTokens,
3177
+ maxChars: cfg.maxChars ?? preset.maxChars,
3178
+ keepLast: cfg.keepLast ?? 3,
3179
+ summarize: cfg.summarize ?? false,
3180
+ promptTemplate: cfg.promptTemplate,
3181
+ emitEvents: cfg.emitEvents ?? true
3182
+ };
3183
+ }
2971
3184
  function ensureArray(value) {
2972
3185
  return Array.isArray(value) ? value : [];
2973
3186
  }
@@ -3381,6 +3594,8 @@ function createAgent(opts) {
3381
3594
  }
3382
3595
  const toolsNode = createToolsNode(toolsBase, opts);
3383
3596
  const finalizeNode = createToolLimitFinalizeNode();
3597
+ const resolvedReasoning = resolveReasoning(opts.reasoning);
3598
+ const reflectNode = resolvedReasoning?.reflection.enabled ? createReflectionNode(opts, resolvedReasoning.reflection) : void 0;
3384
3599
  const mergeGuardrailOutcomes = (prev, next) => {
3385
3600
  if (!prev) return next;
3386
3601
  return {
@@ -3435,6 +3650,13 @@ function createAgent(opts) {
3435
3650
  if (summarizationThreshold === void 0) {
3436
3651
  state = clearNeedsSummarization(state);
3437
3652
  }
3653
+ if (resolvedReasoning?.native) {
3654
+ const ctx = { ...state.ctx || {} };
3655
+ if (!ctx.__reasoning) {
3656
+ ctx.__reasoning = resolvedReasoning.native;
3657
+ state = { ...state, ctx };
3658
+ }
3659
+ }
3438
3660
  const mergedLimits = {
3439
3661
  ...opts.limits || {},
3440
3662
  ...config?.limits || {}
@@ -3663,6 +3885,18 @@ function createAgent(opts) {
3663
3885
  if (state.ctx?.__awaitingApproval) break;
3664
3886
  if (checkpointIfRequested("after_tools")) break;
3665
3887
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3888
+ if (reflectNode && resolvedReasoning?.reflection.enabled) {
3889
+ const cadence = resolvedReasoning.reflection.cadence;
3890
+ if (shouldRunReflection(cadence, state, true)) {
3891
+ try {
3892
+ const patch = await reflectNode(state, cadence);
3893
+ if (patch && Object.keys(patch).length > 0) {
3894
+ state = { ...state, ...patch };
3895
+ }
3896
+ } catch {
3897
+ }
3898
+ }
3899
+ }
3666
3900
  }
3667
3901
  if (soManager && !state.ctx?.__finalizedDueToStructuredOutput) {
3668
3902
  const lastForParse = state.messages[state.messages.length - 1];
@@ -3990,40 +4224,6 @@ function createAgentNode(opts) {
3990
4224
  }
3991
4225
 
3992
4226
  // src/smart/contextPolicy.ts
3993
- function collectRecentFullToolCallIds(state, keepRecentFullCount) {
3994
- if (!keepRecentFullCount || keepRecentFullCount <= 0) return /* @__PURE__ */ new Set();
3995
- const history = state.toolHistory || [];
3996
- const ids = [];
3997
- for (let index = history.length - 1; index >= 0 && ids.length < keepRecentFullCount; index -= 1) {
3998
- const entry = history[index];
3999
- if (!entry || !entry.tool_call_id) continue;
4000
- if (entry.status && entry.status !== "success") continue;
4001
- ids.push(entry.tool_call_id);
4002
- }
4003
- return new Set(ids);
4004
- }
4005
- function applyRecentFullOverride(messages, state, config) {
4006
- const keepN = config.toolResponses.keepRecentFullCount;
4007
- if (!keepN || keepN <= 0) return messages;
4008
- const recentIds = collectRecentFullToolCallIds(state, keepN);
4009
- if (recentIds.size === 0) return messages;
4010
- const history = state.toolHistory || [];
4011
- const byCallId = /* @__PURE__ */ new Map();
4012
- for (const entry of history) {
4013
- if (entry?.tool_call_id) byCallId.set(entry.tool_call_id, entry);
4014
- }
4015
- return messages.map((message) => {
4016
- if (message.role !== "tool" || !message.tool_call_id) return message;
4017
- if (!recentIds.has(message.tool_call_id)) return message;
4018
- const entry = byCallId.get(message.tool_call_id);
4019
- if (!entry) return message;
4020
- const raw = entry.rawOutput ?? entry.output;
4021
- if (raw === void 0) return message;
4022
- const fullContent = typeof raw === "string" ? raw : safeStringify(raw);
4023
- if (!fullContent || fullContent === message.content) return message;
4024
- return { ...message, content: fullContent };
4025
- });
4026
- }
4027
4227
  function collectRecentTurns(messages, lastTurnsToKeep) {
4028
4228
  if (lastTurnsToKeep <= 0) return [];
4029
4229
  const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
@@ -4124,7 +4324,7 @@ function clampToBudget(messages, maxContextTokens) {
4124
4324
  function buildModelMessages(state, config) {
4125
4325
  const rawMessages = state.messages || [];
4126
4326
  if (config.context.policy === "raw") {
4127
- return clampToBudget(applyRecentFullOverride(rawMessages, state, config), config.limits.maxContextTokens);
4327
+ return clampToBudget(rawMessages, config.limits.maxContextTokens);
4128
4328
  }
4129
4329
  const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
4130
4330
  const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
@@ -4144,7 +4344,7 @@ function buildModelMessages(state, config) {
4144
4344
  ...syntheticContextMessages,
4145
4345
  ...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
4146
4346
  ];
4147
- return clampToBudget(applyRecentFullOverride(assembled, state, config), config.limits.maxContextTokens);
4347
+ return clampToBudget(assembled, config.limits.maxContextTokens);
4148
4348
  }
4149
4349
 
4150
4350
  // src/nodes/contextSummarize.ts
@@ -4246,24 +4446,8 @@ function extractCanonicalFacts(messages) {
4246
4446
  }
4247
4447
  return [...facts.values()];
4248
4448
  }
4249
- function flattenToolMessageContent(message) {
4250
- if (typeof message.content === "string") {
4251
- return message.content;
4252
- }
4253
- if (Array.isArray(message.content)) {
4254
- return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? String(part ?? "")).join(" ");
4255
- }
4256
- return "";
4257
- }
4258
4449
  function isSummarizedToolPlaceholder(content) {
4259
- return typeof content === "string" && (content === "SUMMARIZED" || content.startsWith("SUMMARIZED_TOOL_RESPONSE"));
4260
- }
4261
- function summarizeToolMessagePreview(message) {
4262
- const preview = flattenToolMessageContent(message).trim();
4263
- if (!preview) {
4264
- return "No summary available.";
4265
- }
4266
- return preview.length > 240 ? `${preview.slice(0, 240)}...` : preview;
4450
+ return typeof content === "string" && (content === "SUMMARIZED" || content.startsWith("SUMMARIZED_TOOL_RESPONSE") || content.startsWith("ARCHIVED_TOOL_RESPONSE") || content.startsWith("STRUCTURED_TOOL_RESPONSE") || content.startsWith("DROPPED_TOOL_RESPONSE"));
4267
4451
  }
4268
4452
  function findToolHistoryEntry(state, toolCallId, toolName) {
4269
4453
  const entries = [
@@ -4289,29 +4473,6 @@ function findToolHistoryEntry(state, toolCallId, toolName) {
4289
4473
  }
4290
4474
  return void 0;
4291
4475
  }
4292
- function buildSummarizedToolMessage(message, state) {
4293
- const toolName = message.name || "unknown_tool";
4294
- const toolCallId = message.tool_call_id;
4295
- const historyEntry = findToolHistoryEntry(state, toolCallId, toolName);
4296
- const executionId = historyEntry?.executionId;
4297
- const summary = historyEntry?.summary || summarizeToolMessagePreview(message);
4298
- const references = [
4299
- `toolName=${toolName}`,
4300
- toolCallId ? `toolCallId=${toolCallId}` : null,
4301
- executionId ? `executionId=${executionId}` : null
4302
- ].filter(Boolean).join("; ");
4303
- let retrievalHint = "Only use get_tool_response if this summary is missing a specific field, id, snippet, or exact payload you still need.";
4304
- if (executionId && toolCallId) {
4305
- retrievalHint = `Only use get_tool_response with executionId "${executionId}" or tool call id "${toolCallId}" if this summary is missing a specific field, id, snippet, or exact payload you still need.`;
4306
- } else if (executionId || toolCallId) {
4307
- retrievalHint = `Only use get_tool_response with executionId "${executionId || toolCallId}" if this summary is missing a specific field, id, snippet, or exact payload you still need.`;
4308
- }
4309
- return [
4310
- `SUMMARIZED_TOOL_RESPONSE [${references}]`,
4311
- `Summary: ${summary}`,
4312
- retrievalHint
4313
- ].join("\n");
4314
- }
4315
4476
  function mergeStableFacts(summary, additionalFacts) {
4316
4477
  if (additionalFacts.length === 0) return summary;
4317
4478
  const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
@@ -4369,20 +4530,13 @@ function createContextSummarizeNode(opts) {
4369
4530
  const messages = state.messages || [];
4370
4531
  const onEvent = state.ctx?.__onEvent;
4371
4532
  const traceSession = state.ctx?.__traceSession;
4372
- const toolResponsesConfigEarly = resolved.toolResponses;
4373
- const recentFullToolCallIdsEarly = collectRecentFullToolCallIds(state, toolResponsesConfigEarly.keepRecentFullCount);
4374
4533
  const compressableMessages = messages.filter(
4375
4534
  (m) => {
4376
4535
  if (m.role !== "tool") return false;
4377
4536
  if (isSummarizedToolPlaceholder(m.content)) return false;
4378
4537
  if (isSyntheticSummaryMessage(m)) return false;
4379
- if (m.tool_call_id && recentFullToolCallIdsEarly.has(m.tool_call_id)) return false;
4380
- const histEntry = findToolHistoryEntry(state, m.tool_call_id, m.name);
4381
- if (histEntry?.retentionPolicy === "keep_full") return false;
4382
- if (!histEntry?.retentionPolicy) {
4383
- const byTool = toolResponsesConfigEarly.toolResponseRetentionByTool?.[m.name || ""];
4384
- if ((byTool || toolResponsesConfigEarly.defaultPolicy) === "keep_full") return false;
4385
- }
4538
+ const policy = resolveSummarizationRetention(m.name || "", resolved);
4539
+ if (policy === "keep_full") return false;
4386
4540
  return true;
4387
4541
  }
4388
4542
  );
@@ -4562,8 +4716,8 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4562
4716
  break;
4563
4717
  }
4564
4718
  }
4565
- const toolResponsesConfig = resolved.toolResponses;
4566
- const recentFullToolCallIds = collectRecentFullToolCallIds(state, toolResponsesConfig.keepRecentFullCount);
4719
+ const archivedExecutions = [...state.toolHistoryArchived || []];
4720
+ const archivedIds = new Set(archivedExecutions.map((entry) => entry?.executionId).filter(Boolean));
4567
4721
  const newMessages = messages.map((m) => {
4568
4722
  if (m.role === "tool") {
4569
4723
  if (isSyntheticSummaryMessage(m)) {
@@ -4575,25 +4729,39 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4575
4729
  }
4576
4730
  if (isSummarizedToolPlaceholder(m.content)) return m;
4577
4731
  if (toolCallId && protectedToolCallIds.has(toolCallId)) return m;
4578
- if (toolCallId && recentFullToolCallIds.has(toolCallId)) return m;
4579
4732
  const toolName = m.name || "";
4733
+ const policy = resolveSummarizationRetention(toolName, resolved);
4734
+ if (policy === "keep_full") return m;
4580
4735
  const historyEntry = findToolHistoryEntry(state, toolCallId, toolName);
4581
- const entryRetention = historyEntry?.retentionPolicy;
4582
- if (entryRetention === "keep_full") return m;
4583
- if (!entryRetention) {
4584
- const byTool = toolResponsesConfig.toolResponseRetentionByTool?.[toolName];
4585
- const configPolicy = byTool || toolResponsesConfig.defaultPolicy;
4586
- if (configPolicy === "keep_full") return m;
4736
+ const rawOutput = historyEntry?.rawOutput ?? historyEntry?.output ?? m.content;
4737
+ const executionId = historyEntry?.executionId;
4738
+ const prebuiltSummary = historyEntry?.summary || summarizeObject(rawOutput);
4739
+ if ((policy === "summarize_archive" || policy === "drop") && historyEntry && executionId && !archivedIds.has(executionId)) {
4740
+ archivedExecutions.push({
4741
+ ...historyEntry,
4742
+ summarized: true,
4743
+ retentionPolicy: policy,
4744
+ archiveId: executionId,
4745
+ summary: prebuiltSummary
4746
+ });
4747
+ archivedIds.add(executionId);
4587
4748
  }
4588
4749
  return {
4589
4750
  ...m,
4590
- content: buildSummarizedToolMessage(m, state)
4751
+ content: renderRetainedToolMessage({
4752
+ policy,
4753
+ rawOutput,
4754
+ toolName,
4755
+ toolCallId,
4756
+ executionId,
4757
+ prebuiltSummary
4758
+ })
4591
4759
  };
4592
4760
  }
4593
4761
  return m;
4594
4762
  }).filter((m) => m !== null);
4595
4763
  const actuallyCompressed = newMessages.filter(
4596
- (m) => m.role === "tool" && typeof m.content === "string" && m.content.startsWith("SUMMARIZED_TOOL_RESPONSE") && !messages.some((orig) => orig.tool_call_id === m.tool_call_id && isSummarizedToolPlaceholder(orig.content))
4764
+ (m) => m.role === "tool" && isSummarizedToolPlaceholder(m.content) && !messages.some((orig) => orig.tool_call_id === m.tool_call_id && isSummarizedToolPlaceholder(orig.content))
4597
4765
  );
4598
4766
  if (actuallyCompressed.length === 0) {
4599
4767
  return {};
@@ -4672,7 +4840,8 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4672
4840
  return {
4673
4841
  messages: finalMessages,
4674
4842
  summaries,
4675
- summaryRecords
4843
+ summaryRecords,
4844
+ toolHistoryArchived: archivedExecutions
4676
4845
  };
4677
4846
  };
4678
4847
  }
@@ -5737,6 +5906,52 @@ function parseSSEBlock(block) {
5737
5906
  return { event, data: dataLines.join("\n"), id, retry };
5738
5907
  }
5739
5908
 
5909
+ // src/providers/utils/reasoning.ts
5910
+ function applyOpenAIReasoning(body, reasoning, mode = "legacy_effort") {
5911
+ if (!reasoning) return;
5912
+ const effort = normalizeEffort(reasoning.effort);
5913
+ if (mode === "responses") {
5914
+ const obj = {};
5915
+ if (effort) obj.effort = effort;
5916
+ if (reasoning.includeThoughts) obj.summary = "auto";
5917
+ if (reasoning.providerExtras) Object.assign(obj, reasoning.providerExtras);
5918
+ if (Object.keys(obj).length > 0) body.reasoning = obj;
5919
+ } else {
5920
+ if (effort) body.reasoning_effort = effort;
5921
+ if (reasoning.providerExtras) Object.assign(body, reasoning.providerExtras);
5922
+ }
5923
+ }
5924
+ function applyAnthropicReasoning(body, reasoning) {
5925
+ if (!reasoning) return;
5926
+ const budget = resolveBudget(reasoning, { low: 2048, medium: 8192, high: 16384, minimal: 1024 });
5927
+ if (!budget) return;
5928
+ const thinking = { type: "enabled", budget_tokens: budget };
5929
+ if (reasoning.providerExtras) Object.assign(thinking, reasoning.providerExtras);
5930
+ body.thinking = thinking;
5931
+ }
5932
+ function applyGeminiReasoning(generationConfig, reasoning) {
5933
+ if (!reasoning) return;
5934
+ const budget = resolveBudget(reasoning, { low: 1024, medium: 4096, high: 12e3, minimal: 512 });
5935
+ if (budget == null) return;
5936
+ const thinkingConfig = { thinkingBudget: budget };
5937
+ if (reasoning.includeThoughts) thinkingConfig.includeThoughts = true;
5938
+ if (reasoning.providerExtras) Object.assign(thinkingConfig, reasoning.providerExtras);
5939
+ generationConfig.thinkingConfig = thinkingConfig;
5940
+ }
5941
+ function normalizeEffort(effort) {
5942
+ if (!effort) return void 0;
5943
+ const allowed = ["minimal", "low", "medium", "high"];
5944
+ return allowed.includes(effort) ? effort : void 0;
5945
+ }
5946
+ function resolveBudget(reasoning, effortBudget) {
5947
+ if (typeof reasoning.budgetTokens === "number" && reasoning.budgetTokens > 0) {
5948
+ return Math.floor(reasoning.budgetTokens);
5949
+ }
5950
+ const effort = normalizeEffort(reasoning.effort);
5951
+ if (effort && effortBudget[effort]) return effortBudget[effort];
5952
+ return void 0;
5953
+ }
5954
+
5740
5955
  // src/providers/openai.ts
5741
5956
  var OpenAIProvider = class extends BaseProvider {
5742
5957
  constructor(config) {
@@ -5852,6 +6067,7 @@ var OpenAIProvider = class extends BaseProvider {
5852
6067
  if (request.extra) {
5853
6068
  Object.assign(body, request.extra);
5854
6069
  }
6070
+ applyOpenAIReasoning(body, request.reasoning, "legacy_effort");
5855
6071
  return body;
5856
6072
  }
5857
6073
  toOpenAIMessage(m) {
@@ -6110,6 +6326,7 @@ var AnthropicProvider = class extends BaseProvider {
6110
6326
  if (request.extra) {
6111
6327
  Object.assign(body, request.extra);
6112
6328
  }
6329
+ applyAnthropicReasoning(body, request.reasoning);
6113
6330
  return body;
6114
6331
  }
6115
6332
  splitSystemMessages(messages) {
@@ -6764,6 +6981,7 @@ var VertexProvider = class extends BaseProvider {
6764
6981
  }
6765
6982
  }
6766
6983
  }
6984
+ applyGeminiReasoning(generationConfig, request.reasoning);
6767
6985
  if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
6768
6986
  if (request.tools?.length) {
6769
6987
  body.tools = [
@@ -7102,6 +7320,13 @@ function fromNativeProvider(provider, options) {
7102
7320
  if (opts?.maxTokens != null) req.maxTokens = opts.maxTokens;
7103
7321
  if (tools?.length) req.tools = tools;
7104
7322
  if (opts?.extra) req.extra = opts.extra;
7323
+ const reasoningOverride = invokeOptions?.reasoning;
7324
+ const reasoningCfg = reasoningOverride ?? opts?.reasoning;
7325
+ if (reasoningCfg) req.reasoning = reasoningCfg;
7326
+ const tc = invokeOptions?.tool_choice ?? invokeOptions?.toolChoice;
7327
+ if (tc) {
7328
+ req.toolChoice = tc;
7329
+ }
7105
7330
  const rf = invokeOptions?.response_format;
7106
7331
  if (rf && typeof rf === "object" && rf.type) {
7107
7332
  if (rf.type === "json_schema" && rf.json_schema) {