@cognipeer/agent-sdk 0.6.0 → 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.mjs CHANGED
@@ -5,7 +5,7 @@ import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { Buffer as Buffer$1 } from 'buffer';
7
7
  import { nanoid } from 'nanoid';
8
- import crypto, { createHash, createHmac } from 'crypto';
8
+ import crypto, { randomUUID, createHash, createHmac } from 'crypto';
9
9
  import AjvModule from 'ajv';
10
10
 
11
11
  // src/adapters/langchain.ts
@@ -1968,7 +1968,9 @@ function createAgentCoreNode(opts) {
1968
1968
  const cancellationToken = state.ctx?.__cancellationToken;
1969
1969
  const abortSignal = state.ctx?.__abortSignal;
1970
1970
  const responseFormat = runtime.responseFormat;
1971
- const extractText = (chunk) => {
1971
+ const reasoningForCall = state.ctx?.__reasoning;
1972
+ const reasoningInvokeOpts = reasoningForCall ? { reasoning: reasoningForCall } : void 0;
1973
+ const extractText2 = (chunk) => {
1972
1974
  if (chunk == null) return "";
1973
1975
  if (typeof chunk === "string") return chunk;
1974
1976
  if (typeof chunk?.content === "string") return chunk.content;
@@ -1986,7 +1988,7 @@ function createAgentCoreNode(opts) {
1986
1988
  if (streamingEnabled && typeof modelWithTools.stream === "function") {
1987
1989
  let streamedText = "";
1988
1990
  let streamedMessage;
1989
- const streamOptions = { signal: abortSignal, cancellationToken, ...responseFormat };
1991
+ const streamOptions = { signal: abortSignal, cancellationToken, ...responseFormat, ...reasoningInvokeOpts };
1990
1992
  for await (const chunk of modelWithTools.stream(normalizedMessages, streamOptions)) {
1991
1993
  if (cancellationToken && cancellationToken.isCancellationRequested || abortSignal?.aborted) {
1992
1994
  break;
@@ -1994,7 +1996,7 @@ function createAgentCoreNode(opts) {
1994
1996
  if (chunk && typeof chunk === "object" && chunk.role) {
1995
1997
  streamedMessage = chunk;
1996
1998
  }
1997
- const text = extractText(chunk);
1999
+ const text = extractText2(chunk);
1998
2000
  if (text) {
1999
2001
  streamedText += text;
2000
2002
  onStream?.({ text });
@@ -2010,7 +2012,7 @@ function createAgentCoreNode(opts) {
2010
2012
  response = { role: "assistant", content: streamedText };
2011
2013
  }
2012
2014
  } else {
2013
- response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken, ...responseFormat });
2015
+ response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken, ...responseFormat, ...reasoningInvokeOpts });
2014
2016
  }
2015
2017
  } catch (err) {
2016
2018
  const durationMs2 = Date.now() - start;
@@ -2929,6 +2931,246 @@ function createToolLimitFinalizeNode(_opts) {
2929
2931
  return { messages: [...state.messages, notice], ctx };
2930
2932
  };
2931
2933
  }
2934
+ var REFLECTION_SYSTEM_NAME = "agent_reflection";
2935
+ var REFLECTION_ASK_NAME = "agent_reflection_ask";
2936
+ var DEFAULT_REFLECT_PROMPT = `Reflect briefly \u2014 plain text, no JSON, no tool calls.
2937
+ Structure your note (1\u20132 sentences each):
2938
+ 1) What the latest tool results actually tell us (facts, surprises).
2939
+ 2) What is still missing or uncertain before we can finish the user's task.
2940
+ 3) The single next step you plan to take, and why that step over alternatives.
2941
+ Be concrete. Do not repeat the task description. Do not apologize. Max ${"${maxChars}"} characters total.`;
2942
+ function trimToChars(text, maxChars) {
2943
+ if (text.length <= maxChars) return text;
2944
+ return text.slice(0, maxChars - 1).trimEnd() + "\u2026";
2945
+ }
2946
+ function countToolCallsInLastTurn(state) {
2947
+ const ids = [];
2948
+ const names = [];
2949
+ for (let i = state.messages.length - 1; i >= 0; i--) {
2950
+ const m = state.messages[i];
2951
+ if (m?.role === "tool") {
2952
+ if (m.tool_call_id) ids.push(m.tool_call_id);
2953
+ if (m.name) names.push(m.name);
2954
+ } else if (m?.role === "assistant") {
2955
+ break;
2956
+ }
2957
+ }
2958
+ return { ids, names };
2959
+ }
2960
+ function stripReflectionAskMessages(messages) {
2961
+ return messages.filter((m) => !(m?.role === "user" && m?.name === REFLECTION_ASK_NAME));
2962
+ }
2963
+ function shouldRunReflection(cadence, state, ranToolsThisTurn) {
2964
+ if (cadence === "off") return false;
2965
+ if (cadence === "every_turn") return true;
2966
+ if (cadence === "after_tool") return ranToolsThisTurn;
2967
+ if (cadence === "on_branch") {
2968
+ const reflections = state.reflections || [];
2969
+ const lastReflectionTurn = reflections[reflections.length - 1]?.turn ?? -1;
2970
+ const currentTurnId = state.toolCallCount ?? 0;
2971
+ return currentTurnId - lastReflectionTurn >= 2;
2972
+ }
2973
+ return false;
2974
+ }
2975
+ function createReflectionNode(opts, resolved) {
2976
+ return async function reflectNode(state, trigger) {
2977
+ if (!resolved.enabled) return {};
2978
+ const runtime = state.agent || {
2979
+ model: opts.model,
2980
+ name: opts.name,
2981
+ version: opts.version
2982
+ };
2983
+ const model = runtime.model;
2984
+ if (!model?.invoke && !model?.stream) return {};
2985
+ const onEvent = state.ctx?.__onEvent;
2986
+ const traceSession = state.ctx?.__traceSession;
2987
+ const maxChars = resolved.maxChars;
2988
+ const promptBody = (resolved.promptTemplate || DEFAULT_REFLECT_PROMPT).replace(/\$\{maxChars\}/g, String(maxChars));
2989
+ const baseMessages = stripReflectionAskMessages([...state.messages]);
2990
+ const askMessage = {
2991
+ role: "user",
2992
+ name: REFLECTION_ASK_NAME,
2993
+ content: promptBody
2994
+ };
2995
+ const messagesForCall = resolved.mode === "piggyback" ? [...baseMessages, askMessage] : buildSeparateCallMessages(baseMessages, promptBody);
2996
+ const start = Date.now();
2997
+ let response;
2998
+ try {
2999
+ response = await model.invoke(messagesForCall, {
3000
+ // tool_choice:"none" so the model won't try to call tools during reflection
3001
+ tool_choice: "none",
3002
+ toolChoice: "none",
3003
+ // Override max_tokens for the reflection call. BaseChatModel passes this through
3004
+ // to the adapter which forwards into the provider body.
3005
+ max_tokens: resolved.maxTokens,
3006
+ maxTokens: resolved.maxTokens
3007
+ });
3008
+ } catch (err) {
3009
+ recordTraceEvent(traceSession, {
3010
+ type: "reflection",
3011
+ label: "Reflection error",
3012
+ actor: { scope: "agent", name: opts.name || "agent", role: "reflection" },
3013
+ status: "error",
3014
+ error: { message: err?.message || String(err) }
3015
+ });
3016
+ return {};
3017
+ }
3018
+ const durationMs = Date.now() - start;
3019
+ const rawText = extractText(response);
3020
+ const text = trimToChars(String(rawText ?? "").trim(), maxChars);
3021
+ if (!text) return {};
3022
+ const rawUsage = response?.usage_metadata || response?.response_metadata?.usage || response?.usage || response?.additional_kwargs?.usage;
3023
+ const usage = normalizeUsage(rawUsage);
3024
+ const { ids, names } = countToolCallsInLastTurn(state);
3025
+ const turn = state.toolCallCount ?? 0;
3026
+ const record = {
3027
+ id: safeRandomId(),
3028
+ turn,
3029
+ text,
3030
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3031
+ durationMs,
3032
+ anchorMessageIndex: state.messages.length,
3033
+ trigger,
3034
+ usage: usage ? {
3035
+ inputTokens: usage.prompt_tokens,
3036
+ outputTokens: usage.completion_tokens,
3037
+ cachedInputTokens: usage.prompt_tokens_details?.cached_tokens,
3038
+ totalTokens: usage.total_tokens,
3039
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens
3040
+ } : void 0,
3041
+ toolCallIds: ids.length > 0 ? ids : void 0
3042
+ };
3043
+ recordTraceEvent(traceSession, {
3044
+ type: "reflection",
3045
+ label: `Reflection turn ${turn}`,
3046
+ actor: { scope: "agent", name: opts.name || "agent", role: "reflection" },
3047
+ status: "success",
3048
+ durationMs,
3049
+ inputTokens: record.usage?.inputTokens,
3050
+ outputTokens: record.usage?.outputTokens,
3051
+ cachedInputTokens: record.usage?.cachedInputTokens,
3052
+ totalTokens: record.usage?.totalTokens,
3053
+ debug: { text, toolNames: names }
3054
+ });
3055
+ if (resolved.emitEvents) {
3056
+ try {
3057
+ onEvent?.({
3058
+ type: "reflection",
3059
+ id: record.id,
3060
+ turn,
3061
+ text,
3062
+ trigger,
3063
+ durationMs,
3064
+ usage: record.usage,
3065
+ toolCallIds: record.toolCallIds
3066
+ });
3067
+ } catch (e) {
3068
+ }
3069
+ }
3070
+ const nextMessages = stripReflectionAskMessages([...state.messages]);
3071
+ const reflections = [...state.reflections || [], record];
3072
+ const kept = reflections.slice(-Math.max(1, resolved.keepLast));
3073
+ const prunedMessages = nextMessages.filter(
3074
+ (m) => !(m?.role === "system" && m?.name === REFLECTION_SYSTEM_NAME)
3075
+ );
3076
+ for (const r of kept) {
3077
+ prunedMessages.push({
3078
+ role: "system",
3079
+ name: REFLECTION_SYSTEM_NAME,
3080
+ content: `<agent_reflection turn=${r.turn} at=${r.createdAt}>
3081
+ ${r.text}
3082
+ </agent_reflection>`,
3083
+ metadata: { reflectionId: r.id, reflectionTurn: r.turn, reflectionTrigger: r.trigger }
3084
+ });
3085
+ }
3086
+ return { messages: prunedMessages, reflections };
3087
+ };
3088
+ }
3089
+ function extractText(chunk) {
3090
+ if (chunk == null) return "";
3091
+ if (typeof chunk === "string") return chunk;
3092
+ if (typeof chunk?.content === "string") return chunk.content;
3093
+ if (Array.isArray(chunk?.content)) {
3094
+ return chunk.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
3095
+ }
3096
+ if (typeof chunk?.text === "string") return chunk.text;
3097
+ return "";
3098
+ }
3099
+ function buildSeparateCallMessages(base, promptBody) {
3100
+ const systemMessages = base.filter((m) => m?.role === "system");
3101
+ const tail = base.slice(-8);
3102
+ return [
3103
+ ...systemMessages,
3104
+ ...tail,
3105
+ { role: "user", name: REFLECTION_ASK_NAME, content: promptBody }
3106
+ ];
3107
+ }
3108
+ function safeRandomId() {
3109
+ try {
3110
+ return randomUUID();
3111
+ } catch {
3112
+ return `r_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;
3113
+ }
3114
+ }
3115
+
3116
+ // src/smart/reasoning.ts
3117
+ var LEVEL_DEFAULTS = {
3118
+ low: {
3119
+ native: { effort: "low" },
3120
+ reflection: { maxTokens: 200, maxChars: 600, cadence: "on_branch" }
3121
+ },
3122
+ medium: {
3123
+ native: { effort: "medium" },
3124
+ reflection: { maxTokens: 350, maxChars: 1200, cadence: "after_tool" }
3125
+ },
3126
+ high: {
3127
+ native: { effort: "high" },
3128
+ reflection: { maxTokens: 600, maxChars: 2e3, cadence: "after_tool" }
3129
+ }
3130
+ };
3131
+ function resolveReasoning(input) {
3132
+ if (!input || input.enabled === false) return void 0;
3133
+ const enabled = input.enabled ?? (input.native !== void 0 || input.reflection !== void 0 || input.level !== void 0);
3134
+ if (!enabled) return void 0;
3135
+ const level = input.level ?? "medium";
3136
+ const presets = LEVEL_DEFAULTS[level];
3137
+ let native;
3138
+ if (input.native === false) {
3139
+ native = void 0;
3140
+ } else {
3141
+ native = { ...presets.native, ...input.native ?? {} };
3142
+ }
3143
+ const reflection = resolveReflection(input.reflection, presets.reflection);
3144
+ return { enabled: true, level, native, reflection };
3145
+ }
3146
+ function resolveReflection(input, preset) {
3147
+ if (input === false) {
3148
+ return {
3149
+ enabled: false,
3150
+ cadence: "off",
3151
+ mode: "piggyback",
3152
+ maxTokens: preset.maxTokens,
3153
+ maxChars: preset.maxChars,
3154
+ keepLast: 3,
3155
+ summarize: false,
3156
+ emitEvents: true
3157
+ };
3158
+ }
3159
+ const cfg = input ?? {};
3160
+ const enabled = cfg.enabled ?? true;
3161
+ const cadence = cfg.cadence ?? preset.cadence;
3162
+ return {
3163
+ enabled: enabled && cadence !== "off",
3164
+ cadence: enabled ? cadence : "off",
3165
+ mode: cfg.mode ?? "piggyback",
3166
+ maxTokens: cfg.maxTokens ?? preset.maxTokens,
3167
+ maxChars: cfg.maxChars ?? preset.maxChars,
3168
+ keepLast: cfg.keepLast ?? 3,
3169
+ summarize: cfg.summarize ?? false,
3170
+ promptTemplate: cfg.promptTemplate,
3171
+ emitEvents: cfg.emitEvents ?? true
3172
+ };
3173
+ }
2932
3174
  function ensureArray(value) {
2933
3175
  return Array.isArray(value) ? value : [];
2934
3176
  }
@@ -3342,6 +3584,8 @@ function createAgent(opts) {
3342
3584
  }
3343
3585
  const toolsNode = createToolsNode(toolsBase, opts);
3344
3586
  const finalizeNode = createToolLimitFinalizeNode();
3587
+ const resolvedReasoning = resolveReasoning(opts.reasoning);
3588
+ const reflectNode = resolvedReasoning?.reflection.enabled ? createReflectionNode(opts, resolvedReasoning.reflection) : void 0;
3345
3589
  const mergeGuardrailOutcomes = (prev, next) => {
3346
3590
  if (!prev) return next;
3347
3591
  return {
@@ -3396,6 +3640,13 @@ function createAgent(opts) {
3396
3640
  if (summarizationThreshold === void 0) {
3397
3641
  state = clearNeedsSummarization(state);
3398
3642
  }
3643
+ if (resolvedReasoning?.native) {
3644
+ const ctx = { ...state.ctx || {} };
3645
+ if (!ctx.__reasoning) {
3646
+ ctx.__reasoning = resolvedReasoning.native;
3647
+ state = { ...state, ctx };
3648
+ }
3649
+ }
3399
3650
  const mergedLimits = {
3400
3651
  ...opts.limits || {},
3401
3652
  ...config?.limits || {}
@@ -3624,6 +3875,18 @@ function createAgent(opts) {
3624
3875
  if (state.ctx?.__awaitingApproval) break;
3625
3876
  if (checkpointIfRequested("after_tools")) break;
3626
3877
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3878
+ if (reflectNode && resolvedReasoning?.reflection.enabled) {
3879
+ const cadence = resolvedReasoning.reflection.cadence;
3880
+ if (shouldRunReflection(cadence, state, true)) {
3881
+ try {
3882
+ const patch = await reflectNode(state, cadence);
3883
+ if (patch && Object.keys(patch).length > 0) {
3884
+ state = { ...state, ...patch };
3885
+ }
3886
+ } catch {
3887
+ }
3888
+ }
3889
+ }
3627
3890
  }
3628
3891
  if (soManager && !state.ctx?.__finalizedDueToStructuredOutput) {
3629
3892
  const lastForParse = state.messages[state.messages.length - 1];
@@ -5633,6 +5896,52 @@ function parseSSEBlock(block) {
5633
5896
  return { event, data: dataLines.join("\n"), id, retry };
5634
5897
  }
5635
5898
 
5899
+ // src/providers/utils/reasoning.ts
5900
+ function applyOpenAIReasoning(body, reasoning, mode = "legacy_effort") {
5901
+ if (!reasoning) return;
5902
+ const effort = normalizeEffort(reasoning.effort);
5903
+ if (mode === "responses") {
5904
+ const obj = {};
5905
+ if (effort) obj.effort = effort;
5906
+ if (reasoning.includeThoughts) obj.summary = "auto";
5907
+ if (reasoning.providerExtras) Object.assign(obj, reasoning.providerExtras);
5908
+ if (Object.keys(obj).length > 0) body.reasoning = obj;
5909
+ } else {
5910
+ if (effort) body.reasoning_effort = effort;
5911
+ if (reasoning.providerExtras) Object.assign(body, reasoning.providerExtras);
5912
+ }
5913
+ }
5914
+ function applyAnthropicReasoning(body, reasoning) {
5915
+ if (!reasoning) return;
5916
+ const budget = resolveBudget(reasoning, { low: 2048, medium: 8192, high: 16384, minimal: 1024 });
5917
+ if (!budget) return;
5918
+ const thinking = { type: "enabled", budget_tokens: budget };
5919
+ if (reasoning.providerExtras) Object.assign(thinking, reasoning.providerExtras);
5920
+ body.thinking = thinking;
5921
+ }
5922
+ function applyGeminiReasoning(generationConfig, reasoning) {
5923
+ if (!reasoning) return;
5924
+ const budget = resolveBudget(reasoning, { low: 1024, medium: 4096, high: 12e3, minimal: 512 });
5925
+ if (budget == null) return;
5926
+ const thinkingConfig = { thinkingBudget: budget };
5927
+ if (reasoning.includeThoughts) thinkingConfig.includeThoughts = true;
5928
+ if (reasoning.providerExtras) Object.assign(thinkingConfig, reasoning.providerExtras);
5929
+ generationConfig.thinkingConfig = thinkingConfig;
5930
+ }
5931
+ function normalizeEffort(effort) {
5932
+ if (!effort) return void 0;
5933
+ const allowed = ["minimal", "low", "medium", "high"];
5934
+ return allowed.includes(effort) ? effort : void 0;
5935
+ }
5936
+ function resolveBudget(reasoning, effortBudget) {
5937
+ if (typeof reasoning.budgetTokens === "number" && reasoning.budgetTokens > 0) {
5938
+ return Math.floor(reasoning.budgetTokens);
5939
+ }
5940
+ const effort = normalizeEffort(reasoning.effort);
5941
+ if (effort && effortBudget[effort]) return effortBudget[effort];
5942
+ return void 0;
5943
+ }
5944
+
5636
5945
  // src/providers/openai.ts
5637
5946
  var OpenAIProvider = class extends BaseProvider {
5638
5947
  constructor(config) {
@@ -5748,6 +6057,7 @@ var OpenAIProvider = class extends BaseProvider {
5748
6057
  if (request.extra) {
5749
6058
  Object.assign(body, request.extra);
5750
6059
  }
6060
+ applyOpenAIReasoning(body, request.reasoning, "legacy_effort");
5751
6061
  return body;
5752
6062
  }
5753
6063
  toOpenAIMessage(m) {
@@ -6006,6 +6316,7 @@ var AnthropicProvider = class extends BaseProvider {
6006
6316
  if (request.extra) {
6007
6317
  Object.assign(body, request.extra);
6008
6318
  }
6319
+ applyAnthropicReasoning(body, request.reasoning);
6009
6320
  return body;
6010
6321
  }
6011
6322
  splitSystemMessages(messages) {
@@ -6660,6 +6971,7 @@ var VertexProvider = class extends BaseProvider {
6660
6971
  }
6661
6972
  }
6662
6973
  }
6974
+ applyGeminiReasoning(generationConfig, request.reasoning);
6663
6975
  if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
6664
6976
  if (request.tools?.length) {
6665
6977
  body.tools = [
@@ -6998,6 +7310,13 @@ function fromNativeProvider(provider, options) {
6998
7310
  if (opts?.maxTokens != null) req.maxTokens = opts.maxTokens;
6999
7311
  if (tools?.length) req.tools = tools;
7000
7312
  if (opts?.extra) req.extra = opts.extra;
7313
+ const reasoningOverride = invokeOptions?.reasoning;
7314
+ const reasoningCfg = reasoningOverride ?? opts?.reasoning;
7315
+ if (reasoningCfg) req.reasoning = reasoningCfg;
7316
+ const tc = invokeOptions?.tool_choice ?? invokeOptions?.toolChoice;
7317
+ if (tc) {
7318
+ req.toolChoice = tc;
7319
+ }
7001
7320
  const rf = invokeOptions?.response_format;
7002
7321
  if (rf && typeof rf === "object" && rf.type) {
7003
7322
  if (rf.type === "json_schema" && rf.json_schema) {