@nickyzj2023/utils 1.0.75 → 1.0.77

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/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ # utils
2
+
1
3
  男生自用全新前端工具库,0依赖
2
4
 
3
5
  ## 安装到你的项目里
@@ -21,9 +23,11 @@ import { fetcher, to } from "@nickyzj2023/utils";
21
23
 
22
24
  const api = fetcher("https://api.example.com");
23
25
 
24
- const [error, data] = await to(api.get<Blog>("/blogs/hello-world"));
26
+ const [error, data] = await to(
27
+ api.get<Blog>("/blogs/hello-world")
28
+ );
25
29
  if (error) {
26
- console.error(error);
30
+ console.log(error);
27
31
  return;
28
32
  }
29
33
 
package/dist/index.d.mts CHANGED
@@ -1,9 +1,29 @@
1
- //#region src/ai/chatCompletions/types.d.ts
2
- declare namespace ChatCompletions {
1
+ //#region src/ai/types.d.ts
2
+ declare namespace AI {
3
3
  type Model = {
4
- /** 模型名称(如果不传,会尝试从 /models 读取模型) */model?: string; /** API 基础地址 */
5
- baseUrl: string; /** API 密钥(本地模型可不传) */
6
- apiKey?: string;
4
+ baseUrl: string; /** 不传则自动使用{baseUrl}/models接口的第一个模型 */
5
+ model?: string;
6
+ apiKey?: string; /** POST /chat/completions时注入自定义请求体 */
7
+ customBody?: Record<string, any>;
8
+ /**
9
+ * 模型支持的消息输入类型
10
+ * @default ["text"]
11
+ * @remarks 会在调用chatCompletions前校验上下文,含有不支持的输入时抑制请求 */
12
+ inputs?: InputType[];
13
+ /**
14
+ * 模型的最大上下文(输入+输出)
15
+ * @default 128000
16
+ * @remarks 会在调用chatCompletions后智能调用compact(如当前上下文>最大上下文*80%时),可以在chatCompletions里自定义compact执行时机 */
17
+ context?: number;
18
+ };
19
+ type InputType = "text" | "image" | "video" | "audio" | "file";
20
+ type Message = {
21
+ role: "system" | "user" | "assistant" | "tool" | "function"; /** OpenRouter的思考内容字段,其他供应商的会尽可能合并到该字段内 */
22
+ reasoning?: string | null;
23
+ content: string | ContentPart[];
24
+ tool_calls?: ToolCall[];
25
+ tool_call_id?: string;
26
+ [key: string]: unknown;
7
27
  };
8
28
  type TextContent = {
9
29
  type: "text";
@@ -18,7 +38,7 @@ declare namespace ChatCompletions {
18
38
  type AudioContent = {
19
39
  type: "input_audio";
20
40
  input_audio: {
21
- /** 使用公网可访问的音频文件 URL */url?: string; /** 使用 base64 */
41
+ /** 使用公网可访问的音频链接 */url?: string; /** 使用base64 */
22
42
  data?: string;
23
43
  format: string;
24
44
  };
@@ -30,22 +50,22 @@ declare namespace ChatCompletions {
30
50
  };
31
51
  };
32
52
  type ContentPart = TextContent | ImageContent | AudioContent | VideoContent;
33
- type Message = {
34
- role: "system" | "user" | "assistant" | "tool" | "function"; /** 字节的思考字段 */
35
- reasoning_content?: string | null; /** OpenRouter的思考字段 */
36
- reasoning?: string | null;
37
- content: string | ContentPart[];
38
- name?: string;
39
- tool_calls?: ToolCall[];
40
- tool_call_id?: string;
41
- };
42
53
  type ToolDefinition = {
43
54
  type: "function";
44
55
  function: {
45
56
  name: string;
46
- description?: string;
47
- parameters?: Record<string, any>;
57
+ description: string;
58
+ parameters: {
59
+ type: "object";
60
+ properties: Record<string, {
61
+ type: string;
62
+ description?: string; /** 在此处设置的required,发出请求前会自动提到外面去 */
63
+ required?: boolean;
64
+ }>;
65
+ required?: string[];
66
+ };
48
67
  };
68
+ handler: (...args: any) => any;
49
69
  };
50
70
  type ToolCall = {
51
71
  id: string;
@@ -55,44 +75,42 @@ declare namespace ChatCompletions {
55
75
  arguments: string;
56
76
  };
57
77
  };
58
- type ExtraArgs = {
59
- messages?: ChatCompletions.Message[];
60
- [key: string]: any;
61
- };
62
- type ToolHandler = (args: any, extraArgs?: ExtraArgs) => any | Promise<any>;
63
- type ToolHandlers = Record<string, ChatCompletions.ToolHandler>;
64
- type Usage = {
65
- prompt_tokens: number;
66
- completion_tokens: number;
67
- total_tokens: number;
78
+ }
79
+ //#endregion
80
+ //#region src/ai/chatCompletions/types.d.ts
81
+ declare namespace ChatCompletions {
82
+ /** chatCompletions的第三个参数 */
83
+ type Options = {
84
+ stream?: boolean;
85
+ tools?: AI.ToolDefinition[]; /** 工具运行结束后(无论成功失败)的回调,可用于打印日志 */
86
+ onToolHandled?: (name: string, args: string, result: any) => void;
68
87
  };
69
- /** 请求非流式 /chat/completions 的响应结果 */
70
- type Response = {
88
+ /** 非流式POST /chat/completions的响应结果 */
89
+ type NonStreamResponse = {
71
90
  id: string;
72
91
  object: "chat.completion";
73
92
  created: number;
74
93
  model: string;
75
94
  choices: Array<{
76
95
  index: number;
77
- message: Message;
96
+ message: AI.Message;
78
97
  finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | null;
79
98
  }>;
80
99
  usage: Usage;
81
100
  system_fingerprint?: string;
82
101
  };
83
- type ExtraBody = {
84
- /** 工具列表 */tools?: ToolDefinition[]; /** 工具调用函数表,key 为工具名,value 为函数 */
85
- toolHandlers?: ToolHandlers; /** 是否使用流式传输,启用后函数返回异步迭代器 */
86
- stream?: boolean; /** 其他额外参数 */
87
- [key: string]: any;
88
- };
89
- /** 调用 chatCompletions 返回的结果,流式/非流式通用 */
90
- type Result = {
91
- /** 模型的最终回复内容(多模态时取所有 text 拼接) */content: string; /** Token 消耗情况 */
102
+ /** 调用chatCompletions返回的结果,流式/非流式通用 */
103
+ type NonStreamResult = {
104
+ /** 模型的最终回复内容(多模态时取所有text拼接) */content: string; /** Token 消耗情况 */
92
105
  usage: Usage; /** 原始响应中的其他字段 */
93
106
  [key: string]: any;
94
107
  };
95
- /** 流式响应中的单个 SSE 数据块(OpenAI 原始格式) */
108
+ type Usage = {
109
+ prompt_tokens: number;
110
+ completion_tokens: number;
111
+ total_tokens: number;
112
+ };
113
+ /** 流式响应中的单个SSE数据块(OpenAI原始格式) */
96
114
  type StreamResponse = {
97
115
  id: string;
98
116
  object: "chat.completion.chunk";
@@ -100,28 +118,18 @@ declare namespace ChatCompletions {
100
118
  model: string;
101
119
  choices: Array<{
102
120
  index: number;
103
- delta: {
104
- role?: Message["role"];
105
- content?: string | null; /** 字节的思考字段 */
106
- reasoning_content?: string | null; /** OpenRouter的思考字段 */
107
- reasoning?: string | null;
121
+ delta: Pick<AI.Message, "role" | "reasoning" | "content"> & {
108
122
  tool_calls?: Array<{
109
123
  index: number;
110
- id?: string;
111
- type?: "function";
112
- function?: {
113
- name?: string;
114
- arguments?: string;
115
- };
116
- }>;
124
+ } & Partial<AI.ToolCall>>;
117
125
  };
118
126
  finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | null;
119
127
  }>;
120
128
  usage?: Usage;
121
129
  };
122
- /** 流式调用 chatCompletions 时迭代器产出的数据块 */
130
+ /** 流式调用chatCompletions时迭代器产出的数据块 */
123
131
  type StreamChunk = {
124
- /** 模型流式返回的思考内容增量(仅在生成过程中出现) */reasoningContent?: string; /** 模型流式返回的内容增量(仅在生成过程中出现) */
132
+ /** 模型流式返回的思考内容增量(仅在生成过程中出现) */reasoning?: string; /** 模型流式返回的内容增量(仅在生成过程中出现) */
125
133
  content?: string; /** Token 消耗情况(仅在最后一帧出现) */
126
134
  usage?: Usage;
127
135
  };
@@ -129,22 +137,23 @@ declare namespace ChatCompletions {
129
137
  //#endregion
130
138
  //#region src/ai/chatCompletions/index.d.ts
131
139
  /**
132
- * 兼容 OpenAI API 的聊天补全函数
140
+ * 兼容OpenAI API的聊天补全函数
133
141
  * - 自动处理工具调用
134
- * - 同时支持普通响应和流式响应
142
+ * - 支持普通/流式响应
135
143
  *
136
- * @param model 模型配置,包含 model、baseUrl、apiKey
137
- * @param messages OpenAI API 兼容的消息数组
138
- * @param extraBody 可选的额外参数,如 tools、toolHandlers、temperature、stream
139
- * @returns 普通模式下返回 `{ content, usage, ... }`;`stream: true` 时返回异步迭代器
144
+ * @param model 模型配置,包含model、baseUrl、apiKey
145
+ * @param messages OpenAI API兼容的消息数组
146
+ * @param extraBody 可选的额外参数,如tools、temperature、stream等
147
+ * @returns 普通模式下返回`{ content, usage, ... }`;`stream: true`时返回异步迭代器
140
148
  *
141
149
  * @example
142
150
  * // 最简调用
143
- * // 未填写模型名,会自动使用/v1/models的第一个模型
144
- * const { content, usage } = await chatCompletions(
151
+ * // 未填写模型名,会自动使用/v1/models返回的第一个模型
152
+ * const { reasoning, content, usage } = await chatCompletions(
145
153
  * { baseUrl: "http://127.0.0.1:11434/v1" },
146
154
  * [{ role: "user", content: "你好" }],
147
155
  * );
156
+ * console.log(reasoning); // "The user said..."
148
157
  * console.log(content); // "你好!有什么我可以帮你的吗?"
149
158
  * console.log(usage); // { prompt_tokens: 13, completion_tokens: 9, total_tokens: 22 }
150
159
  *
@@ -160,11 +169,9 @@ declare namespace ChatCompletions {
160
169
  * name: "getWeather",
161
170
  * description: "查询城市天气情况",
162
171
  * parameters: { type: "object", properties: { city: { type: "string" } } },
172
+ * handler: (args) => `${args.city}今日晴转多云,25°C`,
163
173
  * },
164
174
  * }],
165
- * toolHandlers: {
166
- * getWeather: (args) => `${args.city}今日晴转多云,25°C`,
167
- * },
168
175
  * },
169
176
  * );
170
177
  *
@@ -183,14 +190,26 @@ declare namespace ChatCompletions {
183
190
  * }
184
191
  * }
185
192
  */
186
- declare function chatCompletions(model: ChatCompletions.Model, messages: ChatCompletions.Message[], extraBody: ChatCompletions.ExtraBody & {
193
+ declare function chatCompletions(model: AI.Model, messages: AI.Message[], options: ChatCompletions.Options & {
187
194
  stream: true;
188
195
  }): Promise<AsyncGenerator<ChatCompletions.StreamChunk>>;
189
- declare function chatCompletions(model: ChatCompletions.Model, messages: ChatCompletions.Message[], extraBody?: ChatCompletions.ExtraBody): Promise<ChatCompletions.Result>;
196
+ declare function chatCompletions(model: AI.Model, messages: AI.Message[], options?: ChatCompletions.Options): Promise<ChatCompletions.NonStreamResult>;
197
+ //#endregion
198
+ //#region src/ai/helper.d.ts
190
199
  /**
191
- * 辅助定义一个 chatCompletions 支持的模型配置
200
+ * 辅助定义一个POST /chat/completions支持的model参数
201
+ * @remarks 只有baseUrl字段是必须的,其他字段请查看AI.Model类型
192
202
  */
193
- declare const defineModel: (config: ChatCompletions.Model) => ChatCompletions.Model;
203
+ declare const defineModel: (config: AI.Model) => AI.Model;
204
+ /**
205
+ * 辅助定义一个POST /chat/completions支持的tools中的子元素
206
+ * @param handler 在AI请求调用工具时用到
207
+ */
208
+ declare const defineTool: (name: AI.ToolDefinition["function"]["name"], description: AI.ToolDefinition["function"]["description"], properties: AI.ToolDefinition["function"]["parameters"]["properties"], handler: AI.ToolDefinition["handler"]) => AI.ToolDefinition;
209
+ /**
210
+ * 从GET /models获取模型名称
211
+ */
212
+ declare const getModelName: (baseUrl: string) => Promise<string>;
194
213
  //#endregion
195
214
  //#region src/dom/logger.d.ts
196
215
  /**
@@ -348,11 +367,15 @@ type Primitive = number | string | boolean | symbol | bigint | undefined | null;
348
367
  declare const isPrimitive: (value: any) => value is Primitive;
349
368
  //#endregion
350
369
  //#region src/network/fetcher.d.ts
351
- type RequestInit = globalThis.RequestInit & {
370
+ type RequestInit = Omit<globalThis.RequestInit, "body"> & {
352
371
  /**
353
372
  * searchParams 查询参数对象
354
373
  */
355
374
  params?: Record<string, any>;
375
+ /**
376
+ * 请求体支持任意类型
377
+ */
378
+ body?: any;
356
379
  /**
357
380
  * 响应解析器,默认的解析方法为 response.json()
358
381
  */
@@ -818,4 +841,4 @@ declare const sleep: (time?: number) => Promise<unknown>;
818
841
  */
819
842
  declare const throttle: <T extends (...args: any[]) => any>(fn: T, delay?: number) => (this: any, ...args: Parameters<T>) => void;
820
843
  //#endregion
821
- export { CamelToSnake, Capitalize, type ChatCompletions, Decapitalize, DeepMapKeys, DeepMapValues, ImageCompressionOptions, LockQueue, LoggerOptions, Primitive, RequestInit, SetTtl, SnakeToCamel, camelToSnake, capitalize, chatCompletions, compactStr, debounce, decapitalize, defineModel, extractErrorMessage, fetcher, getRealURL, imageUrlToBase64, isNil, isObject, isPrimitive, logger, loopUntil, mapKeys, mapValues, mergeObjects, omit, omitBy, parseSSE, pick, pickBy, qs, randomInt, sleep, snakeToCamel, throttle, to, withCache };
844
+ export { AI, CamelToSnake, Capitalize, type ChatCompletions, Decapitalize, DeepMapKeys, DeepMapValues, ImageCompressionOptions, LockQueue, LoggerOptions, Primitive, RequestInit, SetTtl, SnakeToCamel, camelToSnake, capitalize, chatCompletions, compactStr, debounce, decapitalize, defineModel, defineTool, 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 };
package/dist/index.mjs CHANGED
@@ -2,6 +2,6 @@ const e=e=>e==null,t=e=>e?.constructor===Object,n=e=>e==null||typeof e!=`object`
2
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(`
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=async e=>{let t=(await e.get(`/models`)).data[0]?.id;if(!t)throw Error(`无法从 /models 获取模型名称`);return t},w=async(e,t,n)=>{let r=t[e.function.name];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)}`}},T=e=>typeof e==`string`?e:e.filter(e=>e.type===`text`).map(e=>e.text).join(`
6
- `),E=async(e,t,n,r)=>{let{baseUrl:i,apiKey:a=``,model:o}=e,s=_(i,{headers:{Authorization:`Bearer ${a}`}}),c={model:o??await C(s),messages:t,...r};for(;;){let{choices:e,usage:r,...i}=await s.post(`/chat/completions`,c),{message:a}=e?.[0]??{};if(!a)throw Error(`模型没有回复任何内容`);t.push(a);let{content:o=``,tool_calls:l=[],...u}=a,d=u?.reasoning_content||u?.reasoning;if(l.length>0&&Object.keys(n).length>0){for(let e of l){let r=await w(e,n,{messages:t});t.push({role:`tool`,content:r,tool_call_id:e.id})}continue}return{content:T(o),reasoningContent:d,usage:r,...i,...u}}},D=async function*(e,t,n,r){let{baseUrl:i,apiKey:a=``,model:o}=e,s=_(i,{headers:{Authorization:`Bearer ${a}`}}),c={model:o??await C(s),messages:t,stream:!0,...r};for(;;){let e=new Map,r=``,i=null,a,o=await s.post(`/chat/completions`,c,{parser:async e=>e});for await(let t of S(o)){t.usage&&(a=t.usage);let n=t.choices?.[0];if(!n)continue;let{delta:o}=n,{content:s,tool_calls:c}=o,l=o.reasoning_content||o.reasoning;if(l&&(yield{reasoningContent:l}),s&&(r+=s,yield{content:s}),c)for(let t of c){let n=e.get(t.index)??{id:``,type:`function`,function:{name:``,arguments:``}};t.id&&(n.id=t.id),t.function?.name&&(n.function.name+=t.function.name),t.function?.arguments&&(n.function.arguments+=t.function.arguments),e.set(t.index,n)}n.finish_reason&&(i=n.finish_reason)}let l=Array.from(e.values());if(i!==`tool_calls`&&l.length>0&&Object.keys(n).length>0){t.push({role:`assistant`,content:r,tool_calls:l});for(let e of l){let r=await w(e,n);t.push({role:`tool`,content:r,tool_call_id:e.id})}continue}t.push({role:`assistant`,content:r}),a&&(yield{usage:a});break}};async function O(e,t,n={}){let{stream:r,toolHandlers:i={},...a}=n;return(r?D:E)(e,t,i,a)}const k=e=>e;function A(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 j(e,...n){if(t(e)&&(`withTime`in e||`withFileName`in e))return A(e,0);A({},1)(e,...n)}const M=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})且未满足停止执行条件`)},N=(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},P=(e,t)=>Math.floor(Math.random()*(t-e+1))+e,F=(e,t=300)=>{let n=null;return(...r)=>{n&&clearTimeout(n),n=setTimeout(()=>{e(...r)},t)}};var I=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 L=async(e=150)=>new Promise(t=>{setTimeout(t,e)}),R=(e,t=300)=>{let n=null;return function(...r){n||=setTimeout(()=>{n=null,e.apply(this,r)},t)}};export{I as LockQueue,d as camelToSnake,f as capitalize,O as chatCompletions,m as compactStr,F as debounce,p as decapitalize,k as defineModel,h as extractErrorMessage,_ as fetcher,y as getRealURL,x as imageUrlToBase64,e as isNil,t as isObject,n as isPrimitive,j as logger,M 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,P as randomInt,L as sleep,u as snakeToCamel,R as throttle,v as to,N 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 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=>[c(e,[`type`,`function`]),c(e,[`handler`])],D=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)}`}},O=e=>e.reasoning||e.reasoning_content,k=e=>typeof e==`string`?e:e.filter(e=>e.type===`text`).map(e=>e.text).join(`
6
+ `),A=e=>{switch(e.type){case`text`:return`text`;case`image_url`:return`image`;case`input_audio`:return`audio`;case`video_url`:return`video`}},j=(e,t)=>{let{inputs:n=[`text`]}=e;return t.some(e=>{let{content:t}=e;if(typeof t!=`string`){if(Array.isArray(t))for(let e of t){let t=A(e);return!t||!n.includes(t)}return!0}})},M=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=O(s);if(l.length>0&&n.length>0){for(let e of l){let r=await D(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:k(c),reasoning:d,usage:a,...o,...u}}},N=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=O(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 D(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 P(e,t,n){let{baseUrl:r,apiKey:i=``,model:a,customBody:o,inputs:s,...c}=e,{stream:l,tools:u=[],...d}=n??{};if(j(e,t))throw Error(`当前上下文含有模型不支持的输入类型`);let f=_(r,{headers:{Authorization:`Bearer ${i}`},body:{model:a??await T(r),messages:t,tools:u?.map(e=>E(e)[0]),...o}});return(l?N:M)(f,t,u,{...d,...c})}function F(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 I(e,...n){if(t(e)&&(`withTime`in e||`withFileName`in e))return F(e,0);F({},1)(e,...n)}const L=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})且未满足停止执行条件`)},R=(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},z=(e,t)=>Math.floor(Math.random()*(t-e+1))+e,B=(e,t=300)=>{let n=null;return(...r)=>{n&&clearTimeout(n),n=setTimeout(()=>{e(...r)},t)}};var V=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 H=async(e=150)=>new Promise(t=>{setTimeout(t,e)}),U=(e,t=300)=>{let n=null;return function(...r){n||=setTimeout(()=>{n=null,e.apply(this,r)},t)}};export{V as LockQueue,d as camelToSnake,f as capitalize,P as chatCompletions,m as compactStr,B as debounce,p as decapitalize,C as defineModel,w as defineTool,h as extractErrorMessage,_ as fetcher,T as getModelName,y as getRealURL,x as imageUrlToBase64,e as isNil,t as isObject,n as isPrimitive,I as logger,L 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,z as randomInt,H as sleep,u as snakeToCamel,U as throttle,v as to,R as withCache};
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
- {
2
- "name": "@nickyzj2023/utils",
3
- "version": "1.0.75",
4
- "type": "module",
5
- "main": "dist/index.mjs",
6
- "module": "dist/index.mjs",
7
- "types": "dist/index.d.mts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.mts",
11
- "import": "./dist/index.mjs"
12
- }
13
- },
14
- "files": [
15
- "dist"
16
- ],
17
- "scripts": {
18
- "build": "tsdown",
19
- "docs": "typedoc",
20
- "check": "biome check --diagnostic-level=error --write src/"
21
- },
22
- "repository": {
23
- "type": "git",
24
- "url": "https://github.com/Nickyzj628/utils.git"
25
- },
26
- "devDependencies": {
27
- "@types/node": "^25.9.3",
28
- "tsdown": "^0.22.2",
29
- "typedoc": "^0.28.19",
30
- "typedoc-material-theme": "^1.4.1",
31
- "typescript": "^6.0.3"
32
- }
33
- }
1
+ {
2
+ "name": "@nickyzj2023/utils",
3
+ "version": "1.0.77",
4
+ "type": "module",
5
+ "main": "dist/index.mjs",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.mts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.mts",
11
+ "import": "./dist/index.mjs"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsdown",
19
+ "docs": "typedoc",
20
+ "check": "biome check --diagnostic-level=error --write src/"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/Nickyzj628/utils.git"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^25.9.3",
28
+ "tsdown": "^0.22.2",
29
+ "typedoc": "^0.28.19",
30
+ "typedoc-material-theme": "^1.4.1",
31
+ "typescript": "^6.0.3"
32
+ }
33
+ }