feat: 添加 zod 依赖,重构工具和审批示例,删除不再使用的调试文件

This commit is contained in:
2026-02-03 00:40:42 +08:00
parent 96ecbd026e
commit 9a8dedd0ab
7 changed files with 260 additions and 80 deletions

View File

@@ -5,7 +5,8 @@
"private": true,
"devDependencies": {
"@types/bun": "latest",
"dotenv": "^17.2.3"
"dotenv": "^17.2.3",
"zod": "^4.3.6"
},
"peerDependencies": {
"typescript": "^5"

13
pnpm-lock.yaml generated
View File

@@ -23,6 +23,9 @@ importers:
ai:
specifier: ^6.0.67
version: 6.0.67(zod@4.3.6)
typescript:
specifier: ^5
version: 5.9.3
devDependencies:
'@types/bun':
specifier: latest
@@ -30,6 +33,9 @@ importers:
dotenv:
specifier: ^17.2.3
version: 17.2.3
zod:
specifier: ^4.3.6
version: 4.3.6
packages:
@@ -119,6 +125,11 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
@@ -211,6 +222,8 @@ snapshots:
tslib@2.8.1: {}
typescript@5.9.3: {}
undici-types@7.16.0: {}
zod@4.3.6: {}

51
src/common.ts Normal file
View File

@@ -0,0 +1,51 @@
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { createAnthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';
import 'dotenv/config';
export function resolveEnvVars(value: string): string {
return value.replace(/{env:([^}]+)}/g, (_, varName) => {
const envValue = process.env[varName];
if (!envValue) {
throw new Error(`Environment variable ${varName} is not set`);
}
return envValue;
});
}
export const models = {
'doubao-ark-code-latest': 'doubao-ark-code-latest',
'GLM-4.7': 'GLM-4.7',
'MiniMax-M2.1': 'MiniMax-M2.1',
'qwen3-coder-plus': 'qwen3-coder-plus',
'hunyuan-a13b': 'hunyuan-a13b',
}
export const bailian = createOpenAICompatible({
baseURL: 'https://coding.dashscope.aliyuncs.com/v1',
name: 'custom-bailian',
apiKey: process.env.BAILIAN_CODE_API_KEY!,
});
export const zhipu = createOpenAICompatible({
baseURL: 'https://open.bigmodel.cn/api/coding/paas/v4',
name: 'custom-zhipu',
apiKey: process.env.ZHIPU_API_KEY!,
});
export const minimax = createAnthropic({
baseURL: 'https://api.minimaxi.com/anthropic/v1',
name: 'custom-minimax',
apiKey: process.env.MINIMAX_API_KEY!,
});
export const doubao = createOpenAICompatible({
baseURL: 'https://ark.cn-beijing.volces.com/api/coding/v3',
name: 'custom-doubao',
apiKey: process.env.DOUBAO_API_KEY!,
});
export const cnb = createOpenAICompatible({
baseURL: resolveEnvVars('https://api.cnb.cool/{env:CNB_REPO_SLUG}/-/ai/'),
name: 'custom-cnb',
apiKey: process.env.CNB_API_KEY!,
});

View File

@@ -1,33 +0,0 @@
import 'dotenv/config';
const endpoint = 'https://coding.dashscope.aliyuncs.com/apps/anthropic/messages';
// 测试不同的认证方式
const authMethods = [
{ name: 'x-api-key', headers: { 'x-api-key': process.env.BAILIAN_CODE_API_KEY! } },
{ name: 'Authorization Bearer', headers: { 'Authorization': `Bearer ${process.env.BAILIAN_CODE_API_KEY!}` } },
{ name: 'Authorization Bearer sk-', headers: { 'Authorization': `Bearer sk-${process.env.BAILIAN_CODE_API_KEY!}` } },
{ name: 'Authorization sk-', headers: { 'Authorization': `sk-${process.env.BAILIAN_CODE_API_KEY!}` } },
];
for (const { name, headers } of authMethods) {
console.log(`\nTesting: ${name}`);
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...headers,
},
body: JSON.stringify({
model: 'qwen3-coder-plus',
max_tokens: 100,
messages: [{ role: 'user', content: 'Hello' }],
}),
});
console.log(`Status: ${response.status}`);
const text = await response.text();
if (text) {
console.log(`Response: ${text.substring(0, 300)}`);
}
}

