@@ -24,7 +24,7 @@ const FAKE_HEADERS = {
'Accept-Encoding' : 'gzip, deflate, br, zstd' ,
'Accept-Language' : 'zh-CN,zh;q=0.9' ,
'Origin' : 'https://kimi.moonshot.cn' ,
'Cookie': util . generateCookie( ) ,
// 'Cookie': util. generateCookie(),
'R-Timezone' : 'Asia/Shanghai' ,
'Sec-Ch-Ua' : '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"' ,
'Sec-Ch-Ua-Mobile' : '?0' ,
@@ -57,7 +57,7 @@ async function requestToken(refreshToken: string) {
const result = await axios . get ( 'https://kimi.moonshot.cn/api/auth/token/refresh' , {
headers : {
Authorization : ` Bearer ${ refreshToken } ` ,
Referer : 'https://kimi.moonshot.cn' ,
Referer : 'https://kimi.moonshot.cn/ ' ,
. . . FAKE_HEADERS
} ,
timeout : 15000 ,
@@ -74,7 +74,7 @@ async function requestToken(refreshToken: string) {
}
} ) ( )
. then ( result = > {
if ( accessTokenRequestQueueMap [ refreshToken ] ) {
if ( accessTokenRequestQueueMap [ refreshToken ] ) {
accessTokenRequestQueueMap [ refreshToken ] . forEach ( resolve = > resolve ( result ) ) ;
delete accessTokenRequestQueueMap [ refreshToken ] ;
}
@@ -82,13 +82,13 @@ async function requestToken(refreshToken: string) {
return result ;
} )
. catch ( err = > {
if ( accessTokenRequestQueueMap [ refreshToken ] ) {
if ( accessTokenRequestQueueMap [ refreshToken ] ) {
accessTokenRequestQueueMap [ refreshToken ] . forEach ( resolve = > resolve ( err ) ) ;
delete accessTokenRequestQueueMap [ refreshToken ] ;
}
return err ;
} ) ;
if ( _ . isError ( result ) )
if ( _ . isError ( result ) )
throw result ;
return result ;
}
@@ -128,7 +128,7 @@ async function createConversation(name: string, refreshToken: string) {
} , {
headers : {
Authorization : ` Bearer ${ token } ` ,
Referer : 'https://kimi.moonshot.cn' ,
Referer : 'https://kimi.moonshot.cn/ ' ,
. . . FAKE_HEADERS
} ,
timeout : 15000 ,
@@ -164,12 +164,13 @@ async function removeConversation(convId: string, refreshToken: string) {
/**
* 同步对话补全
*
* @param model 模型名称
* @param messages 参考gpt系列消息格式, 多轮对话请完整提供上下文
* @param refreshToken 用于刷新access_token的refresh_token
* @param useSearch 是否开启联网搜索
* @param retryCount 重试次数
*/
async function createCompletion ( messages : any [ ] , refreshToken : string , useSearch = true , retryCount = 0 ) {
async function createCompletion ( model = MODEL_NAME , messages : any [ ] , refreshToken : string , useSearch = true , retryCount = 0 ) {
return ( async ( ) = > {
logger . info ( messages ) ;
@@ -177,6 +178,10 @@ async function createCompletion(messages: any[], refreshToken: string, useSearch
const refFileUrls = extractRefFileUrls ( messages ) ;
const refs = refFileUrls . length ? await Promise . all ( refFileUrls . map ( fileUrl = > uploadFile ( fileUrl , refreshToken ) ) ) : [ ] ;
// 伪装调用获取用户信息
fakeRequest ( refreshToken )
. catch ( err = > logger . error ( err ) ) ;
// 创建会话
const convId = await createConversation ( ` cmpl- ${ util . uuid ( false ) } ` , refreshToken ) ;
@@ -200,7 +205,7 @@ async function createCompletion(messages: any[], refreshToken: string, useSearch
const streamStartTime = util . timestamp ( ) ;
// 接收流为输出文本
const answer = await receiveStream ( convId , result . data ) ;
const answer = await receiveStream ( model , convId, result . data ) ;
logger . success ( ` Stream has completed transfer ${ util . timestamp ( ) - streamStartTime } ms ` ) ;
// 异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略
@@ -210,12 +215,12 @@ async function createCompletion(messages: any[], refreshToken: string, useSearch
return answer ;
} ) ( )
. catch ( err = > {
if ( retryCount < MAX_RETRY_COUNT ) {
if ( retryCount < MAX_RETRY_COUNT ) {
logger . error ( ` Stream response error: ${ err . message } ` ) ;
logger . warn ( ` Try again after ${ RETRY_DELAY / 1000 } s... ` ) ;
return ( async ( ) = > {
await new Promise ( resolve = > setTimeout ( resolve , RETRY_DELAY ) ) ;
return createCompletion ( messages , refreshToken , useSearch , retryCount + 1 ) ;
return createCompletion ( model , messages, refreshToken , useSearch , retryCount + 1 ) ;
} ) ( ) ;
}
throw err ;
@@ -225,12 +230,13 @@ async function createCompletion(messages: any[], refreshToken: string, useSearch
/**
* 流式对话补全
*
* @param model 模型名称
* @param messages 参考gpt系列消息格式, 多轮对话请完整提供上下文
* @param refreshToken 用于刷新access_token的refresh_token
* @param useSearch 是否开启联网搜索
* @param retryCount 重试次数
*/
async function createCompletionStream ( messages : any [ ] , refreshToken : string , useSearch = true , retryCount = 0 ) {
async function createCompletionStream ( model = MODEL_NAME , messages : any [ ] , refreshToken : string , useSearch = true , retryCount = 0 ) {
return ( async ( ) = > {
logger . info ( messages ) ;
@@ -238,6 +244,10 @@ async function createCompletionStream(messages: any[], refreshToken: string, use
const refFileUrls = extractRefFileUrls ( messages ) ;
const refs = refFileUrls . length ? await Promise . all ( refFileUrls . map ( fileUrl = > uploadFile ( fileUrl , refreshToken ) ) ) : [ ] ;
// 伪装调用获取用户信息
fakeRequest ( refreshToken )
. catch ( err = > logger . error ( err ) ) ;
// 创建会话
const convId = await createConversation ( ` cmpl- ${ util . uuid ( false ) } ` , refreshToken ) ;
@@ -260,7 +270,7 @@ async function createCompletionStream(messages: any[], refreshToken: string, use
} ) ;
const streamStartTime = util . timestamp ( ) ;
// 创建转换流将消息格式转换为gpt兼容格式
return createTransStream ( convId , result . data , ( ) = > {
return createTransStream ( model , convId, result . data , ( ) = > {
logger . success ( ` Stream has completed transfer ${ util . timestamp ( ) - streamStartTime } ms ` ) ;
// 流传输结束后异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略
removeConversation ( convId , refreshToken )
@@ -268,18 +278,50 @@ async function createCompletionStream(messages: any[], refreshToken: string, use
} ) ;
} ) ( )
. catch ( err = > {
if ( retryCount < MAX_RETRY_COUNT ) {
if ( retryCount < MAX_RETRY_COUNT ) {
logger . error ( ` Stream response error: ${ err . message } ` ) ;
logger . warn ( ` Try again after ${ RETRY_DELAY / 1000 } s... ` ) ;
return ( async ( ) = > {
await new Promise ( resolve = > setTimeout ( resolve , RETRY_DELAY ) ) ;
return createCompletionStream ( messages , refreshToken , useSearch , retryCount + 1 ) ;
return createCompletionStream ( model , messages, refreshToken , useSearch , retryCount + 1 ) ;
} ) ( ) ;
}
throw err ;
} ) ;
}
/**
* 调用一些接口伪装访问
*
* 随机挑一个
*
* @param refreshToken 用于刷新access_token的refresh_token
*/
async function fakeRequest ( refreshToken : string ) {
const token = await acquireToken ( refreshToken ) ;
const options = {
headers : {
Authorization : ` Bearer ${ token } ` ,
Referer : ` https://kimi.moonshot.cn/ ` ,
. . . FAKE_HEADERS
}
} ;
await [
( ) = > axios . get ( 'https://kimi.moonshot.cn/api/user' , options ) ,
( ) = > axios . get ( 'https://kimi.moonshot.cn/api/chat_1m/user/status' , options ) ,
( ) = > axios . post ( 'https://kimi.moonshot.cn/api/chat/list' , {
offset : 0 ,
size : 50
} , options ) ,
( ) = > axios . post ( 'https://kimi.moonshot.cn/api/show_case/list' , {
offset : 0 ,
size : 4 ,
enable_cache : true ,
order : "asc"
} , options )
] [ Math . floor ( Math . random ( ) * 4 ) ] ( ) ;
}
/**
* 提取消息中引用的文件URL
*
@@ -356,7 +398,7 @@ async function preSignUrl(filename: string, refreshToken: string) {
timeout : 15000 ,
headers : {
Authorization : ` Bearer ${ token } ` ,
Referer : ` https://kimi.moonshot.cn ` ,
Referer : ` https://kimi.moonshot.cn/ ` ,
. . . FAKE_HEADERS
} ,
validateStatus : ( ) = > true
@@ -437,7 +479,7 @@ async function uploadFile(fileUrl: string, refreshToken: string) {
headers : {
'Content-Type' : mimeType ,
Authorization : ` Bearer ${ token } ` ,
Referer : ` https://kimi.moonshot.cn ` ,
Referer : ` https://kimi.moonshot.cn/ ` ,
. . . FAKE_HEADERS
} ,
validateStatus : ( ) = > true
@@ -453,7 +495,7 @@ async function uploadFile(fileUrl: string, refreshToken: string) {
} , {
headers : {
Authorization : ` Bearer ${ token } ` ,
Referer : ` https://kimi.moonshot.cn ` ,
Referer : ` https://kimi.moonshot.cn/ ` ,
. . . FAKE_HEADERS
}
} ) ;
@@ -466,7 +508,7 @@ async function uploadFile(fileUrl: string, refreshToken: string) {
} , {
headers : {
Authorization : ` Bearer ${ token } ` ,
Referer : ` https://kimi.moonshot.cn ` ,
Referer : ` https://kimi.moonshot.cn/ ` ,
. . . FAKE_HEADERS
}
} ) ;
@@ -501,15 +543,16 @@ function checkResult(result: AxiosResponse, refreshToken: string) {
/**
* 从流接收完整的消息内容
*
* @param model 模型名称
* @param convId 会话ID
* @param stream 消息流
*/
async function receiveStream ( convId : string , stream : any ) {
async function receiveStream ( model : string , convId : string , stream : any ) {
return new Promise ( ( resolve , reject ) = > {
// 消息初始化
const data = {
id : convId ,
model : MODEL_NAME ,
model ,
object : 'chat.completion' ,
choices : [
{ index : 0 , message : { role : 'assistant' , content : '' } , finish_reason : 'stop' }
@@ -518,6 +561,7 @@ async function receiveStream(convId: string, stream: any) {
created : util.unixTimestamp ( )
} ;
let refContent = '' ;
const silentSearch = model . indexOf ( 'silent_search' ) != - 1 ;
const parser = createParser ( event = > {
try {
if ( event . type !== "event" ) return ;
@@ -536,7 +580,7 @@ async function receiveStream(convId: string, stream: any) {
resolve ( data ) ;
}
// 处理联网搜索
else if ( result . event == 'search_plus' && result . msg && result . msg . type == 'get_res' )
else if ( ! silentSearch && result . event == 'search_plus' && result . msg && result . msg . type == 'get_res' )
refContent += ` ${ result . msg . title } ( ${ result . msg . url } ) \ n ` ;
// else
// logger.warn(result.event, result);
@@ -558,19 +602,21 @@ async function receiveStream(convId: string, stream: any) {
*
* 将流格式转换为gpt兼容流格式
*
* @param model 模型名称
* @param convId 会话ID
* @param stream 消息流
* @param endCallback 传输结束回调
*/
function createTransStream ( convId : string , stream : any , endCallback? : Function ) {
function createTransStream ( model : string , convId : string , stream : any , endCallback? : Function ) {
// 消息创建时间
const created = util . unixTimestamp ( ) ;
// 创建转换流
const transStream = new PassThrough ( ) ;
let searchFlag = false ;
const silentSearch = model . indexOf ( 'silent_search' ) != - 1 ;
! transStream . closed && transStream . write ( ` data: ${ JSON . stringify ( {
id : convId ,
model : MODEL_NAME ,
model ,
object : 'chat.completion.chunk' ,
choices : [
{ index : 0 , delta : { role : 'assistant' , content : '' } , finish_reason: null }
@@ -588,7 +634,7 @@ function createTransStream(convId: string, stream: any, endCallback?: Function)
if ( result . event == 'cmpl' ) {
const data = ` data: ${ JSON . stringify ( {
id : convId ,
model : MODEL_NAME ,
model ,
object : 'chat.completion.chunk' ,
choices : [
{ index : 0 , delta : { content : ( searchFlag ? '\n' : '' ) + result . text } , finish_reason: null }
@@ -603,7 +649,7 @@ function createTransStream(convId: string, stream: any, endCallback?: Function)
else if ( result . event == 'all_done' || result . event == 'error' ) {
const data = ` data: ${ JSON . stringify ( {
id : convId ,
model : MODEL_NAME ,
model ,
object : 'chat.completion.chunk' ,
choices : [
{
@@ -620,12 +666,12 @@ function createTransStream(convId: string, stream: any, endCallback?: Function)
endCallback && endCallback ( ) ;
}
// 处理联网搜索
else if ( result . event == 'search_plus' && result . msg && result . msg . type == 'get_res' ) {
else if ( ! silentSearch && result . event == 'search_plus' && result . msg && result . msg . type == 'get_res' ) {
if ( ! searchFlag )
searchFlag = true ;
const data = ` data: ${ JSON . stringify ( {
id : convId ,
model : MODEL_NAME ,
model ,
object : 'chat.completion.chunk' ,
choices : [
{