From 85c01d3ef11ee5f7c2b5301092bcf1253977ca30 Mon Sep 17 00:00:00 2001 From: ztimson Date: Mon, 17 Aug 2026 15:50:48 -0400 Subject: [PATCH] Added official file support --- package.json | 2 +- src/llm.ts | 27 ++++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 90018cc..54a12d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/ai-utils", - "version": "1.6.1", + "version": "1.6.2", "description": "AI Utility library", "author": "Zak Timson", "license": "MIT", diff --git a/src/llm.ts b/src/llm.ts index e19d9bd..9255dba 100644 --- a/src/llm.ts +++ b/src/llm.ts @@ -243,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; @@ -413,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; @@ -484,15 +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') { - lastMsg.files = files; - 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; } @@ -506,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) {