beeos-claw 0.5.37 → 0.5.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{AROUTER_PROVIDER_ID,DEFAULT_AROUTER_IMAGE_MODEL,arouterAuthHeaders,arouterFetch,readErrorBody,resolveArouterApiKey,resolveArouterBaseUrl}from"./arouter-config.js";const IMAGE_MODELS=[DEFAULT_AROUTER_IMAGE_MODEL],DEFAULT_IMAGE_TIMEOUT_MS=12e4;function inferImageMimeType(e){return e.length>=8&&137===e[0]&&80===e[1]?"image/png":e.length>=3&&255===e[0]&&216===e[1]&&255===e[2]?"image/jpeg":e.length>=12&&"RIFF"===e.toString("ascii",0,4)&&"WEBP"===e.toString("ascii",8,12)?"image/webp":"image/png"}async function assetFromEntry(e,r,t){let o=null;if(e.b64_json)o=Buffer.from(e.b64_json,"base64");else if(e.url){const r=await arouterFetch(e.url,{deadlineAt:t});if(!r.ok)throw new Error(`ARouter image download failed (${r.status}): ${await readErrorBody(r)}`);o=Buffer.from(await r.arrayBuffer())}if(!o||0===o.length)return null;const a=inferImageMimeType(o);return{buffer:o,mimeType:a,fileName:`image-${r+1}.${"image/jpeg"===a?"jpg":"image/webp"===a?"webp":"png"}`,...e.revised_prompt?{revisedPrompt:e.revised_prompt}:{}}}export function createArouterImageProvider(){return{id:AROUTER_PROVIDER_ID,label:"ARouter Image",defaultModel:DEFAULT_AROUTER_IMAGE_MODEL,defaultTimeoutMs:12e4,models:IMAGE_MODELS,capabilities:{generate:{maxCount:1,supportsSize:!0},edit:{enabled:!1}},isConfigured:()=>Boolean(resolveArouterApiKey()),async generateImage(e){const r=resolveArouterApiKey();if(!r)throw new Error("ARouter image generation: AROUTER_API_KEY is not configured.");const t=resolveArouterBaseUrl(e.cfg),o=e.model?.trim()||DEFAULT_AROUTER_IMAGE_MODEL,a=Date.now()+(e.timeoutMs??12e4),i={model:o,prompt:e.prompt};e.size?.trim()&&(i.size=e.size.trim());const n=e.count&&e.count>1?Math.floor(e.count):1;n>1&&(i.n=n);const s=await arouterFetch(`${t}/images/generations`,{method:"POST",headers:arouterAuthHeaders(r),body:JSON.stringify(i),deadlineAt:a});if(!s.ok)throw new Error(`ARouter image generation failed (${s.status}): ${await readErrorBody(s)}`);const u=await s.json(),g=u.data??[],m=[];for(let e=0;e<g.length;e+=1){const r=await assetFromEntry(g[e],e,a);r&&m.push(r)}if(0===m.length)throw new Error("ARouter image generation response missing image data.");return{images:m,model:o,...u.usage?{metadata:{usage:u.usage}}:{}}}}}
1
+ import{AROUTER_PROVIDER_ID,DEFAULT_AROUTER_IMAGE_MODEL,arouterAuthHeaders,arouterFetch,readErrorBody,resolveArouterApiKey,resolveArouterBaseUrl}from"./arouter-config.js";const IMAGE_MODELS=[DEFAULT_AROUTER_IMAGE_MODEL],DEFAULT_IMAGE_TIMEOUT_MS=12e4,SUPPORTED_ASPECT_RATIOS=["1:1","2:3","3:2","3:4","4:3","4:5","5:4","9:16","16:9","21:9"],SUPPORTED_RESOLUTIONS=["1K","2K","4K"];function isRecord(e){return Boolean(e&&"object"==typeof e&&!Array.isArray(e))}function inferImageMimeType(e){return e.length>=8&&137===e[0]&&80===e[1]?"image/png":e.length>=3&&255===e[0]&&216===e[1]&&255===e[2]?"image/jpeg":e.length>=12&&"RIFF"===e.toString("ascii",0,4)&&"WEBP"===e.toString("ascii",8,12)?"image/webp":"image/png"}function toImageDataUrl(e){return`data:${e.mimeType};base64,${e.buffer.toString("base64")}`}function isGeminiImageModel(e){return/gemini/i.test(e)}function buildMessageContent(e){const t=e.inputImages??[];return 0===t.length?e.prompt:[{type:"text",text:e.prompt},...t.map(e=>({type:"image_url",image_url:{url:toImageDataUrl(e)}}))]}function buildImageConfig(e,t){if(!isGeminiImageModel(t))return{};const r={},o=e.aspectRatio?.trim();o&&(r.aspect_ratio=o);const a=e.resolution?.trim();return a&&(r.image_size=a),r}function decodeDataUrl(e){const t=/^data:([^;,]+)?(;base64)?,([\s\S]*)$/u.exec(e);if(!t)return null;const r=t[1]?.trim()||"image/png",o=Boolean(t[2]),a=t[3]??"",n=o?Buffer.from(a,"base64"):Buffer.from(decodeURIComponent(a),"utf8");return 0===n.length?null:{buffer:n,mimeType:r}}function collectImageUrls(e){const t=[];for(const r of e.choices??[]){const e=r?.message;if(!e)continue;for(const r of e.images??[]){const e=(r.image_url??r.imageUrl)?.url;"string"==typeof e&&e.length>0&&t.push(e)}const o=e.content;if("string"==typeof o&&o.length>0){const e=/data:image\/[^;]+;base64,[A-Za-z0-9+/=]+/gu;for(const r of o.matchAll(e))t.push(r[0])}else if(Array.isArray(o))for(const e of o)if(isRecord(e)&&"image_url"===e.type&&isRecord(e.image_url)){const r=e.image_url.url;"string"==typeof r&&r.length>0&&t.push(r)}}return t}async function assetFromUrl(e,t,r){let o,a=null;if(e.startsWith("data:")){const t=decodeDataUrl(e);t&&(a=t.buffer,o=t.mimeType)}else{const t=await arouterFetch(e,{deadlineAt:r});if(!t.ok)throw new Error(`ARouter image download failed (${t.status}): ${await readErrorBody(t)}`);a=Buffer.from(await t.arrayBuffer())}if(!a||0===a.length)return null;const n=o??inferImageMimeType(a);return{buffer:a,mimeType:n,fileName:`image-${t+1}.${"image/jpeg"===n?"jpg":"image/webp"===n?"webp":"png"}`}}export function createArouterImageProvider(){return{id:AROUTER_PROVIDER_ID,label:"ARouter Image",defaultModel:DEFAULT_AROUTER_IMAGE_MODEL,defaultTimeoutMs:12e4,models:IMAGE_MODELS,capabilities:{generate:{maxCount:1,supportsSize:!1,supportsAspectRatio:!0,supportsResolution:!0},edit:{enabled:!1},geometry:{aspectRatios:[...SUPPORTED_ASPECT_RATIOS],resolutions:[...SUPPORTED_RESOLUTIONS]}},isConfigured:()=>Boolean(resolveArouterApiKey()),async generateImage(e){const t=resolveArouterApiKey();if(!t)throw new Error("ARouter image generation: AROUTER_API_KEY is not configured.");const r=resolveArouterBaseUrl(e.cfg),o=e.model?.trim()||DEFAULT_AROUTER_IMAGE_MODEL,a=Date.now()+(e.timeoutMs??12e4),n=buildImageConfig(e,o),i={model:o,messages:[{role:"user",content:buildMessageContent(e)}],modalities:["image","text"]},s=e.count&&e.count>1?Math.floor(e.count):1;s>1&&(i.n=s),Object.keys(n).length>0&&(i.image_config=n);const u=await arouterFetch(`${r}/chat/completions`,{method:"POST",headers:arouterAuthHeaders(t),body:JSON.stringify(i),deadlineAt:a});if(!u.ok)throw new Error(`ARouter image generation failed (${u.status}): ${await readErrorBody(u)}`);const g=await u.json(),l=collectImageUrls(g),m=[];for(let e=0;e<l.length;e+=1){const t=await assetFromUrl(l[e],e,a);t&&m.push(t)}if(0===m.length)throw new Error("ARouter image generation response missing image data.");return{images:m,model:o,...g.usage?{metadata:{usage:g.usage}}:{}}}}}
@@ -1 +1 @@
1
- import{afterEach,beforeEach,describe,expect,it,vi}from"vitest";import{createArouterImageProvider}from"./image-provider.js";const PNG_BYTES=Buffer.from([137,80,78,71,13,10,26,10,0]);function jsonResponse(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"content-type":"application/json"}})}describe("createArouterImageProvider",()=>{const e=process.env.AROUTER_API_KEY;beforeEach(()=>{process.env.AROUTER_API_KEY="test-key"}),afterEach(()=>{void 0===e?delete process.env.AROUTER_API_KEY:process.env.AROUTER_API_KEY=e,vi.restoreAllMocks()}),it("isConfigured reflects AROUTER_API_KEY presence",()=>{const e=createArouterImageProvider();expect(e.isConfigured?.({})).toBe(!0),delete process.env.AROUTER_API_KEY,expect(e.isConfigured?.({})).toBe(!1)}),it("posts to /images/generations and decodes data[0].b64_json",async()=>{const e=vi.fn(async(e,t)=>{expect(e).toBe("https://api.arouter.ai/v1/images/generations"),expect(t?.method).toBe("POST");const r=new Headers(t?.headers);expect(r.get("authorization")).toBe("Bearer test-key");const o=JSON.parse(String(t?.body));return expect(o).toMatchObject({model:"gemini-2.5-flash-image",prompt:"a cat"}),jsonResponse({created:1,data:[{b64_json:PNG_BYTES.toString("base64")}],usage:{cost:.01}})});vi.stubGlobal("fetch",e);const t=createArouterImageProvider(),r=await t.generateImage({provider:"arouter",model:"gemini-2.5-flash-image",prompt:"a cat",cfg:{models:{providers:{arouter:{baseUrl:"https://api.arouter.ai/v1"}}}}});expect(r.images).toHaveLength(1),expect(r.images[0].mimeType).toBe("image/png"),expect(r.images[0].buffer.equals(PNG_BYTES)).toBe(!0),expect(r.model).toBe("gemini-2.5-flash-image"),expect(e).toHaveBeenCalledTimes(1)}),it("throws a descriptive error on non-2xx",async()=>{vi.stubGlobal("fetch",vi.fn(async()=>new Response("bad model",{status:400})));const e=createArouterImageProvider();await expect(e.generateImage({provider:"arouter",model:"x",prompt:"p"})).rejects.toThrow(/ARouter image generation failed \(400\)/)}),it("throws when no image data is returned",async()=>{vi.stubGlobal("fetch",vi.fn(async()=>jsonResponse({data:[]})));const e=createArouterImageProvider();await expect(e.generateImage({provider:"arouter",model:"x",prompt:"p"})).rejects.toThrow(/missing image data/)})});
1
+ import{afterEach,beforeEach,describe,expect,it,vi}from"vitest";import{createArouterImageProvider}from"./image-provider.js";const PNG_BYTES=Buffer.from([137,80,78,71,13,10,26,10,0]),PNG_DATA_URL=`data:image/png;base64,${PNG_BYTES.toString("base64")}`;function jsonResponse(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"content-type":"application/json"}})}function chatImageResponse(e){return{choices:[{message:{role:"assistant",images:[{image_url:{url:e}}]}}],usage:{cost:.01}}}describe("createArouterImageProvider",()=>{const e=process.env.AROUTER_API_KEY;beforeEach(()=>{process.env.AROUTER_API_KEY="test-key"}),afterEach(()=>{void 0===e?delete process.env.AROUTER_API_KEY:process.env.AROUTER_API_KEY=e,vi.restoreAllMocks()}),it("isConfigured reflects AROUTER_API_KEY presence",()=>{const e=createArouterImageProvider();expect(e.isConfigured?.({})).toBe(!0),delete process.env.AROUTER_API_KEY,expect(e.isConfigured?.({})).toBe(!1)}),it("declares aspectRatio capability + geometry so normalization keeps aspectRatio",()=>{const e=createArouterImageProvider();expect(e.capabilities.generate.supportsAspectRatio).toBe(!0),expect(e.capabilities.generate.supportsSize).toBe(!1),expect(e.capabilities.geometry?.aspectRatios).toContain("3:4"),expect(e.capabilities.geometry?.aspectRatios).toEqual(expect.arrayContaining(["1:1","3:4","16:9","21:9"]))}),it("posts chat/completions with modalities + image_config.aspect_ratio",async()=>{const e=vi.fn(async(e,t)=>{expect(e).toBe("https://api.arouter.ai/v1/chat/completions"),expect(t?.method).toBe("POST");const a=new Headers(t?.headers);expect(a.get("authorization")).toBe("Bearer test-key");const o=JSON.parse(String(t?.body));return expect(o.model).toBe("gemini-2.5-flash-image"),expect(o.messages).toEqual([{role:"user",content:"a cat"}]),expect(o.modalities).toEqual(["image","text"]),expect(o.image_config).toEqual({aspect_ratio:"3:4"}),jsonResponse(chatImageResponse(PNG_DATA_URL))});vi.stubGlobal("fetch",e);const t=createArouterImageProvider(),a=await t.generateImage({provider:"arouter",model:"gemini-2.5-flash-image",prompt:"a cat",aspectRatio:"3:4",cfg:{models:{providers:{arouter:{baseUrl:"https://api.arouter.ai/v1"}}}}});expect(a.images).toHaveLength(1),expect(a.images[0].mimeType).toBe("image/png"),expect(a.images[0].buffer.equals(PNG_BYTES)).toBe(!0),expect(a.images[0].fileName).toBe("image-1.png"),expect(a.model).toBe("gemini-2.5-flash-image"),expect(a.metadata).toEqual({usage:{cost:.01}}),expect(e).toHaveBeenCalledTimes(1)}),it("forwards resolution as image_config.image_size",async()=>{const e=vi.fn(async(e,t)=>{const a=JSON.parse(String(t?.body));return expect(a.image_config).toEqual({aspect_ratio:"3:4",image_size:"2K"}),jsonResponse(chatImageResponse(PNG_DATA_URL))});vi.stubGlobal("fetch",e);const t=createArouterImageProvider();await t.generateImage({provider:"arouter",model:"gemini-2.5-flash-image",prompt:"a cat",aspectRatio:"3:4",resolution:"2K"}),expect(e).toHaveBeenCalledTimes(1)}),it("omits image_config when neither aspectRatio nor resolution is set",async()=>{const e=vi.fn(async(e,t)=>{const a=JSON.parse(String(t?.body));return expect("image_config"in a).toBe(!1),jsonResponse(chatImageResponse(PNG_DATA_URL))});vi.stubGlobal("fetch",e);const t=createArouterImageProvider();await t.generateImage({provider:"arouter",model:"gemini-2.5-flash-image",prompt:"a cat"}),expect(e).toHaveBeenCalledTimes(1)}),it("builds multimodal content when input images are provided",async()=>{const e=vi.fn(async(e,t)=>{const a=JSON.parse(String(t?.body));return expect(a.messages[0].content).toEqual([{type:"text",text:"make it blue"},{type:"image_url",image_url:{url:`data:image/png;base64,${PNG_BYTES.toString("base64")}`}}]),jsonResponse(chatImageResponse(PNG_DATA_URL))});vi.stubGlobal("fetch",e);const t=createArouterImageProvider();await t.generateImage({provider:"arouter",model:"gemini-2.5-flash-image",prompt:"make it blue",inputImages:[{buffer:PNG_BYTES,mimeType:"image/png"}]}),expect(e).toHaveBeenCalledTimes(1)}),it("parses images from message.images[].image_url.url data URL into a Buffer",async()=>{vi.stubGlobal("fetch",vi.fn(async()=>jsonResponse(chatImageResponse(PNG_DATA_URL))));const e=createArouterImageProvider(),t=await e.generateImage({provider:"arouter",model:"gemini-2.5-flash-image",prompt:"p"});expect(t.images).toHaveLength(1),expect(t.images[0].buffer.equals(PNG_BYTES)).toBe(!0)}),it("falls back to data URLs embedded in message.content",async()=>{vi.stubGlobal("fetch",vi.fn(async()=>jsonResponse({choices:[{message:{role:"assistant",content:`here you go ${PNG_DATA_URL}`}}]})));const e=createArouterImageProvider(),t=await e.generateImage({provider:"arouter",model:"gemini-2.5-flash-image",prompt:"p"});expect(t.images).toHaveLength(1),expect(t.images[0].buffer.equals(PNG_BYTES)).toBe(!0)}),it("downloads http(s) image URLs via arouterFetch as a fallback",async()=>{const e=vi.fn(async e=>"https://cdn.arouter.ai/img/1.png"===e?new Response(PNG_BYTES,{status:200}):jsonResponse(chatImageResponse("https://cdn.arouter.ai/img/1.png")));vi.stubGlobal("fetch",e);const t=createArouterImageProvider(),a=await t.generateImage({provider:"arouter",model:"gemini-2.5-flash-image",prompt:"p"});expect(a.images).toHaveLength(1),expect(a.images[0].buffer.equals(PNG_BYTES)).toBe(!0),expect(e).toHaveBeenCalledTimes(2)}),it("throws a descriptive error on non-2xx",async()=>{vi.stubGlobal("fetch",vi.fn(async()=>new Response("bad model",{status:400})));const e=createArouterImageProvider();await expect(e.generateImage({provider:"arouter",model:"x",prompt:"p"})).rejects.toThrow(/ARouter image generation failed \(400\)/)}),it("throws when no image data is returned",async()=>{vi.stubGlobal("fetch",vi.fn(async()=>jsonResponse({choices:[{message:{role:"assistant",content:""}}]})));const e=createArouterImageProvider();await expect(e.generateImage({provider:"arouter",model:"x",prompt:"p"})).rejects.toThrow(/missing image data/)})});
@@ -1 +1 @@
1
- import{access,constants,readFile,stat}from"node:fs/promises";import{basename,extname}from"node:path";import{agentFetch}from"../agent-auth.js";const DEFAULT_TIMEOUT_MS=3e4;let _deps=null;export function setUploadDeps(e){_deps=e}export function getUploadDeps(){return _deps}export async function uploadLocalFile(e,t,a,s){await access(e,constants.R_OK);if(!(await stat(e)).isFile())throw new Error("not a regular file");const n=basename(e),i=s?.fileNameOverride??n,r=inferMimeType(i),o=await readFile(e),c=AbortSignal.timeout(s?.timeoutMs??3e4),p=s?.signal?AbortSignal.any([s.signal,c]):c,l=`${t}/api/v1/agent/files`,m=await agentFetch(l+"/presign",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fileName:i,mimeType:r}),signal:p});if(!m.ok){const e=await m.text().catch(()=>m.statusText);throw new Error(`presign ${m.status}: ${e}`)}const u=await m.json(),f=await fetch(u.uploadUrl,{method:"PUT",headers:{"Content-Type":r},body:new Blob([o],{type:r}),signal:p});if(!f.ok)throw new Error(`S3 PUT failed: ${f.status}`);const g=await agentFetch(l+"/confirm",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fileId:u.fileId}),signal:p});if(!g.ok){const e=await g.text().catch(()=>g.statusText);throw new Error(`confirm ${g.status}: ${e}`)}const d=await g.json();return{fileId:d.fileId,uri:`beeos-file://${d.fileId}`,name:d.fileName||i,mimeType:d.mimeType||r,size:d.size||o.byteLength}}function inferMimeType(e){switch(extname(e).toLowerCase()){case".txt":return"text/plain";case".md":return"text/markdown";case".json":return"application/json";case".html":return"text/html";case".css":return"text/css";case".js":case".mjs":case".cjs":return"text/javascript";case".ts":return"text/x.typescript";case".tsx":case".jsx":return"text/jsx";case".xml":return"application/xml";case".yaml":case".yml":return"application/yaml";case".toml":return"application/toml";case".pdf":return"application/pdf";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".webp":return"image/webp";case".svg":return"image/svg+xml";case".csv":return"text/csv";case".zip":return"application/zip";case".tar":return"application/x-tar";case".gz":case".tgz":return"application/gzip";default:return"application/octet-stream"}}
1
+ import{access,constants,readFile,stat}from"node:fs/promises";import{basename,extname}from"node:path";import{agentFetch}from"../agent-auth.js";const DEFAULT_TIMEOUT_MS=3e4;let _deps=null;export function setUploadDeps(e){_deps=e}export function getUploadDeps(){return _deps}export async function uploadLocalFile(e,t,a,n){await access(e,constants.R_OK);if(!(await stat(e)).isFile())throw new Error("not a regular file");const r=basename(e),s=n?.fileNameOverride??r,i=inferMimeType(s),o=await readFile(e),c=AbortSignal.timeout(n?.timeoutMs??3e4),p=n?.signal?AbortSignal.any([n.signal,c]):c,m=`${t}/api/v1/agent/files`,l=await agentFetch(m+"/presign",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fileName:s,mimeType:i}),signal:p});if(!l.ok){const e=await l.text().catch(()=>l.statusText);throw new Error(`presign ${l.status}: ${e}`)}const u=await l.json(),d=await fetch(u.uploadUrl,{method:"PUT",headers:{"Content-Type":i},body:new Blob([o],{type:i}),signal:p});if(!d.ok)throw new Error(`S3 PUT failed: ${d.status}`);const f=await agentFetch(m+"/confirm",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fileId:u.fileId}),signal:p});if(!f.ok){const e=await f.text().catch(()=>f.statusText);throw new Error(`confirm ${f.status}: ${e}`)}const g=await f.json();return{fileId:g.fileId,uri:`beeos-file://${g.fileId}`,name:g.fileName||s,mimeType:g.mimeType||i,size:g.size||o.byteLength}}function inferMimeType(e){switch(extname(e).toLowerCase()){case".txt":return"text/plain";case".md":return"text/markdown";case".json":return"application/json";case".html":return"text/html";case".css":return"text/css";case".js":case".mjs":case".cjs":return"text/javascript";case".ts":return"text/x.typescript";case".tsx":case".jsx":return"text/jsx";case".xml":return"application/xml";case".yaml":case".yml":return"application/yaml";case".toml":return"application/toml";case".pdf":return"application/pdf";case".doc":return"application/msword";case".docx":return"application/vnd.openxmlformats-officedocument.wordprocessingml.document";case".xls":return"application/vnd.ms-excel";case".xlsx":return"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";case".ppt":return"application/vnd.ms-powerpoint";case".pptx":return"application/vnd.openxmlformats-officedocument.presentationml.presentation";case".rtf":return"application/rtf";case".odt":return"application/vnd.oasis.opendocument.text";case".ods":return"application/vnd.oasis.opendocument.spreadsheet";case".odp":return"application/vnd.oasis.opendocument.presentation";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".webp":return"image/webp";case".svg":return"image/svg+xml";case".mp4":return"video/mp4";case".mov":return"video/quicktime";case".webm":return"video/webm";case".mkv":return"video/x-matroska";case".avi":return"video/x-msvideo";case".m4v":return"video/x-m4v";case".mp3":return"audio/mpeg";case".wav":return"audio/wav";case".flac":return"audio/flac";case".aac":return"audio/aac";case".ogg":return"audio/ogg";case".m4a":return"audio/mp4";case".csv":return"text/csv";case".zip":return"application/zip";case".tar":return"application/x-tar";case".gz":case".tgz":return"application/gzip";default:return"application/octet-stream"}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.5.37",
3
+ "version": "0.5.39",
4
4
  "description": "OpenClaw plugin that bridges the BeeOS platform with the local OpenClaw Gateway, providing file transfer, ACP bridge, and observability.",
5
5
  "license": "MIT",
6
6
  "author": "BeeOS <dev@beeos.ai>",