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:
160
apps/server/src/services/__tests__/llama-args-validator.test.ts
Normal file
160
apps/server/src/services/__tests__/llama-args-validator.test.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
validateExtraArgs,
|
||||
isManagedFlag,
|
||||
stripShadowingFlags,
|
||||
} from '../inference/llama-args-validator.js';
|
||||
import { parseAgentsMd } from '../agents.js';
|
||||
|
||||
describe('validateExtraArgs', () => {
|
||||
describe('deny list — each alias rejected', () => {
|
||||
const denied = [
|
||||
'-m', '--model',
|
||||
'-mu', '--model-url',
|
||||
'-dr', '--docker-repo',
|
||||
'-hf', '-hfr', '--hf-repo',
|
||||
'-hff', '--hf-file',
|
||||
'-hfv', '-hfrv', '--hf-repo-v',
|
||||
'-hffv', '--hf-file-v',
|
||||
'-hft', '--hf-token',
|
||||
'-mm', '--mmproj',
|
||||
'-mmu', '--mmproj-url',
|
||||
'--host', '--port', '--path', '--api-prefix', '--reuse-port',
|
||||
'--api-key', '--api-key-file',
|
||||
'--ssl-key-file', '--ssl-cert-file',
|
||||
'--webui', '--no-webui', '--ui', '--no-ui',
|
||||
'--ui-config', '--ui-config-file',
|
||||
'--ui-mcp-proxy', '--no-ui-mcp-proxy',
|
||||
'--models-dir', '--models-preset', '--models-max',
|
||||
'--models-autoload', '--no-models-autoload',
|
||||
];
|
||||
for (const flag of denied) {
|
||||
it(`rejects ${flag}`, () => {
|
||||
expect(() => validateExtraArgs([flag])).toThrow(/managed/);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('safe flags accepted', () => {
|
||||
const safe = [
|
||||
'-c', '--ctx-size', '-ngl', '--gpu-layers',
|
||||
'--top-k', '--cache-type-k', '--jinja', '--no-jinja',
|
||||
'--spec-draft-n-max', '-fa', '--flash-attn',
|
||||
'-t', '--threads', '-np', '--parallel',
|
||||
];
|
||||
for (const flag of safe) {
|
||||
it(`accepts ${flag}`, () => {
|
||||
expect(() => validateExtraArgs([flag])).not.toThrow();
|
||||
expect(validateExtraArgs([flag])).toEqual([flag]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('handles --flag=value shape (denies the flag part)', () => {
|
||||
expect(() => validateExtraArgs(['--model=evil.gguf'])).toThrow(/managed/);
|
||||
});
|
||||
|
||||
it('handles --flag=value shape (accepts safe flag)', () => {
|
||||
expect(validateExtraArgs(['--ctx-size=4096'])).toEqual(['--ctx-size=4096']);
|
||||
});
|
||||
|
||||
it('returns empty array for undefined input', () => {
|
||||
expect(validateExtraArgs(undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty array for empty input', () => {
|
||||
expect(validateExtraArgs([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('treats negative numbers as values, not flags', () => {
|
||||
expect(validateExtraArgs(['--seed', '-1'])).toEqual(['--seed', '-1']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isManagedFlag', () => {
|
||||
it('returns true for denied flags', () => {
|
||||
expect(isManagedFlag('--model')).toBe(true);
|
||||
expect(isManagedFlag('-m')).toBe(true);
|
||||
expect(isManagedFlag('--api-key')).toBe(true);
|
||||
expect(isManagedFlag('--port')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for safe flags', () => {
|
||||
expect(isManagedFlag('-c')).toBe(false);
|
||||
expect(isManagedFlag('--ctx-size')).toBe(false);
|
||||
expect(isManagedFlag('--top-k')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stripShadowingFlags', () => {
|
||||
it('strips auto -c when user supplies -c', () => {
|
||||
const result = stripShadowingFlags(['-c', '4096', '--top-k', '40']);
|
||||
expect(result).toEqual(['--top-k', '40']);
|
||||
});
|
||||
|
||||
it('retains both when no overlap', () => {
|
||||
const result = stripShadowingFlags(['--top-k', '40', '--top-p', '0.95']);
|
||||
expect(result).toEqual(['--top-k', '40', '--top-p', '0.95']);
|
||||
});
|
||||
|
||||
it('strips --ctx-size=value form', () => {
|
||||
const result = stripShadowingFlags(['--ctx-size=4096']);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('strips boolean --jinja flag (no value consumed)', () => {
|
||||
const result = stripShadowingFlags(['--jinja', '--top-k', '40']);
|
||||
expect(result).toEqual(['--top-k', '40']);
|
||||
});
|
||||
|
||||
it('respects stripContext=false to keep context flags', () => {
|
||||
const result = stripShadowingFlags(['-c', '4096'], { stripContext: false });
|
||||
expect(result).toEqual(['-c', '4096']);
|
||||
});
|
||||
|
||||
it('strips cache flags by default', () => {
|
||||
const result = stripShadowingFlags(['--cache-type-k', 'q8_0']);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('strips spec flags by default', () => {
|
||||
const result = stripShadowingFlags(['--spec-draft-n-max', '16']);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AGENTS.md frontmatter validation', () => {
|
||||
it('rejects agent with managed flag in llama_extra_args', () => {
|
||||
const md = `## Evil Agent
|
||||
---
|
||||
llama_extra_args: ["--model", "evil.gguf"]
|
||||
---
|
||||
You are evil.`;
|
||||
const { agents, errors } = parseAgentsMd(md);
|
||||
expect(agents).toHaveLength(0);
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0]!.reason).toContain('managed');
|
||||
});
|
||||
|
||||
it('accepts agent with safe llama_extra_args', () => {
|
||||
const md = `## Good Agent
|
||||
---
|
||||
llama_extra_args: ["--top-k", "20"]
|
||||
---
|
||||
You are good.`;
|
||||
const { agents, errors } = parseAgentsMd(md);
|
||||
expect(errors).toHaveLength(0);
|
||||
expect(agents).toHaveLength(1);
|
||||
expect(agents[0]!.llama_extra_args).toEqual(['--top-k', '20']);
|
||||
});
|
||||
|
||||
it('agent without llama_extra_args has null field', () => {
|
||||
const md = `## Simple Agent
|
||||
---
|
||||
temperature: 0.5
|
||||
---
|
||||
You are simple.`;
|
||||
const { agents } = parseAgentsMd(md);
|
||||
expect(agents[0]!.llama_extra_args).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user