'use strict'; // node --test internal/vscodeext/ext/lib_test.js // (VS Code's own runtime works too: // ELECTRON_RUN_AS_NODE=1 "/Applications/Visual Studio Code.app/Contents/MacOS/Electron" --test …) const test = require('node:test'); const assert = require('node:assert/strict'); const lib = require('./lib'); // VS Code's parts, as the provider receives them: classes in VS Code, // told apart here by their fields. const text = (value) => ({ value }); const call = (callId, name, input) => ({ callId, name, input }); const result = (callId, ...values) => ({ callId, content: values.map(text) }); test('gatewayBase takes what people paste', () => { assert.equal(lib.gatewayBase('https://llm.example.com/'), 'https://llm.example.com'); assert.equal(lib.gatewayBase('https://llm.example.com/v1'), 'https://llm.example.com'); assert.equal(lib.gatewayBase('llm.example.com'), 'https://llm.example.com'); assert.equal(lib.gatewayBase('http://10.0.0.140:8080/v1/'), 'http://10.0.0.140:8080'); assert.equal(lib.gatewayBase('ftp://x'), ''); assert.equal(lib.gatewayBase(''), ''); }); test('modelInfo keeps chat models and says what the catalog says', () => { const m = lib.modelInfo({ id: 'coder', alias_of: 'qwen3-coder-30b', context_length: 32768, max_output_tokens: 8192, capabilities: { tools: true, vision: false }, }, true); assert.equal(m.id, 'coder'); assert.equal(m.family, 'qwen3-coder-30b'); assert.equal(m.maxOutputTokens, 8192); assert.equal(m.maxInputTokens, 32768 - 8192); assert.deepEqual(m.capabilities, { toolCalling: true, imageInput: false }); assert.equal(lib.modelInfo({ id: 'bge-m3', capabilities: { embeddings: true } }, true), null); assert.equal(lib.modelInfo({ id: 'flux', capabilities: { images: true } }, true), null); assert.equal(lib.modelInfo({ id: 'sf3d', capabilities: { meshes: true, workflows: true } }, true), null); assert.equal(lib.modelInfo({ id: 'plain', capabilities: {} }, false), null); const small = lib.modelInfo({ id: 'small', context_length: 4096, capabilities: {} }, true); assert.equal(small.maxOutputTokens, 1024, 'a quarter of a small context, not 4096 of it'); }); test('chatMessages puts tool calls and results where chat completions want them', () => { const msgs = lib.chatMessages([ { role: lib.USER, content: [text('Read main.go')] }, { role: lib.ASSISTANT, content: [text('Reading it.'), call('c1', 'read_file', { path: 'main.go' })] }, { role: lib.USER, content: [result('c1', 'package main'), text('Now summarise it.')] }, ], false); assert.deepEqual(msgs, [ { role: 'user', content: 'Read main.go' }, { role: 'assistant', content: 'Reading it.', tool_calls: [{ id: 'c1', type: 'function', function: { name: 'read_file', arguments: '{"path":"main.go"}' } }], }, { role: 'tool', tool_call_id: 'c1', content: 'package main' }, { role: 'user', content: 'Now summarise it.' }, ]); }); test('an assistant message of calls alone has null content', () => { const [m] = lib.chatMessages([{ role: lib.ASSISTANT, content: [call('c1', 'ls', {})] }], false); assert.equal(m.content, null); assert.equal(m.tool_calls.length, 1); }); test('images go as data URLs only to a model that takes them', () => { const img = { mimeType: 'image/png', data: new Uint8Array([1, 2, 3]) }; const [withVision] = lib.chatMessages([{ role: lib.USER, content: [text('what is this'), img] }], true); assert.deepEqual(withVision.content[1], { type: 'image_url', image_url: { url: 'data:image/png;base64,AQID' } }); const [without] = lib.chatMessages([{ role: lib.USER, content: [text('what is this'), img] }], false); assert.equal(without.content, 'what is this'); }); test('requestBody asks for tools as functions, and requires one when VS Code does', () => { const model = { id: 'coder', maxOutputTokens: 2048, capabilities: { imageInput: false } }; const body = lib.requestBody(model, [{ role: lib.USER, content: [text('hi')] }], { tools: [{ name: 'ls', description: 'list', inputSchema: undefined }], toolMode: 2, modelOptions: { temperature: 0.2 }, }); assert.equal(body.stream, true); assert.equal(body.max_tokens, 2048); assert.equal(body.tool_choice, 'required'); assert.equal(body.temperature, 0.2); assert.deepEqual(body.tools[0].function.parameters, { type: 'object', properties: {} }); }); async function* chunks(...parts) { for (const p of parts) yield new TextEncoder().encode(p); } test('sseData reads events split anywhere', async () => { const got = []; for await (const d of lib.sseData(chunks('data: {"a":', '1}\r\n\ndata: [DO', 'NE]\n'))) got.push(d); assert.deepEqual(got, ['{"a":1}', '[DONE]']); }); test('Answer joins text and tool-call arguments that arrive in pieces', () => { const a = new lib.Answer(); const ev = (delta) => JSON.stringify({ choices: [{ delta }] }); assert.equal(a.add(ev({ content: 'Hel' })), 'Hel'); assert.equal(a.add(ev({ content: 'lo' })), 'lo'); a.add(ev({ tool_calls: [{ index: 0, id: 'call_a', function: { name: 'read_file', arguments: '{"pa' } }] })); a.add(ev({ tool_calls: [{ index: 0, function: { arguments: 'th":"x"}' } }] })); a.add(ev({ tool_calls: [{ index: 1, id: 'call_b', function: { name: 'ls', arguments: '' } }] })); assert.equal(a.add('[DONE]'), ''); assert.deepEqual(a.toolCalls(), [ { id: 'call_a', name: 'read_file', input: { path: 'x' } }, { id: 'call_b', name: 'ls', input: {} }, ]); }); test('Answer passes on an error the stream carries', () => { const a = new lib.Answer(); assert.throws(() => a.add(JSON.stringify({ error: { message: 'budget spent' } })), /budget spent/); }); test('errorMessage says what to do about a refused key', () => { assert.match(lib.errorMessage(401, '{"error":{"message":"invalid key"}}'), /Sign In/); assert.match(lib.errorMessage(429, '{"error":{"message":"rate limit"}}'), /rate limit/); assert.match(lib.errorMessage(500, 'boom'), /500: boom/); }); test('a link to the same gateway by plain http is a downgrade', () => { assert.equal(lib.downgrades('https://llm.example.com', 'http://llm.example.com'), true); assert.equal(lib.downgrades('https://llm.example.com', 'https://llm.example.com'), false); assert.equal(lib.downgrades('http://llm.example.com', 'https://llm.example.com'), false); assert.equal(lib.downgrades('https://a.example.com', 'http://b.example.com'), false); });