View File

@@ -1,46 +0,0 @@
import 'dotenv/config';
function resolveEnvVars(value: string): string {
return value.replace(/{env:([^}]+)}/g, (_, varName) => {
const envValue = process.env[varName];
if (!envValue) {
throw new Error(`Environment variable ${varName} is not set`);
}
return envValue;
});
}
const endpoint = 'https://cnb.cool/kevisual/cnb/-/ai/chat/completions';
const apiKey = process.env.CNB_API_KEY!;
console.log('Endpoint:', endpoint);
console.log('API Key:', apiKey ? 'Set' : 'Not set');
console.log('API Key length:', apiKey.length);
console.log('API Key prefix:', apiKey.substring(0, 10) + '...');
// 尝试不同的认证头
const authHeaders = [
{ name: 'Authorization Bearer', headers: { 'Authorization': `Bearer ${apiKey}` } },
{ name: 'Authorization (no Bearer)', headers: { 'Authorization': apiKey } },
{ name: 'x-api-key', headers: { 'x-api-key': apiKey } },
{ name: 'api-key', headers: { 'api-key': apiKey } },
];
for (const { name, headers } of authHeaders) {
console.log(`\n=== Testing: ${name} ===`);
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...headers,
},
body: JSON.stringify({
model: 'hunyuan-a13b',
messages: [{ role: 'user', content: '你好' }],
}),
});
console.log(`Status: ${response.status}`);
const text = await response.text();
console.log('Response:', text);
}

60
src/tools/a.ts Normal file
View File

@@ -0,0 +1,60 @@
import { anthropic } from '@ai-sdk/anthropic';
import { generateText, stepCountIs, tool, } from 'ai';
import { zhipu, models, minimax } from '../common.ts';
import { z } from 'zod'
const runCommand = async (command: string): Promise<string> => {
// Implementation to execute the bash command and return the output
// return `Executed command: ${command}`; // Placeholder implementation
return new Promise((resolve, reject) => {
// const { exec } = require('child_process');
// exec(command, (error: any, stdout: string, stderr: string) => {
// if (error) {
// reject(`Error: ${stderr}`);
// } else {
// resolve(stdout);
// }
// });
resolve(`Executed command: ${command}`); // Placeholder implementation
});
}
const result = await generateText({
// model: minimax(models['MiniMax-M2.1']),
model: zhipu(models['GLM-4.7']),
tools: {
// bash: minimax.tools
time: tool({
description: '获取当前系统时间。',
inputSchema: z.object({}),
execute: async (_args: {}) => {
console.log('获取当前系统时间');
return new Date().toString();
},
}),
timeZone: tool({
description: '将指定时间字符串转换为目标时区格式。必须先使用 time 工具获取时间字符串作为输入。',
inputSchema: z.object({
zone: z.string().describe('IANA 时区名称,例如 "America/New_York"。'),
date: z.string().describe('从 time 工具获取的日期字符串。'),
}),
// needsApproval: true,
execute: async (args: { zone: string, date: string }) => {
const date = new Date(args.date);
console.log('格式化日期:', args.date, '到时区:', args.zone);
const options: Intl.DateTimeFormatOptions = {
timeZone: args.zone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
};
return new Intl.DateTimeFormat('zh-CN', options).format(date);
},
}),
},
stopWhen: stepCountIs(5),
prompt: '我的时区是 America/New_York请显示我当前系统在该时区的时间。',
});
console.log('Response:', result.text);

View File

