@cognipeer/agent-sdk 0.6.0 → 0.6.2
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/README.md +58 -8
- package/dist/index.cjs +324 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -5
- package/dist/index.d.ts +117 -5
- package/dist/index.mjs +325 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://npmjs.com/package/@cognipeer/agent-sdk) [Docs Website](https://cognipeer.github.io/agent-sdk/)
|
|
4
4
|
|
|
5
|
-
Lightweight, message-first agent runtime that keeps tool calls transparent, automatically summarizes long histories, and ships with planning, multi-agent handoffs, and structured tracing.
|
|
5
|
+
Lightweight, message-first agent runtime that keeps tool calls transparent, supports provider-native reasoning plus post-tool reflection, automatically summarizes long histories, and ships with planning, multi-agent handoffs, and structured tracing.
|
|
6
6
|
|
|
7
7
|
- SDK source: `src/`
|
|
8
8
|
- Examples: `examples/`
|
|
@@ -15,6 +15,7 @@ Lightweight, message-first agent runtime that keeps tool calls transparent, auto
|
|
|
15
15
|
- [Install](#install)
|
|
16
16
|
- [Quick start](#quick-start)
|
|
17
17
|
- [Smart agent (planning + summarization)](#smart-agent-planning--summarization)
|
|
18
|
+
- [Reasoning & reflection](#reasoning--reflection)
|
|
18
19
|
- [Base agent (minimal loop)](#base-agent-minimal-loop)
|
|
19
20
|
- [Key capabilities](#key-capabilities)
|
|
20
21
|
- [Examples](#examples)
|
|
@@ -33,9 +34,10 @@ Highlights:
|
|
|
33
34
|
- **Message-first design** – assistant tool calls and tool responses stay in the transcript.
|
|
34
35
|
- **Token-aware summarization** – chunked rewriting archives oversized tool outputs while exposing `get_tool_response` for lossless retrieval.
|
|
35
36
|
- **Planning mode** – adaptive system prompt + TODO tool supports full plan writes and version-safe partial updates.
|
|
37
|
+
- **Unified reasoning surface** – one `reasoning` config controls provider-native reasoning and post-tool plain-text reflections.
|
|
36
38
|
- **Structured output** – provide a Zod schema and the agent injects a finalize tool to capture JSON deterministically.
|
|
37
39
|
- **Multi-agent and handoffs** – wrap agents as tools or transfer control mid-run with `asTool` / `asHandoff`.
|
|
38
|
-
- **Usage + events** – normalize provider usage, surface `tool_call`, `plan`, `summarization`, `metadata`, and `handoff` events.
|
|
40
|
+
- **Usage + events** – normalize provider usage, surface `tool_call`, `plan`, `summarization`, `reflection`, `metadata`, and `handoff` events.
|
|
39
41
|
- **Structured tracing** – optional per-invoke JSON traces with metadata, payload capture, and pluggable sinks (file, HTTP, Cognipeer, custom).
|
|
40
42
|
|
|
41
43
|
## What’s inside
|
|
@@ -181,6 +183,52 @@ const agent = createSmartAgent({
|
|
|
181
183
|
});
|
|
182
184
|
```
|
|
183
185
|
|
|
186
|
+
### Reasoning & reflection
|
|
187
|
+
|
|
188
|
+
Both `createAgent(...)` and `createSmartAgent(...)` accept a unified `reasoning` config:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const agent = createSmartAgent({
|
|
192
|
+
model: fromNativeProvider(
|
|
193
|
+
createProvider({ provider: "openai", apiKey: process.env.OPENAI_API_KEY! }),
|
|
194
|
+
{ model: "gpt-5" },
|
|
195
|
+
),
|
|
196
|
+
tools: [echo],
|
|
197
|
+
reasoning: {
|
|
198
|
+
enabled: true,
|
|
199
|
+
level: "high",
|
|
200
|
+
native: { effort: "high" },
|
|
201
|
+
reflection: {
|
|
202
|
+
cadence: "after_tool",
|
|
203
|
+
mode: "piggyback",
|
|
204
|
+
maxTokens: 450,
|
|
205
|
+
keepLast: 4,
|
|
206
|
+
summarize: false,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const result = await agent.invoke({
|
|
212
|
+
messages: [{ role: "user", content: "Research the repo and propose the next implementation step." }],
|
|
213
|
+
}, {
|
|
214
|
+
onEvent(event) {
|
|
215
|
+
if (event.type === "reflection") {
|
|
216
|
+
console.log("reflection:", event.text);
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
console.log(result.state?.reflections?.at(-1)?.text);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
What this does:
|
|
225
|
+
|
|
226
|
+
- `reasoning.native` passes provider-specific reasoning knobs through the native provider layer.
|
|
227
|
+
- `reasoning.reflection` adds a short plain-text reflection after qualifying turns without committing that note as a normal assistant message.
|
|
228
|
+
- Reflection notes are persisted on `result.state.reflections`; only the last `keepLast` are re-injected into the next prompt as synthetic system context.
|
|
229
|
+
|
|
230
|
+
Use this only with models/endpoints that actually support provider-native reasoning. Unsupported models usually return a provider error rather than silently degrading.
|
|
231
|
+
|
|
184
232
|
### Base agent (minimal loop)
|
|
185
233
|
|
|
186
234
|
Prefer a tiny core without system prompt or summarization? Use `createAgent`:
|
|
@@ -213,6 +261,7 @@ console.log(res.content);
|
|
|
213
261
|
|
|
214
262
|
- **Native provider layer** – call OpenAI, Anthropic, Azure, Bedrock, Vertex, and any OpenAI-compatible API directly. No LangChain required. Unified `ChatCompletionRequest` / `ChatCompletionResponse` schema with per-provider wire format conversion.
|
|
215
263
|
- **Full token tracking** – every response surfaces `inputTokens`, `outputTokens`, `cachedInputTokens`, `cachedWriteTokens`, and `reasoningTokens` for all six providers.
|
|
264
|
+
- **Unified reasoning controls** – enable provider-native reasoning (`reasoning.native`) and post-tool reflection (`reasoning.reflection`) from one config surface.
|
|
216
265
|
- **Summarization pipeline** – automatic chunking keeps tool call history within `contextTokenLimit` / `summaryTokenLimit`, archiving originals so `get_tool_response` can fetch them later.
|
|
217
266
|
- **Retention controls** – tool outputs can be kept full, reduced to structured previews, archived, or dropped based on size tiers, critical-tool fallback, per-tool overrides, and recent-response pinning.
|
|
218
267
|
- **Planning discipline** – when planning is enabled the system prompt distinguishes full plan writes from incremental plan updates and emits `plan` events as todos change.
|
|
@@ -220,7 +269,7 @@ console.log(res.content);
|
|
|
220
269
|
- **Multi-agent orchestration** – reuse agents via `agent.asTool({ toolName })` or perform handoffs that swap runtimes mid-execution.
|
|
221
270
|
- **MCP + LangChain tools** – any object satisfying the minimal tool interface works; LangChain’s `Tool` implementations plug in directly.
|
|
222
271
|
- **Vision input** – message parts accept base64 or URL images for multimodal requests.
|
|
223
|
-
- **Observability hooks** – `config.onEvent` surfaces tool lifecycle, summarization, metadata, and final answer events for streaming UIs or CLIs.
|
|
272
|
+
- **Observability hooks** – `config.onEvent` surfaces tool lifecycle, summarization, reflection, metadata, and final answer events for streaming UIs or CLIs.
|
|
224
273
|
|
|
225
274
|
## Examples
|
|
226
275
|
|
|
@@ -269,10 +318,11 @@ OPENAI_API_KEY=... npx tsx basic/basic.ts
|
|
|
269
318
|
The agent is a deterministic while-loop – no external graph runtime. Each turn flows through:
|
|
270
319
|
|
|
271
320
|
1. **resolver** – normalize state (messages, counters, limits).
|
|
272
|
-
2. **contextSummarize** (optional) – when token estimates exceed
|
|
321
|
+
2. **contextSummarize** (optional) – when token estimates exceed the active summarization threshold, archive heavy tool outputs.
|
|
273
322
|
3. **agent** – invoke the model (binding tools when supported).
|
|
274
323
|
4. **tools** – execute proposed tool calls with configurable parallelism.
|
|
275
|
-
5. **
|
|
324
|
+
5. **reflect** (optional) – append a plain-text reflection after tool turns when `reasoning.reflection` is enabled.
|
|
325
|
+
6. **toolLimitFinalize** – if tool-call cap is hit, inject a system notice so the next assistant turn must answer directly.
|
|
276
326
|
|
|
277
327
|
The loop stops when the assistant produces a message without tool calls, a structured output finalize signal is observed, or a handoff transfers control. See `docs/architecture/README.md` for diagrams and heuristics.
|
|
278
328
|
|
|
@@ -289,7 +339,7 @@ Exported helpers (`agent-sdk/src/index.ts`):
|
|
|
289
339
|
- `createProvider(config)` – factory for all six providers
|
|
290
340
|
- `fromNativeProvider(provider, options?)` – wraps a provider as a `BaseChatModel`
|
|
291
341
|
- `OpenAIProvider`, `AnthropicProvider`, `AzureProvider`, `OpenAICompatibleProvider`, `BedrockProvider`, `VertexProvider`
|
|
292
|
-
- Types: `ChatCompletionRequest`, `ChatCompletionResponse`, `TokenUsage`, `ProviderConfig`
|
|
342
|
+
- Types: `ChatCompletionRequest`, `ChatCompletionResponse`, `TokenUsage`, `ProviderConfig`, `ReasoningConfig`, `ReflectionRecord`, `ReflectionEvent`
|
|
293
343
|
|
|
294
344
|
**LangChain adapters (optional):**
|
|
295
345
|
- `fromLangchainModel(model)`
|
|
@@ -300,7 +350,7 @@ Exported helpers (`agent-sdk/src/index.ts`):
|
|
|
300
350
|
- `buildSystemPrompt(extra?, planning?, name?)`
|
|
301
351
|
- Node factories (`nodes/*`), context helpers, token utilities, and full TypeScript types (`SmartAgentOptions`, `SmartState`, `AgentInvokeResult`, etc.).
|
|
302
352
|
|
|
303
|
-
`SmartAgentOptions` accepts the usual suspects (`model`, `tools`, `limits`, `runtimeProfile`, `customProfile`, `useTodoList`, `summarization`, `usageConverter`, `tracing`). See `docs/api/` for detailed type references.
|
|
353
|
+
`SmartAgentOptions` accepts the usual suspects (`model`, `tools`, `limits`, `runtimeProfile`, `customProfile`, `useTodoList`, `summarization`, `reasoning`, `usageConverter`, `tracing`). See `docs/api/` for detailed type references.
|
|
304
354
|
|
|
305
355
|
Tools can also declare `maxExecutionsPerRun` to cap successful executions for that tool within a single agent run. Leave it unset or set it to `null` for unlimited usage. This is separate from global limits such as `limits.maxToolCalls` and `limits.maxParallelTools`.
|
|
306
356
|
|
|
@@ -309,7 +359,7 @@ Tools can also declare `maxExecutionsPerRun` to cap successful executions for th
|
|
|
309
359
|
Enable tracing by passing `tracing: { enabled: true }`. Each invocation writes `trace.session.json` into `logs/<SESSION_ID>/` detailing:
|
|
310
360
|
|
|
311
361
|
- Model/provider, agent name/version, limits, and timing metadata
|
|
312
|
-
- Structured events for model calls, tool executions, summaries, and errors
|
|
362
|
+
- Structured events for model calls, tool executions, summaries, reflections, and errors
|
|
313
363
|
- Optional payload captures (request/response/tool bodies) when `logData` is `true`
|
|
314
364
|
- Aggregated token usage, byte counts, and error summaries for dashboards
|
|
315
365
|
|
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
|
|
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 =
|
|
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;
|
|
@@ -2107,8 +2109,6 @@ var BASE_DEFAULTS = {
|
|
|
2107
2109
|
policy: "hybrid",
|
|
2108
2110
|
lastTurnsToKeep: 8,
|
|
2109
2111
|
toolResponsePolicy: "summarize_archive",
|
|
2110
|
-
archiveLargeToolResponses: true,
|
|
2111
|
-
retrieveArchivedToolResponseOnDemand: true,
|
|
2112
2112
|
budget: {
|
|
2113
2113
|
systemReserveTokens: 1200,
|
|
2114
2114
|
goalsReserveTokens: 1200,
|
|
@@ -2141,8 +2141,7 @@ var BASE_DEFAULTS = {
|
|
|
2141
2141
|
defaultPolicy: "summarize_archive",
|
|
2142
2142
|
toolResponseRetentionByTool: {},
|
|
2143
2143
|
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
2144
|
-
schemaValidation: "strict"
|
|
2145
|
-
retryOnSchemaError: true
|
|
2144
|
+
schemaValidation: "strict"
|
|
2146
2145
|
}
|
|
2147
2146
|
};
|
|
2148
2147
|
function buildProfile(overrides) {
|
|
@@ -2939,6 +2938,246 @@ function createToolLimitFinalizeNode(_opts) {
|
|
|
2939
2938
|
return { messages: [...state.messages, notice], ctx };
|
|
2940
2939
|
};
|
|
2941
2940
|
}
|
|
2941
|
+
var REFLECTION_SYSTEM_NAME = "agent_reflection";
|
|
2942
|
+
var REFLECTION_ASK_NAME = "agent_reflection_ask";
|
|
2943
|
+
var DEFAULT_REFLECT_PROMPT = `Reflect briefly \u2014 plain text, no JSON, no tool calls.
|
|
2944
|
+
Structure your note (1\u20132 sentences each):
|
|
2945
|
+
1) What the latest tool results actually tell us (facts, surprises).
|
|
2946
|
+
2) What is still missing or uncertain before we can finish the user's task.
|
|
2947
|
+
3) The single next step you plan to take, and why that step over alternatives.
|
|
2948
|
+
Be concrete. Do not repeat the task description. Do not apologize. Max ${"${maxChars}"} characters total.`;
|
|
2949
|
+
function trimToChars(text, maxChars) {
|
|
2950
|
+
if (text.length <= maxChars) return text;
|
|
2951
|
+
return text.slice(0, maxChars - 1).trimEnd() + "\u2026";
|
|
2952
|
+
}
|
|
2953
|
+
function countToolCallsInLastTurn(state) {
|
|
2954
|
+
const ids = [];
|
|
2955
|
+
const names = [];
|
|
2956
|
+
for (let i = state.messages.length - 1; i >= 0; i--) {
|
|
2957
|
+
const m = state.messages[i];
|
|
2958
|
+
if (m?.role === "tool") {
|
|
2959
|
+
if (m.tool_call_id) ids.push(m.tool_call_id);
|
|
2960
|
+
if (m.name) names.push(m.name);
|
|
2961
|
+
} else if (m?.role === "assistant") {
|
|
2962
|
+
break;
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
return { ids, names };
|
|
2966
|
+
}
|
|
2967
|
+
function stripReflectionAskMessages(messages) {
|
|
2968
|
+
return messages.filter((m) => !(m?.role === "user" && m?.name === REFLECTION_ASK_NAME));
|
|
2969
|
+
}
|
|
2970
|
+
function shouldRunReflection(cadence, state, ranToolsThisTurn) {
|
|
2971
|
+
if (cadence === "off") return false;
|
|
2972
|
+
if (cadence === "every_turn") return true;
|
|
2973
|
+
if (cadence === "after_tool") return ranToolsThisTurn;
|
|
2974
|
+
if (cadence === "on_branch") {
|
|
2975
|
+
const reflections = state.reflections || [];
|
|
2976
|
+
const lastReflectionTurn = reflections[reflections.length - 1]?.turn ?? -1;
|
|
2977
|
+
const currentTurnId = state.toolCallCount ?? 0;
|
|
2978
|
+
return currentTurnId - lastReflectionTurn >= 2;
|
|
2979
|
+
}
|
|
2980
|
+
return false;
|
|
2981
|
+
}
|
|
2982
|
+
function createReflectionNode(opts, resolved) {
|
|
2983
|
+
return async function reflectNode(state, trigger) {
|
|
2984
|
+
if (!resolved.enabled) return {};
|
|
2985
|
+
const runtime = state.agent || {
|
|
2986
|
+
model: opts.model,
|
|
2987
|
+
name: opts.name,
|
|
2988
|
+
version: opts.version
|
|
2989
|
+
};
|
|
2990
|
+
const model = runtime.model;
|
|
2991
|
+
if (!model?.invoke && !model?.stream) return {};
|
|
2992
|
+
const onEvent = state.ctx?.__onEvent;
|
|
2993
|
+
const traceSession = state.ctx?.__traceSession;
|
|
2994
|
+
const maxChars = resolved.maxChars;
|
|
2995
|
+
const promptBody = (resolved.promptTemplate || DEFAULT_REFLECT_PROMPT).replace(/\$\{maxChars\}/g, String(maxChars));
|
|
2996
|
+
const baseMessages = stripReflectionAskMessages([...state.messages]);
|
|
2997
|
+
const askMessage = {
|
|
2998
|
+
role: "user",
|
|
2999
|
+
name: REFLECTION_ASK_NAME,
|
|
3000
|
+
content: promptBody
|
|
3001
|
+
};
|
|
3002
|
+
const messagesForCall = resolved.mode === "piggyback" ? [...baseMessages, askMessage] : buildSeparateCallMessages(baseMessages, promptBody);
|
|
3003
|
+
const start = Date.now();
|
|
3004
|
+
let response;
|
|
3005
|
+
try {
|
|
3006
|
+
response = await model.invoke(messagesForCall, {
|
|
3007
|
+
// tool_choice:"none" so the model won't try to call tools during reflection
|
|
3008
|
+
tool_choice: "none",
|
|
3009
|
+
toolChoice: "none",
|
|
3010
|
+
// Override max_tokens for the reflection call. BaseChatModel passes this through
|
|
3011
|
+
// to the adapter which forwards into the provider body.
|
|
3012
|
+
max_tokens: resolved.maxTokens,
|
|
3013
|
+
maxTokens: resolved.maxTokens
|
|
3014
|
+
});
|
|
3015
|
+
} catch (err) {
|
|
3016
|
+
recordTraceEvent(traceSession, {
|
|
3017
|
+
type: "reflection",
|
|
3018
|
+
label: "Reflection error",
|
|
3019
|
+
actor: { scope: "agent", name: opts.name || "agent", role: "reflection" },
|
|
3020
|
+
status: "error",
|
|
3021
|
+
error: { message: err?.message || String(err) }
|
|
3022
|
+
});
|
|
3023
|
+
return {};
|
|
3024
|
+
}
|
|
3025
|
+
const durationMs = Date.now() - start;
|
|
3026
|
+
const rawText = extractText(response);
|
|
3027
|
+
const text = trimToChars(String(rawText ?? "").trim(), maxChars);
|
|
3028
|
+
if (!text) return {};
|
|
3029
|
+
const rawUsage = response?.usage_metadata || response?.response_metadata?.usage || response?.usage || response?.additional_kwargs?.usage;
|
|
3030
|
+
const usage = normalizeUsage(rawUsage);
|
|
3031
|
+
const { ids, names } = countToolCallsInLastTurn(state);
|
|
3032
|
+
const turn = state.toolCallCount ?? 0;
|
|
3033
|
+
const record = {
|
|
3034
|
+
id: safeRandomId(),
|
|
3035
|
+
turn,
|
|
3036
|
+
text,
|
|
3037
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3038
|
+
durationMs,
|
|
3039
|
+
anchorMessageIndex: state.messages.length,
|
|
3040
|
+
trigger,
|
|
3041
|
+
usage: usage ? {
|
|
3042
|
+
inputTokens: usage.prompt_tokens,
|
|
3043
|
+
outputTokens: usage.completion_tokens,
|
|
3044
|
+
cachedInputTokens: usage.prompt_tokens_details?.cached_tokens,
|
|
3045
|
+
totalTokens: usage.total_tokens,
|
|
3046
|
+
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens
|
|
3047
|
+
} : void 0,
|
|
3048
|
+
toolCallIds: ids.length > 0 ? ids : void 0
|
|
3049
|
+
};
|
|
3050
|
+
recordTraceEvent(traceSession, {
|
|
3051
|
+
type: "reflection",
|
|
3052
|
+
label: `Reflection turn ${turn}`,
|
|
3053
|
+
actor: { scope: "agent", name: opts.name || "agent", role: "reflection" },
|
|
3054
|
+
status: "success",
|
|
3055
|
+
durationMs,
|
|
3056
|
+
inputTokens: record.usage?.inputTokens,
|
|
3057
|
+
outputTokens: record.usage?.outputTokens,
|
|
3058
|
+
cachedInputTokens: record.usage?.cachedInputTokens,
|
|
3059
|
+
totalTokens: record.usage?.totalTokens,
|
|
3060
|
+
debug: { text, toolNames: names }
|
|
3061
|
+
});
|
|
3062
|
+
if (resolved.emitEvents) {
|
|
3063
|
+
try {
|
|
3064
|
+
onEvent?.({
|
|
3065
|
+
type: "reflection",
|
|
3066
|
+
id: record.id,
|
|
3067
|
+
turn,
|
|
3068
|
+
text,
|
|
3069
|
+
trigger,
|
|
3070
|
+
durationMs,
|
|
3071
|
+
usage: record.usage,
|
|
3072
|
+
toolCallIds: record.toolCallIds
|
|
3073
|
+
});
|
|
3074
|
+
} catch (e) {
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
const nextMessages = stripReflectionAskMessages([...state.messages]);
|
|
3078
|
+
const reflections = [...state.reflections || [], record];
|
|
3079
|
+
const kept = reflections.slice(-Math.max(1, resolved.keepLast));
|
|
3080
|
+
const prunedMessages = nextMessages.filter(
|
|
3081
|
+
(m) => !(m?.role === "system" && m?.name === REFLECTION_SYSTEM_NAME)
|
|
3082
|
+
);
|
|
3083
|
+
for (const r of kept) {
|
|
3084
|
+
prunedMessages.push({
|
|
3085
|
+
role: "system",
|
|
3086
|
+
name: REFLECTION_SYSTEM_NAME,
|
|
3087
|
+
content: `<agent_reflection turn=${r.turn} at=${r.createdAt}>
|
|
3088
|
+
${r.text}
|
|
3089
|
+
</agent_reflection>`,
|
|
3090
|
+
metadata: { reflectionId: r.id, reflectionTurn: r.turn, reflectionTrigger: r.trigger }
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
return { messages: prunedMessages, reflections };
|
|
3094
|
+
};
|
|
3095
|
+
}
|
|
3096
|
+
function extractText(chunk) {
|
|
3097
|
+
if (chunk == null) return "";
|
|
3098
|
+
if (typeof chunk === "string") return chunk;
|
|
3099
|
+
if (typeof chunk?.content === "string") return chunk.content;
|
|
3100
|
+
if (Array.isArray(chunk?.content)) {
|
|
3101
|
+
return chunk.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
|
|
3102
|
+
}
|
|
3103
|
+
if (typeof chunk?.text === "string") return chunk.text;
|
|
3104
|
+
return "";
|
|
3105
|
+
}
|
|
3106
|
+
function buildSeparateCallMessages(base, promptBody) {
|
|
3107
|
+
const systemMessages = base.filter((m) => m?.role === "system");
|
|
3108
|
+
const tail = base.slice(-8);
|
|
3109
|
+
return [
|
|
3110
|
+
...systemMessages,
|
|
3111
|
+
...tail,
|
|
3112
|
+
{ role: "user", name: REFLECTION_ASK_NAME, content: promptBody }
|
|
3113
|
+
];
|
|
3114
|
+
}
|
|
3115
|
+
function safeRandomId() {
|
|
3116
|
+
try {
|
|
3117
|
+
return crypto.randomUUID();
|
|
3118
|
+
} catch {
|
|
3119
|
+
return `r_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
// src/smart/reasoning.ts
|
|
3124
|
+
var LEVEL_DEFAULTS = {
|
|
3125
|
+
low: {
|
|
3126
|
+
native: { effort: "low" },
|
|
3127
|
+
reflection: { maxTokens: 200, maxChars: 600, cadence: "on_branch" }
|
|
3128
|
+
},
|
|
3129
|
+
medium: {
|
|
3130
|
+
native: { effort: "medium" },
|
|
3131
|
+
reflection: { maxTokens: 350, maxChars: 1200, cadence: "after_tool" }
|
|
3132
|
+
},
|
|
3133
|
+
high: {
|
|
3134
|
+
native: { effort: "high" },
|
|
3135
|
+
reflection: { maxTokens: 600, maxChars: 2e3, cadence: "after_tool" }
|
|
3136
|
+
}
|
|
3137
|
+
};
|
|
3138
|
+
function resolveReasoning(input) {
|
|
3139
|
+
if (!input || input.enabled === false) return void 0;
|
|
3140
|
+
const enabled = input.enabled ?? (input.native !== void 0 || input.reflection !== void 0 || input.level !== void 0);
|
|
3141
|
+
if (!enabled) return void 0;
|
|
3142
|
+
const level = input.level ?? "medium";
|
|
3143
|
+
const presets = LEVEL_DEFAULTS[level];
|
|
3144
|
+
let native;
|
|
3145
|
+
if (input.native === false) {
|
|
3146
|
+
native = void 0;
|
|
3147
|
+
} else {
|
|
3148
|
+
native = { ...presets.native, ...input.native ?? {} };
|
|
3149
|
+
}
|
|
3150
|
+
const reflection = resolveReflection(input.reflection, presets.reflection);
|
|
3151
|
+
return { enabled: true, level, native, reflection };
|
|
3152
|
+
}
|
|
3153
|
+
function resolveReflection(input, preset) {
|
|
3154
|
+
if (input === false) {
|
|
3155
|
+
return {
|
|
3156
|
+
enabled: false,
|
|
3157
|
+
cadence: "off",
|
|
3158
|
+
mode: "piggyback",
|
|
3159
|
+
maxTokens: preset.maxTokens,
|
|
3160
|
+
maxChars: preset.maxChars,
|
|
3161
|
+
keepLast: 3,
|
|
3162
|
+
summarize: false,
|
|
3163
|
+
emitEvents: true
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
const cfg = input ?? {};
|
|
3167
|
+
const enabled = cfg.enabled ?? true;
|
|
3168
|
+
const cadence = cfg.cadence ?? preset.cadence;
|
|
3169
|
+
return {
|
|
3170
|
+
enabled: enabled && cadence !== "off",
|
|
3171
|
+
cadence: enabled ? cadence : "off",
|
|
3172
|
+
mode: cfg.mode ?? "piggyback",
|
|
3173
|
+
maxTokens: cfg.maxTokens ?? preset.maxTokens,
|
|
3174
|
+
maxChars: cfg.maxChars ?? preset.maxChars,
|
|
3175
|
+
keepLast: cfg.keepLast ?? 3,
|
|
3176
|
+
summarize: cfg.summarize ?? false,
|
|
3177
|
+
promptTemplate: cfg.promptTemplate,
|
|
3178
|
+
emitEvents: cfg.emitEvents ?? true
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
2942
3181
|
function ensureArray(value) {
|
|
2943
3182
|
return Array.isArray(value) ? value : [];
|
|
2944
3183
|
}
|
|
@@ -3352,6 +3591,8 @@ function createAgent(opts) {
|
|
|
3352
3591
|
}
|
|
3353
3592
|
const toolsNode = createToolsNode(toolsBase, opts);
|
|
3354
3593
|
const finalizeNode = createToolLimitFinalizeNode();
|
|
3594
|
+
const resolvedReasoning = resolveReasoning(opts.reasoning);
|
|
3595
|
+
const reflectNode = resolvedReasoning?.reflection.enabled ? createReflectionNode(opts, resolvedReasoning.reflection) : void 0;
|
|
3355
3596
|
const mergeGuardrailOutcomes = (prev, next) => {
|
|
3356
3597
|
if (!prev) return next;
|
|
3357
3598
|
return {
|
|
@@ -3406,6 +3647,13 @@ function createAgent(opts) {
|
|
|
3406
3647
|
if (summarizationThreshold === void 0) {
|
|
3407
3648
|
state = clearNeedsSummarization(state);
|
|
3408
3649
|
}
|
|
3650
|
+
if (resolvedReasoning?.native) {
|
|
3651
|
+
const ctx = { ...state.ctx || {} };
|
|
3652
|
+
if (!ctx.__reasoning) {
|
|
3653
|
+
ctx.__reasoning = resolvedReasoning.native;
|
|
3654
|
+
state = { ...state, ctx };
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3409
3657
|
const mergedLimits = {
|
|
3410
3658
|
...opts.limits || {},
|
|
3411
3659
|
...config?.limits || {}
|
|
@@ -3634,6 +3882,18 @@ function createAgent(opts) {
|
|
|
3634
3882
|
if (state.ctx?.__awaitingApproval) break;
|
|
3635
3883
|
if (checkpointIfRequested("after_tools")) break;
|
|
3636
3884
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
3885
|
+
if (reflectNode && resolvedReasoning?.reflection.enabled) {
|
|
3886
|
+
const cadence = resolvedReasoning.reflection.cadence;
|
|
3887
|
+
if (shouldRunReflection(cadence, state, true)) {
|
|
3888
|
+
try {
|
|
3889
|
+
const patch = await reflectNode(state, cadence);
|
|
3890
|
+
if (patch && Object.keys(patch).length > 0) {
|
|
3891
|
+
state = { ...state, ...patch };
|
|
3892
|
+
}
|
|
3893
|
+
} catch {
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3637
3897
|
}
|
|
3638
3898
|
if (soManager && !state.ctx?.__finalizedDueToStructuredOutput) {
|
|
3639
3899
|
const lastForParse = state.messages[state.messages.length - 1];
|
|
@@ -5643,6 +5903,52 @@ function parseSSEBlock(block) {
|
|
|
5643
5903
|
return { event, data: dataLines.join("\n"), id, retry };
|
|
5644
5904
|
}
|
|
5645
5905
|
|
|
5906
|
+
// src/providers/utils/reasoning.ts
|
|
5907
|
+
function applyOpenAIReasoning(body, reasoning, mode = "legacy_effort") {
|
|
5908
|
+
if (!reasoning) return;
|
|
5909
|
+
const effort = normalizeEffort(reasoning.effort);
|
|
5910
|
+
if (mode === "responses") {
|
|
5911
|
+
const obj = {};
|
|
5912
|
+
if (effort) obj.effort = effort;
|
|
5913
|
+
if (reasoning.includeThoughts) obj.summary = "auto";
|
|
5914
|
+
if (reasoning.providerExtras) Object.assign(obj, reasoning.providerExtras);
|
|
5915
|
+
if (Object.keys(obj).length > 0) body.reasoning = obj;
|
|
5916
|
+
} else {
|
|
5917
|
+
if (effort) body.reasoning_effort = effort;
|
|
5918
|
+
if (reasoning.providerExtras) Object.assign(body, reasoning.providerExtras);
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
function applyAnthropicReasoning(body, reasoning) {
|
|
5922
|
+
if (!reasoning) return;
|
|
5923
|
+
const budget = resolveBudget(reasoning, { low: 2048, medium: 8192, high: 16384, minimal: 1024 });
|
|
5924
|
+
if (!budget) return;
|
|
5925
|
+
const thinking = { type: "enabled", budget_tokens: budget };
|
|
5926
|
+
if (reasoning.providerExtras) Object.assign(thinking, reasoning.providerExtras);
|
|
5927
|
+
body.thinking = thinking;
|
|
5928
|
+
}
|
|
5929
|
+
function applyGeminiReasoning(generationConfig, reasoning) {
|
|
5930
|
+
if (!reasoning) return;
|
|
5931
|
+
const budget = resolveBudget(reasoning, { low: 1024, medium: 4096, high: 12e3, minimal: 512 });
|
|
5932
|
+
if (budget == null) return;
|
|
5933
|
+
const thinkingConfig = { thinkingBudget: budget };
|
|
5934
|
+
if (reasoning.includeThoughts) thinkingConfig.includeThoughts = true;
|
|
5935
|
+
if (reasoning.providerExtras) Object.assign(thinkingConfig, reasoning.providerExtras);
|
|
5936
|
+
generationConfig.thinkingConfig = thinkingConfig;
|
|
5937
|
+
}
|
|
5938
|
+
function normalizeEffort(effort) {
|
|
5939
|
+
if (!effort) return void 0;
|
|
5940
|
+
const allowed = ["minimal", "low", "medium", "high"];
|
|
5941
|
+
return allowed.includes(effort) ? effort : void 0;
|
|
5942
|
+
}
|
|
5943
|
+
function resolveBudget(reasoning, effortBudget) {
|
|
5944
|
+
if (typeof reasoning.budgetTokens === "number" && reasoning.budgetTokens > 0) {
|
|
5945
|
+
return Math.floor(reasoning.budgetTokens);
|
|
5946
|
+
}
|
|
5947
|
+
const effort = normalizeEffort(reasoning.effort);
|
|
5948
|
+
if (effort && effortBudget[effort]) return effortBudget[effort];
|
|
5949
|
+
return void 0;
|
|
5950
|
+
}
|
|
5951
|
+
|
|
5646
5952
|
// src/providers/openai.ts
|
|
5647
5953
|
var OpenAIProvider = class extends BaseProvider {
|
|
5648
5954
|
constructor(config) {
|
|
@@ -5758,6 +6064,7 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
5758
6064
|
if (request.extra) {
|
|
5759
6065
|
Object.assign(body, request.extra);
|
|
5760
6066
|
}
|
|
6067
|
+
applyOpenAIReasoning(body, request.reasoning, "legacy_effort");
|
|
5761
6068
|
return body;
|
|
5762
6069
|
}
|
|
5763
6070
|
toOpenAIMessage(m) {
|
|
@@ -6016,6 +6323,7 @@ var AnthropicProvider = class extends BaseProvider {
|
|
|
6016
6323
|
if (request.extra) {
|
|
6017
6324
|
Object.assign(body, request.extra);
|
|
6018
6325
|
}
|
|
6326
|
+
applyAnthropicReasoning(body, request.reasoning);
|
|
6019
6327
|
return body;
|
|
6020
6328
|
}
|
|
6021
6329
|
splitSystemMessages(messages) {
|
|
@@ -6670,6 +6978,7 @@ var VertexProvider = class extends BaseProvider {
|
|
|
6670
6978
|
}
|
|
6671
6979
|
}
|
|
6672
6980
|
}
|
|
6981
|
+
applyGeminiReasoning(generationConfig, request.reasoning);
|
|
6673
6982
|
if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
|
|
6674
6983
|
if (request.tools?.length) {
|
|
6675
6984
|
body.tools = [
|
|
@@ -7008,6 +7317,13 @@ function fromNativeProvider(provider, options) {
|
|
|
7008
7317
|
if (opts?.maxTokens != null) req.maxTokens = opts.maxTokens;
|
|
7009
7318
|
if (tools?.length) req.tools = tools;
|
|
7010
7319
|
if (opts?.extra) req.extra = opts.extra;
|
|
7320
|
+
const reasoningOverride = invokeOptions?.reasoning;
|
|
7321
|
+
const reasoningCfg = reasoningOverride ?? opts?.reasoning;
|
|
7322
|
+
if (reasoningCfg) req.reasoning = reasoningCfg;
|
|
7323
|
+
const tc = invokeOptions?.tool_choice ?? invokeOptions?.toolChoice;
|
|
7324
|
+
if (tc) {
|
|
7325
|
+
req.toolChoice = tc;
|
|
7326
|
+
}
|
|
7011
7327
|
const rf = invokeOptions?.response_format;
|
|
7012
7328
|
if (rf && typeof rf === "object" && rf.type) {
|
|
7013
7329
|
if (rf.type === "json_schema" && rf.json_schema) {
|