@yh-ui/ai-sdk 0.1.21

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.
@@ -0,0 +1,765 @@
1
+ import { ref } from "vue";
2
+ export function useReActAgent(config) {
3
+ const {
4
+ type: _type = "react",
5
+ maxIterations = 10,
6
+ maxToolCalls = 20,
7
+ returnReasoning = false,
8
+ tools = [],
9
+ stopConditions = [],
10
+ onError
11
+ } = config;
12
+ const steps = ref([]);
13
+ const isRunning = ref(false);
14
+ const currentOutput = ref("");
15
+ const toolCallCount = ref(0);
16
+ const addStep = (step) => {
17
+ steps.value.push({
18
+ ...step,
19
+ id: `step-${Date.now()}-${Math.random().toString(36).slice(2)}`,
20
+ timestamp: /* @__PURE__ */ new Date()
21
+ });
22
+ };
23
+ const checkStopConditions = (output) => {
24
+ for (const condition of stopConditions) {
25
+ switch (condition.type) {
26
+ case "contains":
27
+ if (condition.value && output.includes(condition.value)) {
28
+ return true;
29
+ }
30
+ break;
31
+ case "custom":
32
+ if (condition.value && condition.value(output)) {
33
+ return true;
34
+ }
35
+ break;
36
+ }
37
+ }
38
+ return false;
39
+ };
40
+ const executeTool = async (toolName, args) => {
41
+ const tool = tools.find((t) => t.name === toolName);
42
+ if (!tool) {
43
+ throw new Error(`Tool not found: ${toolName}`);
44
+ }
45
+ addStep({
46
+ type: "action",
47
+ content: `Executing tool: ${toolName}`,
48
+ toolName,
49
+ toolInput: args
50
+ });
51
+ try {
52
+ const result = await tool.execute(args);
53
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result);
54
+ addStep({
55
+ type: "observation",
56
+ content: resultStr,
57
+ toolName,
58
+ toolOutput: result
59
+ });
60
+ toolCallCount.value++;
61
+ return resultStr;
62
+ } catch (error) {
63
+ addStep({
64
+ type: "observation",
65
+ content: `Error: ${error instanceof Error ? error.message : String(error)}`,
66
+ toolName,
67
+ toolOutput: error
68
+ });
69
+ throw error;
70
+ }
71
+ };
72
+ const parseResponse = (response) => {
73
+ const thoughtMatch = response.match(/Thought:?\s*(.+)/i);
74
+ const actionMatch = response.match(/Action:?\s*(.+)/i);
75
+ const actionInputMatch = response.match(/Action Input:?\s*(.+)/i);
76
+ return {
77
+ thought: thoughtMatch ? thoughtMatch[1].trim() : response,
78
+ action: actionMatch ? actionMatch[1].trim() : void 0,
79
+ actionInput: actionInputMatch ? actionInputMatch[1].trim() : void 0
80
+ };
81
+ };
82
+ const run = async (input, executeFn) => {
83
+ if (isRunning.value) {
84
+ throw new Error("Agent is already running");
85
+ }
86
+ isRunning.value = true;
87
+ steps.value = [];
88
+ currentOutput.value = "";
89
+ toolCallCount.value = 0;
90
+ let iteration = 0;
91
+ let context = `Task: ${input}
92
+
93
+ `;
94
+ try {
95
+ while (iteration < maxIterations && toolCallCount.value < maxToolCalls) {
96
+ iteration++;
97
+ if (checkStopConditions(currentOutput.value)) {
98
+ break;
99
+ }
100
+ addStep({
101
+ type: "thought",
102
+ content: `Iteration ${iteration}: Thinking about how to proceed...`
103
+ });
104
+ const response = await executeFn(context);
105
+ const parsed = parseResponse(response);
106
+ currentOutput.value = parsed.thought;
107
+ if (parsed.action && parsed.actionInput) {
108
+ try {
109
+ const observation = await executeTool(parsed.action, JSON.parse(parsed.actionInput));
110
+ context += `Thought: ${parsed.thought}
111
+ Action: ${parsed.action}
112
+ Action Input: ${parsed.actionInput}
113
+ Observation: ${observation}
114
+
115
+ `;
116
+ } catch (error) {
117
+ context += `Thought: ${parsed.thought}
118
+ Action: ${parsed.action}
119
+ Action Input: ${parsed.actionInput}
120
+ Observation: Error - ${error instanceof Error ? error.message : String(error)}
121
+
122
+ `;
123
+ if (onError) {
124
+ await onError(error, steps.value[steps.value.length - 1]);
125
+ }
126
+ }
127
+ } else {
128
+ addStep({
129
+ type: "result",
130
+ content: parsed.thought
131
+ });
132
+ break;
133
+ }
134
+ }
135
+ return {
136
+ output: currentOutput.value,
137
+ reasoning: returnReasoning ? steps.value : void 0,
138
+ toolCalls: toolCallCount.value,
139
+ finished: iteration >= maxIterations
140
+ };
141
+ } catch (error) {
142
+ return {
143
+ output: currentOutput.value,
144
+ reasoning: returnReasoning ? steps.value : void 0,
145
+ toolCalls: toolCallCount.value,
146
+ finished: false,
147
+ error: error instanceof Error ? error : new Error(String(error))
148
+ };
149
+ } finally {
150
+ isRunning.value = false;
151
+ }
152
+ };
153
+ return {
154
+ steps,
155
+ isRunning,
156
+ currentOutput,
157
+ toolCallCount,
158
+ run
159
+ };
160
+ }
161
+ export function createPlanExecuteAgent(config) {
162
+ const { tools = [] } = config;
163
+ const plans = ref([]);
164
+ const currentPlan = ref(null);
165
+ const results = ref({});
166
+ const executeTool = async (toolName, args) => {
167
+ const tool = tools.find((t) => t.name === toolName);
168
+ if (!tool) {
169
+ throw new Error(`Tool not found: ${toolName}`);
170
+ }
171
+ return tool.execute(args);
172
+ };
173
+ const execute = async (task, llm) => {
174
+ const planPrompt = `\u5206\u89E3\u4EE5\u4E0B\u4EFB\u52A1\u4E3A\u591A\u4E2A\u53EF\u6267\u884C\u7684\u6B65\u9AA4\uFF0C\u8FD4\u56DE JSON \u6570\u7EC4\u683C\u5F0F\uFF1A
175
+ [{"step": "\u6B65\u9AA4\u63CF\u8FF0", "tool": "\u5DE5\u5177\u540D", "input": {"\u53C2\u6570": "\u503C"}}]
176
+
177
+ \u4EFB\u52A1: ${task}
178
+
179
+ \u53EF\u7528\u5DE5\u5177: ${tools.map((t) => t.name).join(", ")}`;
180
+ const planResponse = await llm(planPrompt);
181
+ try {
182
+ const parsed = JSON.parse(planResponse.replace(/```json|```/g, "").trim());
183
+ plans.value = parsed.map(
184
+ (p, i) => ({
185
+ id: `plan-${i}`,
186
+ description: p.step,
187
+ status: "pending"
188
+ })
189
+ );
190
+ } catch {
191
+ plans.value = [{ id: "plan-0", description: planResponse, status: "pending" }];
192
+ }
193
+ for (const plan of plans.value) {
194
+ currentPlan.value = plan.id;
195
+ plan.status = "pending";
196
+ try {
197
+ const execPrompt = `\u6839\u636E\u5F53\u524D\u6B65\u9AA4\u751F\u6210\u5DE5\u5177\u8C03\u7528\uFF1A
198
+
199
+ \u6B65\u9AA4: ${plan.description}
200
+ \u4E0A\u4E0B\u6587: ${JSON.stringify(results.value)}
201
+
202
+ \u8FD4\u56DE\u683C\u5F0F: {"tool": "\u5DE5\u5177\u540D", "input": {"\u53C2\u6570": "\u503C"}}`;
203
+ const execResponse = await llm(execPrompt);
204
+ const exec = JSON.parse(execResponse.replace(/```json|```/g, "").trim());
205
+ if (exec.tool) {
206
+ const result = await executeTool(exec.tool, exec.input || {});
207
+ results.value[plan.id] = result;
208
+ plan.status = "completed";
209
+ } else {
210
+ results.value[plan.id] = execResponse;
211
+ plan.status = "completed";
212
+ }
213
+ } catch (error) {
214
+ plan.status = "failed";
215
+ results.value[plan.id] = { error: error instanceof Error ? error.message : String(error) };
216
+ }
217
+ }
218
+ const summaryPrompt = `\u6839\u636E\u4EE5\u4E0B\u6267\u884C\u7ED3\u679C\uFF0C\u7528\u81EA\u7136\u8BED\u8A00\u603B\u7ED3\u56DE\u7B54\u7528\u6237\uFF1A
219
+
220
+ \u4EFB\u52A1: ${task}
221
+ \u6267\u884C\u7ED3\u679C: ${JSON.stringify(results.value)}
222
+
223
+ \u8BF7\u7528\u4E2D\u6587\u56DE\u7B54\uFF1A`;
224
+ const finalResult = await llm(summaryPrompt);
225
+ return {
226
+ result: finalResult,
227
+ plan: plans.value,
228
+ results: results.value
229
+ };
230
+ };
231
+ return {
232
+ plans,
233
+ currentPlan,
234
+ results,
235
+ execute
236
+ };
237
+ }
238
+ export function createMultiModalMessage(role, contents) {
239
+ return { role, content: contents };
240
+ }
241
+ export function createImageContent(source, value, mimeType) {
242
+ return {
243
+ type: "image",
244
+ ...source === "url" ? { url: value } : { base64: value },
245
+ mimeType: mimeType || (source === "base64" ? "image/png" : void 0)
246
+ };
247
+ }
248
+ export function createImageUrlContent(url, detail) {
249
+ return {
250
+ type: "image_url",
251
+ url,
252
+ mimeType: detail
253
+ };
254
+ }
255
+ export function createAudioContent(base64, mimeType = "audio/m4a") {
256
+ return {
257
+ type: "audio",
258
+ base64,
259
+ mimeType
260
+ };
261
+ }
262
+ export function createVideoContent(base64, mimeType = "video/mp4") {
263
+ return {
264
+ type: "video",
265
+ base64,
266
+ mimeType
267
+ };
268
+ }
269
+ export function createRAGSystem(config) {
270
+ const {
271
+ knowledgeBaseId,
272
+ topK = 3,
273
+ similarityThreshold = 0.5,
274
+ includeSources = true,
275
+ strategy = "similarity"
276
+ } = config;
277
+ const vectorStore = /* @__PURE__ */ new Map();
278
+ const addDocuments = async (documents) => {
279
+ const chunks = documents.map((doc, i) => ({
280
+ id: `chunk-${Date.now()}-${i}`,
281
+ content: doc.content,
282
+ metadata: doc.metadata || {},
283
+ // 简化:实际应该调用 embedding API
284
+ score: Math.random()
285
+ }));
286
+ if (knowledgeBaseId) {
287
+ const existing = vectorStore.get(knowledgeBaseId) || [];
288
+ vectorStore.set(knowledgeBaseId, [...existing, ...chunks]);
289
+ }
290
+ return chunks;
291
+ };
292
+ const retrieve = async (query2, k = topK) => {
293
+ if (!knowledgeBaseId) return [];
294
+ const chunks = vectorStore.get(knowledgeBaseId) || [];
295
+ const sorted = [...chunks].sort((a, b) => (b.score || 0) - (a.score || 0)).slice(0, k);
296
+ return sorted.filter((c) => (c.score || 0) >= similarityThreshold);
297
+ };
298
+ const query = async (question, llm) => {
299
+ const relevantDocs = await retrieve(question);
300
+ if (relevantDocs.length === 0) {
301
+ return {
302
+ answer: "\u62B1\u6B49\uFF0C\u77E5\u8BC6\u5E93\u4E2D\u6CA1\u6709\u627E\u5230\u76F8\u5173\u4FE1\u606F\u3002",
303
+ sources: [],
304
+ contextUsed: ""
305
+ };
306
+ }
307
+ const context = relevantDocs.map((doc, i) => `[\u6587\u6863 ${i + 1}]
308
+ ${doc.content}`).join("\n\n");
309
+ const prompt = `\u57FA\u4E8E\u4EE5\u4E0B\u77E5\u8BC6\u5E93\u5185\u5BB9\uFF0C\u7528\u4E2D\u6587\u56DE\u7B54\u7528\u6237\u7684\u95EE\u9898\u3002\u5982\u679C\u77E5\u8BC6\u5E93\u4E2D\u7684\u4FE1\u606F\u4E0D\u80FD\u56DE\u7B54\u95EE\u9898\uFF0C\u8BF7\u8BF4\u660E\u3002
310
+
311
+ \u77E5\u8BC6\u5E93\u5185\u5BB9:
312
+ ${context}
313
+
314
+ \u7528\u6237\u95EE\u9898: ${question}
315
+
316
+ \u8BF7\u7ED9\u51FA\u56DE\u7B54\uFF1A`;
317
+ const answer = await llm(prompt);
318
+ return {
319
+ answer,
320
+ sources: includeSources ? relevantDocs.map((doc) => ({
321
+ content: doc.content.slice(0, 200) + "...",
322
+ metadata: doc.metadata,
323
+ score: doc.score || 0
324
+ })) : [],
325
+ contextUsed: context.slice(0, 500)
326
+ };
327
+ };
328
+ const clear = () => {
329
+ if (knowledgeBaseId) {
330
+ vectorStore.delete(knowledgeBaseId);
331
+ }
332
+ };
333
+ return {
334
+ addDocuments,
335
+ retrieve,
336
+ query,
337
+ clear,
338
+ config: { topK, similarityThreshold, strategy }
339
+ };
340
+ }
341
+ export function createChainOfThought(config = { mode: "standard" }) {
342
+ const { mode = "standard", maxDepth = 5, showConfidence = false } = config;
343
+ const reasoningSteps = ref([]);
344
+ const think = async (problem, llm) => {
345
+ reasoningSteps.value = [];
346
+ const prompt = mode === "standard" ? `\u9010\u6B65\u601D\u8003\u5E76\u7ED9\u51FA\u7B54\u6848\uFF1A${problem}` : mode === "chain" ? `\u6309\u6B65\u9AA4\u5206\u6790\uFF1A${problem}` : mode === "tree" ? `\u7528\u6811\u72B6\u7ED3\u6784\u5206\u6790\uFF1A${problem}` : `\u7528\u8868\u683C\u5F62\u5F0F\u5206\u6790\uFF1A${problem}`;
347
+ const response = await llm(prompt);
348
+ const lines = response.split("\n").filter((l) => l.trim());
349
+ reasoningSteps.value = lines.map((line, i) => ({
350
+ id: `reason-${i}`,
351
+ content: line.replace(/^\d+[.)::、]\s*/, ""),
352
+ type: i === lines.length - 1 ? "conclusion" : "analysis"
353
+ }));
354
+ return {
355
+ result: response,
356
+ reasoning: reasoningSteps.value
357
+ };
358
+ };
359
+ const addStep = (step) => {
360
+ reasoningSteps.value.push({
361
+ ...step,
362
+ id: `reason-${Date.now()}-${Math.random().toString(36).slice(2)}`
363
+ });
364
+ };
365
+ const clear = () => {
366
+ reasoningSteps.value = [];
367
+ };
368
+ return {
369
+ think,
370
+ addStep,
371
+ clear,
372
+ reasoningSteps,
373
+ config: { mode, maxDepth, showConfidence }
374
+ };
375
+ }
376
+ export function createContextCompressor(config) {
377
+ const { strategy = "summary", targetTokens = 2e3, preserveKeyInfo = [] } = config;
378
+ const estimateTokens = (text) => Math.ceil(text.length / 4);
379
+ const compress = async (content, llm) => {
380
+ const isArray = Array.isArray(content);
381
+ const text = isArray ? content.map((m) => `${m.role}: ${m.content}`).join("\n") : content;
382
+ const originalTokens = estimateTokens(text);
383
+ if (originalTokens <= targetTokens) {
384
+ return {
385
+ compressedContent: text,
386
+ originalTokens,
387
+ compressedTokens: originalTokens,
388
+ compressionRatio: 1,
389
+ extractedKeyInfo: preserveKeyInfo
390
+ };
391
+ }
392
+ let compressed;
393
+ switch (strategy) {
394
+ case "summary": {
395
+ if (!llm) {
396
+ const ratio = targetTokens / originalTokens;
397
+ compressed = text.slice(0, Math.floor(text.length * ratio));
398
+ } else {
399
+ const summaryPrompt = `\u5C06\u4EE5\u4E0B\u5185\u5BB9\u538B\u7F29\u5230\u7EA6 ${targetTokens} \u5B57\u7B26\uFF0C\u4FDD\u7559\u5173\u952E\u4FE1\u606F\uFF1A
400
+
401
+ ${text}`;
402
+ compressed = await llm(summaryPrompt);
403
+ }
404
+ break;
405
+ }
406
+ case "extract": {
407
+ const sentences = text.split(/[。!?\n]/).filter((s) => s.trim());
408
+ const keySentences = sentences.slice(
409
+ 0,
410
+ Math.floor(sentences.length * (targetTokens / originalTokens))
411
+ );
412
+ compressed = keySentences.join("\u3002") + "\u3002";
413
+ break;
414
+ }
415
+ case "prune": {
416
+ const lines = text.split("\n");
417
+ const seen = /* @__PURE__ */ new Set();
418
+ const pruned = lines.filter((line) => {
419
+ const key = line.slice(0, 50);
420
+ if (seen.has(key)) return false;
421
+ seen.add(key);
422
+ return true;
423
+ });
424
+ compressed = pruned.join("\n").slice(0, targetTokens * 4);
425
+ break;
426
+ }
427
+ default:
428
+ compressed = text.slice(0, targetTokens * 4);
429
+ }
430
+ const compressedTokens = estimateTokens(compressed);
431
+ return {
432
+ compressedContent: compressed,
433
+ originalTokens,
434
+ compressedTokens,
435
+ compressionRatio: compressedTokens / originalTokens,
436
+ extractedKeyInfo: preserveKeyInfo
437
+ };
438
+ };
439
+ return {
440
+ compress,
441
+ estimateTokens,
442
+ config: { strategy, targetTokens, preserveKeyInfo }
443
+ };
444
+ }
445
+ export function createCostTracker(config = {}) {
446
+ const { monthlyBudget = 100, maxTokensPerRequest = 1e5, warningThreshold = 0.8 } = config;
447
+ const dailyUsage = {};
448
+ const totalUsage = { prompt: 0, completion: 0, total: 0 };
449
+ const PRICING = {
450
+ "gpt-4": { prompt: 30, completion: 60 },
451
+ "gpt-4-turbo": { prompt: 10, completion: 30 },
452
+ "gpt-3.5-turbo": { prompt: 0.5, completion: 1.5 },
453
+ "claude-3-opus": { prompt: 15, completion: 75 },
454
+ "claude-3-sonnet": { prompt: 3, completion: 15 },
455
+ "gemini-pro": { prompt: 0.5, completion: 1.5 }
456
+ };
457
+ const track = (usage, _model = "gpt-4") => {
458
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
459
+ if (!dailyUsage[today]) {
460
+ dailyUsage[today] = { prompt: 0, completion: 0 };
461
+ }
462
+ dailyUsage[today].prompt += usage.prompt;
463
+ dailyUsage[today].completion += usage.completion;
464
+ totalUsage.prompt += usage.prompt;
465
+ totalUsage.completion += usage.completion;
466
+ totalUsage.total += usage.prompt + usage.completion;
467
+ };
468
+ const calculateCost = (usage, model) => {
469
+ const pricing = PRICING[model] || PRICING["gpt-4"];
470
+ return pricing.prompt * usage.prompt / 1e6 + pricing.completion * usage.completion / 1e6;
471
+ };
472
+ const getStatus = () => {
473
+ const totalCost = calculateCost(totalUsage, "gpt-4");
474
+ const budgetRemaining = monthlyBudget - totalCost;
475
+ return {
476
+ totalCost,
477
+ dailyCost: Object.fromEntries(
478
+ Object.entries(dailyUsage).map(([date, usage]) => [
479
+ date,
480
+ calculateCost({ prompt: usage.prompt, completion: usage.completion, total: 0 }, "gpt-4")
481
+ ])
482
+ ),
483
+ usage: { ...totalUsage },
484
+ budget: { monthlyBudget, warningThreshold },
485
+ remaining: budgetRemaining
486
+ };
487
+ };
488
+ const isOverBudget = () => {
489
+ const status = getStatus();
490
+ return status.remaining < 0;
491
+ };
492
+ const shouldWarn = () => {
493
+ const status = getStatus();
494
+ return status.totalCost >= monthlyBudget * warningThreshold;
495
+ };
496
+ const checkRequestLimit = (tokens) => {
497
+ if (tokens > maxTokensPerRequest) {
498
+ return { allowed: false, reason: `\u8BF7\u6C42 token \u6570 ${tokens} \u8D85\u8FC7\u9650\u5236 ${maxTokensPerRequest}` };
499
+ }
500
+ const status = getStatus();
501
+ const estimatedCost = tokens / 1e6 * 30;
502
+ if (status.remaining - estimatedCost < 0) {
503
+ return { allowed: false, reason: "\u9884\u7B97\u4E0D\u8DB3" };
504
+ }
505
+ return { allowed: true };
506
+ };
507
+ const reset = () => {
508
+ Object.keys(dailyUsage).forEach((key) => delete dailyUsage[key]);
509
+ totalUsage.prompt = 0;
510
+ totalUsage.completion = 0;
511
+ totalUsage.total = 0;
512
+ };
513
+ return {
514
+ track,
515
+ calculateCost,
516
+ getStatus,
517
+ isOverBudget,
518
+ shouldWarn,
519
+ checkRequestLimit,
520
+ reset,
521
+ pricing: PRICING,
522
+ config: { monthlyBudget, maxTokensPerRequest, warningThreshold }
523
+ };
524
+ }
525
+ export function createTracer() {
526
+ const spans = ref([]);
527
+ const events = ref([]);
528
+ const activeSpans = /* @__PURE__ */ new Map();
529
+ const startSpan = (name, attributes = {}) => {
530
+ const id = `span-${Date.now()}-${Math.random().toString(36).slice(2)}`;
531
+ const span = {
532
+ id,
533
+ name,
534
+ startTime: /* @__PURE__ */ new Date(),
535
+ events: [],
536
+ attributes,
537
+ children: []
538
+ };
539
+ spans.value.push(span);
540
+ activeSpans.set(id, span);
541
+ addEvent({
542
+ type: "custom",
543
+ data: { action: "span_start", name }
544
+ });
545
+ return id;
546
+ };
547
+ const endSpan = (id, attributes = {}) => {
548
+ const span = activeSpans.get(id);
549
+ if (span) {
550
+ span.endTime = /* @__PURE__ */ new Date();
551
+ span.attributes = { ...span.attributes, ...attributes };
552
+ activeSpans.delete(id);
553
+ addEvent({
554
+ type: "custom",
555
+ data: {
556
+ action: "span_end",
557
+ name: span.name,
558
+ duration: span.endTime.getTime() - span.startTime.getTime()
559
+ }
560
+ });
561
+ }
562
+ };
563
+ const addEvent = (event) => {
564
+ events.value.push({
565
+ ...event,
566
+ id: `event-${Date.now()}-${Math.random().toString(36).slice(2)}`,
567
+ timestamp: /* @__PURE__ */ new Date()
568
+ });
569
+ };
570
+ const recordRequest = (config, spanId) => {
571
+ const event = {
572
+ id: `req-${Date.now()}`,
573
+ type: "request",
574
+ timestamp: /* @__PURE__ */ new Date(),
575
+ data: config,
576
+ parentId: spanId
577
+ };
578
+ events.value.push(event);
579
+ };
580
+ const recordResponse = (response, duration, spanId) => {
581
+ const event = {
582
+ id: `res-${Date.now()}`,
583
+ type: "response",
584
+ timestamp: /* @__PURE__ */ new Date(),
585
+ data: { response, duration },
586
+ parentId: spanId
587
+ };
588
+ events.value.push(event);
589
+ };
590
+ const recordError = (error, context, spanId) => {
591
+ const event = {
592
+ id: `err-${Date.now()}`,
593
+ type: "error",
594
+ timestamp: /* @__PURE__ */ new Date(),
595
+ data: { message: error.message, stack: error.stack, ...context },
596
+ parentId: spanId
597
+ };
598
+ events.value.push(event);
599
+ };
600
+ const getEvents = () => events.value;
601
+ const getSpans = () => spans.value;
602
+ const clear = () => {
603
+ spans.value = [];
604
+ events.value = [];
605
+ activeSpans.clear();
606
+ };
607
+ const exportJSON = () => JSON.stringify({ spans: spans.value, events: events.value }, null, 2);
608
+ return {
609
+ startSpan,
610
+ endSpan,
611
+ addEvent,
612
+ recordRequest,
613
+ recordResponse,
614
+ recordError,
615
+ getEvents,
616
+ getSpans,
617
+ clear,
618
+ exportJSON
619
+ };
620
+ }
621
+ export function createSafetyFilter(config) {
622
+ const { rules = [] } = config;
623
+ const check = async (content) => {
624
+ const violations = [];
625
+ for (const rule of rules) {
626
+ let passed = true;
627
+ let replacedContent = content;
628
+ if (rule.pattern) {
629
+ const regex = typeof rule.pattern === "string" ? new RegExp(rule.pattern, "gi") : rule.pattern;
630
+ passed = !regex.test(content);
631
+ replacedContent = content.replace(regex, "***");
632
+ }
633
+ if (rule.customCheck && passed) {
634
+ passed = !await rule.customCheck(content);
635
+ }
636
+ if (!passed) {
637
+ violations.push({
638
+ rule,
639
+ content,
640
+ action: rule.action === "replace" && replacedContent !== content ? "replaced" : rule.action === "block" ? "blocked" : "warned",
641
+ replacedContent: rule.action === "replace" ? replacedContent : void 0
642
+ });
643
+ if (rule.action === "block") {
644
+ break;
645
+ }
646
+ }
647
+ }
648
+ return {
649
+ passed: violations.length === 0 || violations.every((v) => v.action !== "blocked"),
650
+ violations
651
+ };
652
+ };
653
+ const addRule = (rule) => {
654
+ rules.push(rule);
655
+ };
656
+ const removeRule = (ruleId) => {
657
+ const index = rules.findIndex((r) => r.id === ruleId);
658
+ if (index > -1) {
659
+ rules.splice(index, 1);
660
+ }
661
+ };
662
+ const createPresetRules = (type) => {
663
+ const presets = {
664
+ strict: [
665
+ {
666
+ id: "s1",
667
+ name: "\u653F\u6CBB\u654F\u611F",
668
+ type: "content_filter",
669
+ pattern: "\u653F\u6CBB|\u9886\u5BFC\u4EBA",
670
+ action: "block"
671
+ },
672
+ {
673
+ id: "s2",
674
+ name: "\u66B4\u529B\u5185\u5BB9",
675
+ type: "content_filter",
676
+ pattern: "\u66B4\u529B|\u8840\u8165",
677
+ action: "block"
678
+ },
679
+ {
680
+ id: "s3",
681
+ name: "\u6076\u610F\u8F6F\u4EF6",
682
+ type: "content_filter",
683
+ pattern: "\u75C5\u6BD2|\u6728\u9A6C",
684
+ action: "block"
685
+ }
686
+ ],
687
+ moderate: [
688
+ {
689
+ id: "m1",
690
+ name: "\u653F\u6CBB\u654F\u611F",
691
+ type: "content_filter",
692
+ pattern: "\u653F\u6CBB|\u9886\u5BFC\u4EBA",
693
+ action: "warn"
694
+ },
695
+ { id: "m2", name: "\u66B4\u529B\u5185\u5BB9", type: "content_filter", pattern: "\u66B4\u529B|\u8840\u8165", action: "warn" }
696
+ ],
697
+ lenient: [
698
+ {
699
+ id: "l1",
700
+ name: "\u6076\u610F\u5185\u5BB9",
701
+ type: "content_filter",
702
+ pattern: "\u75C5\u6BD2|\u6728\u9A6C",
703
+ action: "block"
704
+ }
705
+ ]
706
+ };
707
+ rules.push(...presets[type]);
708
+ };
709
+ return {
710
+ check,
711
+ addRule,
712
+ removeRule,
713
+ createPresetRules,
714
+ rules
715
+ };
716
+ }
717
+ export function fromZodSchema(schema2) {
718
+ return {
719
+ type: "object",
720
+ schema: schema2,
721
+ toJSONSchema: () => schema2
722
+ };
723
+ }
724
+ export function createJSONSchema(definition) {
725
+ return definition;
726
+ }
727
+ export const schema = {
728
+ string: (description) => ({
729
+ type: "string",
730
+ description
731
+ }),
732
+ number: (description) => ({
733
+ type: "number",
734
+ description
735
+ }),
736
+ boolean: (description) => ({
737
+ type: "boolean",
738
+ description
739
+ }),
740
+ enum: (values, description) => ({
741
+ type: "string",
742
+ enum: values,
743
+ description
744
+ }),
745
+ array: (items) => ({
746
+ type: "array",
747
+ items
748
+ }),
749
+ object: (properties, required = []) => ({
750
+ type: "object",
751
+ properties,
752
+ required
753
+ })
754
+ };
755
+ export function parseStructuredOutput(output, _schema) {
756
+ try {
757
+ const jsonMatch = output.match(/\{[\s\S]*\}/);
758
+ if (jsonMatch) {
759
+ return JSON.parse(jsonMatch[0]);
760
+ }
761
+ } catch {
762
+ return null;
763
+ }
764
+ return null;
765
+ }