@@ -0,0 +1,134 @@
import { generateText, tool, type ModelMessage, type ToolApprovalResponse } from 'ai';
import { z } from 'zod';
import { zhipu, models } from '../common.ts';
// 定义一个需要审批的工具
const timeZone = tool({
description: '将指定时间字符串转换为目标时区格式。必须先使用 time 工具获取时间字符串作为输入。',
inputSchema: z.object({
zone: z.string().describe('IANA 时区名称,例如 "America/New_York"。'),
date: z.string().describe('从 time 工具获取的日期字符串。'),
}),
// needsApproval: false, // 设置需要审批
execute: async ({ zone, date }) => {
const dateObj = new Date(date);
console.log('格式化日期:', date, '到时区:', zone);
const options: Intl.DateTimeFormatOptions = {
timeZone: zone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
};
return new Intl.DateTimeFormat('zh-CN', options).format(dateObj);
},
});
const time = tool({
description: '获取当前系统时间。',
inputSchema: z.object({}),
// needsApproval: true,
execute: async () => {
console.log('获取当前系统时间');
return new Date().toString();
},
});
// 模拟审批函数 - 在实际应用中,这可能是用户通过 UI 确认
function mockAskUserApproval(toolCall: { toolName: string; input: any }): boolean {
console.log('=== 待审批的工具调用 ===');
console.log(`工具: ${toolCall.toolName}`);
console.log(`输入: ${JSON.stringify(toolCall.input)}`);
console.log('==========================');
// 在实际应用中,这里会等待用户输入
// 这里模拟用户总是批准
console.log('模拟用户审批: 批准\n');
return true;
}
// 模拟获取拒绝原因
function mockAskDenialReason(): string {
// 在实际应用中,这里会等待用户输入拒绝原因
return '用户取消了操作';
}
async function runApprovalExample() {
// 初始消息
const messages: ModelMessage[] = [
{ role: 'user', content: '我的时区是 America/New_York请显示我当前系统在该时区的时间。' },
];
// 第一步: 发起请求
console.log('=== 第一步: 发起请求 ===\n');
let result = await generateText({
model: zhipu(models['GLM-4.7']),
tools: { time, timeZone },
prompt: '我的时区是 America/New_York请显示我当前系统在该时区的时间。',
});
console.log('=== 第一步结果 ===\n', result.text, '\n');
// 添加响应消息到历史
messages.push(...result.response.messages);
// 检查是否有需要审批的工具调用
let approvalRequests: Array<{ type: 'tool-approval-request', approvalId: string, toolCall: any }> = [];
for (const part of result.content) {
if (part.type === 'tool-approval-request') {
approvalRequests.push(part);
}
}
// 如果有审批请求,处理它们
while (approvalRequests.length > 0) {
console.log('=== 处理审批请求 ===\n');
// 收集审批响应
const approvalResponses: ToolApprovalResponse[] = [];
for (const request of approvalRequests) {
const approved = mockAskUserApproval({
toolName: request.toolCall.toolName,
input: request.toolCall.input,
});
approvalResponses.push({
type: 'tool-approval-response',
approvalId: request.approvalId,
approved,
reason: approved ? '用户批准了操作' : mockAskDenialReason(),
});
}
// 将审批响应添加到消息
messages.push({ role: 'tool', content: approvalResponses });
// 第二步: 使用审批响应继续执行
console.log('=== 第二步: 使用审批响应继续 ===\n');
result = await generateText({
model: zhipu(models['GLM-4.7']),
tools: { time, timeZone },
messages,
});
// 添加响应消息到历史
messages.push(...result.response.messages);
// 检查是否还有新的审批请求(可能有多轮审批)
approvalRequests = [];
for (const part of result.content) {
if (part.type === 'tool-approval-request') {
approvalRequests.push(part);
}
}
}
console.log('=== 最终结果 ===');
console.log('响应:', result.text);
console.log('步骤数:', result.steps.length);
}
runApprovalExample().catch(console.error);