diff --git a/internal/auth/kimi/kimi.go b/internal/auth/kimi/kimi.go index 86052277..8427a057 100644 --- a/internal/auth/kimi/kimi.go +++ b/internal/auth/kimi/kimi.go @@ -30,7 +30,7 @@ const ( // kimiTokenURL is the endpoint for exchanging device codes for tokens. kimiTokenURL = kimiOAuthHost + "/api/oauth/token" // KimiAPIBaseURL is the base URL for Kimi API requests. - KimiAPIBaseURL = "https://api.kimi.com/coding/v1" + KimiAPIBaseURL = "https://api.kimi.com/coding" // defaultPollInterval is the default interval for polling token endpoint. defaultPollInterval = 5 * time.Second // maxPollDuration is the maximum time to wait for user authorization. diff --git a/internal/runtime/executor/kimi_executor.go b/internal/runtime/executor/kimi_executor.go index 1cc66341..94a78331 100644 --- a/internal/runtime/executor/kimi_executor.go +++ b/internal/runtime/executor/kimi_executor.go @@ -25,6 +25,7 @@ import ( // KimiExecutor is a stateless executor for Kimi API using OpenAI-compatible chat completions. type KimiExecutor struct { + ClaudeExecutor cfg *config.Config } @@ -64,6 +65,12 @@ func (e *KimiExecutor) HttpRequest(ctx context.Context, auth *cliproxyauth.Auth, // Execute performs a non-streaming chat completion request to Kimi. func (e *KimiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (resp cliproxyexecutor.Response, err error) { + from := opts.SourceFormat + if from.String() == "claude" { + auth.Attributes["base_url"] = kimiauth.KimiAPIBaseURL + return e.ClaudeExecutor.Execute(ctx, auth, req, opts) + } + baseModel := thinking.ParseSuffix(req.Model).ModelName token := kimiCreds(auth) @@ -71,7 +78,6 @@ func (e *KimiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req reporter := newUsageReporter(ctx, e.Identifier(), baseModel, auth) defer reporter.trackFailure(ctx, &err) - from := opts.SourceFormat to := sdktranslator.FromString("openai") originalPayload := bytes.Clone(req.Payload) if len(opts.OriginalRequest) > 0 { @@ -95,7 +101,7 @@ func (e *KimiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req requestedModel := payloadRequestedModel(opts, req.Model) body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel) - url := kimiauth.KimiAPIBaseURL + "/chat/completions" + url := kimiauth.KimiAPIBaseURL + "/v1/chat/completions" httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { return resp, err @@ -155,14 +161,18 @@ func (e *KimiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req // ExecuteStream performs a streaming chat completion request to Kimi. func (e *KimiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (stream <-chan cliproxyexecutor.StreamChunk, err error) { - baseModel := thinking.ParseSuffix(req.Model).ModelName + from := opts.SourceFormat + if from.String() == "claude" { + auth.Attributes["base_url"] = kimiauth.KimiAPIBaseURL + return e.ClaudeExecutor.ExecuteStream(ctx, auth, req, opts) + } + baseModel := thinking.ParseSuffix(req.Model).ModelName token := kimiCreds(auth) reporter := newUsageReporter(ctx, e.Identifier(), baseModel, auth) defer reporter.trackFailure(ctx, &err) - from := opts.SourceFormat to := sdktranslator.FromString("openai") originalPayload := bytes.Clone(req.Payload) if len(opts.OriginalRequest) > 0 { @@ -190,7 +200,7 @@ func (e *KimiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut requestedModel := payloadRequestedModel(opts, req.Model) body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel) - url := kimiauth.KimiAPIBaseURL + "/chat/completions" + url := kimiauth.KimiAPIBaseURL + "/v1/chat/completions" httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { return nil, err @@ -269,26 +279,8 @@ func (e *KimiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut // CountTokens estimates token count for Kimi requests. func (e *KimiExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) { - baseModel := thinking.ParseSuffix(req.Model).ModelName - - from := opts.SourceFormat - to := sdktranslator.FromString("openai") - body := sdktranslator.TranslateRequest(from, to, baseModel, bytes.Clone(req.Payload), false) - - // Use a generic tokenizer for estimation - enc, err := tokenizerForModel("gpt-4") - if err != nil { - return cliproxyexecutor.Response{}, fmt.Errorf("kimi executor: tokenizer init failed: %w", err) - } - - count, err := countOpenAIChatTokens(enc, body) - if err != nil { - return cliproxyexecutor.Response{}, fmt.Errorf("kimi executor: token counting failed: %w", err) - } - - usageJSON := buildOpenAIUsageJSON(count) - translated := sdktranslator.TranslateTokenCount(ctx, to, from, count, usageJSON) - return cliproxyexecutor.Response{Payload: []byte(translated)}, nil + auth.Attributes["base_url"] = kimiauth.KimiAPIBaseURL + return e.ClaudeExecutor.CountTokens(ctx, auth, req, opts) } // Refresh refreshes the Kimi token using the refresh token.