@nickyzj2023/utils 1.0.78 → 1.0.80

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.
package/dist/index.d.mts CHANGED
@@ -211,7 +211,7 @@ declare namespace Compact {
211
211
  type ReplacerOfToolResultContent = (content: AI.Message["content"]) => AI.Message["content"];
212
212
  type ReplacerOfMediaContent = (content: AI.Message["content"]) => AI.Message["content"];
213
213
  type SummarizeOptions = {
214
- /** 不总结最新的X%条消息 */keepPercent: number; /** 用什么模型总结 */
214
+ /** 保留最新的X条消息不做总结 */keepCount: number; /** 用什么模型总结 */
215
215
  model: AI.Model; /** 用于指导大模型如何总结消息的提示词 */
216
216
  systemPrompt: string;
217
217
  };
@@ -221,13 +221,13 @@ declare namespace Compact {
221
221
  /**
222
222
  * 自动优化上下文,类似AI Coding Agent的/compact命令
223
223
  */
224
- declare const compact: (messages: AI.Message[], model: AI.Model, options?: {
224
+ declare const compactMessages: (messages: AI.Message[], model: AI.Model, options?: {
225
225
  /** 提供token消耗情况时,能更准确地判断上下文是否达到阈值 */usage?: ChatCompletions.Usage;
226
226
  /**
227
227
  * 上下文>总上下文*ratio时压缩工具调用结果
228
228
  * @default 0.6
229
229
  */
230
- ratioOfCompactToolResult?: number;
230
+ ratioToCompactToolResult?: number;
231
231
  /**
232
232
  * 如何压缩工具调用结果,例如让其他模型返回精简后的工具结果
233
233
  * @default (content) => "(已被消费)"
@@ -246,14 +246,14 @@ declare const compact: (messages: AI.Message[], model: AI.Model, options?: {
246
246
  /**
247
247
  * 上下文>总上下文*ratio时总结消息
248
248
  * @default 0.8
249
- * @remarks 如果总结成功,会把summarizeOptions.keepPercent(默认0.2(20%))以外的消息压成一条消息;如果总结失败,会采取兜底压缩方法:硬删除summarizeOptions.keepPercent以外的消息
249
+ * @remarks 如果总结成功,会把summarizeOptions.keepCount(默认10)以前的消息压成一条消息;如果总结失败,会采取兜底压缩方法:硬删除summarizeOptions.keepCount以前的消息
250
250
  */
251
251
  ratioToSummarize?: number;
252
252
  /**
253
253
  * 总结消息时的配置项
254
- * @default { keepPercent: 0.2, model: undefined, systemPrompt: "总结历史消息" }
254
+ * @default { keepCount: 10, model: undefined, systemPrompt: "总结历史消息" }
255
255
  */
256
- summarizeOptions?: Compact.SummarizeOptions;
256
+ summarizeOptions?: Partial<Compact.SummarizeOptions>;
257
257
  }) => Promise<void>;
258
258
  //#endregion
259
259
  //#region src/ai/helper.d.ts
@@ -827,6 +827,28 @@ declare const qs: {
827
827
  }) => string;
828
828
  };
829
829
  //#endregion
830
+ //#region src/string/xml.d.ts
831
+ /**
832
+ * 创建XML标签包裹的文本
833
+ * @param tagName 标签名
834
+ * @param content 标签内容
835
+ * @returns `<tagName>\ncontent\n</tagName>`
836
+ * @example
837
+ * createXMLTag("summary", "摘要内容") // "<summary>\n摘要内容\n</summary>"
838
+ */
839
+ declare const createXMLTag: (tagName: string, content: string) => string;
840
+ /**
841
+ * 检测文本中是否含有XML标签
842
+ * @param text 待检测文本
843
+ * @param allowedTags 允许的标签名白名单(不区分大小写),不传时任何XML标签都会返回true
844
+ * @returns 含有不在白名单中的XML标签时返回true
845
+ * @example
846
+ * hasXmlTags("纯文本") // false
847
+ * hasXmlTags("<summary>摘要</summary>", ["summary"]) // false
848
+ * hasXmlTags("<system-reminder>提醒</system-reminder>", ["summary"]) // true
849
+ */
850
+ declare const hasXmlTags: (text: string, allowedTags?: string[]) => boolean;
851
+ //#endregion
830
852
  //#region src/time/debounce.d.ts
831
853
  /**
832
854
  * 防抖:在指定时间内只执行最后一次调用
@@ -913,4 +935,4 @@ declare const sleep: (time?: number) => Promise<unknown>;
913
935
  */
914
936
  declare const throttle: <T extends (...args: any[]) => any>(fn: T, delay?: number) => (this: any, ...args: Parameters<T>) => void;
915
937
  //#endregion
916
- export { AI, CamelToSnake, Capitalize, type ChatCompletions, type Compact, Decapitalize, DeepMapKeys, DeepMapValues, ImageCompressionOptions, LockQueue, LoggerOptions, Primitive, RequestInit, SetTtl, SnakeToCamel, camelToSnake, capitalize, chatCompletions, compact, compactStr, debounce, decapitalize, defineModel, defineTool, estimateTokens, extractErrorMessage, fetcher, getModelName, getRealURL, imageUrlToBase64, isNil, isObject, isPrimitive, logger, loopUntil, mapKeys, mapValues, mergeObjects, omit, omitBy, parseSSE, pick, pickBy, qs, randomInt, sleep, snakeToCamel, throttle, to, withCache };
938
+ export { AI, CamelToSnake, Capitalize, type ChatCompletions, type Compact, Decapitalize, DeepMapKeys, DeepMapValues, ImageCompressionOptions, LockQueue, LoggerOptions, Primitive, RequestInit, SetTtl, SnakeToCamel, camelToSnake, capitalize, chatCompletions, compactMessages, compactStr, createXMLTag, debounce, decapitalize, defineModel, defineTool, estimateTokens, extractErrorMessage, fetcher, getModelName, getRealURL, hasXmlTags, imageUrlToBase64, isNil, isObject, isPrimitive, logger, loopUntil, mapKeys, mapValues, mergeObjects, omit, omitBy, parseSSE, pick, pickBy, qs, randomInt, sleep, snakeToCamel, throttle, to, withCache };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  const e=e=>e==null,t=e=>e?.constructor===Object,n=e=>e==null||typeof e!=`object`&&typeof e!=`function`,r=(e,n)=>Array.isArray(e)?e.map(e=>r(e,n)):t(e)?Object.keys(e).reduce((t,i)=>{let a=n(i),o=e[i];return t[a]=r(o,n),t},{}):e,i=(e,n,r)=>{let{filter:a}=r??{};if(Array.isArray(e)){let o=e.map((e,a)=>t(e)?i(e,n,r):n(e,a));return a?o.filter((e,t)=>a(e,t)):o}return t(e)?Object.keys(e).reduce((o,s)=>{let c=e[s],l;return l=t(c)||Array.isArray(c)?i(c,n,r):n(c,s),(!a||a(l,s))&&(o[s]=l),o},{}):e},a=(e,r)=>{let i={...e};for(let e of Object.keys(r)){let o=i[e],s=r[e];if(n(o)&&n(s)){i[e]=s;continue}if(Array.isArray(o)&&Array.isArray(s)){i[e]=o.concat(s);continue}if(t(o)&&t(s)){i[e]=a(o,s);continue}i[e]=s}return i},o=(e,t)=>{let n={...e};for(let e of t)delete n[e];return n},s=(e,t)=>{let n={};for(let[r,i]of Object.entries(e))t(r,i)||(n[r]=i);return n},c=(e,t)=>t.reduce((t,n)=>(Object.hasOwn(e,n)&&(t[n]=e[n]),t),{}),l=(e,t)=>{let n={};for(let[r,i]of Object.entries(e))t(r,i)&&(n[r]=i);return n},u=e=>e.replace(/_([a-zA-Z])/g,(e,t)=>t.toUpperCase()),d=e=>e.replace(/([A-Z])/g,(e,t)=>`_${t.toLowerCase()}`),f=e=>e.charAt(0).toUpperCase()+e.slice(1),p=e=>e.charAt(0).toLowerCase()+e.slice(1),m=(e=``,t)=>{if(!e)return``;let{maxLength:n=1/0,disableNewLineReplace:r=!1,disableCollapse:i=!1}=t??{},a=e;return i||(a=a.replace(/[\n\t]+/g,`
2
- `)),a=r?a.replace(/\r?\n/g,` `):a.replace(/\r?\n/g,`\\n`),a=a.replace(/\s+/g,` `).trim(),n>0&&a.length>n?`${a.slice(0,n)}...`:a},h=e=>{if(e instanceof Error)return e.message;if(typeof e==`string`)return e;if(t(e)){let t=e.message||e.msg;if(t)return t;for(let t of Object.values(e)){let e=h(t);if(e)return e}}return JSON.stringify(e,null,2)},g={parse:e=>{let t=new URLSearchParams(e),n={};for(let[e,r]of t)Number.isNaN(Number(r))?n[e]=r:n[e]=Number(r);return n},stringify:(e,t)=>{let{addQueryPrefix:n=!1}=t??{},r=new URLSearchParams(e).toString();return r?n?`?${r}`:r:``}},_=(n=``,r={})=>{let i=async(i,o={})=>{let s=new URL(n?`${n}${i}`:i),{params:c,parser:l,...u}=a(r,o);t(c)&&Object.entries(c).forEach(([t,n])=>{e(n)||s.searchParams.append(t,n.toString())}),(t(u.body)||Array.isArray(u.body))&&(u.body=JSON.stringify(u.body),u.headers={...u.headers,"Content-Type":`application/json`});let d=await fetch(s,u);if(!d.ok){let e=d.statusText||await d.text();try{e=h(JSON.parse(e))}catch{}throw Error(e)}return await(l?.(d)??d.json())};return{get:(e,t)=>i(e,{...t,method:`GET`}),post:(e,t,n)=>i(e,{...n,method:`POST`,body:t}),put:(e,t,n)=>i(e,{...n,method:`PUT`,body:t}),delete:(e,t)=>i(e,{...t,method:`DELETE`})}},v=async e=>{try{return[null,await e]}catch(e){return[e,void 0]}},y=async e=>{let[t,n]=await v(fetch(e,{method:`HEAD`,redirect:`manual`}));return t?e:n.headers.get(`location`)||e},b=e=>{let t=new Uint8Array(e),n=``;for(let e=0;e<t.byteLength;e++)n+=String.fromCharCode(t[e]);return btoa(n)},x=async(e,t={})=>{let{quality:n=.92,compressor:r,fetcher:i=fetch}=t;if(!e.startsWith(`http`))throw Error(`图片地址必须以http或https开头`);let a=await i(e);if(!a.ok)throw Error(`获取图片失败: ${a.statusText}`);let o=a.headers.get(`Content-Type`)||`image/jpeg`,s=await a.arrayBuffer();if(o!==`image/jpeg`&&o!==`image/png`)return`data:${o};base64,${b(s)}`;if(r)return await r(s,o,n);if(typeof OffscreenCanvas<`u`){let e=null;try{let t=new Blob([s],{type:o});e=await createImageBitmap(t);let r=new OffscreenCanvas(e.width,e.height),i=r.getContext(`2d`);if(!i)throw Error(`无法获取 OffscreenCanvas context`);return i.drawImage(e,0,0),e.close(),e=null,`data:${o};base64,${b(await(await r.convertToBlob({type:o,quality:n})).arrayBuffer())}`}catch{return e?.close(),`data:${o};base64,${b(s)}`}}return`data:${o};base64,${b(s)}`};async function*S(e){let t=e.body?.getReader();if(!t)return;let n=new TextDecoder,r=``;try{for(;;){let{value:e,done:i}=await t.read();if(i)break;r+=n.decode(e,{stream:!0});let a=r.split(`
2
+ `)),a=r?a.replace(/\r?\n/g,` `):a.replace(/\r?\n/g,`\\n`),a=a.replace(/\s+/g,` `).trim(),n>0&&a.length>n?`${a.slice(0,n)}...`:a},h=e=>{if(e instanceof Error)return e.message;if(typeof e==`string`)return e;if(t(e)){let t=e.message||e.msg;if(t)return t;for(let t of Object.values(e)){let e=h(t);if(e)return e}}return JSON.stringify(e,null,2)},g={parse:e=>{let t=new URLSearchParams(e),n={};for(let[e,r]of t)Number.isNaN(Number(r))?n[e]=r:n[e]=Number(r);return n},stringify:(e,t)=>{let{addQueryPrefix:n=!1}=t??{},r=new URLSearchParams(e).toString();return r?n?`?${r}`:r:``}},_=(e,t)=>`<${e}>\n${t}\n</${e}>`,v=(e,t=[])=>{let n=new Set(t.map(e=>e.toLowerCase()));for(let t of e.matchAll(/<\/?([a-zA-Z][\w-]*)/g))if(!n.has((t[1]??``).toLowerCase()))return!0;return!1},y=(n=``,r={})=>{let i=async(i,o={})=>{let s=new URL(n?`${n}${i}`:i),{params:c,parser:l,...u}=a(r,o);t(c)&&Object.entries(c).forEach(([t,n])=>{e(n)||s.searchParams.append(t,n.toString())}),(t(u.body)||Array.isArray(u.body))&&(u.body=JSON.stringify(u.body),u.headers={...u.headers,"Content-Type":`application/json`});let d=await fetch(s,u);if(!d.ok){let e=d.statusText||await d.text();try{e=h(JSON.parse(e))}catch{}throw Error(e)}return await(l?.(d)??d.json())};return{get:(e,t)=>i(e,{...t,method:`GET`}),post:(e,t,n)=>i(e,{...n,method:`POST`,body:t}),put:(e,t,n)=>i(e,{...n,method:`PUT`,body:t}),delete:(e,t)=>i(e,{...t,method:`DELETE`})}},b=async e=>{try{return[null,await e]}catch(e){return[e,void 0]}},x=async e=>{let[t,n]=await b(fetch(e,{method:`HEAD`,redirect:`manual`}));return t?e:n.headers.get(`location`)||e},S=e=>{let t=new Uint8Array(e),n=``;for(let e=0;e<t.byteLength;e++)n+=String.fromCharCode(t[e]);return btoa(n)},C=async(e,t={})=>{let{quality:n=.92,compressor:r,fetcher:i=fetch}=t;if(!e.startsWith(`http`))throw Error(`图片地址必须以http或https开头`);let a=await i(e);if(!a.ok)throw Error(`获取图片失败: ${a.statusText}`);let o=a.headers.get(`Content-Type`)||`image/jpeg`,s=await a.arrayBuffer();if(o!==`image/jpeg`&&o!==`image/png`)return`data:${o};base64,${S(s)}`;if(r)return await r(s,o,n);if(typeof OffscreenCanvas<`u`){let e=null;try{let t=new Blob([s],{type:o});e=await createImageBitmap(t);let r=new OffscreenCanvas(e.width,e.height),i=r.getContext(`2d`);if(!i)throw Error(`无法获取 OffscreenCanvas context`);return i.drawImage(e,0,0),e.close(),e=null,`data:${o};base64,${S(await(await r.convertToBlob({type:o,quality:n})).arrayBuffer())}`}catch{return e?.close(),`data:${o};base64,${S(s)}`}}return`data:${o};base64,${S(s)}`};async function*w(e){let t=e.body?.getReader();if(!t)return;let n=new TextDecoder,r=``;try{for(;;){let{value:e,done:i}=await t.read();if(i)break;r+=n.decode(e,{stream:!0});let a=r.split(`
3
3
 
4
4
  `);r=a.pop()||``;for(let e of a){let t=e.split(`
5
- `);for(let e of t){if(!e.startsWith(`data:`))continue;let t=e.replace(/^data:\s*/,``).trim();try{yield JSON.parse(t)}catch{yield t}}}}}finally{t.releaseLock()}}const C=e=>({inputs:[`text`],context:128e3,...e}),w=(e,t,n,r)=>{let i=[];return{type:`function`,function:{name:e,description:t,parameters:{type:`object`,properties:l(n,e=>e===`required`?(i.push(e),!1):!0),required:i}},handler:r}},T=async e=>{let t=(await _(e).get(`/v1/models`)).data[0]?.id;if(!t)throw Error(`无法从/models获取模型名称`);return t},E=e=>{if(!e?.length)return 0;let t=new Intl.Segmenter([],{granularity:`word`}),n=e=>{let n=0,r=0;for(let i of t.segment(e))i.isWordLike?n++:r++;return Math.ceil(n*1.5+r/4)};return e.reduce((e,t)=>{let{content:r,tool_calls:i,...a}=t;if(typeof r==`string`)e+=n(r);else for(let t of r)t.type===`text`?e+=n(t.text):e+=1e4;return i&&(e+=n(JSON.stringify(i))),e+=n(JSON.stringify(a)),e},0)},D=e=>[c(e,[`type`,`function`]),c(e,[`handler`])],O=async(e,t,n)=>{let r=t.find(t=>t.function.name===e.function.name)?.handler;if(!r)return`没有找到工具“${e.function.name}”的处理函数`;try{let t=await r(JSON.parse(e.function.arguments),n);return typeof t==`string`?t:JSON.stringify(t)}catch(t){if(t instanceof Error&&t.name===e.function.name)throw t;return`工具“${e.function.name}”处理失败:${h(t)}`}},k=e=>e.reasoning||e.reasoning_content,A=e=>typeof e==`string`?e:e.filter(e=>e.type===`text`).map(e=>e.text).join(`
6
- `),j=e=>{switch(e.type){case`text`:return`text`;case`image_url`:return`image`;case`input_audio`:return`audio`;case`video_url`:return`video`}},M=(e,t)=>{let{inputs:n=[`text`]}=e;for(let e of t){let{content:t}=e;if(typeof t!=`string`&&Array.isArray(t))for(let e of t){let t=j(e);if(!t||!n.includes(t))return t||`unknown`}}return``},N=async(e,t,n=[],r)=>{let{onToolHandled:i}=r??{};for(;;){let{choices:r,usage:a,...o}=await e.post(`/chat/completions`,{}),{message:s}=r?.[0]??{};if(!s)throw Error(`模型没有回复任何内容`);t.push(s);let{content:c=``,tool_calls:l=[],...u}=s,d=k(s);if(l.length>0&&n.length>0){for(let e of l){let r=await O(e,n,{messages:t});t.push({role:`tool`,content:r,tool_call_id:e.id}),i?.(e.function.name,e.function.arguments,r)}continue}return{content:A(c),reasoning:d,usage:a,...o,...u}}},P=async function*(e,t,n=[],r){let{onToolHandled:i}=r??{};for(;;){let r=new Map,a=``,o=null,s,c=await e.post(`/chat/completions`,{stream:!0},{parser:async e=>e});for await(let e of S(c)){e.usage&&(s=e.usage);let t=e.choices?.[0];if(!t)continue;let{delta:n}=t,{content:i,tool_calls:c}=n,l=k(n);if(l&&(yield{reasoning:l}),i&&(a+=i,yield{content:i}),c)for(let e of c){let t=r.get(e.index)??{id:``,type:`function`,function:{name:``,arguments:``}};e.id&&(t.id=e.id),e.function?.name&&(t.function.name+=e.function.name),e.function?.arguments&&(t.function.arguments+=e.function.arguments),r.set(e.index,t)}t.finish_reason&&(o=t.finish_reason)}let l=Array.from(r.values());if(o===`tool_calls`&&l.length>0&&n.length>0){t.push({role:`assistant`,content:a,tool_calls:l});for(let e of l){let r=await O(e,n,{messages:t});t.push({role:`tool`,content:r,tool_call_id:e.id}),i?.(e.function.name,e.function.arguments,r)}continue}t.push({role:`assistant`,content:a}),s&&(yield{usage:s});break}};async function F(e,t,n){let{baseUrl:r,apiKey:i=``,model:a,customBody:o,inputs:s,...c}=e,{stream:l,tools:u=[],...d}=n??{},f=M(e,t);if(f)throw Error(`当前上下文含有模型不支持的输入类型:${f}`);let p=_(r,{headers:{Authorization:`Bearer ${i}`},body:{model:a??await T(r),messages:t,tools:u?.map(e=>D(e)[0]),...o}});return(l?P:N)(p,t,u,{...d,...c})}function I(e={},t=0){let{withTime:n=!0,withFileName:r=!0}=e;return(...e)=>{let i=[];if(n&&i.push(`[${new Date().toLocaleTimeString()}]`),r){let{stack:e}=Error(),n=(e?.split(`
7
- `)[2+t]?.trim())?.match(/^at\s+(?:.*?\s*\()?(.*?):(\d+)(?::(\d+))?\)?/);if(n?.[1]){let e=n[1].split(/[/\\]/).pop();i.push(`[${e}:${n[2]}]`)}}i.push(...e),console.log(...i)}}function L(e,...n){if(t(e)&&(`withTime`in e||`withFileName`in e))return I(e,0);I({},1)(e,...n)}const R=e=>e?.role===`assistant`&&Array.isArray(e.tool_calls),z=(e,t)=>{for(t=Math.min(t,e.length),R(e[t-1])&&e[t]?.role!==`tool`&&t--;e[t]?.role===`tool`&&!R(e[t-1]);)t++;return t},B=(e,t)=>{let n=e.reduce((e,n)=>(n.role===`tool`&&(n.content=t(n.content),e++),e),0);n>0&&L(`软删除了${n}条工具调用结果消息`)},V=(e,t)=>{let n=[`image_url`,`input_audio`,`video_url`],r=e.reduce((e,r)=>(Array.isArray(r.content)&&r.content.some(e=>n.includes(e.type))&&(r.content=t(r.content),e++),e),0);r>0&&L(`软删除了${r}条旧图片/音频/视频消息`)},H=async(e,t)=>{let{model:n,keepPercent:r,systemPrompt:i}=t??{},a=e.findIndex(e=>e.role===`user`),o=Math.ceil(e.length*r),s=e.length-o;if(s<=a)return L(`消息太少,无需总结`),!1;s=z(e,s);let c=e.slice(a,s);c.push({role:`system`,content:i},{role:`user`,content:`开始总结上下文`});let[l,u]=await v(F(n,c));if(l)return L(`总结失败:${l.message}`),!1;e.splice(a,s-a,{role:`user`,content:`<summary>\n${u.content}\n</summary>`})},U=(e,t)=>{let n=e.findIndex(e=>e.role===`user`),r=Math.ceil(e.length*t),i=e.length-r;if(i<=n){L(`消息太少,无需硬删除`);return}i=z(e,i);let a=i-n;e.splice(n,a),L(`硬删除了${a}条较早的消息`)},W=async(e,t,n)=>{let{usage:r,ratioOfCompactToolResult:i=.6,replacerOfToolResultContent:a=()=>`已被消费`,ratioToCompactMedia:o=.7,replacerOfMediaContent:s=()=>`已被丢弃`,ratioToSummarize:c=.8,summarizeOptions:l}=n??{},u=t?.context??128e3,d=r?.total_tokens??E(e);if(d>u*i&&B(e,a),d>u*o&&V(e,s),d>u*c){let{keepPercent:n=.2,systemPrompt:r=`总结历史消息`}=l??{},[i]=await v(H(e,{model:t,keepPercent:n,systemPrompt:r}));if(!i)return;U(e,n)}},G=async(e,t)=>{let{maxRetries:n=5,shouldStop:r}=t??{},i;for(let t=0;t<n;t++)if(i=await e(t),r?.(i)===!0)return i;if(!r)return i;throw Error(`超过了最大循环次数(${n})且未满足停止执行条件`)},K=(e,t=-1)=>{let n=new Map,r=(...r)=>{let i=JSON.stringify(r),a=Date.now(),o=n.get(i);if(o&&a<o.expiresAt)return o.value;let s=t===-1?1/0:a+t*1e3,c=e.apply({setTtl:e=>{s=a+e*1e3}},r);if(c instanceof Promise){let e=c.then(e=>(n.set(i,{value:e,expiresAt:s}),e));return n.set(i,{value:e,expiresAt:s}),e}return n.set(i,{value:c,expiresAt:s}),c};return r.clear=()=>n.clear(),r.updateTtl=e=>{t=e;let r=Date.now(),i=r+e*1e3;for(let[e,t]of n.entries())t.expiresAt>r&&(t.expiresAt=i,n.set(e,t))},r},q=(e,t)=>Math.floor(Math.random()*(t-e+1))+e,J=(e,t=300)=>{let n=null;return(...r)=>{n&&clearTimeout(n),n=setTimeout(()=>{e(...r)},t)}};var Y=class{queue;constructor(){this.queue=Promise.resolve()}waitInQueue(){let e,t=new Promise(t=>{e=t}),n=this.queue.then(()=>e);return this.queue=t,n}};const X=async(e=150)=>new Promise(t=>{setTimeout(t,e)}),Z=(e,t=300)=>{let n=null;return function(...r){n||=setTimeout(()=>{n=null,e.apply(this,r)},t)}};export{Y as LockQueue,d as camelToSnake,f as capitalize,F as chatCompletions,W as compact,m as compactStr,J as debounce,p as decapitalize,C as defineModel,w as defineTool,E as estimateTokens,h as extractErrorMessage,_ as fetcher,T as getModelName,y as getRealURL,x as imageUrlToBase64,e as isNil,t as isObject,n as isPrimitive,L as logger,G as loopUntil,r as mapKeys,i as mapValues,a as mergeObjects,o as omit,s as omitBy,S as parseSSE,c as pick,l as pickBy,g as qs,q as randomInt,X as sleep,u as snakeToCamel,Z as throttle,v as to,K as withCache};
5
+ `);for(let e of t){if(!e.startsWith(`data:`))continue;let t=e.replace(/^data:\s*/,``).trim();try{yield JSON.parse(t)}catch{yield t}}}}}finally{t.releaseLock()}}const T=e=>({inputs:[`text`],context:128e3,...e}),E=(e,t,n,r)=>{let i=[];return{type:`function`,function:{name:e,description:t,parameters:{type:`object`,properties:l(n,e=>e===`required`?(i.push(e),!1):!0),required:i}},handler:r}},D=async e=>{let t=(await y(e).get(`/v1/models`)).data[0]?.id;if(!t)throw Error(`无法从/models获取模型名称`);return t},O=e=>{if(!e?.length)return 0;let t=new Intl.Segmenter([],{granularity:`word`}),n=e=>{let n=0,r=0;for(let i of t.segment(e))i.isWordLike?n++:r++;return Math.ceil(n*1.5+r/4)};return e.reduce((e,t)=>{let{content:r,tool_calls:i,...a}=t;if(typeof r==`string`)e+=n(r);else for(let t of r)t.type===`text`?e+=n(t.text):e+=1e4;return i&&(e+=n(JSON.stringify(i))),e+=n(JSON.stringify(a)),e},0)},k=e=>[c(e,[`type`,`function`]),c(e,[`handler`])],A=async(e,t,n)=>{let r=t.find(t=>t.function.name===e.function.name)?.handler;if(!r)return`没有找到工具“${e.function.name}”的处理函数`;try{let t=await r(JSON.parse(e.function.arguments),n);return typeof t==`string`?t:JSON.stringify(t)}catch(t){if(t instanceof Error&&t.name===e.function.name)throw t;return`工具“${e.function.name}”处理失败:${h(t)}`}},j=e=>e.reasoning||e.reasoning_content,M=e=>typeof e==`string`?e:e.filter(e=>e.type===`text`).map(e=>e.text).join(`
6
+ `),N=e=>{switch(e.type){case`text`:return`text`;case`image_url`:return`image`;case`input_audio`:return`audio`;case`video_url`:return`video`}},P=(e,t)=>{let{inputs:n=[`text`]}=e;for(let e of t){let{content:t}=e;if(typeof t!=`string`&&Array.isArray(t))for(let e of t){let t=N(e);if(!t||!n.includes(t))return t||`unknown`}}return``},F=async(e,t,n=[],r)=>{let{onToolHandled:i}=r??{};for(;;){let{choices:r,usage:a,...o}=await e.post(`/chat/completions`,{}),{message:s}=r?.[0]??{};if(!s)throw Error(`模型没有回复任何内容`);t.push(s);let{content:c=``,tool_calls:l=[],...u}=s,d=j(s);if(l.length>0&&n.length>0){for(let e of l){let r=await A(e,n,{messages:t});t.push({role:`tool`,content:r,tool_call_id:e.id}),i?.(e.function.name,e.function.arguments,r)}continue}return{content:M(c),reasoning:d,usage:a,...o,...u}}},I=async function*(e,t,n=[],r){let{onToolHandled:i}=r??{};for(;;){let r=new Map,a=``,o=null,s,c=await e.post(`/chat/completions`,{stream:!0},{parser:async e=>e});for await(let e of w(c)){e.usage&&(s=e.usage);let t=e.choices?.[0];if(!t)continue;let{delta:n}=t,{content:i,tool_calls:c}=n,l=j(n);if(l&&(yield{reasoning:l}),i&&(a+=i,yield{content:i}),c)for(let e of c){let t=r.get(e.index)??{id:``,type:`function`,function:{name:``,arguments:``}};e.id&&(t.id=e.id),e.function?.name&&(t.function.name+=e.function.name),e.function?.arguments&&(t.function.arguments+=e.function.arguments),r.set(e.index,t)}t.finish_reason&&(o=t.finish_reason)}let l=Array.from(r.values());if(o===`tool_calls`&&l.length>0&&n.length>0){t.push({role:`assistant`,content:a,tool_calls:l});for(let e of l){let r=await A(e,n,{messages:t});t.push({role:`tool`,content:r,tool_call_id:e.id}),i?.(e.function.name,e.function.arguments,r)}continue}t.push({role:`assistant`,content:a}),s&&(yield{usage:s});break}};async function L(e,t,n){let{baseUrl:r,apiKey:i=``,model:a,customBody:o,inputs:s,...c}=e,{stream:l,tools:u=[],...d}=n??{},f=P(e,t);if(f)throw Error(`当前上下文含有模型不支持的输入类型:${f}`);let p=y(r,{headers:{Authorization:`Bearer ${i}`},body:{model:a??await D(r),messages:t,tools:u?.map(e=>k(e)[0]),...o}});return(l?I:F)(p,t,u,{...d,...c})}function R(e={},t=0){let{withTime:n=!0,withFileName:r=!0}=e;return(...e)=>{let i=[];if(n&&i.push(`[${new Date().toLocaleTimeString()}]`),r){let{stack:e}=Error(),n=(e?.split(`
7
+ `)[2+t]?.trim())?.match(/^at\s+(?:.*?\s*\()?(.*?):(\d+)(?::(\d+))?\)?/);if(n?.[1]){let e=n[1].split(/[/\\]/).pop();i.push(`[${e}:${n[2]}]`)}}i.push(...e),console.log(...i)}}function z(e,...n){if(t(e)&&(`withTime`in e||`withFileName`in e))return R(e,0);R({},1)(e,...n)}const B=e=>{if(e.role===`system`)return!1;let t;if(typeof e.content==`string`)t=e.content;else if(Array.isArray(e.content))t=e.content.filter(e=>e.type===`text`).map(e=>e.text).join(``);else return!1;return!v(t,[`summary`])},V=e=>e?.role===`assistant`&&Array.isArray(e.tool_calls),H=(e,t)=>{for(t=Math.min(t,e.length);e[t]?.role===`tool`;)t++;return t},U=(e,t)=>{let n=e.reduce((e,n)=>(n.role===`tool`&&(n.content=t(n.content),e++),e),0);n>0&&z(`软删除了${n}条工具调用结果消息`)},W=(e,t)=>{let n=[`image_url`,`input_audio`,`video_url`],r=e.reduce((e,r)=>(Array.isArray(r.content)&&r.content.some(e=>n.includes(e.type))&&(r.content=t(r.content),e++),e),0);r>0&&z(`软删除了${r}条旧图片/音频/视频消息`)},G=async(e,t)=>{let{model:n,keepCount:r,systemPrompt:i}=t??{},a=e.length-r;if(a<=0){z(`消息太少,无需总结`);return}a=H(e,a);let o=[],s=[];for(let t=0;t<a;t++){let n=e[t];if(n){if(V(n)){let n=t+1;for(;n<a&&e[n]?.role===`tool`;)n++;let r=e.slice(t,n);if(r.every(B)){for(let e=t;e<n;e++)o.push(e);s.push(...r)}t=n-1;continue}n.role!==`tool`&&B(n)&&(o.push(t),s.push(n))}}if(o.length===0){z(`没有可总结的消息`);return}s.push({role:`system`,content:i},{role:`user`,content:`开始总结上下文`});let[c,l]=await b(L(n,s));if(c){z(`总结失败:${c.message}`);return}let u=o[0]??0;for(let t=o.length-1;t>=0;t--){let n=o[t];n!==void 0&&e.splice(n,1)}e.splice(u,0,{role:`user`,content:_(`summary`,l.content)})},K=(e,t)=>{let n=e.findIndex(e=>e.role===`user`),r=e.length-t;if(r<=n){z(`消息太少,无需硬删除`);return}r=H(e,r);let i=r-n;e.splice(n,i),z(`硬删除了${i}条较早的消息`)},q=async(e,t,n)=>{let{usage:r,ratioToCompactToolResult:i=.6,replacerOfToolResultContent:a=()=>`已被消费`,ratioToCompactMedia:o=.7,replacerOfMediaContent:s=()=>`已被丢弃`,ratioToSummarize:c=.8,summarizeOptions:l}=n??{},u=t?.context??128e3,d=r?.total_tokens??O(e);if(d>u*i&&U(e,a),d>u*o&&W(e,s),d>u*c){let{keepCount:n=10,systemPrompt:r=`总结历史消息`}=l??{},[i]=await b(G(e,{model:t,keepCount:n,systemPrompt:r}));if(!i)return;K(e,n)}},J=async(e,t)=>{let{maxRetries:n=5,shouldStop:r}=t??{},i;for(let t=0;t<n;t++)if(i=await e(t),r?.(i)===!0)return i;if(!r)return i;throw Error(`超过了最大循环次数(${n})且未满足停止执行条件`)},Y=(e,t=-1)=>{let n=new Map,r=(...r)=>{let i=JSON.stringify(r),a=Date.now(),o=n.get(i);if(o&&a<o.expiresAt)return o.value;let s=t===-1?1/0:a+t*1e3,c=e.apply({setTtl:e=>{s=a+e*1e3}},r);if(c instanceof Promise){let e=c.then(e=>(n.set(i,{value:e,expiresAt:s}),e));return n.set(i,{value:e,expiresAt:s}),e}return n.set(i,{value:c,expiresAt:s}),c};return r.clear=()=>n.clear(),r.updateTtl=e=>{t=e;let r=Date.now(),i=r+e*1e3;for(let[e,t]of n.entries())t.expiresAt>r&&(t.expiresAt=i,n.set(e,t))},r},X=(e,t)=>Math.floor(Math.random()*(t-e+1))+e,Z=(e,t=300)=>{let n=null;return(...r)=>{n&&clearTimeout(n),n=setTimeout(()=>{e(...r)},t)}};var Q=class{queue;constructor(){this.queue=Promise.resolve()}waitInQueue(){let e,t=new Promise(t=>{e=t}),n=this.queue.then(()=>e);return this.queue=t,n}};const $=async(e=150)=>new Promise(t=>{setTimeout(t,e)}),ee=(e,t=300)=>{let n=null;return function(...r){n||=setTimeout(()=>{n=null,e.apply(this,r)},t)}};export{Q as LockQueue,d as camelToSnake,f as capitalize,L as chatCompletions,q as compactMessages,m as compactStr,_ as createXMLTag,Z as debounce,p as decapitalize,T as defineModel,E as defineTool,O as estimateTokens,h as extractErrorMessage,y as fetcher,D as getModelName,x as getRealURL,v as hasXmlTags,C as imageUrlToBase64,e as isNil,t as isObject,n as isPrimitive,z as logger,J as loopUntil,r as mapKeys,i as mapValues,a as mergeObjects,o as omit,s as omitBy,w as parseSSE,c as pick,l as pickBy,g as qs,X as randomInt,$ as sleep,u as snakeToCamel,ee as throttle,b as to,Y as withCache};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nickyzj2023/utils",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "module": "dist/index.mjs",