UNPKG

z-ai-web-dev-sdk

Version:

SDK for Z AI Web Dev

383 lines (382 loc) 13.3 kB
import fs from 'fs/promises'; import path from 'path'; import os from 'os'; // Function to load configuration with specified priority const loadConfig = async () => { const homeDir = os.homedir(); const configPaths = [ path.join(process.cwd(), '.z-ai-config'), path.join(homeDir, '.z-ai-config'), '/etc/.z-ai-config' ]; for (const filePath of configPaths) { try { const configStr = await fs.readFile(filePath, 'utf-8'); const config = JSON.parse(configStr); if (config.baseUrl && config.apiKey) { return config; } } catch (error) { if (error.code !== 'ENOENT') { console.error(`Error reading or parsing config file at ${filePath}:`, error); } // Continue to the next file if it doesn't exist or is invalid } } throw new Error('Configuration file not found or invalid. Please create .z-ai-config in your project, home directory, or /etc.'); }; class ZAI { constructor(config) { this.config = config; this.chat = { completions: { create: this.createChatCompletion.bind(this), createVision: this.createChatCompletionVision.bind(this), }, }; this.audio = { tts: { create: this.createAudioTTS.bind(this), }, asr: { create: this.createAudioASR.bind(this), }, }; this.images = { generations: { create: this.createImageGeneration.bind(this), }, }; this.video = { generations: { create: this.createVideoGeneration.bind(this), }, }; this.async = { result: { query: this.queryAsyncResult.bind(this), }, }; this.functions = { invoke: this.invokeFunction.bind(this), }; } static async create() { const config = await loadConfig(); return new ZAI(config); } async createChatCompletion(body) { const { baseUrl, chatId, userId, apiKey } = this.config; const url = `${baseUrl}/chat/completions`; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'X-Z-AI-From': 'Z', }; if (chatId) { headers['X-Chat-Id'] = chatId; } if (userId) { headers['X-User-Id'] = userId; } // 设置 thinking 默认值为 disabled const requestBody = { ...body, thinking: body.thinking || { type: 'disabled' }, }; try { const response = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(requestBody), }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`API request failed with status ${response.status}: ${errorBody}`); } // 处理流式响应 const contentType = response.headers.get('content-type') || ''; if (requestBody.stream && (contentType.includes('text/event-stream') || contentType.includes('text/plain'))) { // 返回 ReadableStream 供调用者处理 return response.body; } return await response.json(); } catch (error) { console.error('Failed to make API request:', error); throw error; } } async createChatCompletionVision(body) { const { baseUrl, chatId, userId, apiKey } = this.config; const url = `${baseUrl}/chat/completions/vision`; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'X-Z-AI-From': 'Z', }; if (chatId) { headers['X-Chat-Id'] = chatId; } if (userId) { headers['X-User-Id'] = userId; } // 设置 thinking 默认值为 disabled const requestBody = { ...body, thinking: body.thinking || { type: 'disabled' }, }; try { const response = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(requestBody), }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`API request failed with status ${response.status}: ${errorBody}`); } // 处理流式响应 const contentType = response.headers.get('content-type') || ''; if (requestBody.stream && (contentType.includes('text/event-stream') || contentType.includes('text/plain'))) { // 返回 ReadableStream 供调用者处理 return response.body; } return await response.json(); } catch (error) { console.error('Failed to make vision API request:', error); throw error; } } async createAudioTTS(body) { const { baseUrl, chatId, userId, apiKey } = this.config; const url = `${baseUrl}/audio/tts`; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'X-Z-AI-From': 'Z', }; if (chatId) { headers['X-Chat-Id'] = chatId; } if (userId) { headers['X-User-Id'] = userId; } try { const response = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(body), }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`API request failed with status ${response.status}: ${errorBody}`); } // 直接返回 Response 对象,让调用者自己处理 // 调用者可以: // - 获取 headers(包括 content-type): response.headers.get('content-type') // - 处理 body: response.arrayBuffer(), response.blob(), response.text() 等 return response; } catch (error) { console.error('Failed to make TTS API request:', error); throw error; } } async createAudioASR(body) { const { baseUrl, chatId, userId, apiKey } = this.config; const url = `${baseUrl}/audio/asr`; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'X-Z-AI-From': 'Z', }; if (chatId) { headers['X-Chat-Id'] = chatId; } if (userId) { headers['X-User-Id'] = userId; } try { const response = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(body), }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`API request failed with status ${response.status}: ${errorBody}`); } return await response.json(); } catch (error) { console.error('Failed to make ASR API request:', error); throw error; } } async createImageGeneration(body) { const { baseUrl, apiKey, chatId, userId } = this.config; const url = `${baseUrl}/images/generations`; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'X-Z-AI-From': 'Z', }; if (chatId) { headers['X-Chat-Id'] = chatId; } if (userId) { headers['X-User-Id'] = userId; } // Add user_id to body if available in config const requestBody = { ...body }; // if (this.config.userId && !requestBody.user_id) { // requestBody.user_id = this.config.userId; // } try { const response = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(requestBody), }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`API request failed with status ${response.status}: ${errorBody}`); } const result = await response.json(); // Convert image URLs to base64 const processedData = await Promise.all(result.data.map(async (item) => { if (item.url) { const base64 = await this.downloadImageAsBase64(item.url); return { base64, format: "png" }; } return item; })); return { ...result, data: processedData, }; } catch (error) { console.error('Failed to make image generation request:', error); throw error; } } async downloadImageAsBase64(imageUrl) { try { const response = await fetch(imageUrl); if (!response.ok) { throw new Error(`Failed to download image: ${response.status}`); } const arrayBuffer = await response.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); const base64 = buffer.toString('base64'); return `${base64}`; } catch (error) { console.error('Failed to download and convert image to base64:', error); throw error; } } async createVideoGeneration(body) { const { baseUrl, apiKey, chatId, userId } = this.config; const url = `${baseUrl}/video/generation`; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'X-Z-AI-From': 'Z', }; if (chatId) { headers['X-Chat-Id'] = chatId; } if (userId) { headers['X-User-Id'] = userId; } try { const response = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(body), }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`API request failed with status ${response.status}: ${errorBody}`); } return await response.json(); } catch (error) { console.error('Failed to make video generation request:', error); throw error; } } async queryAsyncResult(taskId) { const { baseUrl, apiKey, chatId, userId } = this.config; const url = `${baseUrl}/async-result?id=${encodeURIComponent(taskId)}`; const headers = { 'Authorization': `Bearer ${apiKey}`, 'X-Z-AI-From': 'Z', }; if (chatId) { headers['X-Chat-Id'] = chatId; } if (userId) { headers['X-User-Id'] = userId; } try { const response = await fetch(url, { method: 'GET', headers: headers, }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`API request failed with status ${response.status}: ${errorBody}`); } return await response.json(); } catch (error) { console.error('Failed to query async result:', error); throw error; } } // 通用函数调用实现 async invokeFunction(function_name, args) { const { baseUrl, apiKey, chatId, userId } = this.config; const url = `${baseUrl}/functions/invoke`; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'X-Z-AI-From': 'Z', }; if (chatId) { headers['X-Chat-Id'] = chatId; } if (userId) { headers['X-User-Id'] = userId; } const body = { function_name, arguments: args, }; try { const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body), }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`Function invoke failed with status ${response.status}: ${errorBody}`); } const result = await response.json(); // 假设后端返回格式为 { result: ... } return result.result; } catch (error) { console.error('Failed to invoke remote function:', error); throw error; } } } export default ZAI;