@nickyzj2023/utils 1.0.76 → 1.0.78
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 +6 -2
- package/dist/index.d.mts +122 -39
- package/dist/index.mjs +3 -3
- package/package.json +1 -1
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(
|
|
26
|
+
const [error, data] = await to(
|
|
27
|
+
api.get<Blog>("/blogs/hello-world")
|
|
28
|
+
);
|
|
25
29
|
if (error) {
|
|
26
|
-
console.
|
|
30
|
+
console.log(error);
|
|
27
31
|
return;
|
|
28
32
|
}
|
|
29
33
|
|
package/dist/index.d.mts
CHANGED
|
@@ -4,10 +4,19 @@ declare namespace AI {
|
|
|
4
4
|
baseUrl: string; /** 不传则自动使用{baseUrl}/models接口的第一个模型 */
|
|
5
5
|
model?: string;
|
|
6
6
|
apiKey?: string; /** POST /chat/completions时注入自定义请求体 */
|
|
7
|
-
customBody?: Record<string, any>;
|
|
8
|
-
|
|
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.autoCompact配置 */
|
|
9
17
|
context?: number;
|
|
10
18
|
};
|
|
19
|
+
type InputType = "text" | "image" | "video" | "audio" | "file";
|
|
11
20
|
type Message = {
|
|
12
21
|
role: "system" | "user" | "assistant" | "tool" | "function"; /** OpenRouter的思考内容字段,其他供应商的会尽可能合并到该字段内 */
|
|
13
22
|
reasoning?: string | null;
|
|
@@ -70,6 +79,13 @@ declare namespace AI {
|
|
|
70
79
|
//#endregion
|
|
71
80
|
//#region src/ai/chatCompletions/types.d.ts
|
|
72
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; /** 自动压缩上下文 */
|
|
87
|
+
autoCompact?: {};
|
|
88
|
+
};
|
|
73
89
|
/** 非流式POST /chat/completions的响应结果 */
|
|
74
90
|
type NonStreamResponse = {
|
|
75
91
|
id: string;
|
|
@@ -122,14 +138,42 @@ declare namespace ChatCompletions {
|
|
|
122
138
|
//#endregion
|
|
123
139
|
//#region src/ai/chatCompletions/index.d.ts
|
|
124
140
|
/**
|
|
125
|
-
* 兼容OpenAI API
|
|
141
|
+
* 兼容OpenAI API的聊天补全函数(流式模式)
|
|
126
142
|
* - 自动处理工具调用
|
|
127
|
-
* -
|
|
143
|
+
* - 传入 `stream: true` 时返回异步迭代器,逐块产出内容与 usage
|
|
128
144
|
*
|
|
129
145
|
* @param model 模型配置,包含model、baseUrl、apiKey
|
|
130
146
|
* @param messages OpenAI API兼容的消息数组
|
|
131
|
-
* @param
|
|
132
|
-
* @returns
|
|
147
|
+
* @param options 额外参数,须包含 `stream: true`,也可含 tools、temperature 等
|
|
148
|
+
* @returns 异步迭代器,逐块产出 `{ content?, reasoning?, usage? }`
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* // 流式传输
|
|
152
|
+
* const result = await chatCompletions(
|
|
153
|
+
* { baseUrl: "http://127.0.0.1:11434/v1" },
|
|
154
|
+
* [{ role: "user", content: "你好" }],
|
|
155
|
+
* { stream: true },
|
|
156
|
+
* );
|
|
157
|
+
* for await (const { content, usage } of result) {
|
|
158
|
+
* if (content) {
|
|
159
|
+
* console.log("流式传输中:", content);
|
|
160
|
+
* } else if (usage) {
|
|
161
|
+
* console.log("对话结束,消耗:", usage);
|
|
162
|
+
* }
|
|
163
|
+
* }
|
|
164
|
+
*/
|
|
165
|
+
declare function chatCompletions(model: AI.Model, messages: AI.Message[], options: ChatCompletions.Options & {
|
|
166
|
+
stream: true;
|
|
167
|
+
}): Promise<AsyncGenerator<ChatCompletions.StreamChunk>>;
|
|
168
|
+
/**
|
|
169
|
+
* 兼容OpenAI API的聊天补全函数(普通模式)
|
|
170
|
+
* - 自动处理工具调用
|
|
171
|
+
* - 默认返回完整结果,非流式
|
|
172
|
+
*
|
|
173
|
+
* @param model 模型配置,包含model、baseUrl、apiKey
|
|
174
|
+
* @param messages OpenAI API兼容的消息数组
|
|
175
|
+
* @param options 可选的额外参数,如tools、temperature等
|
|
176
|
+
* @returns `{ content, usage, ... }` 完整结果
|
|
133
177
|
*
|
|
134
178
|
* @example
|
|
135
179
|
* // 最简调用
|
|
@@ -140,12 +184,12 @@ declare namespace ChatCompletions {
|
|
|
140
184
|
* );
|
|
141
185
|
* console.log(reasoning); // "The user said..."
|
|
142
186
|
* console.log(content); // "你好!有什么我可以帮你的吗?"
|
|
143
|
-
* console.log(usage); // { prompt_tokens:
|
|
187
|
+
* console.log(usage); // { prompt_tokens: [redacted], completion_tokens: [redacted], total_tokens: [redacted] }
|
|
144
188
|
*
|
|
145
189
|
* @example
|
|
146
190
|
* // 工具调用
|
|
147
191
|
* const { content, usage } = await chatCompletions(
|
|
148
|
-
* { baseUrl: "http://127.0.0.1:11434/v1", model: "model.gguf", apiKey: "sk-
|
|
192
|
+
* { baseUrl: "http://127.0.0.1:11434/v1", model: "model.gguf", apiKey: "sk-loc**********-key" },
|
|
149
193
|
* [{ role: "user", content: "查询上海天气" }],
|
|
150
194
|
* {
|
|
151
195
|
* tools: [{
|
|
@@ -159,30 +203,58 @@ declare namespace ChatCompletions {
|
|
|
159
203
|
* }],
|
|
160
204
|
* },
|
|
161
205
|
* );
|
|
162
|
-
*
|
|
163
|
-
* @example
|
|
164
|
-
* // 流式传输
|
|
165
|
-
* const result = await chatCompletions(
|
|
166
|
-
* { baseUrl: "http://127.0.0.1:11434/v1" },
|
|
167
|
-
* [{ role: "user", content: "你好" }],
|
|
168
|
-
* { stream: true },
|
|
169
|
-
* );
|
|
170
|
-
* for await (const { content, usage } of result) {
|
|
171
|
-
* if (content) {
|
|
172
|
-
* console.log("流式传输中:", content);
|
|
173
|
-
* } else if (usage) {
|
|
174
|
-
* console.log("对话结束,消耗:", usage);
|
|
175
|
-
* }
|
|
176
|
-
* }
|
|
177
206
|
*/
|
|
178
|
-
declare function chatCompletions(model: AI.Model, messages: AI.Message[], options:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
207
|
+
declare function chatCompletions(model: AI.Model, messages: AI.Message[], options?: ChatCompletions.Options): Promise<ChatCompletions.NonStreamResult>;
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/ai/compact/types.d.ts
|
|
210
|
+
declare namespace Compact {
|
|
211
|
+
type ReplacerOfToolResultContent = (content: AI.Message["content"]) => AI.Message["content"];
|
|
212
|
+
type ReplacerOfMediaContent = (content: AI.Message["content"]) => AI.Message["content"];
|
|
213
|
+
type SummarizeOptions = {
|
|
214
|
+
/** 不总结最新的X%条消息 */keepPercent: number; /** 用什么模型总结 */
|
|
215
|
+
model: AI.Model; /** 用于指导大模型如何总结消息的提示词 */
|
|
216
|
+
systemPrompt: string;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/ai/compact/index.d.ts
|
|
221
|
+
/**
|
|
222
|
+
* 自动优化上下文,类似AI Coding Agent的/compact命令
|
|
223
|
+
*/
|
|
224
|
+
declare const compact: (messages: AI.Message[], model: AI.Model, options?: {
|
|
225
|
+
/** 提供token消耗情况时,能更准确地判断上下文是否达到阈值 */usage?: ChatCompletions.Usage;
|
|
226
|
+
/**
|
|
227
|
+
* 上下文>总上下文*ratio时压缩工具调用结果
|
|
228
|
+
* @default 0.6
|
|
229
|
+
*/
|
|
230
|
+
ratioOfCompactToolResult?: number;
|
|
231
|
+
/**
|
|
232
|
+
* 如何压缩工具调用结果,例如让其他模型返回精简后的工具结果
|
|
233
|
+
* @default (content) => "(已被消费)"
|
|
234
|
+
*/
|
|
235
|
+
replacerOfToolResultContent?: Compact.ReplacerOfToolResultContent;
|
|
236
|
+
/**
|
|
237
|
+
* 上下文>总上下文*ratio时压缩图片/音频/视频消息
|
|
238
|
+
* @default 0.7
|
|
239
|
+
*/
|
|
240
|
+
ratioToCompactMedia?: number;
|
|
241
|
+
/**
|
|
242
|
+
* 如何压缩媒体消息,例如让其他模型用自然语言简短描述一遍
|
|
243
|
+
* @default (content) => "(已被丢弃)"
|
|
244
|
+
*/
|
|
245
|
+
replacerOfMediaContent?: Compact.ReplacerOfMediaContent;
|
|
246
|
+
/**
|
|
247
|
+
* 上下文>总上下文*ratio时总结消息
|
|
248
|
+
* @default 0.8
|
|
249
|
+
* @remarks 如果总结成功,会把summarizeOptions.keepPercent(默认0.2(20%))以外的消息压成一条消息;如果总结失败,会采取兜底压缩方法:硬删除summarizeOptions.keepPercent以外的消息
|
|
250
|
+
*/
|
|
251
|
+
ratioToSummarize?: number;
|
|
252
|
+
/**
|
|
253
|
+
* 总结消息时的配置项
|
|
254
|
+
* @default { keepPercent: 0.2, model: undefined, systemPrompt: "总结历史消息" }
|
|
255
|
+
*/
|
|
256
|
+
summarizeOptions?: Compact.SummarizeOptions;
|
|
257
|
+
}) => Promise<void>;
|
|
186
258
|
//#endregion
|
|
187
259
|
//#region src/ai/helper.d.ts
|
|
188
260
|
/**
|
|
@@ -199,6 +271,13 @@ declare const defineTool: (name: AI.ToolDefinition["function"]["name"], descript
|
|
|
199
271
|
* 从GET /models获取模型名称
|
|
200
272
|
*/
|
|
201
273
|
declare const getModelName: (baseUrl: string) => Promise<string>;
|
|
274
|
+
/**
|
|
275
|
+
* 根据上下文里的中/英文/多模态消息,估算出可能消耗的token
|
|
276
|
+
* - 单词 ≈ 1.5token
|
|
277
|
+
* - 标点/空白等非词字符每 4 个 ≈ 1token
|
|
278
|
+
* - 图片/音频/视频/文件 ≈ 10000token(不好估算,取个较大的值)
|
|
279
|
+
*/
|
|
280
|
+
declare const estimateTokens: (messages?: AI.Message[]) => number;
|
|
202
281
|
//#endregion
|
|
203
282
|
//#region src/dom/logger.d.ts
|
|
204
283
|
/**
|
|
@@ -219,18 +298,11 @@ interface LoggerOptions {
|
|
|
219
298
|
/**
|
|
220
299
|
* 带额外信息的 console.log
|
|
221
300
|
*
|
|
222
|
-
* **直接调用**:使用默认选项打印消息
|
|
223
|
-
* @param messages - 日志消息,支持多条
|
|
224
|
-
*
|
|
225
301
|
* **预配置**:先传入选项返回 logger 函数,再调用打印
|
|
226
302
|
* @param options - 配置选项
|
|
227
303
|
* @returns 配置后的 logger 函数
|
|
228
304
|
*
|
|
229
305
|
* @example
|
|
230
|
-
* // 直接调用(默认显示时间和文件名)
|
|
231
|
-
* logger("调试信息"); // "[14:30:00] [index.ts:15:2] 调试信息"
|
|
232
|
-
* logger("消息1", "消息2"); // "[14:30:00] [index.ts:15:2] 消息1 消息2"
|
|
233
|
-
*
|
|
234
306
|
* // 预配置后调用
|
|
235
307
|
* const myLogger = logger({ withTime: true, withFileName: true });
|
|
236
308
|
* myLogger("一段消息", "另一段消息"); // "[18:59:47] [index.ts:137:2] 一段消息 另一段消息"
|
|
@@ -240,6 +312,17 @@ interface LoggerOptions {
|
|
|
240
312
|
* plainLogger("纯文件名前缀"); // "[index.ts:15:2] 纯文件名前缀"
|
|
241
313
|
*/
|
|
242
314
|
declare function logger(options?: LoggerOptions): (...messages: any[]) => void;
|
|
315
|
+
/**
|
|
316
|
+
* 带额外信息的 console.log
|
|
317
|
+
*
|
|
318
|
+
* **直接调用**:使用默认选项打印消息
|
|
319
|
+
* @param messages - 日志消息,支持多条
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* // 直接调用(默认显示时间和文件名)
|
|
323
|
+
* logger("调试信息"); // "[14:30:00] [index.ts:15:2] 调试信息"
|
|
324
|
+
* logger("消息1", "消息2"); // "[14:30:00] [index.ts:15:2] 消息1 消息2"
|
|
325
|
+
*/
|
|
243
326
|
declare function logger(...messages: any[]): void;
|
|
244
327
|
//#endregion
|
|
245
328
|
//#region src/function/loop-until.d.ts
|
|
@@ -830,4 +913,4 @@ declare const sleep: (time?: number) => Promise<unknown>;
|
|
|
830
913
|
*/
|
|
831
914
|
declare const throttle: <T extends (...args: any[]) => any>(fn: T, delay?: number) => (this: any, ...args: Parameters<T>) => void;
|
|
832
915
|
//#endregion
|
|
833
|
-
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 };
|
|
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 };
|
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=e=>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`])],
|
|
6
|
-
`),
|
|
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
|
|
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};
|