@nextclaw/ncp-agent-runtime 0.2.0 → 0.2.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.d.ts +9 -2
- package/dist/index.js +101 -14
- package/package.json +2 -2
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
519
|
-
|
|
589
|
+
constructor(reasoningNormalizationMode = "off") {
|
|
590
|
+
this.reasoningNormalizationMode = reasoningNormalizationMode;
|
|
591
|
+
}
|
|
592
|
+
rawText = "";
|
|
593
|
+
explicitReasoning = "";
|
|
520
594
|
toolCallBuffers = /* @__PURE__ */ new Map();
|
|
521
595
|
clear() {
|
|
522
|
-
this.
|
|
523
|
-
this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
3
|
+
"version": "0.2.1",
|
|
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.
|
|
18
|
+
"@nextclaw/ncp": "0.3.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^20.17.6",
|