@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
90 lines • 3.58 kB
JavaScript
import { cleanJSONToolCalls, detectMalformedJSONToolCall, parseJSONToolCalls, } from '../tool-calling/json-parser.js';
import { XMLToolCallParser } from '../tool-calling/xml-parser.js';
import { ensureString } from '../utils/type-helpers.js';
/**
* Strip tags from content (some models output thinking that shouldn't be shown)
*/
function stripThinkTags(content) {
return (content
// Strip complete blocks
.replace(/<think>[\s\S]*?<\/think>/gi, '')
// Strip orphaned/incomplete think tags
.replace(/<think>[\s\S]*$/gi, '')
.replace(/<\/think>/gi, ''));
}
/**
* Normalize whitespace in content to remove excessive blank lines and spacing
*/
function normalizeWhitespace(content) {
return (content
// Remove trailing whitespace from each line
.replace(/[ \t]+$/gm, '')
// Collapse multiple spaces (but not at start of line for indentation)
.replace(/([^ \t\n]) {2,}/g, '$1 ')
// Remove lines that are only whitespace
.replace(/^[ \t]+$/gm, '')
// Collapse 3+ consecutive newlines to exactly 2 (one blank line)
.replace(/\n{3,}/g, '\n\n')
.trim());
}
/**
* Unified tool call parser that tries XML first, then falls back to JSON
* Type-preserving: Accepts unknown type, converts to string for processing
*/
export function parseToolCalls(content) {
// 1. Safety Coercion
const contentStr = ensureString(content);
// Strip tags first - some models (like GLM-4) emit these for chain-of-thought
const strippedContent = stripThinkTags(contentStr);
// 1. Try XML parser for valid tool calls (OPTIMISTIC: Success first!)
if (XMLToolCallParser.hasToolCalls(strippedContent)) {
// Parse valid XML tool calls
const parsedCalls = XMLToolCallParser.parseToolCalls(strippedContent);
const convertedCalls = XMLToolCallParser.convertToToolCalls(parsedCalls);
if (convertedCalls.length > 0) {
const cleanedContent = XMLToolCallParser.removeToolCallsFromContent(strippedContent);
return {
success: true,
toolCalls: convertedCalls,
cleanedContent,
};
}
}
// 2. Check for malformed XML patterns (DEFENSIVE: Error second!)
const xmlMalformed = XMLToolCallParser.detectMalformedToolCall(strippedContent);
if (xmlMalformed) {
return {
success: false,
error: xmlMalformed.error,
examples: xmlMalformed.examples,
};
}
// 3. Fall back to JSON parser
// FIX: Check for valid JSON tool calls FIRST (optimistic approach)
// This prevents malformed detection from catching text that's NOT a tool call attempt
const jsonCalls = parseJSONToolCalls(strippedContent);
if (jsonCalls.length > 0) {
const cleanedContent = cleanJSONToolCalls(strippedContent, jsonCalls);
return {
success: true,
toolCalls: jsonCalls,
cleanedContent,
};
}
// 4. If no valid tools found, check for malformed patterns
const jsonMalformed = detectMalformedJSONToolCall(strippedContent);
if (jsonMalformed) {
return {
success: false,
error: jsonMalformed.error,
examples: jsonMalformed.examples,
};
}
// 5. No tool calls found - still normalize whitespace in content
return {
success: true,
toolCalls: [],
cleanedContent: normalizeWhitespace(strippedContent),
};
}
//# sourceMappingURL=tool-parser.js.map