reasonix 0.4.1 → 0.4.5
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/cli/index.js +368 -29
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +119 -5
- package/dist/index.js +157 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -366,7 +366,7 @@ declare class ToolCallRepair {
|
|
|
366
366
|
private readonly storm;
|
|
367
367
|
private readonly opts;
|
|
368
368
|
constructor(opts: ToolCallRepairOptions);
|
|
369
|
-
process(declaredCalls: ToolCall[], reasoningContent: string | null): {
|
|
369
|
+
process(declaredCalls: ToolCall[], reasoningContent: string | null, content?: string | null): {
|
|
370
370
|
calls: ToolCall[];
|
|
371
371
|
report: RepairReport;
|
|
372
372
|
};
|
|
@@ -585,11 +585,34 @@ declare class CacheFirstLoop {
|
|
|
585
585
|
* answer instead of a cliff. Called by the TUI on Esc.
|
|
586
586
|
*/
|
|
587
587
|
abort(): void;
|
|
588
|
+
/**
|
|
589
|
+
* Drop everything in the log after (and including) the most recent
|
|
590
|
+
* user message. Used by `/retry` so the caller can re-send that
|
|
591
|
+
* message with a fresh turn instead of layering another response on
|
|
592
|
+
* top of the prior exchange. Returns the content of the dropped user
|
|
593
|
+
* message, or `null` if there isn't one yet.
|
|
594
|
+
*
|
|
595
|
+
* Persists by rewriting the session file — otherwise the next
|
|
596
|
+
* launch would rehydrate the old exchange and `/retry` would seem
|
|
597
|
+
* to have done nothing.
|
|
598
|
+
*/
|
|
599
|
+
retryLastUser(): string | null;
|
|
588
600
|
step(userInput: string): AsyncGenerator<LoopEvent>;
|
|
589
601
|
private forceSummaryAfterIterLimit;
|
|
590
602
|
run(userInput: string, onEvent?: (ev: LoopEvent) => void): Promise<string>;
|
|
591
603
|
private assistantMessage;
|
|
592
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* R1 occasionally hallucinates tool-call markup as plain text when the
|
|
607
|
+
* real tool channel has been closed — typically our forced-summary
|
|
608
|
+
* path, where `tools: undefined` is supposed to force prose but isn't
|
|
609
|
+
* always respected. The markup isn't parsed by our tool-call path
|
|
610
|
+
* (the API response's structured `tool_calls` field is empty), so
|
|
611
|
+
* it's just noise in the user's view. Strip known envelope shapes.
|
|
612
|
+
*
|
|
613
|
+
* Exported so tests can exercise it against concrete R1 outputs.
|
|
614
|
+
*/
|
|
615
|
+
declare function stripHallucinatedToolMarkup(s: string): string;
|
|
593
616
|
/**
|
|
594
617
|
* Truncate any tool-role message whose content exceeds the cap. User
|
|
595
618
|
* and assistant messages are left alone because (a) they're almost
|
|
@@ -878,8 +901,9 @@ declare function renderMarkdown(report: DiffReport): string;
|
|
|
878
901
|
* insulated as long as we keep up with the spec itself.
|
|
879
902
|
*
|
|
880
903
|
* Spec reference: https://spec.modelcontextprotocol.io/ (2024-11-05 draft
|
|
881
|
-
* at time of writing).
|
|
882
|
-
*
|
|
904
|
+
* at time of writing). Reasonix models the subset it consumes: tools
|
|
905
|
+
* list/call, resources list/read, prompts list/get, plus the init
|
|
906
|
+
* handshake. Sampling and progress notifications remain deferred.
|
|
883
907
|
*
|
|
884
908
|
* Transport note: the wire format for stdio MCP is **newline-delimited
|
|
885
909
|
* JSON** (NDJSON), not the LSP-style Content-Length header framing that
|
|
@@ -966,6 +990,78 @@ interface CallToolResult {
|
|
|
966
990
|
/** True = tool raised an error; the content describes it. */
|
|
967
991
|
isError?: boolean;
|
|
968
992
|
}
|
|
993
|
+
/**
|
|
994
|
+
* A resource the server can expose — think "file the model can read."
|
|
995
|
+
* The URI is opaque to the client: servers may use `file://`, custom
|
|
996
|
+
* schemes, or bare strings. Reasonix doesn't interpret them.
|
|
997
|
+
*/
|
|
998
|
+
interface McpResource {
|
|
999
|
+
uri: string;
|
|
1000
|
+
name: string;
|
|
1001
|
+
description?: string;
|
|
1002
|
+
/** Hint for the content type (e.g. "text/markdown"). Purely informational. */
|
|
1003
|
+
mimeType?: string;
|
|
1004
|
+
}
|
|
1005
|
+
interface ListResourcesResult {
|
|
1006
|
+
resources: McpResource[];
|
|
1007
|
+
nextCursor?: string;
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* One resource can return multiple content blobs (e.g. the file + a
|
|
1011
|
+
* side-car). `text` is the common case for UTF-8 content; `blob` is
|
|
1012
|
+
* base64-encoded bytes for binary content. Servers populate exactly
|
|
1013
|
+
* one of the two for each entry.
|
|
1014
|
+
*/
|
|
1015
|
+
interface McpResourceContentsText {
|
|
1016
|
+
uri: string;
|
|
1017
|
+
mimeType?: string;
|
|
1018
|
+
text: string;
|
|
1019
|
+
}
|
|
1020
|
+
interface McpResourceContentsBlob {
|
|
1021
|
+
uri: string;
|
|
1022
|
+
mimeType?: string;
|
|
1023
|
+
blob: string;
|
|
1024
|
+
}
|
|
1025
|
+
type McpResourceContents = McpResourceContentsText | McpResourceContentsBlob;
|
|
1026
|
+
interface ReadResourceResult {
|
|
1027
|
+
contents: McpResourceContents[];
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* A parameterizable prompt template the server exposes. Clients fetch
|
|
1031
|
+
* it with `prompts/get` and pass the result to the model as-is.
|
|
1032
|
+
*/
|
|
1033
|
+
interface McpPromptArgument {
|
|
1034
|
+
name: string;
|
|
1035
|
+
description?: string;
|
|
1036
|
+
required?: boolean;
|
|
1037
|
+
}
|
|
1038
|
+
interface McpPrompt {
|
|
1039
|
+
name: string;
|
|
1040
|
+
description?: string;
|
|
1041
|
+
arguments?: McpPromptArgument[];
|
|
1042
|
+
}
|
|
1043
|
+
interface ListPromptsResult {
|
|
1044
|
+
prompts: McpPrompt[];
|
|
1045
|
+
nextCursor?: string;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* MCP prompt messages are modeled after chat completions: role + content.
|
|
1049
|
+
* Content can be a text block OR (per the spec) a resource/image block;
|
|
1050
|
+
* Reasonix cares about text in v1, but surfaces the raw array so callers
|
|
1051
|
+
* can render other kinds if they need to.
|
|
1052
|
+
*/
|
|
1053
|
+
interface McpPromptMessage {
|
|
1054
|
+
role: "user" | "assistant";
|
|
1055
|
+
content: McpContentBlock | McpPromptResourceBlock;
|
|
1056
|
+
}
|
|
1057
|
+
interface McpPromptResourceBlock {
|
|
1058
|
+
type: "resource";
|
|
1059
|
+
resource: McpResourceContents;
|
|
1060
|
+
}
|
|
1061
|
+
interface GetPromptResult {
|
|
1062
|
+
description?: string;
|
|
1063
|
+
messages: McpPromptMessage[];
|
|
1064
|
+
}
|
|
969
1065
|
/** Current MCP protocol version Reasonix is coded against. */
|
|
970
1066
|
declare const MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
971
1067
|
/** Type guard — success vs error response. */
|
|
@@ -1068,6 +1164,24 @@ declare class McpClient {
|
|
|
1068
1164
|
listTools(): Promise<ListToolsResult>;
|
|
1069
1165
|
/** Invoke a tool by name. Returns the raw MCP result (caller unwraps content). */
|
|
1070
1166
|
callTool(name: string, args?: Record<string, unknown>): Promise<CallToolResult>;
|
|
1167
|
+
/**
|
|
1168
|
+
* List resources the server exposes. Supports a pagination cursor;
|
|
1169
|
+
* callers interested in the full set should loop on `nextCursor`.
|
|
1170
|
+
* Servers that don't support resources respond with method-not-found
|
|
1171
|
+
* (−32601) — we surface that as a thrown Error so callers can gate
|
|
1172
|
+
* on the `serverCapabilities.resources` field first.
|
|
1173
|
+
*/
|
|
1174
|
+
listResources(cursor?: string): Promise<ListResourcesResult>;
|
|
1175
|
+
/** Read the contents of a resource by URI. */
|
|
1176
|
+
readResource(uri: string): Promise<ReadResourceResult>;
|
|
1177
|
+
/** List prompt templates the server exposes. */
|
|
1178
|
+
listPrompts(cursor?: string): Promise<ListPromptsResult>;
|
|
1179
|
+
/**
|
|
1180
|
+
* Fetch a rendered prompt by name. `args` supplies values for any
|
|
1181
|
+
* required template arguments; the server validates. Returns messages
|
|
1182
|
+
* ready to prepend to the model's input.
|
|
1183
|
+
*/
|
|
1184
|
+
getPrompt(name: string, args?: Record<string, string>): Promise<GetPromptResult>;
|
|
1071
1185
|
/** Close the transport and reject any outstanding requests. */
|
|
1072
1186
|
close(): Promise<void>;
|
|
1073
1187
|
private assertInitialized;
|
|
@@ -1416,6 +1530,6 @@ declare function redactKey(key: string): string;
|
|
|
1416
1530
|
|
|
1417
1531
|
/** Reasonix — DeepSeek-native agent framework. Library entry point. */
|
|
1418
1532
|
|
|
1419
|
-
declare const VERSION = "0.4.
|
|
1533
|
+
declare const VERSION = "0.4.3";
|
|
1420
1534
|
|
|
1421
|
-
export { AppendOnlyLog, type ApplyResult, type ApplyStatus, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CODE_SYSTEM_PROMPT, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DEFAULT_MAX_RESULT_CHARS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EditBlock, type EditSnapshot, type EventRole, type FlattenDecision, type FlattenOptions, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, type ListToolsResult, type LoopEvent, MCP_PROTOCOL_VERSION, McpClient, type McpClientOptions, type McpContentBlock, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, applyEditBlock, applyEditBlocks, bridgeMcpTools, claudeEquivalentCost, codeSystemPrompt, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenMcpResult, flattenSchema, formatLoopError, harvest, healLoadedMessages, isJsonRpcError, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, openTranscriptFile, parseEditBlocks, parseMcpSpec, parseTranscript, readConfig, readTranscript, recordFromLoopEvent, redactKey, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, restoreSnapshots, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, snapshotBeforeEdits, truncateForModel, writeConfig, writeMeta, writeRecord };
|
|
1535
|
+
export { AppendOnlyLog, type ApplyResult, type ApplyStatus, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CODE_SYSTEM_PROMPT, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DEFAULT_MAX_RESULT_CHARS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EditBlock, type EditSnapshot, type EventRole, type FlattenDecision, type FlattenOptions, type GetPromptResult, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, type ListPromptsResult, type ListResourcesResult, type ListToolsResult, type LoopEvent, MCP_PROTOCOL_VERSION, McpClient, type McpClientOptions, type McpContentBlock, type McpPrompt, type McpPromptArgument, type McpPromptMessage, type McpPromptResourceBlock, type McpResource, type McpResourceContents, type McpResourceContentsBlob, type McpResourceContentsText, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type ReadResourceResult, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, applyEditBlock, applyEditBlocks, bridgeMcpTools, claudeEquivalentCost, codeSystemPrompt, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenMcpResult, flattenSchema, formatLoopError, harvest, healLoadedMessages, isJsonRpcError, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, openTranscriptFile, parseEditBlocks, parseMcpSpec, parseTranscript, readConfig, readTranscript, recordFromLoopEvent, redactKey, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, restoreSnapshots, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, snapshotBeforeEdits, stripHallucinatedToolMarkup, truncateForModel, writeConfig, writeMeta, writeRecord };
|
package/dist/index.js
CHANGED
|
@@ -673,7 +673,19 @@ function scavengeToolCalls(reasoningContent, opts) {
|
|
|
673
673
|
const max = opts.maxCalls ?? 4;
|
|
674
674
|
const notes = [];
|
|
675
675
|
const out = [];
|
|
676
|
-
for (const
|
|
676
|
+
for (const invoke of iterateDsmlInvokes(reasoningContent)) {
|
|
677
|
+
if (out.length >= max) break;
|
|
678
|
+
if (!opts.allowedNames.has(invoke.name)) continue;
|
|
679
|
+
out.push({
|
|
680
|
+
function: {
|
|
681
|
+
name: invoke.name,
|
|
682
|
+
arguments: JSON.stringify(invoke.args)
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
notes.push(`scavenged DSML call: ${invoke.name}`);
|
|
686
|
+
}
|
|
687
|
+
const nonDsml = stripDsmlBlocks(reasoningContent);
|
|
688
|
+
for (const candidate of iterateJsonObjects(nonDsml)) {
|
|
677
689
|
if (out.length >= max) break;
|
|
678
690
|
const call = coerceToToolCall(candidate, opts.allowedNames);
|
|
679
691
|
if (call) {
|
|
@@ -683,6 +695,40 @@ function scavengeToolCalls(reasoningContent, opts) {
|
|
|
683
695
|
}
|
|
684
696
|
return { calls: out, notes };
|
|
685
697
|
}
|
|
698
|
+
function stripDsmlBlocks(text) {
|
|
699
|
+
let out = text;
|
|
700
|
+
out = out.replace(/<[||]DSML[||]function_calls>[\s\S]*?<\/?[||]DSML[||]function_calls>/g, "");
|
|
701
|
+
out = out.replace(/<[||]DSML[||]invoke\s+[^>]*>[\s\S]*?<\/[||]DSML[||]invoke>/g, "");
|
|
702
|
+
return out;
|
|
703
|
+
}
|
|
704
|
+
function* iterateDsmlInvokes(text) {
|
|
705
|
+
const INVOKE_RE = /<[||]DSML[||]invoke\s+name="([^"]+)">([\s\S]*?)<\/[||]DSML[||]invoke>/g;
|
|
706
|
+
for (const match of text.matchAll(INVOKE_RE)) {
|
|
707
|
+
const name = match[1];
|
|
708
|
+
const body = match[2];
|
|
709
|
+
if (!name || body === void 0) continue;
|
|
710
|
+
yield { name, args: parseDsmlParameters(body) };
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function parseDsmlParameters(body) {
|
|
714
|
+
const PARAM_RE = /<[||]DSML[||]parameter\s+name="([^"]+)"(?:\s+string="(true|false)")?\s*>([\s\S]*?)<\/[||]DSML[||]parameter>/g;
|
|
715
|
+
const args = {};
|
|
716
|
+
for (const m of body.matchAll(PARAM_RE)) {
|
|
717
|
+
const key = m[1];
|
|
718
|
+
const stringFlag = m[2];
|
|
719
|
+
const raw = (m[3] ?? "").trim();
|
|
720
|
+
if (!key) continue;
|
|
721
|
+
if (stringFlag === "false") {
|
|
722
|
+
try {
|
|
723
|
+
args[key] = JSON.parse(raw);
|
|
724
|
+
continue;
|
|
725
|
+
} catch {
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
args[key] = raw;
|
|
729
|
+
}
|
|
730
|
+
return args;
|
|
731
|
+
}
|
|
686
732
|
function* iterateJsonObjects(text) {
|
|
687
733
|
for (let i = 0; i < text.length; i++) {
|
|
688
734
|
if (text[i] !== "{") continue;
|
|
@@ -868,14 +914,15 @@ var ToolCallRepair = class {
|
|
|
868
914
|
this.opts = opts;
|
|
869
915
|
this.storm = new StormBreaker(opts.stormWindow ?? 6, opts.stormThreshold ?? 3);
|
|
870
916
|
}
|
|
871
|
-
process(declaredCalls, reasoningContent) {
|
|
917
|
+
process(declaredCalls, reasoningContent, content = null) {
|
|
872
918
|
const report = {
|
|
873
919
|
scavenged: 0,
|
|
874
920
|
truncationsFixed: 0,
|
|
875
921
|
stormsBroken: 0,
|
|
876
922
|
notes: []
|
|
877
923
|
};
|
|
878
|
-
const
|
|
924
|
+
const combined = [reasoningContent ?? "", content ?? ""].filter(Boolean).join("\n");
|
|
925
|
+
const scavenged = scavengeToolCalls(combined || null, {
|
|
879
926
|
allowedNames: this.opts.allowedToolNames,
|
|
880
927
|
maxCalls: this.opts.maxScavenge ?? 4
|
|
881
928
|
});
|
|
@@ -1234,6 +1281,39 @@ var CacheFirstLoop = class {
|
|
|
1234
1281
|
abort() {
|
|
1235
1282
|
this._aborted = true;
|
|
1236
1283
|
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Drop everything in the log after (and including) the most recent
|
|
1286
|
+
* user message. Used by `/retry` so the caller can re-send that
|
|
1287
|
+
* message with a fresh turn instead of layering another response on
|
|
1288
|
+
* top of the prior exchange. Returns the content of the dropped user
|
|
1289
|
+
* message, or `null` if there isn't one yet.
|
|
1290
|
+
*
|
|
1291
|
+
* Persists by rewriting the session file — otherwise the next
|
|
1292
|
+
* launch would rehydrate the old exchange and `/retry` would seem
|
|
1293
|
+
* to have done nothing.
|
|
1294
|
+
*/
|
|
1295
|
+
retryLastUser() {
|
|
1296
|
+
const entries = this.log.entries;
|
|
1297
|
+
let lastUserIdx = -1;
|
|
1298
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1299
|
+
if (entries[i].role === "user") {
|
|
1300
|
+
lastUserIdx = i;
|
|
1301
|
+
break;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (lastUserIdx < 0) return null;
|
|
1305
|
+
const raw = entries[lastUserIdx].content;
|
|
1306
|
+
const userText = typeof raw === "string" ? raw : "";
|
|
1307
|
+
const preserved = entries.slice(0, lastUserIdx).map((m) => ({ ...m }));
|
|
1308
|
+
this.log.compactInPlace(preserved);
|
|
1309
|
+
if (this.sessionName) {
|
|
1310
|
+
try {
|
|
1311
|
+
rewriteSession(this.sessionName, preserved);
|
|
1312
|
+
} catch {
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return userText;
|
|
1316
|
+
}
|
|
1237
1317
|
async *step(userInput) {
|
|
1238
1318
|
this._turn++;
|
|
1239
1319
|
this.scratch.reset();
|
|
@@ -1247,9 +1327,17 @@ var CacheFirstLoop = class {
|
|
|
1247
1327
|
yield {
|
|
1248
1328
|
turn: this._turn,
|
|
1249
1329
|
role: "warning",
|
|
1250
|
-
content: `aborted at iter ${iter}/${this.maxToolIters} \u2014
|
|
1330
|
+
content: `aborted at iter ${iter}/${this.maxToolIters} \u2014 stopped without producing a summary (press \u2191 + Enter or /retry to resume)`
|
|
1331
|
+
};
|
|
1332
|
+
const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
|
|
1333
|
+
this.appendAndPersist({ role: "assistant", content: stoppedMsg });
|
|
1334
|
+
yield {
|
|
1335
|
+
turn: this._turn,
|
|
1336
|
+
role: "assistant_final",
|
|
1337
|
+
content: stoppedMsg,
|
|
1338
|
+
forcedSummary: true
|
|
1251
1339
|
};
|
|
1252
|
-
yield
|
|
1340
|
+
yield { turn: this._turn, role: "done", content: stoppedMsg };
|
|
1253
1341
|
return;
|
|
1254
1342
|
}
|
|
1255
1343
|
if (!warnedForIterBudget && iter >= warnAt) {
|
|
@@ -1412,7 +1500,8 @@ var CacheFirstLoop = class {
|
|
|
1412
1500
|
const planState = preHarvestedPlanState ? preHarvestedPlanState : this.harvestEnabled ? await harvest(reasoningContent || null, this.client, this.harvestOptions) : emptyPlanState();
|
|
1413
1501
|
const { calls: repairedCalls, report } = this.repair.process(
|
|
1414
1502
|
toolCalls,
|
|
1415
|
-
reasoningContent || null
|
|
1503
|
+
reasoningContent || null,
|
|
1504
|
+
assistantContent || null
|
|
1416
1505
|
);
|
|
1417
1506
|
this.appendAndPersist(this.assistantMessage(assistantContent, repairedCalls));
|
|
1418
1507
|
yield {
|
|
@@ -1471,12 +1560,18 @@ var CacheFirstLoop = class {
|
|
|
1471
1560
|
async *forceSummaryAfterIterLimit(opts = { reason: "budget" }) {
|
|
1472
1561
|
try {
|
|
1473
1562
|
const messages = this.buildMessages(null);
|
|
1563
|
+
messages.push({
|
|
1564
|
+
role: "user",
|
|
1565
|
+
content: "I'm out of tool-call budget for this turn. Summarize in plain prose what you learned from the tool results above. Do NOT emit any tool calls, function-call markup, DSML invocations, or SEARCH/REPLACE edit blocks \u2014 they will be silently discarded. Just plain text."
|
|
1566
|
+
});
|
|
1474
1567
|
const resp = await this.client.chat({
|
|
1475
1568
|
model: this.model,
|
|
1476
1569
|
messages
|
|
1477
1570
|
// no tools → model is forced to answer in text
|
|
1478
1571
|
});
|
|
1479
|
-
const
|
|
1572
|
+
const rawContent = resp.content?.trim() ?? "";
|
|
1573
|
+
const cleaned = stripHallucinatedToolMarkup(rawContent);
|
|
1574
|
+
const summary = cleaned || "(model emitted fake tool-call markup instead of a prose summary \u2014 try /retry with a narrower question, or /think to inspect R1's reasoning)";
|
|
1480
1575
|
const reasonPrefix = reasonPrefixFor(opts.reason, this.maxToolIters);
|
|
1481
1576
|
const annotated = `${reasonPrefix}
|
|
1482
1577
|
|
|
@@ -1517,6 +1612,14 @@ ${summary}`;
|
|
|
1517
1612
|
return msg;
|
|
1518
1613
|
}
|
|
1519
1614
|
};
|
|
1615
|
+
function stripHallucinatedToolMarkup(s) {
|
|
1616
|
+
let out = s;
|
|
1617
|
+
out = out.replace(/<|DSML|function_calls>[\s\S]*?<\/?|DSML|function_calls>/g, "");
|
|
1618
|
+
out = out.replace(/<\|DSML\|function_calls>[\s\S]*?<\/?\|DSML\|function_calls>/g, "");
|
|
1619
|
+
out = out.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, "");
|
|
1620
|
+
out = out.replace(/<|DSML|[\s\S]*$/g, "");
|
|
1621
|
+
return out.trim();
|
|
1622
|
+
}
|
|
1520
1623
|
function reasonPrefixFor(reason, iterCap) {
|
|
1521
1624
|
if (reason === "aborted") return "[aborted by user (Esc) \u2014 summarizing what I found so far]";
|
|
1522
1625
|
if (reason === "context-guard") {
|
|
@@ -2111,7 +2214,12 @@ var McpClient = class {
|
|
|
2111
2214
|
this.startReaderIfNeeded();
|
|
2112
2215
|
const result = await this.request("initialize", {
|
|
2113
2216
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
2114
|
-
|
|
2217
|
+
// Advertise every method the client can consume so servers know
|
|
2218
|
+
// they can send listChanged notifications etc. Sub-feature flags
|
|
2219
|
+
// (e.g. `resources.subscribe`) are omitted — we don't implement
|
|
2220
|
+
// those yet and the empty object means "method-level support, no
|
|
2221
|
+
// sub-features."
|
|
2222
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
2115
2223
|
clientInfo: this.clientInfo
|
|
2116
2224
|
});
|
|
2117
2225
|
this._serverCapabilities = result.capabilities ?? {};
|
|
@@ -2135,6 +2243,45 @@ var McpClient = class {
|
|
|
2135
2243
|
arguments: args ?? {}
|
|
2136
2244
|
});
|
|
2137
2245
|
}
|
|
2246
|
+
/**
|
|
2247
|
+
* List resources the server exposes. Supports a pagination cursor;
|
|
2248
|
+
* callers interested in the full set should loop on `nextCursor`.
|
|
2249
|
+
* Servers that don't support resources respond with method-not-found
|
|
2250
|
+
* (−32601) — we surface that as a thrown Error so callers can gate
|
|
2251
|
+
* on the `serverCapabilities.resources` field first.
|
|
2252
|
+
*/
|
|
2253
|
+
async listResources(cursor) {
|
|
2254
|
+
this.assertInitialized();
|
|
2255
|
+
return this.request("resources/list", {
|
|
2256
|
+
...cursor ? { cursor } : {}
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
/** Read the contents of a resource by URI. */
|
|
2260
|
+
async readResource(uri) {
|
|
2261
|
+
this.assertInitialized();
|
|
2262
|
+
return this.request("resources/read", {
|
|
2263
|
+
uri
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
/** List prompt templates the server exposes. */
|
|
2267
|
+
async listPrompts(cursor) {
|
|
2268
|
+
this.assertInitialized();
|
|
2269
|
+
return this.request("prompts/list", {
|
|
2270
|
+
...cursor ? { cursor } : {}
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Fetch a rendered prompt by name. `args` supplies values for any
|
|
2275
|
+
* required template arguments; the server validates. Returns messages
|
|
2276
|
+
* ready to prepend to the model's input.
|
|
2277
|
+
*/
|
|
2278
|
+
async getPrompt(name, args) {
|
|
2279
|
+
this.assertInitialized();
|
|
2280
|
+
return this.request("prompts/get", {
|
|
2281
|
+
name,
|
|
2282
|
+
...args ? { arguments: args } : {}
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2138
2285
|
/** Close the transport and reject any outstanding requests. */
|
|
2139
2286
|
async close() {
|
|
2140
2287
|
for (const [, pending] of this.pending) {
|
|
@@ -2766,7 +2913,7 @@ function redactKey(key) {
|
|
|
2766
2913
|
}
|
|
2767
2914
|
|
|
2768
2915
|
// src/index.ts
|
|
2769
|
-
var VERSION = "0.4.
|
|
2916
|
+
var VERSION = "0.4.3";
|
|
2770
2917
|
export {
|
|
2771
2918
|
AppendOnlyLog,
|
|
2772
2919
|
CODE_SYSTEM_PROMPT,
|
|
@@ -2835,6 +2982,7 @@ export {
|
|
|
2835
2982
|
sessionsDir,
|
|
2836
2983
|
similarity,
|
|
2837
2984
|
snapshotBeforeEdits,
|
|
2985
|
+
stripHallucinatedToolMarkup,
|
|
2838
2986
|
truncateForModel,
|
|
2839
2987
|
writeConfig,
|
|
2840
2988
|
writeMeta,
|