@roll-agent/runtime 0.15.0 → 0.15.1
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
|
-
const t="[图像内容已随后作为用户消息提供]",e="以下图像来自工具 ",o="[历史工具图像已省略]",n=2;function
|
|
1
|
+
const t="[图像内容已随后作为用户消息提供]",e="以下图像来自工具 ",o="[历史工具图像已省略]",n=2,r="rollRuntime",s="relocatedToolImages";function a(t){return"object"==typeof t&&null!==t}function i(t){if(!a(t)||"file"!==t.type)return!1;const e=t.mediaType;if("string"!=typeof e||!e.startsWith("image/"))return!1;const o=t.data;return a(o)&&"data"===o.type&&"string"==typeof o.data}export function relocateToolImagesToUserMessages(o){const n=[];for(const u of o){if("tool"!==u.role||!Array.isArray(u.content)){n.push(u);continue}const o=[],l=u.content.map(e=>{if(!a(e)||"tool-result"!==e.type)return e;const n=e.output;if(!a(n)||"content"!==n.type||!Array.isArray(n.value))return e;const r=[];let s=0;for(const t of n.value)i(t)?(o.push({part:t,toolName:"string"==typeof e.toolName?e.toolName:"tool"}),s+=1):r.push(t);return 0===s?e:(r.push({type:"text",text:t}),{...e,output:{...n,value:r}})});if(n.push({...u,content:l}),o.length>0){const t=o[0]?.toolName??"tool";n.push({role:"user",content:[{type:"text",text:`${e}${t} 的返回结果:`},...o.map(({part:t})=>({type:"file",data:t.data.data,mediaType:t.mediaType}))],providerOptions:{[r]:{[s]:!0}}})}}return l(n)}function u(t){if("user"!==t.role)return!1;const e=t.providerOptions?.[r];return a(e)&&!0===e[s]}function l(t){const e=t.flatMap((t,e)=>u(t)?[e]:[]),n=e.length-2;if(n<=0)return t;for(const r of e.slice(0,n)){const e=t[r];if(void 0===e)continue;const n=e.content,s=n.filter(t=>!(a(t)&&"file"===t.type));s.length!==n.length&&(t[r]={...e,content:[...s,{type:"text",text:o}]})}return t}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e,randomUUID as t}from"node:crypto";import{appendFileSync as a,lstatSync as s,mkdirSync as r,readdirSync as i,readFileSync as c,rmSync as d,writeFileSync as n}from"node:fs";import{basename as o,extname as m,isAbsolute as h,join as u}from"node:path";import{attachmentIdSchema as l,attachmentMediaTypeSchema as g,RUNTIME_ERROR_CODES as
|
|
1
|
+
import{createHash as e,randomUUID as t}from"node:crypto";import{appendFileSync as a,lstatSync as s,mkdirSync as r,readdirSync as i,readFileSync as c,rmSync as d,writeFileSync as n}from"node:fs";import{basename as o,extname as m,isAbsolute as h,join as u}from"node:path";import{attachmentIdSchema as l,attachmentMediaTypeSchema as g,RUNTIME_ERROR_CODES as p,RUNTIME_V14_MAX_ATTACHMENT_BYTES as f,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as y,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as k,SUPPORTED_ATTACHMENT_MEDIA_TYPES as x}from"@roll-agent/protocol";const I=6e5,S=18e5,T={"image/png":["png"],"image/jpeg":["jpg","jpeg"],"image/gif":["gif"],"image/webp":["webp"],"application/pdf":["pdf"],"text/plain":["txt","log","text"],"text/markdown":["md","markdown"]};function v(e){const t=o(e).replace(/[\\/:]+/gu,"").replace(/\p{Cc}+/gu,"").trim();return t.length>0?t:"attachment"}function w(t){return e("sha256").update(t).digest("hex")}function B(e,t){const a=T[t];if(void 0===a)return!1;const s=m(e).slice(1).toLowerCase();return a.includes(s)}function A(e,t){let a;try{a=i(e,{withFileTypes:!0})}catch{return}const r=Date.now()-t;for(const t of a){if(!t.isDirectory())continue;const a=u(e,t.name);try{s(a).mtimeMs<r&&d(a,{recursive:!0,force:!0})}catch{continue}}}export class AttachmentStore{dir;maxAttachmentBytes;maxStagedAttachments;stagedTtlMs;committedTtlMs;now;records=new Map;closed=!1;constructor(e){this.maxAttachmentBytes=e.maxAttachmentBytes??f,this.maxStagedAttachments=e.maxStagedAttachments??k,this.stagedTtlMs=e.stagedTtlMs??I,this.committedTtlMs=e.committedTtlMs??S,this.now=e.now??Date.now,this.dir=u(e.dir,t()),A(e.dir,2*Math.max(this.stagedTtlMs,this.committedTtlMs)),r(this.dir,{recursive:!0})}get limits(){return{maxAttachmentBytes:this.maxAttachmentBytes,maxAttachmentChunkBytes:y,maxStagedAttachments:this.maxStagedAttachments}}stage(e){if(this.sweepExpired(),e.bytes>this.maxAttachmentBytes)return{ok:!1,code:p.attachmentTooLarge,message:`附件超过单文件上限 ${String(this.maxAttachmentBytes)} 字节`};if(!(e.mediaType in T))return{ok:!1,code:p.attachmentTypeUnsupported,message:`不支持的附件类型: ${e.mediaType}(支持 ${x.join(", ")})`};if(!B(e.fileName,e.mediaType))return{ok:!1,code:p.attachmentTypeUnsupported,message:`文件扩展名与 mediaType "${e.mediaType}" 不一致`};if(this.records.size>=this.maxStagedAttachments)return{ok:!1,code:p.attachmentQuotaExceeded,message:`附件数已达上限 ${String(this.maxStagedAttachments)}:附件被 turn.start 引用后不会自动释放,请调用 attachment.release 释放不再需要的附件,或等待 TTL 回收`,retryable:!0};const a=l.parse(t()),s={attachmentId:a,threadId:e.threadId,fileName:e.fileName,displayName:v(e.fileName),mediaType:e.mediaType,declaredBytes:e.bytes,declaredSha256:e.sha256,source:e.source,createdAtMs:this.now(),state:"staged",receivedBytes:0,nextSequence:0,committedAtMs:void 0};if("chunks"===e.source)return n(this.filePath(a),Buffer.alloc(0)),this.records.set(a,s),{ok:!0,attachmentId:a,state:"staged"};const r=this.loadLocalFile(e.sourcePath??"",e.mediaType,e.bytes,e.sha256);return r.ok?(n(this.filePath(a),r.data),s.state="committed",s.receivedBytes=r.data.length,s.committedAtMs=this.now(),this.records.set(a,s),{ok:!0,attachmentId:a,state:"committed",descriptor:this.describe(s)}):r}appendChunk(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);if(void 0===t)return this.notFound(e.attachmentId);if("staged"!==t.state||"chunks"!==t.source)return{ok:!1,code:p.invalidParams,message:"附件不处于 chunk 上传状态"};if(e.sequence!==t.nextSequence)return{ok:!1,code:p.invalidParams,message:`chunk sequence 不连续:期望 ${String(t.nextSequence)},收到 ${String(e.sequence)}`};const s=Buffer.from(e.dataBase64,"base64");return 0===s.length||s.length>y?{ok:!1,code:p.invalidParams,message:`chunk 大小必须在 1..${String(y)} 字节内`}:t.receivedBytes+s.length>t.declaredBytes?(this.remove(t.attachmentId),{ok:!1,code:p.attachmentTooLarge,message:"累计上传字节数超过申报大小,附件已回收"}):(a(this.filePath(t.attachmentId),s),t.receivedBytes+=s.length,t.nextSequence+=1,{ok:!0,receivedBytes:t.receivedBytes,nextSequence:t.nextSequence})}commit(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);if(void 0===t)return this.notFound(e.attachmentId);if("committed"===t.state)return{ok:!0,descriptor:this.describe(t)};if(t.receivedBytes!==t.declaredBytes)return{ok:!1,code:p.attachmentUploadIncomplete,message:`上传未完成:已收 ${String(t.receivedBytes)} 字节,申报 ${String(t.declaredBytes)} 字节`};return w(c(this.filePath(t.attachmentId)))!==t.declaredSha256?(this.remove(t.attachmentId),{ok:!1,code:p.attachmentHashMismatch,message:"附件内容 sha256 与申报值不匹配,附件已回收"}):(t.state="committed",t.committedAtMs=this.now(),{ok:!0,descriptor:this.describe(t)})}release(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);return void 0===t?{ok:!0,released:!1}:(this.remove(t.attachmentId),{ok:!0,released:!0})}readCommitted(e){this.sweepExpired();const t=this.findOwned(e.threadId,e.attachmentId);if(void 0===t)return this.notFound(e.attachmentId);if("committed"!==t.state)return{ok:!1,code:p.attachmentNotCommitted,message:`附件 "${e.attachmentId}" 尚未 commit`};let a;try{a=c(this.filePath(t.attachmentId))}catch{return this.remove(t.attachmentId),{ok:!1,code:p.internalError,message:"附件数据文件缺失,附件已回收"}}return{ok:!0,descriptor:this.describe(t),dataBase64:a.toString("base64")}}releaseThread(e){for(const t of[...this.records.values()])t.threadId===e&&this.remove(t.attachmentId)}close(){this.closed||(this.closed=!0,this.records.clear(),d(this.dir,{recursive:!0,force:!0}))}describe(e){return{attachmentId:e.attachmentId,fileName:e.fileName,displayName:e.displayName,mediaType:g.parse(e.mediaType),bytes:e.declaredBytes,sha256:e.declaredSha256,source:e.source,createdAt:new Date(e.createdAtMs).toISOString()}}loadLocalFile(e,t,a,r){if(!h(e))return{ok:!1,code:p.attachmentPathRejected,message:"sourcePath 必须是绝对路径"};if(!B(e,t))return{ok:!1,code:p.attachmentTypeUnsupported,message:`sourcePath 扩展名与 mediaType "${t}" 不一致`};let i;try{i=s(e)}catch{return{ok:!1,code:p.attachmentPathRejected,message:"sourcePath 不存在或不可读"}}if(i.isSymbolicLink())return{ok:!1,code:p.attachmentPathRejected,message:"sourcePath 不允许是符号链接"};if(!i.isFile())return{ok:!1,code:p.attachmentPathRejected,message:"sourcePath 不是普通文件"};if(i.size>this.maxAttachmentBytes)return{ok:!1,code:p.attachmentTooLarge,message:`文件实际大小 ${String(i.size)} 超过单文件上限 ${String(this.maxAttachmentBytes)} 字节`};if(i.size!==a)return{ok:!1,code:p.invalidParams,message:`文件实际大小 ${String(i.size)} 与申报 ${String(a)} 不一致`};const d=c(e);return w(d)!==r?{ok:!1,code:p.attachmentHashMismatch,message:"文件内容 sha256 与申报值不匹配"}:{ok:!0,data:d}}findOwned(e,t){const a=this.records.get(t);if(void 0!==a&&a.threadId===e)return a}notFound(e){return{ok:!1,code:p.attachmentNotFound,message:`附件 "${e}" 不存在、已过期或不属于该 Thread`}}remove(e){this.records.delete(e),d(this.filePath(e),{force:!0})}sweepExpired(){const e=this.now();for(const t of[...this.records.values()]){const a="staged"===t.state?this.stagedTtlMs:this.committedTtlMs;e-(t.committedAtMs??t.createdAtMs)>a&&this.remove(t.attachmentId)}}filePath(e){return u(this.dir,`${e}.bin`)}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/runtime",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^22.0.0",
|
|
37
|
-
"@roll-agent/core": "0.30.
|
|
37
|
+
"@roll-agent/core": "0.30.1"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/obfuscate.mjs",
|