import type { Message, ToolCall } from "../types";
import { AnthropicInbandScanner } from "./anthropic";
import { buildArgShapes, type ToolArgShape } from "./coercion";
import { DeepSeekInbandScanner } from "./deepseek";
import {
escapeXmlAttr,
renderDelimitedThinking,
renderLegacyTextTranscript,
renderToolResponseResults,
stringifyJson,
} from "./rendering";
import type {
DialectDefinition,
DialectRenderOptions,
DialectToolResult,
InbandScanEvent,
InbandScanner,
InbandScannerOptions,
} from "./types";
import dialectPrompt from "./xml.md" with { type: "text" };
export class XmlInbandScanner implements InbandScanner {
readonly #inner: InbandScanner;
constructor(options: InbandScannerOptions = {}) {
this.#inner =
options.xmlTagset === "dsml" ? new DeepSeekInbandScanner(options) : new AnthropicInbandScanner(options);
}
feed(text: string): InbandScanEvent[] {
return this.#inner.feed(text);
}
flush(): InbandScanEvent[] {
return this.#inner.flush();
}
}
function renderToolCall(call: ToolCall, options: DialectRenderOptions = {}): string {
return renderInvoke(call, buildArgShapes(options.tools).get(call.name));
}
function renderAssistantToolCalls(calls: readonly ToolCall[], options: DialectRenderOptions = {}): string {
return renderInvokes(calls, options.tools ?? []);
}
function renderToolResults(results: readonly DialectToolResult[]): string {
return renderToolResponseResults(results);
}
function renderThinking(text: string): string {
return renderDelimitedThinking("", "", text);
}
function renderTranscript(messages: readonly Message[], options: DialectRenderOptions = {}): string {
return renderLegacyTextTranscript(messages, options, {
renderThinking,
renderCalls: renderAssistantToolCalls,
renderResults: renderToolResults,
});
}
function renderInvoke(call: ToolCall, shape: ToolArgShape | undefined): string {
let body = ``;
for (const key in call.arguments) {
const value = call.arguments[key];
const isString = shape?.stringArgs.has(key) === true;
const rendered = isString && typeof value === "string" ? value : stringifyJson(value);
body += `${rendered}`;
}
return `${body}`;
}
function renderInvokes(calls: readonly ToolCall[], tools: NonNullable): string {
const shapes = buildArgShapes(tools);
return calls.map(call => renderInvoke(call, shapes.get(call.name))).join("\n");
}
const definition: DialectDefinition = {
dialect: "xml",
prompt: dialectPrompt,
createScanner: options => new XmlInbandScanner(options),
renderToolCall,
renderAssistantToolCalls,
renderToolResults,
renderThinking,
renderTranscript,
};
export default definition;