v2.4.0-unsloth-studio-lift: port 3 Unsloth Studio AGPL-3.0 modules
Batch 1 — tool-call-parser.ts: replaces xml-parser.ts with a port of
Unsloth's tool_call_parser.py. Adds balanced-brace JSON scanner,
single-param fast path, hasToolSignal/stripToolMarkup/parseToolCallsFromText
exports, and stream-finalization stripping at all three final-write sites
(error-handler, finalizeCompletion, executeToolPhase). Anthropic <invoke>
shape preserved. 75+12 tests.
Batch 2 — web/html-to-md.ts: parse5 tree-walking HTML-to-Markdown converter
ported from Unsloth's _html_to_md.py. Replaces web_fetch's regex stripHtml
with structured markdown output (headings, links, lists, tables, code blocks,
blockquotes, entity decoding). 29 tests.
Batch 3 — llama-args-validator.ts: port of llama_server_args.py deny-list
validator. Wired into AGENTS.md frontmatter parser — llama_extra_args field
validated at load time, rejects managed flags (model identity, networking,
auth/TLS, server UI). No runtime consumer yet (llama-swap boundary). 76 tests.
All three files carry SPDX-License-Identifier: AGPL-3.0-only headers.
LICENSE flipped to AGPL-3.0-only in prior commit (a938cf1).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { promises as fs } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import type { Agent, AgentsResponse, AgentParseError } from '../types/api.js';
|
||||
import { ALL_TOOLS, resolveToolTier } from './tools.js';
|
||||
import { validateExtraArgs } from './inference/llama-args-validator.js';
|
||||
|
||||
// v1.8.1: global agents live at /data/AGENTS.md inside the container
|
||||
// (./data:/data:ro mount on the host). Per-project AGENTS.md at the project
|
||||
@@ -97,6 +98,7 @@ interface ParsedFrontmatter {
|
||||
// (200) in the outer loop. Integer ≥ 0; steps: 0 means "no tool calls
|
||||
// allowed" — the model responds text-only.
|
||||
steps?: number;
|
||||
llama_extra_args?: string[];
|
||||
}
|
||||
|
||||
function stripQuotes(s: string): string {
|
||||
@@ -227,6 +229,34 @@ function parseFrontmatter(yaml: string): { data: ParsedFrontmatter; errors: stri
|
||||
} else {
|
||||
errors.push(`steps must be a non-negative integer (got "${valueRaw}")`);
|
||||
}
|
||||
} else if (key === 'llama_extra_args') {
|
||||
if (valueRaw === '') {
|
||||
data.llama_extra_args = [];
|
||||
// No arrayKey support — llama_extra_args uses inline list only.
|
||||
} else if (valueRaw.startsWith('[') && valueRaw.endsWith(']')) {
|
||||
const inner = valueRaw.slice(1, -1);
|
||||
const parsed = inner
|
||||
.split(',')
|
||||
.map((s) => stripQuotes(s.trim()))
|
||||
.filter((s) => s.length > 0);
|
||||
try {
|
||||
validateExtraArgs(parsed);
|
||||
data.llama_extra_args = parsed;
|
||||
} catch (err) {
|
||||
errors.push(err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
} else {
|
||||
const parsed = valueRaw
|
||||
.split(',')
|
||||
.map((s) => stripQuotes(s.trim()))
|
||||
.filter((s) => s.length > 0);
|
||||
try {
|
||||
validateExtraArgs(parsed);
|
||||
data.llama_extra_args = parsed;
|
||||
} catch (err) {
|
||||
errors.push(err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Unknown keys silently ignored — forward-compat.
|
||||
}
|
||||
@@ -328,6 +358,7 @@ function parseAgentSection(section: RawSection): Omit<Agent, 'source'> {
|
||||
model: typeof fm.model === 'string' && fm.model.length > 0 ? fm.model : null,
|
||||
max_tool_calls: typeof fm.max_tool_calls === 'number' ? fm.max_tool_calls : null,
|
||||
steps: typeof fm.steps === 'number' ? fm.steps : null,
|
||||
llama_extra_args: Array.isArray(fm.llama_extra_args) ? fm.llama_extra_args : null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user