feat: 添加 zod 依赖,重构工具和审批示例,删除不再使用的调试文件
This commit is contained in:
@@ -5,7 +5,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"dotenv": "^17.2.3"
|
"dotenv": "^17.2.3",
|
||||||
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|||||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -23,6 +23,9 @@ importers:
|
|||||||
ai:
|
ai:
|
||||||
specifier: ^6.0.67
|
specifier: ^6.0.67
|
||||||
version: 6.0.67(zod@4.3.6)
|
version: 6.0.67(zod@4.3.6)
|
||||||
|
typescript:
|
||||||
|
specifier: ^5
|
||||||
|
version: 5.9.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/bun':
|
'@types/bun':
|
||||||
specifier: latest
|
specifier: latest
|
||||||
@@ -30,6 +33,9 @@ importers:
|
|||||||
dotenv:
|
dotenv:
|
||||||
specifier: ^17.2.3
|
specifier: ^17.2.3
|
||||||
version: 17.2.3
|
version: 17.2.3
|
||||||
|
zod:
|
||||||
|
specifier: ^4.3.6
|
||||||
|
version: 4.3.6
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -119,6 +125,11 @@ packages:
|
|||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
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:
|
undici-types@7.16.0:
|
||||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||||
|
|
||||||
@@ -211,6 +222,8 @@ snapshots:
|
|||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
undici-types@7.16.0: {}
|
undici-types@7.16.0: {}
|
||||||
|
|
||||||
zod@4.3.6: {}
|
zod@4.3.6: {}
|
||||||
|
|||||||
51
src/common.ts
Normal file
51
src/common.ts
Normal 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!,
|
||||||
|
});
|
||||||
@@ -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)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
60
src/tools/a.ts
Normal 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);
|
||||||
134
src/tools/approval-example.ts
Normal file
134
src/tools/approval-example.ts
Normal 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);
|
||||||
Reference in New Issue
Block a user