@nextclaw/ncp-agent-runtime 0.2.0 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NcpContextBuilder, NcpToolRegistry, NcpAgentRunInput, NcpContextPrepareOptions, NcpLLMApiInput, NcpRoundBuffer, NcpToolCallResult, NcpStreamEncoder, OpenAIChatChunk, NcpEncodeContext, NcpEndpointEvent, NcpTool, NcpToolDefinition, NcpLLMApi, NcpLLMApiOptions, NcpAgentRuntime, NcpAgentConversationStateManager, NcpAgentRunOptions } from '@nextclaw/ncp';
1
+ import { NcpContextBuilder, NcpToolRegistry, NcpAgentRunInput, NcpContextPrepareOptions, NcpLLMApiInput, NcpRoundBuffer, NcpToolCallResult, NcpStreamEncoder, NcpAssistantReasoningNormalizationMode, OpenAIChatChunk, NcpEncodeContext, NcpEndpointEvent, NcpTool, NcpToolDefinition, NcpLLMApi, NcpLLMApiOptions, NcpAgentRuntime, NcpAgentConversationStateManager, NcpAgentRunOptions } from '@nextclaw/ncp';
2
2
 
3
3
  declare class DefaultNcpContextBuilder implements NcpContextBuilder {
4
4
  private readonly toolRegistry?;
@@ -24,12 +24,17 @@ declare class DefaultNcpRoundBuffer implements NcpRoundBuffer {
24
24
  clear: () => void;
25
25
  }
26
26
 
27
+ type DefaultNcpStreamEncoderConfig = {
28
+ reasoningNormalizationMode?: NcpAssistantReasoningNormalizationMode;
29
+ };
27
30
  /**
28
31
  * Converts LLM stream chunks to NCP events (text, reasoning, tool calls).
29
32
  * Does not emit RunFinished; that is the runtime's responsibility after the loop completes.
30
33
  */
31
34
  declare class DefaultNcpStreamEncoder implements NcpStreamEncoder {
32
- encode: (stream: AsyncIterable<OpenAIChatChunk>, context: NcpEncodeContext) => AsyncGenerator<NcpEndpointEvent>;
35
+ private readonly reasoningNormalizationMode;
36
+ constructor(config?: DefaultNcpStreamEncoderConfig);
37
+ encode(stream: AsyncIterable<OpenAIChatChunk>, context: NcpEncodeContext): AsyncGenerator<NcpEndpointEvent>;
33
38
  }
34
39
 
35
40
  declare class DefaultNcpToolRegistry implements NcpToolRegistry {
@@ -52,6 +57,7 @@ type DefaultNcpAgentRuntimeConfig = {
52
57
  toolRegistry: NcpToolRegistry;
53
58
  stateManager: NcpAgentConversationStateManager;
54
59
  streamEncoder?: NcpStreamEncoder;
60
+ reasoningNormalizationMode?: NcpAssistantReasoningNormalizationMode;
55
61
  };
56
62
  declare class DefaultNcpAgentRuntime implements NcpAgentRuntime {
57
63
  private readonly contextBuilder;
@@ -59,6 +65,7 @@ declare class DefaultNcpAgentRuntime implements NcpAgentRuntime {
59
65
  private readonly toolRegistry;
60
66
  private readonly stateManager;
61
67
  private readonly streamEncoder;
68
+ private readonly reasoningNormalizationMode;
62
69
  constructor(config: DefaultNcpAgentRuntimeConfig);
63
70
  run: (this: DefaultNcpAgentRuntime, input: NcpAgentRunInput, options?: NcpAgentRunOptions) => AsyncGenerator<NcpEndpointEvent>;
64
71
  /**
package/dist/index.js CHANGED
@@ -172,6 +172,9 @@ import {
172
172
  } from "@nextclaw/ncp";
173
173
 
174
174
  // src/stream-encoder.utils.ts
175
+ import {
176
+ NcpAssistantTextStreamNormalizer
177
+ } from "@nextclaw/ncp";
175
178
  import { NcpEventType } from "@nextclaw/ncp";
176
179
  function getToolCallIndex(toolDelta, fallback) {
177
180
  const idx = toolDelta.index;
@@ -196,14 +199,68 @@ function applyToolDelta(current, toolDelta) {
196
199
  function* emitTextDeltas(delta, ctx, state) {
197
200
  const content = delta.content;
198
201
  if (typeof content !== "string" || content.length === 0) return state;
202
+ if (state.normalizer) {
203
+ let textStarted = state.textStarted;
204
+ for (const segment of state.normalizer.push(content)) {
205
+ if (segment.type === "reasoning") {
206
+ yield {
207
+ type: NcpEventType.MessageReasoningDelta,
208
+ payload: { ...ctx, delta: segment.text }
209
+ };
210
+ continue;
211
+ }
212
+ if (!textStarted) {
213
+ yield { type: NcpEventType.MessageTextStart, payload: ctx };
214
+ textStarted = true;
215
+ }
216
+ yield {
217
+ type: NcpEventType.MessageTextDelta,
218
+ payload: { ...ctx, delta: segment.text }
219
+ };
220
+ }
221
+ return {
222
+ ...state,
223
+ textStarted
224
+ };
225
+ }
199
226
  if (!state.textStarted) {
200
227
  yield { type: NcpEventType.MessageTextStart, payload: ctx };
201
228
  yield { type: NcpEventType.MessageTextDelta, payload: { ...ctx, delta: content } };
202
- return { textStarted: true };
229
+ return {
230
+ ...state,
231
+ textStarted: true
232
+ };
203
233
  }
204
234
  yield { type: NcpEventType.MessageTextDelta, payload: { ...ctx, delta: content } };
205
235
  return state;
206
236
  }
237
+ function* flushTextDeltas(ctx, state) {
238
+ if (!state.normalizer) {
239
+ return state;
240
+ }
241
+ let textStarted = state.textStarted;
242
+ for (const segment of state.normalizer.finish()) {
243
+ if (segment.type === "reasoning") {
244
+ yield {
245
+ type: NcpEventType.MessageReasoningDelta,
246
+ payload: { ...ctx, delta: segment.text }
247
+ };
248
+ continue;
249
+ }
250
+ if (!textStarted) {
251
+ yield { type: NcpEventType.MessageTextStart, payload: ctx };
252
+ textStarted = true;
253
+ }
254
+ yield {
255
+ type: NcpEventType.MessageTextDelta,
256
+ payload: { ...ctx, delta: segment.text }
257
+ };
258
+ }
259
+ return {
260
+ ...state,
261
+ textStarted
262
+ };
263
+ }
207
264
  function* emitReasoningDelta(delta, ctx) {
208
265
  const reasoning = delta.reasoning_content ?? delta.reasoning;
209
266
  if (typeof reasoning !== "string" || !reasoning) return;
@@ -241,12 +298,22 @@ function* flushToolCalls(buffers, ctx) {
241
298
  };
242
299
  }
243
300
  }
301
+ function createStreamContentState(reasoningNormalizationMode) {
302
+ return {
303
+ textStarted: false,
304
+ normalizer: reasoningNormalizationMode === "think-tags" ? new NcpAssistantTextStreamNormalizer(reasoningNormalizationMode) : null
305
+ };
306
+ }
244
307
 
245
308
  // src/stream-encoder.ts
246
309
  var DefaultNcpStreamEncoder = class {
247
- encode = async function* (stream, context) {
310
+ reasoningNormalizationMode;
311
+ constructor(config = {}) {
312
+ this.reasoningNormalizationMode = config.reasoningNormalizationMode ?? "off";
313
+ }
314
+ async *encode(stream, context) {
248
315
  const { sessionId, messageId } = context;
249
- let state = { textStarted: false };
316
+ let state = createStreamContentState(this.reasoningNormalizationMode);
250
317
  const toolCallBuffers = /* @__PURE__ */ new Map();
251
318
  for await (const chunk of stream) {
252
319
  const choice = chunk.choices?.[0];
@@ -260,13 +327,14 @@ var DefaultNcpStreamEncoder = class {
260
327
  }
261
328
  const finishReason = choice.finish_reason;
262
329
  if (typeof finishReason === "string" && finishReason.trim().length > 0) {
330
+ state = yield* flushTextDeltas({ sessionId, messageId }, state);
263
331
  yield* flushToolCalls(toolCallBuffers, { sessionId, messageId });
264
332
  if (state.textStarted) {
265
333
  yield { type: NcpEventType2.MessageTextEnd, payload: { sessionId, messageId } };
266
334
  }
267
335
  }
268
336
  }
269
- };
337
+ }
270
338
  };
271
339
 
272
340
  // src/tool-registry.ts
@@ -514,13 +582,19 @@ function appendToolRoundToInput(input, reasoning, text, toolResults) {
514
582
  }
515
583
 
516
584
  // src/round-collector.ts
585
+ import {
586
+ normalizeAssistantText
587
+ } from "@nextclaw/ncp";
517
588
  var DefaultNcpRoundCollector = class {
518
- text = "";
519
- reasoning = "";
589
+ constructor(reasoningNormalizationMode = "off") {
590
+ this.reasoningNormalizationMode = reasoningNormalizationMode;
591
+ }
592
+ rawText = "";
593
+ explicitReasoning = "";
520
594
  toolCallBuffers = /* @__PURE__ */ new Map();
521
595
  clear() {
522
- this.text = "";
523
- this.reasoning = "";
596
+ this.rawText = "";
597
+ this.explicitReasoning = "";
524
598
  this.toolCallBuffers.clear();
525
599
  }
526
600
  consumeChunk(chunk) {
@@ -533,11 +607,11 @@ var DefaultNcpRoundCollector = class {
533
607
  return;
534
608
  }
535
609
  if (typeof delta.content === "string" && delta.content.length > 0) {
536
- this.text += delta.content;
610
+ this.rawText += delta.content;
537
611
  }
538
612
  const reasoning = delta.reasoning_content ?? delta.reasoning;
539
613
  if (typeof reasoning === "string" && reasoning.length > 0) {
540
- this.reasoning += reasoning;
614
+ this.explicitReasoning += reasoning;
541
615
  }
542
616
  const toolDeltas = delta.tool_calls;
543
617
  if (!Array.isArray(toolDeltas)) {
@@ -551,10 +625,19 @@ var DefaultNcpRoundCollector = class {
551
625
  }
552
626
  }
553
627
  getText() {
554
- return this.text;
628
+ if (this.reasoningNormalizationMode !== "think-tags") {
629
+ return this.rawText;
630
+ }
631
+ return normalizeAssistantText(this.rawText, this.reasoningNormalizationMode).text;
555
632
  }
556
633
  getReasoning() {
557
- return this.reasoning;
634
+ if (this.explicitReasoning.length > 0) {
635
+ return this.explicitReasoning;
636
+ }
637
+ if (this.reasoningNormalizationMode !== "think-tags") {
638
+ return "";
639
+ }
640
+ return normalizeAssistantText(this.rawText, this.reasoningNormalizationMode).reasoning;
558
641
  }
559
642
  getToolCalls() {
560
643
  const orderedEntries = Array.from(this.toolCallBuffers.entries()).sort(([left], [right]) => left - right);
@@ -580,12 +663,16 @@ var DefaultNcpAgentRuntime = class {
580
663
  toolRegistry;
581
664
  stateManager;
582
665
  streamEncoder;
666
+ reasoningNormalizationMode;
583
667
  constructor(config) {
584
668
  this.contextBuilder = config.contextBuilder;
585
669
  this.llmApi = config.llmApi;
586
670
  this.toolRegistry = config.toolRegistry;
587
671
  this.stateManager = config.stateManager;
588
- this.streamEncoder = config.streamEncoder ?? new DefaultNcpStreamEncoder();
672
+ this.reasoningNormalizationMode = config.reasoningNormalizationMode ?? "off";
673
+ this.streamEncoder = config.streamEncoder ?? new DefaultNcpStreamEncoder({
674
+ reasoningNormalizationMode: this.reasoningNormalizationMode
675
+ });
589
676
  }
590
677
  run = async function* (input, options) {
591
678
  const ctx = {
@@ -622,7 +709,7 @@ var DefaultNcpAgentRuntime = class {
622
709
  * The stream encoder does not emit RunFinished; it only converts chunks to NCP events.
623
710
  */
624
711
  async *runLoop(llmInput, ctx, options) {
625
- const roundCollector = new DefaultNcpRoundCollector();
712
+ const roundCollector = new DefaultNcpRoundCollector(this.reasoningNormalizationMode);
626
713
  let currentInput = llmInput;
627
714
  let done = false;
628
715
  while (!done && !options?.signal?.aborted) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-agent-runtime",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "description": "Default agent runtime implementation built on NCP interfaces.",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@nextclaw/ncp": "0.3.0"
18
+ "@nextclaw/ncp": "0.3.2"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.17.6",