guanghu-world-entry/skills/guanghu-feishu-glp/contract.mjs

29 lines
4.4 KiB
JavaScript

import crypto from 'node:crypto';
export const ZERO='GLW-2.0-BRAIN-BINGSHUO-MASTER-0001', CHANNEL='ICE-CH-ZC001';
export const canonical=x=>Array.isArray(x)?'['+x.map(canonical).join(',')+']':x&&typeof x==='object'?'{'+Object.keys(x).sort().map(k=>JSON.stringify(k)+':'+canonical(x[k])).join(',')+'}':JSON.stringify(x);
export const hash=x=>crypto.createHash('sha256').update(typeof x==='string'?x:canonical(x)).digest('hex');
export function seal(m){const c=structuredClone(m);c.integrity={checksum:'',signature:''};c.integrity.checksum=hash(c);return {glp_message:c};}
export function request(question,{type='REQUEST',id=crypto.randomUUID(),sender='ICE-P-X1016',session,task,now=Date.now()}={}){
return seal({protocol:'GLP/1.0',message_id:id,message_type:type,created_at:new Date(now).toISOString(),sender:{object_id:sender,object_type:'persona',world_path:'glw://GLW-2.0-0001/persona/'+sender},receiver:{object_id:CHANNEL,object_type:'channel',routing_mode:'direct'},context:{conversation_id:session,parent_message_id:'',relation_id:'',task_id:task,hldp_anchor:''},payload:{language:'zh-CN',content_type:'text',content:question,attachments:[]},control:{priority:'normal',ack_required:true,receipt_required:true,expires_at:new Date(now+300000).toISOString(),retry_policy:'safe'},integrity:{}});
}
const need=(v,c)=>{if(!v)throw Error(c);};
function keys(o,list){need(o&&typeof o==='object'&&!Array.isArray(o)&&Object.keys(o).length===list.length&&list.every(k=>Object.hasOwn(o,k)),'GLP_FIELDS_INVALID');}
export function validate(input,client,now=Date.now()){
keys(input,['glp_message']);const m=input.glp_message;
keys(m,['protocol','message_id','message_type','created_at','sender','receiver','context','payload','control','integrity']);
need(m.protocol==='GLP/1.0'&&['REQUEST','SYNC'].includes(m.message_type),'GLP_TYPE_UNSUPPORTED');
need(typeof m.message_id==='string'&&/^[-A-Za-z0-9._:]{1,160}$/.test(m.message_id),'GLP_ID_INVALID');
keys(m.sender,['object_id','object_type','world_path']);need(m.sender.object_type==='persona'&&client.personas?.includes(m.sender.object_id),'GLP_SENDER_OUTSIDE_SCOPE');
need(m.sender.world_path==='glw://GLW-2.0-0001/persona/'+m.sender.object_id,'GLP_WORLD_INVALID');
keys(m.receiver,['object_id','object_type','routing_mode']);need(m.receiver.object_id===CHANNEL&&m.receiver.object_type==='channel'&&m.receiver.routing_mode==='direct','GLP_TARGET_INVALID');
keys(m.context,['conversation_id','parent_message_id','relation_id','task_id','hldp_anchor']);
need(Object.values(m.context).every(x=>typeof x==='string'&&x.length<=300)&&m.context.conversation_id.trim()&&m.context.task_id.trim(),'GLP_CONTEXT_REQUIRED');
keys(m.payload,['language','content_type','content','attachments']);need(m.payload.language==='zh-CN'&&m.payload.content_type==='text'&&typeof m.payload.content==='string'&&m.payload.content.trim()&&Buffer.byteLength(m.payload.content)<=12000&&Array.isArray(m.payload.attachments)&&m.payload.attachments.length===0,'GLP_PAYLOAD_INVALID');
keys(m.control,['priority','ack_required','receipt_required','expires_at','retry_policy']);
const created=Date.parse(m.created_at),expires=Date.parse(m.control.expires_at);
need(Number.isFinite(created)&&Number.isFinite(expires)&&created<=now+30000&&expires>now&&expires>created&&expires-created<=300000,'GLP_EXPIRED_OR_TIME_INVALID');
need(['normal','low'].includes(m.control.priority)&&m.control.ack_required===true&&typeof m.control.receipt_required==='boolean'&&['none','safe'].includes(m.control.retry_policy),'GLP_CONTROL_UNSUPPORTED');
keys(m.integrity,['checksum','signature']);need(m.integrity.signature===''&&/^[a-f0-9]{64}$/.test(m.integrity.checksum),'GLP_INTEGRITY_INVALID');
const clean=structuredClone(m);clean.integrity.checksum='';need(hash(clean)===m.integrity.checksum,'GLP_CHECKSUM_MISMATCH');return m;
}
export function reply(m,type,content){return seal({protocol:'GLP/1.0',message_id:crypto.randomUUID(),message_type:type,created_at:new Date().toISOString(),sender:{object_id:ZERO,object_type:'system',world_path:'glw://GLW-2.0-0001/channel/'+CHANNEL},receiver:{object_id:m.sender.object_id,object_type:m.sender.object_type,routing_mode:'direct'},context:{...m.context,parent_message_id:m.message_id},payload:{language:'zh-CN',content_type:'state',content,attachments:[]},control:{priority:'normal',ack_required:false,receipt_required:false,expires_at:m.control.expires_at,retry_policy:'none'},integrity:{}}).glp_message;}