Compare commits

...

2 Commits
1.6.0 ... 1.6.2

Author SHA1 Message Date
85c01d3ef1 Added official file support
All checks were successful
Publish Library / Build NPM Project (push) Successful in 30s
Publish Library / Tag Version (push) Successful in 10s
2026-08-17 15:50:48 -04:00
5826573d5c Added official file support
All checks were successful
Publish Library / Build NPM Project (push) Successful in 50s
Publish Library / Tag Version (push) Successful in 13s
2026-08-17 15:16:32 -04:00
2 changed files with 17 additions and 13 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@ztimson/ai-utils",
"version": "1.6.0",
"version": "1.6.2",
"description": "AI Utility library",
"author": "Zak Timson",
"license": "MIT",

View File

@@ -50,6 +50,8 @@ export type LLMMessage = {
role: 'assistant' | 'system' | 'user';
/** Message content */
content: string | any;
/** Files attached to request */
files?: LLMFile[];
/** Timestamp */
timestamp?: number;
/** Response duration in ms */
@@ -241,10 +243,8 @@ class LLM {
} else if(isText) {
text = (await this.loadBuffer(file, true)).toString('utf-8');
} else {
text = `Unsupported file type: ${ext || mime}`;
text = typeof file.content === 'string' ? file.content : `[Binary file, unable to extract: ${name}]`;
}
// Cache result, skip re-extraction on future turns of the same conversation
file.content = text;
file.extracted = true;
delete file.path;
@@ -411,7 +411,8 @@ ${a.system}`,
let tools: AiTool[] = options.tools || this.ai.options.llm?.tools || [];
const prompts: string[] = [];
let history = options.history || [];
if(message) history.push({role: 'user', content: message, timestamp: Date.now()});
const files = options.files || [];
if(message || files.length) history.push({role: 'user', content: message || '', timestamp: Date.now()});
// MCP
const mcp = options.mcp || this.ai.options?.llm?.mcp;
@@ -482,14 +483,16 @@ Linked: ${makeUnique([...r.links, ...r.backlinks]).join(', ')}
if(aborted) throw Object.assign(new Error('Aborted'), {name: 'AbortError'});
// Files
const files = options.files || [];
const lastMsg = history[history.length - 1];
const originalContent = lastMsg?.content;
if(files.length && lastMsg?.role === 'user') {
const {text, images} = await this.resolveFiles(files);
const merged = text ? `${originalContent}\n\n${text}` : originalContent;
lastMsg.content = images.length
if(files.length && lastMsg?.role === 'user') lastMsg.files = files;
const restores: {msg: LLMMessage, content: any}[] = [];
for(const msg of history) {
if(msg.role !== 'user' || !msg.files?.length) continue;
const {text, images} = await this.resolveFiles(msg.files);
if(!text && !images.length) continue;
restores.push({msg, content: msg.content});
const merged = text ? [msg.content, text].filter(Boolean).join('\n\n') : msg.content;
msg.content = images.length
? [...images.map(i => ({type: 'image', mime: i.mime, data: i.data})), {type: 'text', text: merged}]
: merged;
}
@@ -503,7 +506,8 @@ Linked: ${makeUnique([...r.links, ...r.backlinks]).join(', ')}
request = this.models[m].ask('', {...options, tools, system: prompts.filter(Boolean).join('\n\n')});
let resp = await request;
if(files.length && lastMsg?.role === 'user') lastMsg.content = originalContent;
// Strip the file injection shim
restores.forEach(({msg, content}) => msg.content = content);
// Capture meta (duration / tps)
for(const h of history) {